New feature: Image management (crop sizes + modern format optimization) - #289
Open
Castellon-ACM wants to merge 28 commits into
Open
New feature: Image management (crop sizes + modern format optimization)#289Castellon-ACM wants to merge 28 commits into
Castellon-ACM wants to merge 28 commits into
Conversation
The bulk regenerate/convert/list AJAX endpoints required manage_options, but the settings page itself only requires edit_theme_options. A user able to reach the page (e.g. an editor with theme-options access) would always get a 403 on the bulk-action buttons.
WordPress core has no knowledge of the modern-format variants this module generates, so without explicit cleanup they were left orphaned on disk in two cases: when their attachment is deleted, and when variants are regenerated after a format/size change (e.g. switching from "both" to "webp" left the old .avif files behind).
Disabling a size only stopped future generation — existing files for that size (and any of its WebP/AVIF variants) stuck around forever, so the disk-usage estimate in the sizes table never actually translated into reclaimed space. Adds a third bulk action, following the same batched-AJAX pattern as regenerate/convert, that deletes those files and updates the attachment metadata.
Size names (including admin-editable custom size names) were concatenated directly into innerHTML, so a name containing quotes or angle brackets could break the row markup or inject arbitrary HTML.
Saved custom sizes are registered via add_image_size(), which puts them into \$_wp_additional_image_sizes — the same global the main table reads to list theme/plugin sizes. Once saved, a custom size showed up both there (as a plain, non-editable row) and again in its own editable "custom" row.
wp_content_img_tag only covers the_content/the_excerpt, so featured images rendered via the_post_thumbnail() (a very common case for hero images and archive cards) never got the <picture> rewrite. Reuses the same filter_content_img_tag() logic via the post_thumbnail_html filter.
- Store generated variants per size, keyed by full MIME type, with each variant's file size recorded alongside its filename — a more self-describing structure than the previous short format-key map, and it captures the data needed to report real compression savings later. - Default frontend delivery now rewrites the <img> src/srcset in place (AVIF preferred over WebP when both exist) instead of always wrapping in a <picture> element, since modern-format browser support is high enough that the extra markup usually isn't needed. srcset rewriting now covers every candidate URL, not just the one matching the filter's own src. - The <picture>-element wrap is kept as an opt-in mode (via a new image_format_use_picture setting) for sites that want an explicit, guaranteed fallback. - Original files are never modified or deleted by this module in either mode — only additional variant files are ever generated.
- New "Use a <picture> element for delivery" toggle, matching the Frontend rework: off (default) rewrites the img tag directly, on wraps it with an explicit fallback. - Custom sizes can now be given a Label and flagged "Show in picker" so they appear, under that label, in the image-size dropdown shown when inserting or editing an image — the same dropdown WordPress core uses to list Thumbnail/Medium/Large/Full Size.
…eader The issue explicitly rejects <picture>-element markup as a non-goal in favor of direct src/srcset rewriting, but the delivery code supported both and offered picture wrapping as an opt-in setting. Removed the setting, the wrapping method, its CSS asset, and all related UI/docs. Also fixed the modern-format selection logic to actually honor the issue's stated behavior: WebP is rewritten unconditionally, AVIF only when the visitor's Accept header confirms support for it. Previously AVIF was always preferred over WebP whenever both variants existed, with no request-time negotiation at all.
# Conflicts: # includes/Plugin_Main.php # readme.md
Image Management's format-output delivery relies on filters that aren't reliable before 5.8. Updates the plugin header, readme.txt, and AGENTS.md, which all still claimed 5.0.
…them The 'Modern format' select still offered WebP/AVIF/Both even when the server's image library couldn't produce one of them, only showing a warning text below — meaning it was possible to pick and save a broken option. The select now only lists options the server can actually produce, and sanitize_settings() downgrades a saved target that requires an unsupported format instead of trusting it blindly, in case support changed since the settings page was last loaded.
The issue asks for a quality/compression setting per format; there was only one shared image_format_quality slider. Replaced it with image_format_quality_webp and image_format_quality_avif, each with its own default (82 for WebP, 60 for AVIF — the same numeric quality produces a noticeably smaller AVIF file at a visually comparable result, so a shared default made AVIF either over-compressed or WebP under-compressed). Updated generation code, settings UI, sanitization, and docs accordingly.
Full requirement from the issue that had no implementation at all: on upload, downscale the stored original if it exceeds a configured width/height, so oversized source images don't bloat storage or slow down thumbnail generation. Filters big_image_size_threshold, enabled by default at 2048px (overriding core's own 2560px default), configurable to a different value or disabled entirely to keep full-size originals untouched.
Settings::render_section() only renders a section whose ID is explicitly wired to a tab panel — there's no wildcard fallback. The Image Management admin section (its own settings section, fields, sizes table, and bulk actions) was registered via the normal frontblocks_register_settings hook but no tab panel ever referenced frontblocks_section_image_management, so the entire feature — despite being fully implemented — was unreachable from the actual settings screen. Added its own 'Image Management' tab and wired its panel to the section, and added the section id to the full-width-card render path alongside Maintenance/Cookie Notice/CPT (it has a custom sizes table and bulk-action UI, not a simple toggle grid). Verified in-browser: the tab now appears, renders the sizes table, and the max-upload-dimension and per-format quality fields added in prior commits are visible and functional.
…agement # Conflicts: # readme.md
…arget The in_array() check correctly defaulted to 'none' via ?? when image_format_target is absent from $posted, but the ternary's true-branch then read $posted['image_format_target'] directly without the same fallback — an undefined-array-key access whenever the target key is missing from the request entirely (not just an unchecked select, which always sends a value). Found while writing AdminImageManagementTest.php.
Adds woocommerce/action-scheduler as a runtime Composer dependency and requires its bootstrap file before plugins_loaded, matching its own standard shared-library loading pattern: the bootstrap self-registers a versioned instance via ActionScheduler_Versions and only initializes the highest version found across every plugin that bundles it, so multiple plugins can each vendor their own copy without conflict. vendor/ stays gitignored for development as before; the release build (composer install --no-dev) already vendors and ships whatever is in composer.json's require section, so this is bundled in the shipped plugin the same way as any other runtime dependency would be.
Regenerate/convert/cleanup used to run as a client-driven admin-ajax loop: the browser fetched all attachment IDs, then looped POSTing batches of 5, updating a progress bar between calls. That meant processing only continued while the tab stayed open — closing it, navigating away, or a slow request timing out aborted the whole run with no way to resume. Bulk actions now schedule one Action Scheduler action per image attachment (ajax_start_bulk_job()), each processed independently by process_bulk_item() regardless of whether anyone still has the settings page open. Progress is tracked in a small per-job option (total/done), polled via ajax_bulk_job_status() and cleaned up once the job completes.
Replaces the client-side batch loop with: start the job (schedules everything server-side), then poll its status every 1.5s until done. The active job is also tracked in localStorage, so reopening the settings page (after closing the tab, or a simple reload) resumes showing its progress — the job itself was never actually lost, only the visible progress bar was, since processing runs independently on the Action Scheduler queue either way.
Replaces the old per-batch AJAX tests with coverage for: starting a job schedules one Action Scheduler action per image attachment (never for non-image attachments) and seeds its progress option; job-status reports progress and cleans up the option once complete; an unknown job id and invalid job type are rejected; and process_bulk_item() itself for each of the three job types (regenerate/convert/cleanup), including that its progress counter increments correctly, never exceeds the total, and silently no-ops for an unknown job id.
Admin\ImageManagement was only instantiated inside the is_admin() block, so its process_bulk_item() callback for frontblocks_image_management_process_item was never registered in the contexts Action Scheduler actually runs scheduled actions in — WP-Cron and WP-CLI, neither of which is_admin(). Every single bulk job action was permanently failing with "no callbacks are registered", silently: the progress bar just hung forever at 0% with no error surfaced. Found via live testing: 44 real scheduled actions all failed identically after triggering WP-Cron, confirmed via wp_get_attachment_metadata() staying byte-identical before/after a "regenerate thumbnails" run that appeared to run to completion in the UI.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #258.
Adds Image Management: control over which image sizes WordPress generates and at what dimensions, and automatic WebP/AVIF delivery on the frontend. Entirely a free-plugin feature, no PRO gating.
Size / crop management
big_image_size_thresholddefault, configurable or disable-able.Modern format delivery
wp_image_editor_supports()capability detection per format — an unsupported format is hidden from the settings UI rather than offered broken.<img>/srcsetURL rewriting (no<picture>restructuring): WebP applied unconditionally when a variant exists; AVIF only when the visitor'sAcceptheader confirms support.Background bulk jobs (Action Scheduler)
The three bulk actions (Regenerate Thumbnails, Convert to modern formats, Clean Up Disabled Sizes) run on a real Action Scheduler background queue — one scheduled action per attachment — instead of a client-driven admin-ajax batch loop. This means:
Found and fixed via live testing: a critical bug where the bulk-job callback was only registered inside an
is_admin()guard, so Action Scheduler's real execution contexts (WP-Cron, WP-CLI) — none of which areis_admin()— never had the callback registered. Every single scheduled action failed silently with "no callbacks are registered", leaving the progress bar hung forever with no visible error. Fixed by loading that module unconditionally; re-verified live that jobs now actually reachcompletestatus via real WP-Cron execution, with file-level proof (deleted thumbnail files reappearing with fresh timestamps after a real cron-driven run).Cleanup behavior
Requirements bump
WordPress minimum raised to 5.8 (required for the image-format-output filters), reflected in
AGENTS.md/frontblocks.php/readme.txt.Test plan
menu_icon-equivalent size CRUD, format capability downgrade (all 4 support combinations), fullsanitize_settingssurface, and the Action Scheduler job system — starting a job schedules one action per image attachment (never non-images), job-status reports progress and cleans up on completion, andprocess_bulk_item()for all three job types including progress-counter correctness.composer lint(phpcs) clean.is_admin()bug via real WP-Cron execution (44 actions, 100% failure), then re-verified after the fix — 28 actions across two job runs, 0 failed, 0 pending, progress bar reaching "Done", and file-level proof of real work (deleted thumbnail/variant files reappearing with fresh timestamps after a cron-driven run).