Releases: ClickClickMedia/ccm-tools
Release list
v7.44.1
Circuit-breaker to suppress Redis connection-refused log flooding on sites where the Redis PHP extension is installed but the server is unreachable (e.g. shared hosting). After the first refused connection, subsequent requests skip retrying silently for 60 seconds, then try again — so at most one error per minute instead of one per page load. Configurable via WP_REDIS_REFUSED_BACKOFF (seconds, default 60; set 0 to disable). Also captures the real PHP error message from the Redis extension instead of the generic 'Connection failed'.
v7.44.0
Changes in v7.44.0
WebP now actually served on sites that use <picture> elements
On themes that hand-code <picture> markup, the browser selects a matching <source> and serves that URL — it only falls back to the <img> when no source matches. The converter previously only rewrote the <img> src to WebP and never touched <source>, so the browser kept serving the original PNG/JPG. The frontend WebP pass now rewrites src and srcset on both <img> and <source> tags. Each candidate is verified against the on-disk WebP (respecting the Convert On-Demand setting) and the original URL is kept whenever no WebP exists — no broken images. The pass is idempotent and only runs for WebP-capable browsers (with Vary: Accept). An explicit <source type="image/png|jpeg|gif"> hint is corrected to image/webp when its URL is swapped.
Removed the "Use <picture> Tags" option
The setting made the plugin wrap <img> tags in generated <picture> elements — fragile across themes/page builders and impossible to guarantee safe on every site. It's gone (toggle, generation code, and its content/widget/WooCommerce filters). Existing installs lose no functionality: WebP is served purely by rewriting existing markup plus WordPress's native srcset filter.
Also
Swapped the CSS-drawn loading spinners for the branded CCM img/spinner.svg.
v7.43.0
Redis cache defaults
- Default Redis max-TTL raised from 1 hour to 7 days (
604800). The managedWP_REDIS_MAXTTLdefault was3600, which capped every cache entry — including the many objects WordPress stores with no expiry (expire = 0) — at one hour, forcing needless cache misses and DB churn. Withallkeys-lruhandling memory pressure, the new604800default keeps a sane safety ceiling while letting long-lived objects actually live. Applied across the one-step Save flow, the legacy "Add to wp-config" path, the System Info auto-config, and the settings-screen placeholder. Existing installs with an explicit Max TTL set are untouched. - Serializer defaults to igbinary when the extension is present (else
php). Smaller payloads, faster encode/decode. The constant is still only written towp-config.phpwhen non-php and the extension is loaded, and the drop-in's serializer-drift detection auto-flushes its own keys once on the switch, so existing installs migrate safely. Compression remainsnone(LZ4 + igbinary previously caused production OOMs — see v7.41.4).
No drop-in (object-cache.php) change in this release; its @version is unchanged.
v7.42.1
Fix "plugin deactivated itself" after a manual zip install + wrapper-folder zips
Flat release zips + manual Plugins → Add New → Upload made WordPress name the folder after the zip file (ccm-tools-<ver>/) beside the canonical ccm-tools/. Two active copies tripped the duplicate guard, which deactivated the plugin itself (often the good copy). The in-dashboard auto-updater was unaffected.
- Prevent: release zips now ship with a top-level
ccm-tools/wrapper folder, so manual uploads always install to/wp-content/plugins/ccm-tools/regardless of the zip filename. - Heal: the duplicate guard no longer self-deactivates. The canonical
/ccm-tools/install detects version-suffixedccm-tools-*duplicates, silently deactivates them (no teardown — wp-config and the drop-in are untouched), deletes the stale folders via the filesystem API (no uninstall hooks — shared options survive), and shows a notice. A lone version-suffixed install keeps running and warns. - Atomic drop-in replace: the object-cache auto-refresh now writes a temp file and
rename()s it into place, so a concurrent request can't read a half-writtenobject-cache.php.
The 7.42.0 and 7.41.4 release assets have also been rebuilt with the ccm-tools/ wrapper.
v7.42.0
Redis drop-in lifecycle automation + one-step Save
- Auto-replace the drop-in on plugin update.
upgrader_process_complete+ anadmin_initself-heal bring the deployedwp-content/object-cache.phpinto line with the bundled version automatically (connection-less, never clobbers a foreign drop-in, only copies when the bundled@versionis newer). Previously a version bump only raised a notice the admin had to click — so fixes like v7.41.4 never reached sites until a manual reinstall. - Auto-(re)install on activation. Covers the WP-Cron update deactivate→reactivate dance leaving a stale/missing drop-in.
- Clean teardown on disable/deactivation. Removes the drop-in and strips the managed Redis block from
wp-config.php. Genuine deactivations only — WordPress deactivates silently during updates, so caching survives updates. Saved settings are retained. - One Save does the lot. Saving Redis settings now also rewrites
wp-config.phpand refreshes the drop-in when Redis is enabled; enabling writeswp-config.phptoo. wp-config write skipped when unchanged; backups pruned to the most recent 5.
Includes everything in v7.41.4 (the 4 GB alloptions OOM fix).
v7.41.4
Harden Redis object cache against alloptions corruption causing 4 GB OOM crashes
thesportingbase.com suffered two outages (2026-05-28, 2026-06-03) where unserialize() read a corrupted length prefix from the alloptions blob (LZ4+igbinary fails to round-trip) and tried to allocate 4,295,229,440 bytes, fatalling every FPM worker that read it. v7.39.10 fixed one trigger; this hardens the cache itself.
- P1 (the real fix): the object-cache drop-in no longer persists the
options/site-optionsgroups to Redis. WP core memoisesalloptionsper request, so the cost is at most one indexedwp_optionsSELECT per worker. Override withdefine('WP_REDIS_PERSIST_OPTIONS', true);. - P2:
get()rejects a non-arrayoptions:alloptions/notoptionsas a cache miss and rebuilds from DB. - P3: auto-flush on serializer/compression drift, detected via a
rawCommandsentinel key (covers manual wp-config edits / extension changes). - P4: admin UI warns on LZ4+igbinary and documents the skipped groups.
Drop-in @version 7.19.0 → 7.41.4.
v7.41.3
Fix silent "an error occurred" deactivation after auto-update
During a WP-Cron auto-update, WordPress's Plugin_Upgrader silently deactivates the plugin via active_before, replaces the files, then calls activate_plugin() to re-enable it. Both WP's active_after and our own after_install invoke activate_plugin() — and because the plugin is no longer in active_plugins at that point, WP runs the full activation path including plugin_sandbox_scrape(), which includes ccm.php a second time within the same request.
The OLD ccm.php was already loaded at request boot, so the second include re-executed the unguarded global function definitions (ccm_tools_hide_all_notices, ccm_initialize_plugin, class CCMSettings, …) and PHP fatal'd with "Cannot redeclare function". WordPress's fatal-error handler caught it, paused the plugin, and surfaced the generic "an error occurred" notice — but with no entry in debug.log unless WP_DEBUG_LOG was enabled.
Fix: added a CCM_TOOLS_FILE_LOADED sentinel at the very top of ccm.php that cleanly returns on the second include, so activation completes without re-declaring symbols.
v7.41.2
Fix wp / jQuery undefined errors from defer/delay
Root cause
WordPress emits inline scripts via wp_add_inline_script(handle, ..., 'after') — these run at parse time and reference symbols (wp.i18n, jQuery) defined by the parent script. defer_js had no always-exclude list, so it was deferring wp-i18n / wp-a11y / wp-hooks, which made the inline _after companions throw ReferenceErrors.
Three of the four console errors on a real site traced to this exact pattern.
Plugin-side fixes
- New
ccm_tools_perf_has_inline_companion($handle)helper inspects$wp_scripts->registered[$handle]->extra['after'|'before']and returns true if any inline companion is registered. Bothdefer_jsanddelay_jsnow skip these handles automatically. - Added
wp-a11yandwp-polyfillto the always-exclude list for both defer and delay.
Visual check
The dynamic-content filter previously dismissed any issue mentioning "carousel" or "slider" — but the AI was correctly flagging "carousel JS not initializing, testimonials stacked vertically" as a real regression. New filter overrides the dismissal when breakage words (not initializing, broken, regression, stacked vertically, falling back, etc.) or plugin setting names appear.
v7.41.1
Guard against orphan parent toggles in AI apply
When the AI returned a feature toggle (critical_css, preconnect, dns_prefetch, lcp_preload, preload_key_requests, delay_third_party) without the companion data key in the same response, the plugin used to flip the toggle on with empty data — a no-op in the rendered HTML but a confusing UI state, and PSI variance could blame it for unrelated score drops (a real run showed -19pts attributed to critical_css alone).
Two layers of defence:
- Server-side: post-validate apply_recommendations and force any parent toggle back to false if its required data is empty.
- Client-side: pre-filter detects orphan parent toggles before the apply loop and skips them, logging the reason. Saves a ~30-second mobile retest per orphan and the key is added to the session already-applied set so the AI is told.
v7.41.0
AI Performance Optimiser — reliability fixes
Three issues surfaced in a real optimisation run on a tall WooCommerce site:
1. Visual check on tall pages no longer fails silently
Hub now scales screenshots wider/taller than 7800px to fit Claude Vision's 8000px hard limit. Plugin detects the legacy oversize error, logs a clear "skipped — page too tall" message, and stops uselessly retrying. New + "image_clamped" + flag surfaces when auto-scaling occurred.
2. Stops re-suggesting already-applied settings
Before the apply loop runs, the plugin now compares each AI recommendation against current settings and drops anything that would be a no-op. Skipped keys are sent back to the AI on the next iteration so it picks fresh levers instead of wasting a slot.
3. Persistent per-URL learning
Settings that cause a ≥10pt mobile drop on a URL are now remembered across runs (60-day TTL, 50-key cap, LRU evicted). On the next One-Click Optimise for the same URL, those settings are filtered out before apply and the AI is told they're proven incompatible. New AJAX handlers: + "ai_record_known_bad, ai_get_known_bad, ai_clear_known_bad" + .