=== EffortLess Simple Image Optimiser ===
Contributors:      domclic
Donate link: https://id7.dev/donate/?plugin=effortless-simple-image-optimiser
Tags:              image optimisation, compress images, GD library, resize images, WebP
Requires at least: 5.9
Tested up to:      7.1
Requires PHP: 7.4
Stable tag:        1.0.75
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Automatically optimise uploaded images using the PHP GD library — no external APIs, no account required.

== Description ==

**Effortless Simple Image Optimiser** hooks directly into the WordPress Media Library upload pipeline and compresses, resizes and (optionally) converts images the moment they are uploaded — all using the built-in PHP GD extension. It also provides admin tools under **Settings → Image Optimiser** to process images already in your Media Library, find and remove duplicate files, regenerate thumbnails, and manage the original-file backups the plugin keeps automatically.

= Upload-time optimisation =

* **JPEG compression** — re-encodes JPEGs at a configurable quality level (default: 82). GD strips EXIF metadata automatically, further reducing file size.
* **PNG compression** — applies lossless GD compression to PNG files.
* **PNG → JPEG conversion** — converts opaque (non-transparent) PNGs to JPEG for significant space savings. Transparent PNGs are always kept as PNG.
* **Image resizing** — scales down images that exceed a configurable maximum width or height while maintaining the original aspect ratio.
* **WebP generation** — optionally saves a `.webp` sidecar file alongside every optimised image (requires GD compiled with WebP support).
* **Automatic original backup** — before any image is optimised, the plugin saves an untouched copy of the original file. This happens every time, with no setting to turn it off — see the Originals tab below to restore or discard these backups.
* **Graceful degradation** — if GD is not available the plugin skips processing and logs a notice; uploads are never blocked.

= Bulk Optimise tab =

Processes every image already in your Media Library using your current settings, one image at a time. Shows total / optimised / pending counts, offers a "re-optimise all" option to force reprocessing, and includes a ghost-attachment scanner (finds and lets you delete Media Library records whose file no longer exists on disk). On a large library, both counting and processing automatically continue across as many requests as needed rather than stopping partway through.

= Regenerate Thumbnails tab =

Regenerates every registered thumbnail size for existing Media Library images — useful after a theme changes its image sizes or adds new crop dimensions. Also scales across a library of any size the same way as Bulk Optimise.

= Find Duplicates tab =

Scans the Media Library for exact duplicate image files (compared by file hash). The oldest copy (lowest attachment ID) is always kept; the "Fix all duplicates" action updates every reference to a duplicate in post content, post meta and featured images to point at the kept copy, then deletes the redundant files.

= Originals tab =

Shows how many original backups are stored and how much disk space they use. **Restore all originals** copies every stored original back over its optimised version, removes any WebP sidecar, and regenerates thumbnails. **Discard all originals** permanently deletes the stored backups to free disk space — do this only once you are satisfied with the optimised results, since restoring is no longer possible afterwards for a discarded image.

= Settings =

Navigate to **Settings → Image Optimiser** to configure:

* Enable / disable automatic optimisation on upload
* JPEG quality (1–100, default 82)
* Enable PNG → JPEG conversion
* Enable WebP sidecar generation + WebP quality
* Enable / disable resizing
* Maximum width in pixels (default 1920)
* Maximum height in pixels (default 1920)

= Requirements =

* PHP 7.4 or higher
* PHP GD extension (enabled on virtually all shared hosting)
* WordPress 5.9 or higher

== Installation ==

**From the WordPress admin (recommended)**

1. Go to **Plugins → Add New**.
2. Search for *Effortless Simple Image Optimiser*.
3. Click **Install Now**, then **Activate**.
4. Configure the plugin at **Settings → Image Optimiser**.

**Manual installation**

1. Download the plugin zip file.
2. Unzip and upload the `effortless-simple-image-optimiser` folder to `/wp-content/plugins/`.
3. Activate the plugin from the **Plugins** screen.
4. Go to **Settings → Image Optimiser** to configure your preferences.

== Frequently Asked Questions ==

= Does the plugin require an API key or external account? =

No. Everything is processed locally using the PHP GD library that ships with PHP.

= Will my existing images be optimised? =

Not automatically on their own — but you don't need to re-upload them. Go to **Settings → Image Optimiser → Bulk Optimise** and click the start button to process every image already in your Media Library using your current settings.

= Is the original image kept as a backup? =

Yes, automatically, every time — there is no setting to turn this off. Before an image is optimised, the plugin saves an untouched copy of the original file. Go to **Settings → Image Optimiser → Originals** to see how many backups are stored, restore all images to their pre-optimisation state, or permanently discard the backups once you're satisfied with the results and want to free the disk space.

= I ran Bulk Optimise / Regenerate Thumbnails and the counts look incomplete or it stopped partway through — is that a bug? =

No. On a large Media Library, counting and processing both continue automatically across as many requests as the server needs — if a count is shown as possibly incomplete (a notice explains this on the tab), reload the tab to keep counting, or just start processing directly since processing itself is not affected by the same limit.

= What happens if GD is not installed? =

The plugin fails gracefully — uploads proceed normally, a notice is logged to the PHP error log, and a warning is displayed on the settings page.

= Why does PNG → JPEG conversion only apply to some PNGs? =

PNGs that contain any transparent pixels are never converted to JPEG because JPEG does not support transparency. The plugin samples the image to detect transparency before converting.

= Does the plugin support WebP uploads? =

Yes. Uploaded WebP images are re-compressed using the configured WebP quality setting.

= Will this slow down my site or uploads? =

Processing is synchronous and happens during the upload request. On typical shared hosting the overhead is negligible for standard web images (< 5 MB). Very large files may add a second or two.

== Screenshots ==

1. The settings page under Settings → Image Optimiser showing all available options.
2. The environment sidebar confirming GD availability and supported formats.

== Changelog ==

= 1.0.75 =
* Documentation: the readme previously only described upload-time optimisation. It now documents the Bulk Optimise, Regenerate Thumbnails, Find Duplicates and Originals admin tabs, and corrects a FAQ answer that incorrectly said originals are not kept as backups by default (they always are, with no setting to disable it). Also corrected "Requires at least" from 6.2 to 5.9 to match what the plugin actually enforces on activation.

= 1.0.74 =
* Fix: the duplicate-image scan could exhaust available memory on a very large media library, since the accumulated duplicate-detection map had no upper bound once the scan itself became resumable across many requests. It now stops and safely resumes mid-batch if memory usage gets close to the host's limit, instead of risking a fatal error partway through a scan.

= 1.0.73 =
* Change: the settings page always opens on the Settings tab now, and no tab ever starts its check-and-process flow on its own. The 1.0.69 behaviour of auto-navigating to Bulk Optimise and starting it automatically when a pending-image backlog was detected has been removed — every tab now waits for its own Start button to be clicked.

= 1.0.72 =
* Fix: on a media library larger than one batch, the Bulk Optimise and Regenerate Thumbnails tabs could report wildly incorrect counts (a real report of "1,507,500 total images" for a library that actually had 814) and could still hit a "Maximum execution time exceeded" fatal error. The root cause was WordPress's `get_posts()` silently ignoring the plugin's own pagination filter unless explicitly told not to, causing every batch after the first to accidentally re-fetch the same rows instead of moving forward. Counting and processing now correctly advance through the whole library, verified end-to-end for libraries of any size from empty up through many thousands of images. Also hardened the two-pass stats check used by both tabs to share one overall time budget instead of each pass claiming its own, closing a related fatal-error edge case on large libraries.

= 1.0.71 =
* Fix: loading the settings page (specifically the Bulk Optimise / Regenerate Thumbnails tabs) on a large media library could still hit a "Maximum execution time exceeded" fatal error — a regression from 1.0.69's own fix. The same time-bounded stats check could legitimately run 3-4 times in a single page load (the new auto-navigate check, the tab display, and its notice), and running that bounded-but-still-substantial scan repeatedly in one request could add back up to the same execution-time fatal it was meant to prevent. The stats check now runs at most once per page load.

= 1.0.70 =
* New: full translations bundled for French, Spanish, Traditional Chinese (Taiwan), Italian, German, Korean, Portuguese and Japanese, alongside English.

= 1.0.69 =
* Fix: on a large enough media library, simply loading the Bulk Optimise, Regenerate Thumbnails, or Originals tab (or the Duplicates scan) could crash the whole admin page with a "Maximum execution time exceeded" fatal error, before any actual work even started — counting every image across enough pages to build the full list could itself run out of time on a slow host.
* New: Bulk Optimise, Regenerate Thumbnails, Find Duplicates, Ghost Attachment cleanup, and both Originals actions (Restore All / Discard All) now work through a media library of any size across as many automatic steps as it takes, instead of only ever covering what fits in one server request. A very large library that used to stop partway through (or crash outright) is now fully covered every time.
* New: if you land on the settings page and a real backlog of pending (not-yet-optimised) images is found, the page now opens directly on the Bulk Optimise tab and starts checking automatically, instead of leaving the backlog to be noticed by chance on the Settings tab.

= 1.0.68 =
* Change: the bundled donate-notice class is renamed from the shared `Effortless_Donate_Notice` to `ELSIO_Donate_Notice`, so the class name carries this plugin's own prefix and cannot collide with another EffortLess plugin loading its own copy.

= 1.0.67 =
* Change: tested against WordPress 7.1 and the "Tested up to" header updated to 7.1. No functional changes.
* Change: the donate notice's outbound link no longer includes the site address as a query parameter. It still identifies the plugin and version so support requests can be matched to a release, but not which installation was viewing it.

= 1.0.66 =
* Fix: update plugin name casing to EffortLess; add plugin identifier to donate link.

= 1.0.65 =
* Refactor: donate notice file kept as includes/class-donate-notice.php (preserves SVN file history); class remains Effortless_Donate_Notice.


= 1.0.64 =
* Fix: donate notice now correctly displays on Network Admin pages — screen ID normalization strips WordPress's -network/-user suffix before matching.


= 1.0.63 =
* Refactor: donate notice class renamed Donate_Notice → Effortless_Donate_Notice (WP-standard prefix); file renamed to class-effortless-donate-notice.php. No phpcs:ignore needed anywhere.


= 1.0.62 =
* Fix: donate notice now uses current_screen hook for reliable screen detection on single-site and multisite Network Admin pages.


= 1.0.61 =
* Improvement: added dismissible donate notice on the plugin settings screen.
* Improvement: added Donate link header field and includes/class-donate-notice.php.

= 1.0.59 =
* Improvement: elsio_options_updated() now uses the $new_value parameter passed by the hook instead of re-reading options from the database.
* Improvement: elsio_activate() no longer unconditionally inserts .htaccess WebP rules; rules are only written when the generate_webp option is enabled.
* Improvement: Bulk optimiser eliminates a redundant get_post_meta() call on re-optimisation runs by reusing the value already read before backup().
* Improvement: Uninstall WebP cleanup delegates to ELSIO_Bulk_Optimizer::delete_webp_sidecar(), removing duplicated code and gaining PNG→JPEG sidecar coverage.
* Improvement: ROW_KSES promoted from a static local variable to a class constant in ELSIO_Settings.
* Improvement: Uninstall get_posts() query no longer triggers a SQL COUNT or populates meta/term caches (no_found_rows, update_post_meta_cache, update_post_term_cache).

= 1.0.58 =
* Security: Replace transient-based process lock with wp_cache_add() to eliminate TOCTOU race condition on Redis/Memcached object-cache backends.
* Security: Uninstall WebP cleanup now deletes only sidecars adjacent to known Media Library attachments instead of scanning the entire uploads directory, preventing accidental removal of files owned by other plugins.
* Security: Tighten wp_kses allowlist in render_env_row() from wp_kses_post() to span+style and a+href+class only.

= 1.0.57 =
* Fix: `.htaccess` WebP rewrite rules are now removed when "Generate WebP copy" is disabled, preventing stale sidecars from being served after the setting is turned off.
* Fix: Bulk optimiser no longer performs a redundant file copy on first-time runs; the backup→file restore is now skipped when no prior backup existed, saving I/O for every image on initial bulk passes.
* Fix: Removed dead `elsio_originals_restore_all` AJAX hook (was an unused alias for `elsio_originals_scan`).

= 1.0.56 =
* Fix: Prefix all global variables in uninstall.php to comply with WordPress plugin coding standards.

= 1.0.55 =
* Revert: Removed pixel-scan cap on PNG transparency detection — every pixel is checked to ensure even a single transparent pixel (icons, logos) keeps the image as PNG.

Older entries (1.0.0–1.0.54) are omitted here to keep this changelog a reasonable length. Full history is available in the plugin's SVN repository.

== Upgrade Notice ==

= 1.0.1 =
Documentation update — no functional changes.
