Skip to content

3.3.6 - #1237

Open
gabrielcld2 wants to merge 49 commits into
masterfrom
uat
Open

3.3.6#1237
gabrielcld2 wants to merge 49 commits into
masterfrom
uat

Conversation

airbuzz and others added 30 commits June 26, 2026 10:33
`get_size_from_slug()` returned the registered width/height from
`wp_get_registered_image_subsizes()` for any matching slug, ignoring the
`crop` flag. For non-crop sizes ( crop => false ) those values are a maximum
bounding box, not exact dimensions: WordPress scales the image to fit inside
the box while preserving aspect ratio.

Treating them as a fixed crop made the delivery layer emit, for the default
`large` ( 1024x1024, crop => false ), a transformation of `w_1024,h_1024,c_scale`
applied to a landscape/portrait source, visibly stretching it into a square.
This surfaced on blocks whose stored `<img src>` had no size suffix (so the
URL-based size lookup returned nothing and the figure-class slug fallback ran).

Return early for non-crop sizes so only genuine hard-crop sizes are pinned to
exact dimensions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
install() called get_plugin_instance()->init() unconditionally, which
re-ran the bootstrap out of sequence during plugin activation and could
destabilise plugin load order for the request (e.g. triggering ACF to
initialise too early). Only run init() when the plugin version has not
already been populated; the DB routines only need the version.
init_asset_parents() and process_parent_assets() run internal WP_Query
lookups over Cloudinary's own asset post type. These are not public-facing
content queries, but they still fire third-party the_posts/posts_results
filters, which can cause a theme/plugin to run expensive per-query work
(e.g. author-archive injection) on every one of Cloudinary's internal
queries until the request exhausts memory.

Set suppress_filters => true on these fully-specified internal queries so
they skip content filters. The WordPressVIPMinimum SuppressFilters rule is
suppressed with an explanation: these queries do not rely on the
posts_where/join/orderby filters the rule protects.
WordPress 7.1 enforces the iframed post editor and ships React 19.
Update the Cloudinary Gallery block accordingly:

- Bump the block to Block API v2 (compatible with the plugin's declared
  minimum of WP 5.6) and wrap the edit output with useBlockProps so it
  renders correctly inside the enforced iframed editor.
- Keep the save output unchanged (bare container div) so existing
  published galleries remain valid without a deprecation/migration. The
  front-end render only depends on the inner container class, not a block
  wrapper, so no markup change is needed.
- Move the block editor stylesheet to enqueue_block_assets so WP injects
  it into the iframe correctly (avoids the "added to the iframe
  incorrectly" 7.1 warning).
- Move the render-time setAttributes calls into effects to resolve the
  React 19 "Cannot update a component while rendering a different
  component" warning.
- Fix the generated container id, which produced "undefined<id>" now
  that className is no longer passed to Edit under API v2.

Verified against WordPress 7.1-beta3: fresh insert/save/reload and the
upgrade path (loading legacy v1 content) both validate with no console
errors or block validation warnings.
The Cloudinary inspector controls added to core/image and core/video
blocks had two WordPress 7.1 issues:

- setAttributes was called during render when the attachment had
  transformations, which triggers the React 19 "Cannot update a component
  while rendering a different component" warning. Moved into an effect.
- The controls read InspectorControls from the deprecated wp.editor alias.
  Switched to wp.blockEditor and declared the script dependencies
  explicitly, since the block editor globals were previously relied on
  without being registered.
The gallery settings page enqueued js/gallery.js under two handles:
'gallery_config' in enqueue_admin_scripts() and 'gallery-widget' via the
React UI component's script param. The bundle executed twice, calling
createRoot() twice on the same container, which React 19 (WordPress 7.1)
reports as an error on every settings page load.

Drop the script param from the React panel definition and keep the single
enqueue that already carries the generated asset dependencies.
src/js/blocks.js and its components accessed @WordPress packages through
the wp.* globals (wp.hooks, wp.blockEditor), which the dependency
extraction plugin cannot detect, so js/block-editor.asset.php was missing
wp-block-editor and wp-hooks. The hand-written dependency list in
class-video.php compensated for those but omitted wp-api-fetch, which
blocks.js genuinely imports and only worked because other editor scripts
loaded it first.

Convert the wp.hooks/wp.blockEditor global usages to @wordpress/hooks and
@wordpress/block-editor imports so the generated asset file is complete,
and consume it in enqueue_block_assets() instead of the static list.
…l-queries

Suppress content filters on internal asset bookkeeping queries
…activation analytics events

Wires the remaining 7 event categories from the analytics tracking spec on
top of the existing WPP-1210 custom-events framework: connection management,
asset sync, settings & navigation, media & asset actions, non-media cache,
extensions & gallery, and deactivation. All call sites reuse Analytics::track()
/ Analytics.track() and were live-verified against wp-env via WP-CLI/REST
dispatch. Adds a permanent e2e analytics-capture mu-plugin and two Playwright
specs covering connection and deactivation events.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit only included src/js/* source changes. This repo ships
compiled js/* output directly, so the extension_toggled, special_offer_clicked,
deactivation_modal_viewed, and deactivation_skipped tracking calls weren't
actually live until this rebuild. Live-verified via the full Playwright e2e
suite against wp-env with real Cloudinary credentials (13/13 passing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nts-tracking

# Conflicts:
#	php/class-admin.php
#	php/class-deactivation.php
develop bumped phpstan to level 5 after this branch was cut. The Analytics
component (added in WPP-1210) was missing from get_component()'s @return
union, so every ->track() call site read as "method.notFound" once merged.
Re-verified clean at level 5 and against the full Playwright e2e suite.
Fix: don't force non-crop size slugs to exact dimensions
…e load

Image_Preview::preview() set the sample <img>'s src to a bare '#', which
resolves to the current document's own URL — every load of Image Settings,
Video Settings, or the Responsive/Breakpoints preview (Video_Preview and
Breakpoints_Preview both extend Image_Preview) fired a second real HTTP
request back at the same admin page before global-transformations.js
replaced it with a real preview URL.

Found while investigating why settings_page_viewed fired twice per page
load: confirmed via wp-env access logs that the second request carried an
image/* Accept header and a self-referencing Referer, matching this exact
placeholder-image bug. Fixed with an inert 1x1 data-URI placeholder instead.
Re-verified: single event per load, single request in the access log,
full Playwright suite still green (13/13).
…nts-tracking

# Conflicts:
#	js/cloudinary.js
#	js/syntax-highlight.js
#	src/js/main.js
The merge commit resolved js/cloudinary.js and js/syntax-highlight.js
conflicts by taking develop's compiled output, dropping the SpecialOffer
wiring and the deactivate.js Prettier reformat. Rebuilding restores both on
top of develop's dependency upgrades and ESLint flat-config migration.
Re-verified: phpcs/phpstan clean, full Playwright suite green (13/13).
Ensure compatibility with WordPress 7.1
Covers bulk_sync_started (via a real authenticated REST call, matching what
the "Start bulk sync" button does), sync_completed + asset_sync_failed (via
direct invocation of Sync_Queue's run-tracking state machine — a full
real-upload sync run is slow/flaky in CI, and this exercises the exact same
code path already verified manually), and sync_settings_changed (real UI:
toggling the auto-sync radio on the Connect page).

Needed test isolation: a real bulk_sync_started call kicks off a genuine
background sync thread via a non-blocking loopback request, which would
otherwise keep running and race later tests.
Covers asset_edited + transformation_applied (scope: asset) via a real
authenticated REST call to save_asset, and transformation_applied
(scope: global) via a real settings save on the Image Settings page.

transformation_count reliably comes back 0 for the asset-scope case since
the synthetic (fileless) test attachment has no real resource type to
derive it from — asserted the event shape instead of the count value.
Covers cache_items_viewed, cache_items_toggled, asset_cache_purged,
all_cache_purged, and cache_uploaded — all via real authenticated REST
calls / direct action dispatch against the live Rest_Assets endpoints.

Found along the way: rest_purge_all() resolves its `parent` param via
Assets::get_param(), which is only ever populated by the in-memory
activate_parent() call inside Assets::activate_parents() for settings-
configured paths — a different (and non-persisted-across-requests)
lookup than get_asset_parent() (DB-backed, used by show_cache/etc). A
custom test-created cache point can't satisfy that lookup without
also registering a real settings path, so the purge test targets one
of the plugin's own default non-media paths instead, which is already
active for exactly this reason.
gabriel-detassigny and others added 19 commits August 6, 2026 13:04
Covers extension_toggled (real UI: a native checkbox click, bypassing
Playwright's visibility check for the collapsed Extensions sidebar panel)
and gallery_configured (via Admin::save_settings() directly with a
synthetic gallery_config payload, matching what the React gallery panel
serializes — avoids driving the full React UI for one settings save).

Two timing/state gotchas fixed along the way: extensions.js debounces its
change handler by 1000ms before firing, and gallery_config payloads need a
value guaranteed to differ from whatever's already saved, since
save_settings() silently no-ops on an unchanged value.
… plugin_uninstalled

Rounds out connection management and deactivation analytics coverage.
Also fixes fakeCloudinaryConnected() to upsert cloudinary_connect instead
of a raw UPDATE, which silently no-ops once a real plugin_uninstalled run
deletes the option row.
…fix asset_cache_purged flake

The e2e capture mu-plugin only logged outgoing analytics-api.cloudinary.com
requests and let them proceed, so every local/CI test run was leaking
synthetic events and deactivation-reason submissions into the real
production collector. It now preempts with a synthetic 200 instead.

Also fixes cache-analytics.spec.js's asset_cache_purged test: none of the
plugin's default non-media cache paths are active on a fresh install (they
default off), so the test now explicitly enables the uploads path via a real
settings save before purging, rather than assuming it's already active.
…-visit side effect

Enabling the cache path was already explicit via a real settings save, but
materializing the underlying asset-parent post depended on a subsequent
admin page load's Assets::update_asset_paths() side effect — a
timing-sensitive path that flaked once under CI load (passed on retry).
Create the post directly instead, removing that dependency entirely.
json_decode($content, true) in String_Replace::replace_strings() collapsed
empty JSON objects ({}) into PHP arrays, which wp_json_encode() then
re-serialized as []. Since this runs on every REST API response site-wide,
it silently corrupted the shape of any JSON payload containing empty
objects - including MCP JSON-RPC handshakes (e.g. Novamira), whose clients
validate responses against a strict schema and reject [] where {} is
expected.

Decode as objects instead of associative arrays so shape survives the
round trip.
Add taffydb dev dependency required for docs generation
Fix JSON shape corruption in REST API responses breaking MCP integrations (Novamira)
…exit tracking

- connectivity_check_failed: split check_status() into a pure method and a
  new cron_check_status() wrapper, so interactive connection checks/saves
  (test_connection()) no longer emit it alongside connection_test_result.
- bulk sync: rest_start_sync() now calls mark_run_started() before
  start_queue() (was after, dropping early thread results from the tally),
  and uses shutdown_queue('queue') instead of stop_queue() on manual stop
  so the run state doesn't leak into the next run's sync_completed.
- sync_completed tally: split into two atomically-incremented options
  (RUN_TALLY_SYNCED_KEY/RUN_TALLY_ERRORS_KEY) instead of a single
  get/modify/update array, which could lose updates across concurrent
  sync threads.
- sync_settings_changed: added a diff guard so no-op saves (e.g. the
  wizard re-submitting auto_sync unconditionally) don't fire the event.
- cache_items_viewed: only tracked on page 1 with no search term, instead
  of on every pagination/search request against an open cache point.
- notice_dismissed: sanitize the notice token via sanitize_key() before
  using it as the transient key and event param.
- deactivation_skipped: replaced a 150ms setTimeout guess with a new
  Analytics.trackReliable() using navigator.sendBeacon(), which the
  browser guarantees to dispatch across the immediate navigation.
- Moved a misplaced class constant to the top of Admin with the others.

tests/e2e/sync-analytics.spec.js's cleanup helper updated for the new
split tally option names. Full 32-test e2e suite verified green.
…e-dismissal regression

- increment_option(): after the raw SQL increment, also purge the option
  from the 'notoptions' WP object-cache group. mark_run_started()'s
  delete_option() calls mark these keys as known-absent there; on sites
  with a persistent object cache (Redis/Memcached — not present in
  wp-env/CI, so this needed direct verification via simulation) the raw
  insert never cleared that flag, so every later get_option() would
  short-circuit to 0 and sync_completed would always report 0/0.
- rest_dismiss_notice(): switch from sanitize_key() to a dot-preserving
  regex for the notice token. The token is a dotted setting slug (e.g.
  `_cld_notices.0`) that Notice::is_enabled() looks up verbatim via
  get_transient(); sanitize_key() stripped the dot, writing the transient
  under a key that never matched, so dismissible notices reappeared on
  every page load.

Both regressions were introduced by the previous review-fix commit
(6d30260) and verified directly via wpEvalFile since the e2e suite
cannot exercise a persistent object cache. Full 32-test suite still green.
Instrument connection, sync, settings, media, cache, features, and deactivation analytics events
…ull-init

Avoid re-running full plugin init during activation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants