{"id":343268,"date":"2026-07-21T12:10:47","date_gmt":"2026-07-21T12:10:47","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/photolab-for-woocommerce\/"},"modified":"2026-07-21T12:40:28","modified_gmt":"2026-07-21T12:40:28","slug":"todot-photolab","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/todot-photolab\/","author":22028371,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.0.5","stable_tag":"0.0.5","tested":"7.0.2","requires":"6.5","requires_php":"8.1","requires_plugins":null,"header_name":"Todot Photolab for WooCommerce","header_author":"Photolab Team","header_description":"Gestione e vendita massiva di album fotografici su WooCommerce.","assets_banners_color":"d1e5e3","last_updated":"2026-07-21 12:40:28","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/todot.it","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":89,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.0.5":{"tag":"0.0.5","author":"todotweb","date":"2026-07-21 12:40:28"}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.jpg":{"filename":"icon-128x128.jpg","revision":3617134,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.jpg":{"filename":"icon-256x256.jpg","revision":3617134,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3617134,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3617134,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.0.5"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3617134,"resolution":"1","location":"assets","locale":"","width":1486,"height":895},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3617134,"resolution":"2","location":"assets","locale":"","width":1532,"height":903},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3617141,"resolution":"3","location":"assets","locale":"","width":1486,"height":872}},"screenshots":{"1":"Admin panel \u2014 create an album, upload photos, set price and expiration.","2":"Galleries status \u2014 track all albums, expiration dates, and per-album photo counts.","3":"Upload complete \u2014 photos saved and ready, watermarking done."}},"plugin_section":[],"plugin_tags":[21756,5346,205,3241,286],"plugin_category":[45,50],"plugin_contributors":[272631],"plugin_business_model":[],"class_list":["post-343268","plugin","type-plugin","status-publish","hentry","plugin_tags-bulk-upload","plugin_tags-digital-downloads","plugin_tags-photos","plugin_tags-watermark","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_category-media","plugin_contributors-todotweb","plugin_committers-todotweb"],"banners":{"banner":"https:\/\/ps.w.org\/todot-photolab\/assets\/banner-772x250.jpg?rev=3617134","banner_2x":"https:\/\/ps.w.org\/todot-photolab\/assets\/banner-1544x500.jpg?rev=3617134","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/todot-photolab\/assets\/icon-128x128.jpg?rev=3617134","icon_2x":"https:\/\/ps.w.org\/todot-photolab\/assets\/icon-256x256.jpg?rev=3617134","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/todot-photolab\/assets\/screenshot-1.png?rev=3617134","caption":"Admin panel \u2014 create an album, upload photos, set price and expiration."},{"src":"https:\/\/ps.w.org\/todot-photolab\/assets\/screenshot-2.png?rev=3617134","caption":"Galleries status \u2014 track all albums, expiration dates, and per-album photo counts."},{"src":"https:\/\/ps.w.org\/todot-photolab\/assets\/screenshot-3.png?rev=3617141","caption":"Upload complete \u2014 photos saved and ready, watermarking done."}],"raw_content":"<!--section=description-->\n<p>Photolab streamlines selling digital photos at scale through WooCommerce. Upload thousands of photos in batches, apply watermarks inline during upload, and publish each as a downloadable product \u2014 all from a single admin panel. Built for event photographers, photo studios, and anyone selling large galleries online.<\/p>\n\n<p><strong>Key features:<\/strong><\/p>\n\n<ul>\n<li>Bulk chunked upload (5 files per chunk, supports 2000+ photos per album)<\/li>\n<li>Inline watermarking during upload \u2014 no background queue drift; Imagick preferred, GD fallback<\/li>\n<li>Configurable watermark position: full-width or bottom-right<\/li>\n<li>Each photo becomes a virtual downloadable WooCommerce product<\/li>\n<li>Original photos protected from public access; customers download only after purchase<\/li>\n<li>Download blocked until processing completes (HTTP 425 during watermarking, 410 if expired)<\/li>\n<li>Multi-upload: up to 3 concurrent album uploads per user with rate limiting<\/li>\n<li>Automatic recovery of interrupted uploads (client heartbeat + server-side cron, FSM with aborted state and manual reset)<\/li>\n<li>Idempotency-Key on chunk endpoints for safe retry after network timeouts<\/li>\n<li>Distributed lock (MySQL GET_LOCK \/ transient fallback) for multi-server deployments<\/li>\n<li>Daily cleanup failsafe: stuck photo detection, retry budget, orphan scan, log retention<\/li>\n<li>Structured logging with auto-context (user_id, album_id, photo_id) and sensitive data redaction<\/li>\n<li>Album expiration management with automatic cleanup<\/li>\n<li>SHA-256 deduplication to prevent duplicate photos in the same album<\/li>\n<li>SPA admin panel with zero page reloads, REST API communication<\/li>\n<li>Imagick support (recommended) with GD fallback for image processing<\/li>\n<li>WooCommerce HPOS compatible<\/li>\n<\/ul>\n\n<p><strong>Requirements:<\/strong><\/p>\n\n<ul>\n<li>WordPress 6.5+<\/li>\n<li>WooCommerce 8.0+<\/li>\n<li>PHP 8.1+<\/li>\n<li>PHP Imagick extension (recommended) or GD<\/li>\n<li>memory_limit &gt;= 256MB<\/li>\n<li>max_execution_time &gt;= 60s<\/li>\n<li>upload_max_filesize &gt;= 20MB<\/li>\n<li>post_max_size &gt;= 100MB<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>photolab<\/code> folder to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin from the WordPress Plugins screen.<\/li>\n<li>Ensure WooCommerce is installed and active before activating Photolab.<\/li>\n<li>Verify Pretty Permalinks are enabled in Settings \u2192 Permalinks (any structure except \"Plain\").<\/li>\n<\/ol>\n\n<p><strong>Original photo protection:<\/strong><\/p>\n\n<p>The <code>wp-content\/uploads\/Photolab\/photos\/<\/code> directory is protected via an <code>.htaccess<\/code> file denying direct access. This works on <strong>Apache<\/strong> servers with <code>mod_rewrite<\/code> enabled.<\/p>\n\n<p>If using <strong>Nginx<\/strong>, <code>.htaccess<\/code> files are not read. Add a rule to your server block to protect the directory:<\/p>\n\n<pre><code>location ~* \/wp-content\/uploads\/Photolab\/photos\/ {\n    deny all;\n    return 403;\n}\n<\/code><\/pre>\n\n<p>Consult your hosting provider documentation to apply this configuration.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20many%20photos%20can%20i%20upload%20per%20album%3F\"><h3>How many photos can I upload per album?<\/h3><\/dt>\n<dd><p>The plugin handles high volumes, up to 2000+ photos per album. Uploads use chunks of 5 files (filterable via <code>photolab_chunk_size<\/code>) for compatibility with shared hosting.<\/p><\/dd>\n<dt id=\"can%20i%20use%20the%20plugin%20without%20imagick%3F\"><h3>Can I use the plugin without Imagick?<\/h3><\/dt>\n<dd><p>Yes. If Imagick is unavailable, Photolab automatically uses GD for watermarking. Performance on large volumes will be reduced compared to Imagick.<\/p><\/dd>\n<dt id=\"does%20the%20watermark%20affect%20original%20photos%3F\"><h3>Does the watermark affect original photos?<\/h3><\/dt>\n<dd><p>No. The watermark is applied to a copy (saved in <code>wp-content\/uploads\/Photolab\/watermarked\/<\/code>). Originals are stored separately and protected from public access.<\/p><\/dd>\n<dt id=\"when%20is%20the%20watermark%20applied%3F\"><h3>When is the watermark applied?<\/h3><\/dt>\n<dd><p>Watermarking is inline: it runs during the same HTTP request as the upload chunk. Photos are watermarked immediately as they arrive, not via a background queue. The <code>\/upload\/chunk<\/code> endpoint returns after processing completes. Download is blocked until the photo reaches the <code>watermarked<\/code> state (HTTP 425).<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20close%20the%20browser%20during%20upload%3F\"><h3>What happens if I close the browser during upload?<\/h3><\/dt>\n<dd><p>The client sends a heartbeat every 30 seconds. If the server receives no heartbeat for 5 minutes (or 10 minutes if none was ever received), a recovery job transitions the album to <code>aborted<\/code>. From the admin panel you can reset the album (<code>POST \/albums\/{id}\/reset<\/code>) to resume, or delete it. Already-uploaded photos remain as valid WooCommerce products.<\/p><\/dd>\n<dt id=\"can%20i%20upload%20multiple%20albums%20at%20once%3F\"><h3>Can I upload multiple albums at once?<\/h3><\/dt>\n<dd><p>Yes, up to 3 concurrent album uploads per user. Attempts beyond this limit return HTTP 429. Albums with the same name already in flight for the same user return HTTP 409.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20a%20chunk%20upload%20times%20out%3F\"><h3>What happens if a chunk upload times out?<\/h3><\/dt>\n<dd><p>The client can retry the same <code>\/upload\/chunk<\/code> with an <code>Idempotency-Key<\/code> header. The original response is replayed from a transient (TTL 24h) without reprocessing files. Without the header, behavior is unchanged.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20a%20photo%20expires%3F\"><h3>What happens when a photo expires?<\/h3><\/dt>\n<dd><p>If an expiration date is set for the album, the plugin automatically deletes the WooCommerce product, watermarked image, and database record via a daily job managed by Action Scheduler (bundled with WooCommerce).<\/p><\/dd>\n<dt id=\"how%20does%20the%20daily%20cleanup%20work%3F\"><h3>How does the daily cleanup work?<\/h3><\/dt>\n<dd><p>An Action Scheduler job <code>photolab_daily_cleanup<\/code> runs every 24 hours (default 03:00 UTC) performing a failsafe sweep: detects stuck watermark jobs &gt;1h, re-enqueues stalled photos, retries failed photos within a 5-retry budget per photo, purges expired idempotency transients, scans for disk\/DB orphans (log only), and deletes Photolab logs older than 30 days (filter <code>photolab_log_retention_days<\/code>). Action Scheduler logs are auto-deleted after 7 days.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20work%20in%20multi-server%20environments%3F\"><h3>Does the plugin work in multi-server environments?<\/h3><\/dt>\n<dd><p>Yes. An optional distributed lock is available: if WordPress uses an external object cache (e.g. Redis), the lock uses <code>wp_cache_add<\/code> (atomic SET NX). Without external object cache, the fallback is a transient. Concurrent chunk attempts for the same album from different nodes return HTTP 423. CAS on the database is the final safeguard. Disable via filter <code>photolab_use_distributed_lock<\/code>.<\/p><\/dd>\n<dt id=\"are%20pretty%20permalinks%20required%3F\"><h3>Are Pretty Permalinks required?<\/h3><\/dt>\n<dd><p>Yes. The WordPress REST API requires a permalink structure other than \"Plain\". The plugin shows an admin notice if Pretty Permalinks are not active.<\/p><\/dd>\n<dt id=\"does%20photolab%20work%20with%20woocommerce%20hpos%3F\"><h3>Does Photolab work with WooCommerce HPOS?<\/h3><\/dt>\n<dd><p>Yes. Photolab does not access <code>wp_posts<\/code> directly for WooCommerce products and is fully compatible with High-Performance Order Storage (HPOS).<\/p><\/dd>\n<dt id=\"where%20do%20i%20find%20the%20logs%3F\"><h3>Where do I find the logs?<\/h3><\/dt>\n<dd><p>In <strong>WooCommerce \u2192 Status \u2192 Logs<\/strong>, filter by source <code>photolab*<\/code>. Available sources: <code>photolab<\/code>, <code>photolab-fsm<\/code>, <code>photolab-upload<\/code>, <code>photolab-heartbeat<\/code>, <code>photolab-recovery<\/code>, <code>photolab-watermark-job<\/code>, <code>photolab-rate-limit<\/code>, <code>photolab-ownership<\/code>, <code>photolab-download-guard<\/code>, <code>photolab-idempotency<\/code>, <code>photolab-cleanup<\/code>, <code>photolab-lock<\/code>, <code>photolab-logger<\/code>. Each entry automatically includes <code>user_id<\/code>, <code>album_id<\/code>, and other contextual keys. Sensitive data (passwords, tokens, emails) is automatically redacted.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.0.5 \u2014 Initial public release<\/h4>\n\n<ul>\n<li>Bulk chunked upload with inline watermarking (Imagick + GD)<\/li>\n<li>WooCommerce product auto-creation per photo<\/li>\n<li>Configurable watermark position (full-width \/ bottom-right)<\/li>\n<li>Album expiration with automatic cleanup<\/li>\n<li>Finite State Machine with CAS transitions<\/li>\n<li>Upload recovery (heartbeat + server-side scan + manual reset)<\/li>\n<li>Multi-upload rate limiting (3 concurrent per user)<\/li>\n<li>Idempotency-Key for safe chunk retry<\/li>\n<li>Distributed lock for multi-server deployments<\/li>\n<li>Download guard (HTTP 425 during processing, 410 expired)<\/li>\n<li>Structured logging with 13 sources via wc_get_logger<\/li>\n<li>SHA-256 deduplication within albums<\/li>\n<li>HPOS compatible<\/li>\n<li>SPA admin panel (vanilla JS + Tailwind CSS local build)<\/li>\n<li>CI pipeline: PHPStan level 6, PHPCS WordPress, PHPUnit (30 test files)<\/li>\n<\/ul>","raw_excerpt":"Bulk photo album management and sales for WooCommerce. Upload thousands of photos, auto-watermark, and sell as downloadable products.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/343268","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=343268"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/todotweb"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=343268"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=343268"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=343268"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=343268"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=343268"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=343268"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}