v0.2.0
A hardening release for field use: an extremely thorough security and reliability review of v0.1.0 (~30 verified findings) fixed silent photo-loss paths, crash and memory-exhaustion vectors, symlink confinement, and editor breakage.
Added
- New filter
kntnt_photo_drop_max_input_megapixels(default 50): sources declaring more pixels are rejected per-file before any decode, so a decompression bomb or an oversized original can no longer exhaust PHP's memory and kill the upload request or CLI batch. - Drop Zone session recovery — when the page has been open long enough for the WordPress session nonce to expire, the uploader now fetches a fresh nonce automatically and retries the upload once; failing that, it shows the server's actionable message instead of a generic "Upload failed".
- Real upload progress — uploads now report true per-file transfer progress, and a 60-second inactivity watchdog aborts a stalled connection with a retryable error instead of freezing the upload queue indefinitely.
- The Drop Zone warns before the page is closed or navigated away while files are still queued or uploading.
- The Drop Zone pre-filters camera-folder noise: RAW files (CR2/CR3/NEF/ARW/DNG/RAF/ORF/RW2/SRW/PEF) and videos are rejected locally with a clear status line instead of uploading hundreds of megabytes only to be rejected by the server.
- The Drop Zone status list now keeps one row per file that updates through its states (converting, uploading, uploaded, skipped, failed) plus a live summary count, instead of an append-only log with contradictory lines.
- The Photo Gallery block's editor preview now uses
ServerSideRender, so the editor shows the real rendered gallery instead of placeholder tiles. - The lightbox shows loading and error states for the enlarged image, locks the page scroll while open, and feeds the enlarged image a responsive
srcset. - An admin notice warns when the host's PHP can encode WebP with neither GD nor Imagick — previously every upload failed with an opaque error and no explanation anywhere.
- The plugin row's "View version details" modal now shows the GitHub release notes instead of a "plugin not found" error.
- Translations now load from the plugin's
languages/directory, and the FilePond interface strings (drag-and-drop prompt, per-file states) are translatable.
Changed
- WP-CLI exit codes are now scriptable:
image importexits non-zero when every source was rejected (a partial failure still succeeds with a warning and counts), and a report-onlycollection doctorexits non-zero when actionable findings exist. collection doctor --repair --forcenow also prunes thumbnail directories left behind by a thumbnail-width change, instead of letting de-configured widths accumulate on disk forever.- The auto-updater caches the GitHub release lookup in a site transient (6 h, with a short-lived failure marker), sends a proper API
Acceptheader and timeout, and logs failed lookups — previously it hit the GitHub API uncached on every admin load and went silently blind when rate-limited. The update row now reportstested,requires, andrequires_phpcorrectly. - Gallery
sizeshints are layout-aware (derived from the column width or the justified row height) instead of100vw, so browsers no longer download the full-size main image for every thumbnail-sized tile. - The unused FilePond image-resize plugin was removed from the bundle; the Canvas pipeline has always done the actual downscaling.
Fixed
- Folder drag-and-drop no longer loses photos silently. The folder warning never fired (its listener was attached to an element FilePond removes), and FilePond recursively traversed dropped folders while flattening every path — so same-named files from different camera folders (
100CANON/IMG_0001.JPG,101CANON/IMG_0001.JPG) silently overwrote or skipped each other while the UI claimed success. A dropped folder is now intercepted, warned about, and on confirmation uploads its top-level images flat, exactly as designed. - The Photo Gallery block no longer crashes the editor when the Layout panel opens (
UnitControlis not a stable@wordpress/componentsexport; the experimental export is now used, as core blocks do). - A blank image can no longer be uploaded as if it were the photo: when the browser cannot provide a canvas context (memory pressure) or the image exceeds the safe canvas area on iOS, the original file is uploaded instead and the server performs the conversion.
- Uploading an image with an extreme aspect ratio (wide panoramas) crashed the request mid-batch with an uncaught
ValueErrorfrom GD; the scaled height is now clamped to one pixel and any codec failure becomes a clean per-file rejection. - Portrait photos imported via
wp kntnt-photo-drop image importor POSTed directly to the REST API rendered sideways: the server now applies EXIF orientation to the pixels before scaling and encoding, in both the GD and Imagick codecs. - Palette-based PNG/GIF images (screenshots, logos) within the size ceiling were wrongly rejected; they are now promoted to truecolor (transparency preserved) and convert correctly.
- A truncated or corrupt WebP could pass the header-only check and be stored as a permanently broken image; already-conforming WebP files are now decode-validated before being accepted byte-identical.
collection.json— the one irreplaceable file — could be destroyed by a crash or full disk mid-write; the descriptor, the index, every main image, and every thumbnail are now published atomically (temp file + rename) with short writes detected, so a reader only ever sees the old or the complete new file.- An image uploaded within the same second as an index rebuild could stay invisible in the gallery indefinitely (mtime has one-second granularity); an index stamped in the current second is no longer persisted, closing the race.
- A photographer's
--ignoreglob that happened to match stored images de-classified them and deleted their thumbnails under--repair; ignore globs now apply only to files that are not main images. - The doctor flagged the plugin's own directory-listing guard (
index.php) as a foreign file in every collection. - The REST route's parameter sanitization silently mangled legitimate filenames containing
%-sequences or doubled spaces, and bypassed the path guard's documented decoding defence; the raw value now reaches the guard, which is the real sanitizer. - A collection containing an unreadable subdirectory white-screened the whole admin page, including the delete action; the image count now degrades to an em-dash with a logged warning.
- Lightbox keyboard handling went dead after clicking the enlarged image (Escape/arrows stopped working and Tab escaped the dialog); keys are now handled at the document level while the lightbox is open.
- Pinch-zoom gestures over the lightbox no longer trigger spurious image changes, Cmd/Ctrl/Shift-clicking a thumbnail opens it in a new tab as expected, holding an arrow key no longer floods the network with uncancelled full-resolution preloads, and Alt/Cmd+Arrow (browser back/forward) is no longer hijacked.
- The justified layout's last row is now corrected client-side against the real container width, so mid-gallery rows no longer render ragged on themes narrower or wider than the assumed width.
- A failed collection scan (
glob()error) and a failed block registration are now logged instead of silently rendering "no collections" or removing the blocks from the inserter. build-release-zip.shrestores the development dependencies even when a build step fails, and excludes macOS junk files from the release archive.- Uninstalling now removes the plugin's transients (release cache, admin notices).
- Documentation: the thumbnail-regeneration examples in the README, the design notes, and ADR-0002 now show the complete, runnable WP-CLI command (
wp kntnt-photo-drop collection doctor <slug> --repair --force) instead of a barecollection doctor --repair --forcefragment that omitted thewp kntnt-photo-dropprefix.
Security
collection doctor --repaircan no longer be tricked into deleting or writing files outside the collection — every doctor walk now skips symbolic links and refuses to unlink or write through them, matching the confinement the rest of the plugin already enforced.- The gallery's index rebuild also skips symbolic links, so a planted link can no longer send the recursive gallery walk into an infinite loop (denial of service) or expose files outside the collection.
- GPS positions and other EXIF/XMP metadata are now stripped server-side — losslessly — from already-conforming WebP files that are stored byte-identical, closing a privacy leak where a directly-POSTed WebP published the photographer's location. Re-encoded uploads were already stripped.
- Every newly created collection directory is seeded with a directory-listing guard (
index.php), so a server with autoindex enabled cannot enumerate collection contents.
Full changelog: https://github.com/Kntnt/kntnt-photo-drop/blob/v0.2.0/CHANGELOG.md