=== Fmtify: Auto WebP and AVIF Converter ===
Contributors: chathurya
Tags: webp, avif, image, convert, optimize
Requires at least: 6.9
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 1.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Automatically converts your media library images to WebP and AVIF for faster page loads.

== Description ==

Fmtify automatically converts images uploaded to your WordPress media library into modern WebP and AVIF formats. Serve smaller, faster images with zero manual work.

**Features:**

* Automatic conversion on upload
* WebP and/or AVIF output
* Multiple conversion backends: GD, Imagick, cwebp, cavif
* Bulk convert your existing media library
* Apache .htaccess rewrite rules for seamless serving
* Nginx configuration snippet for manual setup
* Compatibility wizard to detect server capabilities

== Installation ==

1. Upload the `fmtify` folder to `/wp-content/plugins/`
2. Activate the plugin through the **Plugins** menu in WordPress
3. Go to **Settings → Fmtify** to configure conversion options
4. Visit **Media → Bulk Convert** to convert existing images

== Frequently Asked Questions ==

= Which image formats are supported as input? =

JPEG, PNG, GIF, and BMP source images are supported.

= Do I need a special server setup? =

No. Fmtify detects your available conversion libraries automatically and picks the best one. For Apache, it writes .htaccess rewrite rules. For Nginx, it provides a copy-paste configuration snippet.

= Will my original images be deleted? =

Never. Converted files are stored alongside the originals. You can delete them at any time from the settings page.

= Are animated GIFs converted? =

No. Animated GIFs are skipped on purpose: WebP and AVIF sidecars would only contain the first frame, so the animation would be lost. Static GIFs are converted normally.

== Changelog ==

= 1.3.0 =
* Added: Image Engine setting on the main settings page (Auto / GD faster / Imagick better quality, slower). It replaces the per-run Backend selector on the Bulk Convert page and now applies to ALL conversions: uploads, Bulk Convert, and WP-CLI. Auto keeps the previous behavior (GD first, Imagick fallback). When the selected engine is unavailable or cannot produce a format, Fmtify falls back automatically.
* Fixed: Every GIF (and PNG-8 / indexed BMP) failed on the GD engine with "Palette image not supported by webp" and "avif doesn't support palette images" warnings. GD loads these as palette images; they are now converted to truecolor before encoding, preserving transparency.
* Fixed: BMP images always failed on the GD engine because the BMP loader was missing.
* Fixed: Static thumbnails of an animated GIF are now converted; the animated files themselves are excluded up front instead of being counted as conversion issues and retried on every bulk run.
* Improved: More precise animated GIF detection (anchored frame headers, fewer false positives from pixel data).
* Improved: Engine availability probes now run once per request instead of once per converted file.

= 1.2.15 =
* Fixed: Bulk Convert could report 100% complete while leaving most of the library unconverted. The fresh-pass query window did not advance past images that stay in the result set (failed, missing on disk, excluded type, or any image when conversion tracking is off), so each batch re-read the same items. The query now paginates with a persistent offset in all modes and orders by ID for deterministic batches.
* Fixed: An image was marked as fully converted when only some of its sizes succeeded. The _fmtify_converted meta now records a format only when every file (original plus all thumbnails) converted, so a later fresh pass retries partially failed images. Applies to upload conversion, Bulk Convert, and WP-CLI.
* Fixed: The PHP-fallback rewrite rule was registered only during activation, so saving permalinks (or any plugin flushing rewrite rules) silently removed it. The rule is now registered on every request and self-heals after plugin updates.
* Fixed: Requests for missing images could render the homepage with HTTP 200 instead of returning a 404 when serving via .htaccess. The image handler is now active for every serving method and sends a real 404 for missing files.
* Fixed: The rewrite rule assumed uploads live at wp-content/uploads under the site root. The pattern is now derived from the actual uploads URL, supporting subdirectory installs, the UPLOADS constant, and multisite upload paths.
* Fixed: The Imagick backend reported success when it wrote a 0-byte file (for example on an unwritable directory). It now removes the empty file and reports the failure, matching the GD backend.
* Fixed: Serving an original PNG, GIF, or BMP through the PHP fallback could send Content-Type: image/jpeg when MIME detection failed. The type is now derived from the file extension.
* Fixed: Animated GIFs were silently flattened to a static first frame. They are now detected and skipped so the animation is preserved.
* Fixed: wp fmtify bulk loaded every attachment ID into memory at once and could exhaust memory on large libraries. It now pages through the library 100 attachments at a time.
* Fixed: The "Conversion issues" counter could exceed the total because it counted every failed file and format. It now counts each image once.
* Fixed: Conversion stats counted files that had failed. The total now reflects actual successful conversions.
* Fixed: Bulk job progress and lock transients are now removed on uninstall.
* Changed: The PHP image handler refuses to serve anything but the supported image extensions, even if the query variables are set directly.
* Changed: Removed unused internal converter methods and an unused setting key left over from an earlier architecture.

= 1.2.14 =
* Fixed: register_setting() sanitize_callback was an instance method reference ([$obj, 'sanitize']) that WP Plugin Check cannot resolve statically, causing a false "missing sanitize_callback" warning. Fmtify_Settings::sanitize() is now a static method registered as [Fmtify_Settings::class, 'sanitize'], which is fully statically-analysable.
* Hardened: sanitize() now accepts mixed input — previously the array type hint would throw a PHP TypeError if WordPress called the callback with a non-array value (e.g. null on a fresh install).

= 1.2.13 =
* Performance: `.htaccess` active state is now cached in a WordPress option (`fmtify_htaccess_active`). Previously Fmtify read `.htaccess` from disk on every front-end request (in `auto` serving mode) to decide whether the PHP image handler should register. The option is kept in sync whenever rules are written or removed.
* Performance: `Fmtify_Compatibility::get_status()` result is now cached in a transient for one hour. Previously the full server-probe (including two `Imagick::queryFormats()` calls) ran on every admin page render.
* Fixed: `is_path_in_uploads()` path-prefix check now appends a trailing slash to both sides of the comparison, preventing false positives for sibling directories such as `uploads_backup/` or `uploads-old/`.
* Fixed: Canonical list of convertible source MIME types is now defined once in `Fmtify_File_Helper::CONVERTIBLE_MIME` (jpeg/png/gif/bmp) and referenced by all conversion paths — upload hooks, bulk processor, and CLI. Previously the upload hook silently accepted `image/tiff` (never served by the rewrite rules) and `image/webp` (already a modern format; no sidecar needed).

= 1.2.12 =
* Fixed: readme.txt had duplicate == Upgrade Notice == headers causing the WP Plugin Check 300-character limit warning. Merged all version entries under one header.
* Fixed: set_time_limit() in the AJAX batch handler now suppresses the Plugin Check discouraged-function warning via phpcs:ignore. The call is intentional.

= 1.2.11 =
* Updated translations for all 8 bundled locales (de_DE, fr_FR, ja, pt_PT, ru_RU, fa_IR, vi, si_LK) to cover new strings added in 1.2.8 and 1.2.9: Strip metadata, Conversion tracking, Backend selector, and related UI descriptions.

= 1.2.10 =
* Fixed: Stale in-flight AJAX poll responses could overwrite the final "Done." / count display after a bulk run completed. Added a jobDone flag that discards any poll response arriving after polling stops.
* Fixed: PHPCS code-style violations introduced in 1.2.9 — array double-arrow alignment, assignment alignment, empty catch clause, and PHP tag placement in view files. No functional change.

= 1.2.9 =
* Changed: GD is now the default conversion backend when available (was Imagick). GD encodes AVIF faster; Imagick is now the fallback for servers without GD.
* Added: Backend selector on the Bulk Convert page. When both GD and Imagick are available, choose which backend to use for a bulk run — GD (faster, default) or Imagick (better quality, slower).

= 1.2.8 =
* Added: Strip metadata setting (Settings → Fmtify, off by default). When enabled, EXIF, ICC profile, and XMP are removed from every converted sidecar file. All four backends support it natively: Imagick (stripImage()), GD (skips the Imagick metadata-copy step), cwebp (-metadata none), cavif (--no-metadata).
* Added: wp fmtify bulk --mode=rebuild re-converts every eligible image from scratch, matching the Full rebuild scope in the admin UI.
* Added: Conversion tracking setting. When disabled, Fresh pass checks sidecar files on disk instead of _fmtify_converted post meta — always reflects actual files, useful after manually deleting sidecars.
* Fixed: wp fmtify bulk now converts all registered thumbnail sizes per attachment, matching the WP admin Bulk Convert.
* Fixed: cwebp backend was stripping all EXIF metadata. Added -metadata all so EXIF, ICC profile, and XMP are preserved by default.
* Fixed: GD backend now copies EXIF and ICC profile from the source via Imagick after conversion. On GD-only servers without Imagick, metadata is still stripped (a fundamental GD limitation).

= 1.2.5 =
* Improved: AVIF encoding speed raised to 8 (libaom cpu-used) across all three backends — Imagick, GD, and cavif CLI — cutting AVIF encode time by ~30–50% with imperceptible quality difference at web resolutions.

= 1.2.4 =
* Improved: Imagick backend now sets WebP encoding method to 4 (was default 6), cutting WebP conversion time roughly in half with negligible quality difference.
* Improved: GD backend now passes speed 6 to imageavif(), halving AVIF encode time vs the library default.

= 1.2.3 =
* Fixed: Bulk Convert progress always showed "0 / N" when WP-Cron held the batch lock. The progress transient is now saved after every image so AJAX polls see live progress even while WP-Cron is processing a long-running batch.
* Fixed: Batch lock TTL was 60 seconds, shorter than a typical batch on slow hardware (10 images × 5 thumbnail sizes × 2 formats). Raised to 300 seconds to prevent the lock from expiring mid-batch and triggering concurrent batch runs.

= 1.2.2 =
* Fixed: AJAX batch loop ran for up to 20 seconds per poll, causing 504 Gateway Timeout errors on sites behind an Nginx reverse proxy (e.g. wordpress.test on Laragon/Herd). The per-poll processing budget is now capped at 8 seconds.

= 1.2.1 =
* Fixed: Bulk Convert progress bar never advanced on local dev environments (Laragon, XAMPP, Herd) where WP-Cron cannot spawn HTTP requests. The AJAX progress poll now drives batch processing directly.
* Fixed: Batch lock could be left set indefinitely after an unexpected exception in process_batch(), causing all subsequent polls to return stale zeros. The lock is now always released via try-finally.
* Fixed: Conversion errors from non-RuntimeException sources were silently dropped instead of incrementing the "Conversion issues" counter. Changed inner catch to \Throwable.
* Fixed: get_batch() was processing attachment ID 0 instead of real IDs. WP_Query with fields='ids' returns plain integers; wp_list_pluck() returned null for each, intval(null) = 0. Replaced with array_map('intval', $query->posts).
* Fixed: Images whose source file is in the database but missing from disk were silently skipped. They are now counted as "Conversion issues".
* Added: "Already up to date" counter — images whose WebP/AVIF sidecars already exist on disk are now reported separately, replacing the confusing "0 written, 0 issues" result.

= 1.2.0 =
* Added: Fresh pass / Full rebuild scope selector on the Bulk Convert page. Fresh pass skips already-converted images; Full rebuild regenerates all output files — use after changing quality settings or adding a format.
* Added: Live stats box on Bulk Convert showing Images written and Conversion issues, updated with each polling interval.
* Fixed: Stats counters always showed 0 — the progress transient stored `errors` but the UI read `converted` and `failed`. Replaced with separate converted/failed counters throughout.
* Fixed: Bulk Convert now converts all registered thumbnail sizes for each attachment, not just the original.
* Fixed: .htaccess rules are now re-written whenever settings are saved and on plugin activation, so Apache/LiteSpeed sites serve WebP/AVIF correctly after a settings change.
* Fixed: GD backend now detects 0-byte output files (silent failure on unwritable directories), logs an error, and cleans up the empty file.
* Changed: Stats labels updated — "Images written" and "Conversion issues" replace the previous wording.
* Changed: Bulk Convert start button shortened to "Start".

= 1.1.7 =
* Fixed: LiteSpeed was not accepted as a valid server_type value in the settings sanitizer. LiteSpeed servers had their server_type silently reset to "apache" on every settings save.

= 1.1.6 =
* Renamed plugin internals from "Pictur" to "Fmtify": plugin entry point, PHP classes, JS/CSS assets, language files, option keys, and hook tags. No functional changes.

= 1.1.5 =
* Fixed AVIF images not being served on Apache when both WebP and AVIF are enabled. Apache re-reads .htaccess after the internal rewrite and served the sidecar file using the wrong MIME type (image/png instead of image/avif). Added AddType directives so the correct Content-Type is always used.

= 1.1.4 =
* Fixed Apache serving: .htaccess rules are now written automatically on plugin activation and whenever settings are saved.
* Fixed PHP fallback in auto mode — now activates on any server when .htaccess rules are absent, instead of silently failing on Apache.
* Removed marketing language ("Free, no upsells") from plugin description.
* Updated translations for all 8 bundled locales.

= 1.1.3 =
* Renamed plugin to "Fmtify - Auto WebP and AVIF" for clarity in the WordPress plugin directory.

= 1.1.2 =
* Fixed bulk conversion showing "No unconverted images found" when the Themes folder was selected but all Media Library images were already converted.

= 1.1.1 =
* Sidecar files (.webp/.avif) and all converted thumbnail sizes are now always deleted when an image is removed from the Media Library. The opt-in setting has been removed — cleanup is always on.
* Added a note to the Nginx Setup tab for local dev environments (Herd, Valet, Laragon) that require an explicit root directive inside the location block.
* Updated translations for all 8 bundled locales.

= 1.1.0 =
* Browsers now receive WebP/AVIF bytes under the original image URL — no filename changes. Works on Apache (.htaccess), Nginx (config snippet), and via PHP fallback.
* Added Serving Method setting (auto-detect, .htaccess, Nginx, PHP).
* Bulk conversion now supports the Themes directory in addition to Uploads.
* Fixed bulk image count (was unreliable on large libraries).
* Fixed progress bar not resetting when there is nothing to convert.
* Added translations for German, French, Japanese, Portuguese, Russian, Persian, Vietnamese, and Sinhala.
* Changed delete-on-remove default to off to prevent accidental file deletion.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.3.0 =
Fixes GIF, PNG-8, and BMP conversion failing on the GD engine, and adds the Image Engine setting (Settings, Fmtify), which replaces the Backend selector on the Bulk Convert page. Run a Fresh pass after upgrading to convert images earlier versions missed.

= 1.2.15 =
Major correctness release: Bulk Convert no longer stops early on large or partially failing libraries, rewrite rules survive permalink saves, and missing images return real 404s. Run a Fresh pass after upgrading to pick up anything earlier versions missed.

= 1.2.14 =
Fixes a WP Plugin Check false positive for missing sanitize_callback on register_setting(). Safe to upgrade; no functional changes.

= 1.2.13 =
Performance and correctness fixes. Safe to upgrade; no breaking changes.

= 1.2.12 =
No functional changes. Safe to upgrade.

= 1.2.11 =
Updated translations for all 8 locales. Safe to upgrade.

= 1.2.10 =
Fixes bulk convert progress display flickering back to an intermediate count after completing. Safe to upgrade.

= 1.2.9 =
GD is now the default backend (faster AVIF). A Backend selector appears on the Bulk Convert page when both GD and Imagick are available. Safe to upgrade; no breaking changes.

= 1.2.8 =
Adds Strip metadata setting, --mode=rebuild for WP-CLI, and Conversion tracking setting. Fixes cwebp stripping EXIF by default. Run Full rebuild after upgrading to regenerate sidecars with correct metadata.

= 1.2.5 =
Faster AVIF encoding. Safe to upgrade.

= 1.2.4 =
Faster conversion for large images. Safe to upgrade.

= 1.2.3 =
Fixes bulk conversion progress always stuck at 0 when WP-Cron is active. Safe to upgrade.

= 1.2.2 =
Fixes 504 Gateway Timeout errors during bulk conversion on sites behind an Nginx proxy. Safe to upgrade.

= 1.2.1 =
Fixes bulk conversion progress bar never advancing on local dev environments, a batch lock that could get permanently stuck, and a critical bug where get_batch() processed attachment ID 0 instead of real IDs. Upgrade recommended for all users.

= 1.2.0 =
Fixes bulk conversion stats always showing 0, adds Fresh pass / Full rebuild scope selector, and ensures .htaccess rules are kept in sync. Safe to upgrade; no breaking changes.

= 1.1.7 =
Fixes settings sanitizer for LiteSpeed servers. Safe to upgrade.

= 1.1.6 =
No functional changes. Deactivate and reactivate after upgrading so the plugin rewrites the .htaccess block under the new "Fmtify" marker.

= 1.1.5 =
Fixes AVIF serving on Apache when both WebP and AVIF are enabled. Go to Settings → Fmtify and click Save Settings to update your .htaccess rules.

= 1.1.4 =
Fixes Apache serving — .htaccess rules now write automatically on activation. Safe to upgrade; deactivate and reactivate the plugin to apply rules to existing installs.

= 1.1.3 =
Renames the plugin to "Fmtify - Auto WebP and AVIF". No functional changes; safe to upgrade.

= 1.1.2 =
Fixes bulk conversion not starting when Themes folder is selected and all uploads are already converted. Safe to upgrade.

= 1.1.1 =
Sidecar cleanup is now always on when deleting images — no configuration needed. Safe to upgrade.

= 1.1.0 =
Adds transparent WebP/AVIF content-type serving, bulk folder selection, and translations for 8 languages. No breaking changes; safe to upgrade.

= 1.0.0 =
Initial release.
