=== Pixelcinch Image Optimizer ===
Requires at least: 5.6
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.6.2
Author: Tayyab (https://tayyabatta.com)
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Converts your Media Library images to compressed WebP, targeting roughly 100KB per image.

== Description ==

Converts images to WebP and compresses each one toward a 100KB target:

1. Media Library optimizer - scans existing attachments (JPG/PNG/GIF,
   plus optional WebP/SVG/RAW support) and converts them to WebP in
   small batches, avoiding PHP timeouts.
2. Manage WebP images - review any WebP image in your library, compress
   individual ones further, and choose exactly which original files to
   permanently delete once you're happy with the result.

== Compression modes ==

Choose one mode; it applies wherever conversion happens:

- Less Compress (quality mode): starts at WebP quality 90, only steps
  down to a floor of quality 70, and never resizes the image. If a
  detailed image can't get under 100KB without going below that quality
  floor, it is left larger rather than degraded further.
- High Compress (aggressive mode): starts at quality 78, steps down to
  a floor of quality 20, and if still too big, shrinks dimensions by
  20% at a time down to a 120px floor - forcing the result under 100KB
  "at any price."

== Requirements ==

- Imagick PHP extension with WebP support (preferred), or
- GD PHP extension with WebP support (imagewebp())

== Security notes ==

- All admin actions require the `manage_options` capability and a
  verified nonce.
- Every file this plugin processes is validated as a real image via
  getimagesize() + fileinfo mime detection (or, for SVG, a dedicated
  content-safety scan), not by trusting its extension.
- Filenames are sanitized and destination paths are checked to stay
  inside wp-content/uploads before any file is written.
- All AJAX output is via wp_send_json_success/error; all admin UI output
  is escaped (esc_html/esc_attr/esc_url).

The Media Library optimizer keeps your original files on disk; it only
repoints the attachment's registered file to the new .webp version, so
you can restore from the original if needed - until you use the new
"Delete original files" tool below (see 1.2.0).

== For Developers ==

Extension points other plugins can hook into - none of these change
default behavior on their own, they're no-ops until something uses them:

- `pixelcinch_after_convert` (action) - fires after an attachment is
  converted, with the attachment ID and a result summary array.
- `pixelcinch_after_delete_original` (action) - fires after an original
  (pre-WebP) file is actually deleted from disk.
- `pixelcinch_compression_profile` (filter) - the quality/resize tuning
  values used for a given compression mode.
- `pixelcinch_scannable_mime_types` (filter) - the list of MIME types
  offered as scan/convert source formats.
- `pixelcinch_default_settings` (filter) - the default settings array.
- `pixelcinch_sanitize_settings` (filter) - the sanitized settings array
  right before it's stored; add your own keys here alongside your own
  entries in `pixelcinch_default_settings`.

== Changelog ==

= 2.6.2 =
- Compliance: removed the custom AJAX output-buffering mechanism entirely.
  It previously opened an output buffer in one method and closed it from
  a separate 'shutdown'-hooked method, which left buffer ownership
  dependent on hook execution order rather than a single function scope.
  The plugin no longer performs any output buffering.

= 2.6.1 =
- Compliance: the AJAX output buffer opened to strip stray PHP
  warnings/notices from JSON responses is now explicitly closed via
  ob_get_clean() on 'shutdown', rather than left open for PHP's implicit
  end-of-request flush.

= 2.6.0 =
- Renamed to "Pixelcinch Image Optimizer" (slug: pixelcinch-image-optimizer).
  Display name, text domain, admin page slugs, and internal references
  updated throughout. No functional changes.

= 2.5.1 =
- Ran the plugin through PHP_CodeSniffer with the WordPress Coding
  Standards ruleset and cleaned up everything it flagged: statement
  alignment, increment style, an array-formatting spot, an unused
  filter-callback parameter, and a reserved-keyword parameter name.
  No behavior changes.

= 2.5.0 =
- Added six action/filter hooks (see "For Developers" above) so other
  code can extend conversion, deletion, compression tuning, supported
  formats, and settings without modifying this plugin's files. Purely
  additive - no existing behavior changes.

= 2.4.3 =
- Compliance: replaced an `ini_set( 'display_errors', ... )` call used to
  keep stray PHP notices out of our AJAX JSON responses with output
  buffering instead, since plugins altering that php.ini directive at
  runtime is flagged by Plugin Check. No behavior changes.

= 2.4.2 =
- Compliance: bumped "Tested up to" to 7.1.
- Compliance: prefixed the two variables in uninstall.php that Plugin
  Check flagged as unprefixed globals. No behavior changes.

= 2.4.1 =
- Renamed the plugin to "Pixelcinch Image Optimizer" (previously "WebP
  Image Fetcher & Compressor"), per WordPress.org naming guidelines -
  descriptive of what the plugin actually does (compress images) with a
  distinct identifier at the front instead of leading with "WebP".
- Compliance: the dashboard widget's inline `<style>` block is now
  enqueued properly via wp_enqueue_style() instead.
- Compliance: every function/class/constant/option/hook prefix changed
  from `iwc`/`IWC` to a prefix derived from the new name, to go with the
  rename above. No behavior changes.

= 2.4.0 =
- Removed: the "Fetch images from a URL" tool, which scanned a given
  page for image sources and downloaded them. The WordPress.org Plugin
  Directory review team classified this as scraping functionality,
  which isn't permitted regardless of the safeguards that were in place
  (SSRF protections, same-image validation, an explicit on-screen notice
  to only use it on sites you own/have permission for, etc.) — the
  capability itself is what's restricted, not the intent behind it. This
  plugin now only ever processes images already in your own Media
  Library, or new uploads made through WordPress's normal upload flow.
- The "Manage WebP images" step is renumbered to step 2 accordingly.
  No other functionality changed.

= 2.3.0 =
- New: additional source formats for conversion, each opt-in via checkbox
  on the Tools page or Settings:
  - WebP: recompresses an already-WebP image in place using the full
    quality/resize search (not just the single-shot "compress further"
    slider), useful for a one-time pass over WebP files that arrived
    from somewhere other than this plugin.
  - SVG: rasterized to WebP via ImageMagick, only when the server's
    ImageMagick build actually has SVG support (most shared hosting
    does not — the checkbox is disabled with a clear note when
    unavailable). Every SVG is scanned before processing and rejected
    if it contains scripts, event handler attributes, XML external
    entity declarations, or references to external URLs — the class of
    content that makes ImageMagick+SVG a known security risk
    ("ImageTragick"). Rendering also runs under tight ImageMagick
    resource limits (memory/width/height/time) so a pathological file
    fails safely instead of exhausting server resources.
  - RAW camera files (CR2, CRW, NEF, NRW, ARW, RAF, ORF, RW2, PEF, SRW,
    DNG): converted via ImageMagick, only when the server's build
    supports that specific format. Automatically picks the
    highest-resolution embedded frame (RAW files often bundle a
    thumbnail and preview alongside the full frame).
- New: optional "Allow SVG/RAW uploads" setting (off by default) so
  these file types can actually be uploaded through the Media Library
  at all — WordPress blocks them otherwise. The same SVG safety scan
  runs at upload time, before a file is ever written to disk, not just
  when converting it later.
- Fix: the image-conversion loop is now wrapped in try/catch for every
  source type (previously only some paths were) — an unexpected
  mid-conversion failure always returns a clean error now instead of
  risking an uncaught exception.
- The "unsupported format" case (e.g. an SVG/RAW file on a server
  without the right ImageMagick support) is tracked distinctly so the
  scanner doesn't keep retrying it, and is reflected in the Tools page
  library overview.

= 2.2.2 =
- Compliance: resolved 6 remaining Plugin Check warnings in
  class-pixelcinch-local.php (WordPress.Security.ValidatedSanitizedInput.InputNotSanitized).
  A recognized sanitizer (absint()/sanitize_text_field()) is now applied
  directly at every $_POST read, rather than only inside this plugin's
  own whitelist helper methods a few lines later - the previous version
  was already safe (the helpers do whitelist/absint everything), but
  static analysis couldn't see past the function call boundary. No
  behavior changes.

= 2.2.1 =
- Compliance: addressed every item from a WordPress.org Plugin Check
  scan - added the required License/License URI header and a LICENSE.txt
  file, updated readme.txt's "Tested up to" and added the missing Short
  Description, replaced a raw is_writable() call with WordPress's
  wp_is_writable() wrapper in uninstall.php, added a missing translators
  comment for a string with a placeholder, and annotated every direct
  database call and nonce-verified-via-shared-helper AJAX input with the
  specific reasoning (no WP_Query equivalent exists, cache is invalidated
  immediately after, or the nonce check already happened one line above
  via PIXELCINCH_Security::assert_request() - which static analysis can't trace
  into on its own). No behavior changes.

= 2.2.0 =
- Change: "Manage WebP images" (step 3) now detects every WebP image in
  your library over 100KB - not just ones this plugin converted. Images
  uploaded as WebP directly, or produced by something else (a
  hosting-level optimizer, another plugin), now show up too and can be
  compressed further the same way. Untracked images that are already
  under 100KB are simply left alone, same as before.
- Deleting an original only applies to images this plugin converted
  (there's a file to delete); for untracked WebP images there's nothing
  to delete since the WebP file is the only copy - the row clearly notes
  this and you can still compress it further.

= 2.1.0 =
- Change: split the single admin page into three - Tools (day-to-day
  scan/convert/manage actions), Features (integrations like automatic
  WebP serving and Media Library detection), and Settings (persisted
  defaults) - with tabs to move between them.
- New: "Your Media Library right now" overview on the Tools page, showing
  real counts of images pending conversion, already converted by this
  plugin, and already WebP but not tracked by this plugin (e.g. uploaded
  as WebP directly, or optimized automatically by some hosts). Clears up
  the confusing case where "Scan & Convert" reports nothing to do while
  the manage list also shows nothing - usually it just means there's
  nothing outside what's already covered.
- Fix: the "Install to uploads/.htaccess" button was refusing to run on
  any server that didn't literally identify itself as "Apache" - which
  wrongly blocked LiteSpeed (used by many shared hosts, including
  Hostinger), even though LiteSpeed reads .htaccess the same way Apache
  does. It now only blocks on Nginx, which is the one case where
  .htaccess genuinely has no effect.

= 2.0.0 =
- New: Settings page (right on the main screen) to set your defaults —
  default compression mode, default scan filters, auto-convert-on-upload,
  background conversion, and auto-purge — so you don't have to reconfigure
  every run.
- New: convert new uploads automatically as soon as they're added to the
  Media Library (optional, off by default), respecting your saved
  file-type/size filters and any excluded images.
- New: "Convert to WebP" / "Exclude from WebP conversion" row actions and
  bulk actions right in the normal Media Library screen (Media > Library),
  so you don't have to leave that screen.
- New: exclude specific images permanently from every scan, auto-convert,
  and the background job, from the Media Library row action.
- New: automatic WebP-serving rules for images this plugin didn't convert
  directly (theme assets, hardcoded template images) — an Apache
  installer that only ever writes to wp-content/uploads/.htaccess (never
  your site's main .htaccess), plus a copyable Nginx snippet.
- New: detects common offload/CDN plugins (WP Offload Media, WP-Stateless,
  etc.) and warns that local file operations may not match what's
  actually served, since this plugin only reads/writes local disk.
- New: optional auto-purge — automatically delete original files N days
  after conversion, instead of relying on remembering to clean up
  manually. Off by default.
- New: optional background conversion via WP-Cron — processes a small
  batch of pending images every 5 minutes so a large library finishes
  converting without needing the browser tab open. Off by default.
- New: dashboard widget showing images converted, space saved, images
  still over 100KB, and originals still awaiting review.
- Security: centralized error logging. Real error detail (which can
  include server file paths, e.g. from Imagick exceptions) now only ever
  goes to the server's own debug log when WP_DEBUG_LOG is enabled - AJAX
  responses only ever contain short, generic messages. Also suppresses
  PHP error/warning display specifically during this plugin's AJAX
  requests as defense in depth, and wraps every risky operation
  (compression, batch conversion, deletion) in try/catch so an unexpected
  failure can't leak internals to the browser.

= 1.4.0 =
- New: scan filters on the Media Library optimizer. Choose which file
  types to include (JPG/PNG/GIF, individually), and optionally set a
  minimum file size so only images at or above that size are scanned -
  e.g. "only images over 100KB" - leaving smaller, already-lean images
  untouched. Unchecking the size filter scans images of every size, and
  checking every file type scans everything, so both "scan all" and
  narrower runs use the same controls.

= 1.3.0 =
- New: converting an image now automatically rewrites any existing pages
  or posts that link to the old file, pointing them at the new WebP file
  instead. This is a direct content rewrite - no redirects are created.
  It covers the full-size image and every registered thumbnail size, and
  runs again as a safety check right before an original file is deleted
  (useful for images converted by an older version of the plugin).
- New: "Manage converted images" replaces the old all-or-nothing delete
  tool. It lists your converted images (by default only the ones still
  over 100KB), and lets you tick just the ones you want and delete only
  their originals - not the whole library at once.
- New: per-image "Compress further" control. Pick a quality level with
  the slider and re-encode that single image on demand, without waiting
  for a full library re-scan. Uses the original file as the source when
  it's still available for a cleaner result, and keeps the image at its
  current dimensions so it doesn't jump back up to full resolution.

= 1.2.0 =
- Fix: the settings page is now registered as its own top-level "WebP
  Fetcher" menu item, so it's easy to find in wp-admin instead of only
  appearing as a submenu under Media.
- Fix: the admin page now always appears, even on servers missing
  Imagick/GD WebP support. Previously the whole menu item silently
  disappeared in that case; now the page loads and clearly explains
  the missing requirement, with the tools disabled until it's resolved.
- New: "Delete original files" tool. After converting your Media
  Library to WebP, once you've reviewed the results and are sure, you
  can permanently delete the leftover original JPG/PNG/GIF files to
  reclaim disk space. This requires ticking a confirmation checkbox
  plus a browser confirmation dialog, and only ever deletes files this
  plugin itself converted and recorded - never anything else.
