Automatically optimise uploaded images using the PHP GD library — no external APIs, no account required.
| Author: | domclic (profile at wordpress.org) |
| WordPress version required: | 5.9 |
| WordPress version tested: | 6.9.4 |
| Plugin version: | 1.0.56 |
| Added to WordPress repository: | 28-03-2026 |
| Last updated: | 28-03-2026 |
| Rating, %: | 0 |
| Rated by: | 0 |
| Plugin URI: | |
| Total downloads: | 42 |
![]() Click to start download |
|
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.
Features
- 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
.webpsidecar file alongside every optimised image (requires GD compiled with WebP support). - Zero external dependencies — no API keys, no third-party services, no composer packages.
- Graceful degradation — if GD is not available the plugin skips processing and logs a notice; uploads are never blocked.
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
FAQ
No. Everything is processed locally using the PHP GD library that ships with PHP.
Not automatically. The plugin only processes images at the time of upload. To re-optimise existing images you will need to re-upload them or use a separate bulk-processing tool.
By default the plugin overwrites the original file with the optimised version. Make sure you keep your own backups if you need to recover originals.
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.
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.
Yes. Uploaded WebP images are re-compressed using the configured WebP quality setting.
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.
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.
1.0.54
- Security: Nonce is now verified before accessing
$_GETparameters on the settings page. - Security:
.htaccesswrite in originals directory uses exclusive file locking and logs errors on failure. - Security:
render_env_row()now escapes output withwp_kses_post()instead of relying on caller discipline. - Performance: All unbounded
posts_per_page => -1queries replaced with paginated batching viaelsio_get_all_ids()to prevent memory exhaustion on large sites. - Performance: Dedup scan primes the post meta cache in batches, eliminating N+1 queries.
- Fix: Stale backup detection — if backup meta exists but the file is missing on disk, a fresh backup is created instead of silently skipping.
- Improvement: WordPress 5.9+ version enforced on activation with a clear error message.
- Improvement: Direct database queries in URL replacer and dedup now invalidate WordPress object cache (
clean_post_cache()/wp_cache_delete()) after updates.
1.0.52
- Fix:
preg_replace()null return now triggers a WP_Error instead of silently writing an empty restore path during format-changed restores (PNG→JPEG or vice versa). - Fix: Restored file is validated after
copy()— a 0-byte write now returns a WP_Error and the file is removed rather than silently replacing the live image. - Fix: Bulk optimisation aborts with an error response when the pre-optimisation backup cannot be created, preventing data loss.
- Fix: Originals tab now auto-scans and shows backup stats as soon as the page loads, without requiring a manual Refresh click.
1.0.51
- New: Original file backup and restore. Before each optimisation (upload and bulk), the plugin saves a copy of the source image to elsio-originals/ inside the uploads folder. A new Originals tab lets you restore all images to their pre-optimisation state or discard the backups to free disk space. Backups are protected from direct browser access on Apache and are deleted automatically when an attachment is removed.
1.0.50
- Security: Path traversal via symlinks — upload directory guard now uses
realpath()to resolve symlinks before comparing paths, preventing a symlink inside uploads from targeting files elsewhere on the filesystem. Applies to both bulk optimisation and thumbnail regeneration AJAX handlers. - Security:
wp_upload_dir()error return is now checked before accessingbasedir; if the uploads directory cannot be resolved the request is rejected cleanly. - Fixed:
filesize()returnsfalseon stat failure; all call sites now check forfalsebefore casting toint, preventing silent 0-byte size comparisons that could produce wrong PNG→JPEG or WebP sidecar decisions. - Fixed:
set_error_handler()in the bulk optimiser is now wrapped in try/finally, guaranteeing the handler is restored even ifgetimagesize()throws an exception.
1.0.49
- Fixed: WebP sidecar was not generated for images that were already within size limits and did not need recompression. The early-return path in
optimize()now generates the sidecar when missing andgenerate_webpis enabled.
1.0.48
- Fixed:
insert_rules()inELSIO_Htaccessnow aborts when the initial block-removal step fails, preventing duplicate marker blocks from being written to.htaccess.
1.0.47
- Fixed: WebP rewrite rules were inserted after the WordPress block in .htaccess, causing Apache to short-circuit on its “file exists → stop [L]” rule before ever evaluating the WebP conditions. Rules are now always placed before
# BEGIN WordPress. Existing installations that have the block in the wrong position are automatically corrected on next settings save or plugin activation.
1.0.46
- Fixed:
.webp.webpdouble extension created when the source file was already a WebP (e.g. an uploaded.webpimage or a WebP thumbnail).generate_webp_sidecar()now skips WebP sources entirely — the.htaccessrewrite rule only covers JPEG/PNG requests so a WebP sidecar for a WebP file would never be served anyway.
1.0.45
- Fixed: Original image was left without a .webp sidecar after running Regenerate Thumbnails.
compress_thumbnails_filternow also generates the main file’s WebP sidecar when missing. During normal uploads the existing sidecar is detected and skipped, so no double-processing occurs.
1.0.44
- Fixed: Thumbnail .webp sidecars were orphaned on disk when an attachment was deleted.
delete_webp_sidecar()now reads attachment metadata and removes the .webp sidecar for every thumbnail size alongside the main file’s sidecar.
1.0.43
- Fixed: Dead
result.style.display = 'none'assignment in ghost-scan handler was immediately overridden — removed the redundant line. - Fixed: Orphaned JSDoc block that no longer documented any function was removed from admin JS.
- Fixed: File-level docblock in
class-elsio-bulk-optimizer.phpnow correctly lists all six AJAX endpoints (was “three”). - Fixed:
get_active_tab()docblock now includes'regen'in its return value list.
1.0.42
- Fixed: Thumbnail regeneration no longer fails with “metadata could not be saved” —
wp_update_attachment_metadata()returns false both on DB error and when the stored value is unchanged, so false was treated as success rather than a hard error.
1.0.41
- Improved:
wp_enqueue_scriptnow uses the WP 6.3+ array form withstrategy: defer, allowing the browser to download the admin script in parallel and execute it after DOM parsing — no change to behaviour, better page load performance. - Improved:
register_settingnow declarestype: objectfor schema completeness as recommended since WP 5.5.
1.0.40
- Added:
uninstall.php— removes all plugin options and post-meta (elsio_options,_elsio_optimised,_elsio_thumbs_regen,_elsio_file_hash) when the plugin is deleted.
1.0.39
- Fixed:
require_oncefor admin image helpers now loaded after permission check in both bulk-optimizer and thumb-regen AJAX handlers. - Fixed:
wp_update_attachment_metadata()return value checked in thumbnail regeneration — returns an error instead of silently marking success if the DB write fails. - Fixed: All admin buttons now have explicit
type="button"to prevent accidental form submission. - Fixed: Added documentation comment in url-replacer noting the
allowed_classes => falselimitation for PHP-serialized object properties.
1.0.38
- Fixed: Critical JavaScript syntax error introduced in 1.0.37 — macOS
sedwrote literal\n\tcharacters instead of real newlines when splitting the timer variable declarations, breaking the entire admin script (regen, dedup scan, bulk optimise, and ghost cleanup all non-functional).
1.0.37
- Fixed: Regenerate Thumbnails tab was completely broken —
regenLog()was called but onlyregenLogMsg()was defined, causing a JavaScript runtime error. - Fixed:
unserialize()inELSIO_Url_Replacernow passes['allowed_classes' => false]to prevent PHP object injection from malicious serialised postmeta values. - Fixed: Replaced the single shared
pendingTimerwith four separate timers (bulkTimer,ghostTimer,dedupTimer,regenTimer) so concurrent or back-to-back operations can never cancel each other’s inter-image delay.
1.0.36
- Added: Regenerate Thumbnails tab — bulk-regenerates all thumbnail sizes for existing Media Library images, one at a time with progress bar, stop button, and reset flags. Useful after changing image sizes in your theme.
1.0.35
- Fixed: PNG→JPEG URL replacement now covers
post_excerptand all postmeta values (including PHP-serialised data stored by Elementor, ACF, WPBakery, and other page builders), not justpost_content. The newELSIO_Url_Replacerhelper is also used by the duplicate-finder cleanup.
1.0.34
- Fixed: Bulk optimiser no longer calls
wp_generate_attachment_metadata()unconditionally on every image. It now only regenerates metadata (and thumbnails) when the format changed (PNG→JPEG) or the image was resized — i.e. when stored metadata is genuinely stale. For pure compression passes, existing thumbnails are compressed in-place and metadata is left untouched, preventing valid attachment records from being overwritten with potentially incomplete data. - Fixed: Thumbnails were previously double-processed during bulk runs — once by
compress_thumbnails_filter(triggered insidewp_generate_attachment_metadata) and again by the manual loop that always followed. The two code paths are now mutually exclusive.
1.0.33
- Fixed: The “Optimised” and “Pending” stat counters on the Bulk Optimise tab now update automatically after a run completes or flags are reset. A dedicated
elsio_bulk_get_statsAJAX endpoint was added to return all three counters (total / optimised / pending) in one call.
1.0.32
- Fixed:
png_has_transparency()now returnstrue(assume transparency, skip conversion) when GD cannot load the PNG, instead offalse— prevents producing a broken JPEG from an unreadable image. - Fixed:
resize_image()now returns early when the calculated output dimensions round to zero, avoiding aimagecreatetruecolor(0, 0)failure on very small source images. - Fixed: PNG→JPEG URL replacement in the bulk optimiser now fetches matching rows and replaces in PHP (
str_replace) instead of using SQLREPLACE(), so only exact URL occurrences are updated and surrounding content cannot be corrupted. - Fixed: Duplicate file scan now caches the MD5 hash in post meta (keyed on file mtime) so repeated scans skip unchanged files — large libraries scan significantly faster on subsequent runs.
- Fixed:
ajax_process_one()now validates that the attachment file path falls within the uploads directory before processing, guarding against third-party filters that could redirect to an unexpected location. - Improved: PNG transparency Stage 3 is now split into a fast 20×20 grid sample (3a) followed by a full pixel scan (3b). Transparent images with scattered pixels exit after the grid; opaque images are still guaranteed correct via the full scan.
1.0.31
- Fixed: Race condition in WebP sidecar size comparison —
filesize()calls are now guarded withfile_exists()to prevent PHP warnings if a concurrent process removes the file between stat-cache clear and size read. - Fixed:
generate_webp_sidecar()now guardsimagewebp()with an internalfunction_exists()check, making the method safe to call directly without relying on the caller’s guard. - Fixed:
resize_image()now returns false immediately when either source dimension is zero, preventing a division-by-zero on corrupted image files. - Fixed:
png_has_transparency()now validates the 8-byte PNG signature before reading the color-type byte, so a truncated or mis-identified file cannot produce a false result. - Fixed: Duplicate-finder regex patterns now use
preg_quote()on the ID string for future-proofing, even thoughabsint()already guarantees digits only. - Improved: Replaced magic literals (
4,6,0x7F,9,26) inELSIO_Image_Optimizerwith named class constants (PNG_COLOR_TYPE_GREYSCALE_ALPHA,PNG_COLOR_TYPE_RGBA,GD_ALPHA_BITMASK,GD_PNG_MAX_COMPRESSION,PNG_HEADER_READ_BYTES) for clarity. - Improved: Informational
error_logmessages (PNG→JPEG discarded, WebP sidecar discarded) now carry an[INFO]tag to distinguish them from actual errors in the log.
1.0.30
- Fixed:
png_has_transparency()Stage 3 previously sampled a 10×10 pixel grid, which could miss a small transparent area. It now scans every pixel so that even a single transparent pixel prevents PNG→JPEG conversion.
1.0.29
- Fixed: PNG→JPEG conversion broke image links in posts. Two root causes:
handle_upload_filter()returned the original$uploadarray unchanged after conversion — WordPress created the attachment record withimage/pngMIME type and the old.pngURL even though the file on disk was already a.jpg. The filter now updates$upload['file'],$upload['url']and$upload['type']when conversion happened, so the attachment is registered correctly from the start.- Bulk optimizer never updated existing
<img>references in post content after conversion. It now captures the old PNG URL before changing the stored file path, callswp_update_post()to setpost_mime_typetoimage/jpeg, then runs aREPLACE()SQL query to rewrite all occurrences of the old URL to the new JPEG URL across all post content.
1.0.28
- Fixed: Palette PNGs (color type 3, e.g. icons) had their transparency silently missed by
png_has_transparency()—imagecolorat()returns a palette index on non-truecolor images whose high bits are not alpha. The function now callsimagepalettetotruecolor()before sampling so alpha bits are meaningful. - Fixed: Transparent corners (typical for circular/icon PNGs) could theoretically be missed by the 10×10 grid if all sampled points fell inside an opaque area. The function now explicitly checks all four corners as a dedicated stage before the grid scan, guaranteeing corner transparency is always detected.
1.0.27
- Fixed:
PHP Warning: imagewebp(): Palette image not supported by webp— palette-based PNGs (color type 3, indexed color) loaded by GD are not truecolor and cannot be passed toimagewebp()or certainimagepng()operations. Added animageistruecolor()check inload_image()immediately afterimagecreatefrompng(): if the result is a palette image,imagepalettetotruecolor()is called before returning the resource. This also prevents three duplicate warnings (original + thumbnails).
1.0.26
- Fixed: Pending count in the stats row was not refreshed after ghost attachment cleanup completed. Added
refreshPendingCount()call at the end of the ghostcleanNext()loop.
1.0.25
- Fixed: “Fix all duplicates” had no effect — duplicate attachments were never actually deleted.
new URLSearchParams({ 'dup_ids[]': [1,2,3] })serialised the array as a single comma-joined string (dup_ids[]=1%2C2%2C3), so PHP received a string rather than an array,is_array()returned false, and the ID list was discarded. Replaced theObject.assign+URLSearchParams(object)pattern inpost()andpostRaw()with abuildParams()helper that iterates array values and appends them individually, producing correctdup_ids[]=1&dup_ids[]=2serialisation.
1.0.24
- Fixed: PHPCS warning — replaced
fopen/fread/fcloseinpng_has_transparency()withfile_get_contents(..., 0, 26)+phpcs:ignoreannotation; reads only the 26-byte PNG header without loading the whole file via WP_Filesystem.
1.0.23
- Fixed: XSS risk in dedup summary —
buildDedupSummaryHTML()injected backend values directly into.innerHTML. Replaced with DOM-basedbuildDedupSummary()usingtextContentthroughout; no user-controlled data can reach the parser. - Fixed:
dedupProcessNext()anddedupFinish()accesseddedupBar,dedupProgressWrap,dedupLabel,btnDedupFix,btnDedupScanwithout null checks — any missing DOM element crashed the entire fix flow. Added guards throughout. - Fixed:
onGhostsCleanClick()calledJSON.parse(btnClean.dataset.ids)without try/catch — malformed data would throw and break the cleanup handler. Wrapped in try/catch with an error log message. - Fixed: PNG→JPEG path detection in bulk optimizer checked
file_exists($jpg_path)alone — a pre-existing .jpg with the same stem would be mistakenly used. Now also verifies the original PNG is gone (!file_exists($file_path)) before treating the .jpg as the converted output. - Fixed:
formatBytes()did not handle negative or non-numeric input — normalised withMath.max(0, parseInt(...) || 0). - Fixed:
sprintf()returnedundefinedfor out-of-bounds positional args — added bounds check, returns empty string instead. - Fixed: Pending
setTimeouttimers were not cancelled on page unload — AJAX requests could fire after navigation. Addedbeforeunloadlistener that clearspendingTimer. - Fixed:
is_apache()returned false for Apache servers whereSERVER_SOFTWAREis masked by a proxy. Addedfunction_exists('apache_get_modules')as a fallback. - Clarified: PNG compression intentionally reuses the JPEG quality setting for GD compression level — added inline comment to make this explicit.
1.0.22
- Fixed: PNG→JPEG conversion block destroyed the GD image resource before falling through to step 3 — any PNG that triggered the path (JPEG not smaller, or save failed) would attempt to use a freed resource. Restructured to early-return only when JPEG is actually kept;
$imageis now destroyed exactly once. - Fixed:
png_has_transparency()only sampled a 10×10 pixel grid, so PNGs with an alpha channel (color type 4 or 6) that happened to have all sampled pixels fully opaque were incorrectly converted to JPEG. Added a pre-check of the PNG color-type header byte (offset 25) — types 4 and 6 now returntrueimmediately without loading the image.
1.0.21
- Fixed: Gutenberg block
"id":5LIKE pattern also matched"id":15— replaced SQL REPLACE with PHP-sidepreg_replaceusing a negative-lookahead(?!\d)boundary, preventing IDs that start with the same digits from being corrupted. - Fixed: Dedup progress bar had class
elsio-progress-barinstead ofelsio-progress-fill— the animated fill bar was invisible. - Fixed:
ajax_process_group()had no upper bound ondup_ids[]— added a hard limit of 1000 IDs per request to prevent DoS via oversized payloads. - Fixed: PNG→JPEG conversion always deleted the source PNG even when the resulting JPEG was larger — now compares file sizes and discards the JPEG if it is not smaller, keeping the original PNG.
- Fixed: WebP sidecar deletion (when sidecar was not smaller than source) was silent — now logs a notice under
WP_DEBUG_LOG.
1.0.20
- New feature: “Clean Up Ghost Attachments” section in the Bulk Optimise tab. Scans for Media Library records whose files no longer exist on disk, then deletes the orphaned DB records one by one with a confirmation step. Files are already missing — only the database records are removed.
1.0.19
- Fixed: Ghost attachments (DB record exists but file missing on disk) were counted as errors during bulk optimisation. Now returned as a success with
skipped:true, logged in orange as a warning, and excluded from the error count.
1.0.18
- Fixed: “Fix all duplicates” button had no click handler —
initDedup()ran on page load when the Duplicates tab was not active, so allgetElementById()calls returned null. Replaced with document-level event delegation so clicks are captured regardless of which tab rendered the page.
1.0.17
- Fixed: Duplicate finder stat boxes showed
undefinedlabels — i18n keysgroups,redundantFiles,recoverablewere missing from the PHP localization array. - Fixed: “Scanning Media Library…” displayed the HTML entity literally — replaced with plain ASCII ellipsis since
textContentdoes not parse HTML entities.
1.0.16
- New feature: “Find Duplicates” tab under Settings → Image Optimiser.
- Scans the entire Media Library for exact duplicate images using MD5 file hashing.
- Keeps the oldest copy (lowest attachment ID) and updates all references: image URLs in post content, Gutenberg block
idattributes,wp-image-XCSS classes, and_thumbnail_idfeatured image meta. - Duplicate attachments are then permanently deleted via
wp_delete_attachment(). - Scan uses a 120-second timeout to handle large libraries gracefully.
1.0.15
- Fixed: WordPress-generated thumbnail sizes (150×150, 300×225, 768×576, 1024×768, etc.) were never compressed — only the original file was processed. Added
wp_generate_attachment_metadatafilter to compress all thumbnails on upload, and added thumbnail compression loop to the bulk optimiser. Resize and PNG→JPEG conversion are intentionally disabled for thumbnails to preserve WordPress filename expectations.
1.0.14
- Fixed: PHP 8.1 deprecation notice “Implicit conversion from float to int loses precision” on
imagecolorat()inpng_has_transparency(). Operator precedence caused$stepto remain a float ((int)was only applied tomin(), not to the division result). Fixed by wrapping the full expression:(int) round( min( $w, $h ) / 10 ).
1.0.13
- Fixed: Bulk optimiser did not update WordPress attachment metadata after resizing. The Media Library was still showing original dimensions from the database even though the file on disk had been resized. Now calls
wp_generate_attachment_metadata()andwp_update_attachment_metadata()after each optimisation. - Fixed: PNG→JPEG conversion during bulk optimise now correctly updates the stored file path via
update_attached_file().
1.0.12
- Fixed: PNG images within the configured max dimensions were silently skipped and never compressed.
needs_recompression()now includesimage/pngso PNG files are always re-encoded regardless of their dimensions.
1.0.11
- Fixed: HTML entity
—displayed as literal text in the bulk log (e.g.saved — 74%). Thelog()function usestextContentwhich does not parse HTML entities — replaced the entity with the actual UTF-8 em dash character—in the PHP string.
1.0.10
- Fixed: Double percent sign (
%%) displayed in bulk optimisation log and progress counter. The PHP%%escape (for PHP sprintf) was incorrectly used in strings passed to the JavaScript sprintf — replaced with a single literal%.
1.0.9
- Fixed: Added
@packagetag to main plugin file doc block to satisfy PHPCS file comment requirements. - Fixed: Added
phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecatedon allimagedestroy()calls — function remains callable in PHP 8 and is required for PHP 7.4 compatibility. - Fixed: Added
phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handleronset_error_handler()calls used to suppress GD warnings without the@operator. - Fixed: Corrected doc comment long descriptions in
class-elsio-htaccess.phpthat started with lowercase (PHPCS capitalization rule). - Fixed: Added loop incrementer
phpcs:ignoreannotation on the pixel-sampling loop inpng_has_transparency().
1.0.8
- Fixed: WordPress.Security.EscapeOutput.OutputNotEscaped on $notice_text — store raw translated string with __() and escape at the point of output with esc_html().
1.0.7
- Fixed: WordPress.WP.I18n.MissingTranslatorsComment — extracted all __() and _n() calls that contain placeholders into standalone variables so the /* translators: */ comment sits on the line immediately above the i18n function call, satisfying WPCS token-level validation.
1.0.6
- i18n: Fixed
\uXXXXJS escape sequences used inside PHP strings — replaced with HTML entities (…,—,“,”). - i18n: Switched multi-placeholder strings to positional format (
%1$s,%2$s…) so translators can reorder arguments. - i18n: Added
/* translators: */comments to all strings containing placeholders. - i18n: Updated JS
sprintf()to support both sequential (%s) and positional (%1$s) token formats. - i18n: Created
languages/effortless-simple-image-optimiser.potwith all 60+ translatable strings.
1.0.5
- Fixed: PNG compression level formula was inverted — quality 82 now correctly maps to GD level 7 instead of level 2.
- Fixed:
imagewebp()return value now checked; failures are logged under WP_DEBUG_LOG. - Fixed: WebP sidecar is now deleted if it is not smaller than the source file, preventing .htaccess from serving a larger WebP.
- Fixed: Bulk optimizer now resolves the correct output path when PNG is converted to JPEG, preventing false 100% saving reports.
- Fixed: Added
delete_attachmenthook —.webpsidecar files are now removed when the parent image is deleted from the Media Library. - Fixed: AJAX
fetch()requests in JS now carry a 60-secondAbortControllertimeout to prevent the bulk queue hanging indefinitely.
1.0.4
- PHP/WordPress/PHPCS/WPCS compliance pass across all files.
- Replaced
@error suppression withset_error_handler/restore_error_handler. error_log()calls are now guarded byWP_DEBUG_LOG.unlink()replaced withwp_delete_file().file_get_contents()replaced with WP_Filesystem in htaccess class.is_writable()replaced withwp_is_writable().insert_with_markers()andget_home_path()dependencies loaded explicitly.$_SERVER['SERVER_SOFTWARE']sanitised withsanitize_text_field()+wp_unslash().- All
$_POSTinputs sanitised withabsint()+wp_unslash(). $_GETtab/action values sanitised withsanitize_key()+wp_unslash().- Inline
oninputJS removed from range fields; replaced withdata-linkedattributes handled in JS. printfwith raw HTML replaced with properwp_kses()escaping viarender_env_row()helper.echo $noticereplaced with pre-escaped output split into typed variables.$wpdb->delete()direct query replaced withdelete_metadata( 'post', 0, $key, '', true ).new WP_Query()->found_postsreplaced withget_posts()+count()andno_found_rows => true.- Added
returnstatements afterwp_send_json_error()calls to make flow explicit. - Added
label_forto all settings fields for accessible form labels. - IIFE closure in JS uses
}()form;finallychain preserved for modern browsers.
1.0.3
- Added Apache
.htaccessWebP delivery via mod_rewrite (Option 1). - Rules inserted automatically on activation, removed on deactivation, re-synced on settings save.
Vary: Acceptheader appended so CDNs cache JPEG and WebP variants separately.- Environment sidebar now shows Apache detection,
.htaccesswritability, and rewrite rule status. - “Insert now” button to manually inject rules if they are missing post-activation.
1.0.2
- Added Bulk Optimise tab under Settings → Image Optimiser.
- New AJAX endpoints:
elsio_bulk_get_ids,elsio_bulk_process_one,elsio_bulk_reset_flags. - Progress bar, live log and stats panel for bulk runs.
- Post-meta flag
_elsio_optimisedprevents reprocessing already-optimised images. - “Re-optimise all” checkbox and “Reset flags” button for manual re-runs.
1.0.1
- Added readme.txt with full documentation.
- Minor version bump following project conventions.
1.0.0
- Initial release.
- JPEG compression with configurable quality.
- PNG compression and optional PNG → JPEG conversion (transparency-aware).
- Image resizing with aspect-ratio preservation.
- Optional WebP sidecar generation.
- Admin settings page under Settings → Image Optimiser.
- GD availability check with graceful fallback.
