Skip to content

Release - #2978

Merged
milicavs merged 13 commits into
masterfrom
development
Aug 20, 2026
Merged

Release#2978
milicavs merged 13 commits into
masterfrom
development

Conversation

@pirate-bot

@pirate-bot pirate-bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Linked issues

This release will close the following issues once merged:

Public changelog

  • Fixed WooCommerce product data controls hidden in WooCommerce Builder.
  • Fixed frontend pages stopping when widget CSS cannot use the WordPress filesystem.
  • Fixed PHP warnings when the Post Content dynamic value renders.
  • Fixed animation pages crashing when another plugin loads a different CSS parser.
  • Fixed frontend pages stopping when an Otter plugin class cannot load.
  • Fixed a security issue in the Stripe Checkout block where the checkout price could be manipulated via the URL. Thanks to sungbyeongchan for the responsible disclosure.

pirate-bot and others added 7 commits August 12, 2026 15:51
…m() (#2938)

CSS_Handler::is_writable() called WP_Filesystem() before its
function_exists() fallback, so a frontend request with an active
sidebar and no generated widget stylesheet fataled with
"Call to undefined function WP_Filesystem()" when the include did
not expose the function. Run the guard first so the request degrades
to the inline widget CSS fallback, and verify the initialized
$wp_filesystem instance before reading its method.

Adds an isolated-process PHPUnit regression test and a frontend e2e
spec covering the blocked-filesystem fallback and the written-file
path.

Fixes #2937

Co-authored-by: Luca Dobrescu <luca.dobrescu@vertistudio.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… context post (#2939)

* fix: postContent dynamic tag reads the loop globals instead of its context post

get_the_content() takes the post as its third argument; the context post ID
was passed as the first one ($more_link_text), so $post stayed null and core
fell back to the loop globals. When a theme or plugin leaves those globals
clobbered, post-template.php warns "Undefined array key -1" (plus a
preg_match() deprecation on PHP 8) and the tag renders empty.

Fixes #2929

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover the postContent recursion guard and isolate the e2e fixtures

Addresses the Copilot review on #2939.

The guard in mark_exceptions() is what the get_the_content() argument fix
actually changes: it reads the context post's content to decide whether a
nested postContent tag would recurse. Both mismatch cases are now covered -
a nested tag in the context post must fire the guard, and a nested tag in
the loop-global post must not blank a clean context post. The guard is
asserted directly before rendering because an unfired guard makes
apply_dynamic_content() recurse until the process dies, so the test has to
fail fast instead of hanging.

The e2e spec namespaced its Query Loop search token per run and now deletes
its own post and page in afterAll. wp-env is persistent and the fixed token
had already accumulated twelve records over six local runs, any of which
the perPage:1 loop could resolve instead of the run's own post. Teardown is
best-effort per record so one failed request cannot orphan the others.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Luca Dobrescu <luca.dobrescu@vertistudio.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…2956)

* fix: frontend fatal when a listed class cannot be autoloaded

Main::autoload_classes() instantiated every entry of its class list without
checking that the class is loadable. On a package with a stale Composer
classmap the Atomic Wind entry resolved to nothing and `new $classname()`
threw an uncaught Error on `init`, taking down every request (#2954).

Skip entries that are not loadable so a packaging or third-party filter
problem degrades to a missing feature instead of a site-wide fatal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: resolve the plugin's own classes when the Composer classmap is stale

Composer exposes `inc/` through a generated classmap in `vendor/`, so a map that
does not match the files on disk — an interrupted plugin update, an OPcache entry
compiled from the previous version — makes a class that is present unloadable.

Register a fallback loader for `ThemeIsle\GutenbergBlocks\*` that resolves a class
from its file name. It is appended to the SPL stack, so Composer still answers
first and the fallback only runs when Composer has no answer. The class then
loads and its feature keeps working, instead of being skipped by the guard in
Main::autoload_classes().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: drop the fallback autoloader, log skipped classes instead

Per review: a stale Composer classmap is an environment issue (broken or
partial update) that fixes itself on reinstall, so keeping the class_exists()
guard is enough. Remove inc/class-autoloader.php and its SPL registration —
it locked us into the file-naming convention, added a second load path, and
could hide a genuinely broken release. When the guard skips an unloadable
entry it now logs it, so a lost feature is visible instead of silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Luca Dobrescu <luca.dobrescu@vertistudio.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…S parsing (#2958)

* fix: fatal when another plugin loads a different php-css-parser release

Base_CSS::get_animation_css() parsed the animation stylesheet with the
bundled Sabberworm parser even when another plugin had already loaded
classes from a different release of it. Mixing the two fatals at
class-link time ("Declaration of CSSList::addComments... must be
compatible") and that error is not catchable, so a guard now verifies
every sentinel class resolves to Otter's own vendor directory before
anything parser-related loads — including the transient read, which
stores parser objects. When the guard fails, the full stock
otter-animation stylesheet is enqueued instead, matching the
optimize-off delivery path.

Adds an isolated-process PHPUnit regression test and a frontend e2e
spec covering both the foreign-parser fallback and the bundled-parser
optimized path.

Fixes #2942

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: serve the stock animation stylesheet from the frontend loader on parser collision

Copilot review: the get_animation_css() fallback enqueue was lost once
the generated post CSS was cached — later requests never reach the
parser path, and the cached CSS carries no animation rules. Move the
fallback delivery into Blocks_Animation::frontend_load(), which runs on
every request that renders an animated block, and cover a cached-CSS
reload in the e2e spec. Also delete the spec's probe posts in afterAll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: reject any preloaded foreign Sabberworm symbol, not only the sentinels

Copilot review: the parser touches more classes than the three sentinels
(OutputFormat, DeclarationBlock, KeyFrame, and the cached object graph),
so a foreign copy of any of them slipped past the guard and still mixed
releases. has_own_css_parser() now first rejects every already-declared
class, interface, or trait under Sabberworm\CSS that does not resolve to
the bundled vendor directory — before the sentinel checks can autoload
anything — and only then resolves the sentinel entry points. The sandbox
gains an outputformat scenario covering a foreign non-sentinel class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: match foreign Sabberworm declarations case-insensitively

Copilot review (suppressed note): PHP class and namespace names are
case-insensitive, so a foreign symbol declared with different casing is
the same runtime class but slipped past the case-sensitive prefix scan.
Use stripos so every colliding declaration is rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: trim animation-CSS collision comments to the load-bearing why

Cut the verbose guard and preflight comments in Base_CSS and
Blocks_Animation down to the constraint a reader cannot infer: the
foreign-parser fatal is uncatchable, so the guard must run before any
parser class loads. Also correct the has_own_css_parser() docblock to
say it checks every loaded Sabberworm\CSS symbol, not only the ones the
parser touches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: log when a foreign php-css-parser release disables animation CSS

The parser-collision guard silently served the stock animation stylesheet,
so a conflicting Sabberworm release looked like the optimization had stopped
working for no reason. Log the skip, mirroring the autoload-skip notice from
 #2956, so the cause is visible in the debug log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Luca Dobrescu <luca.dobrescu@vertistudio.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ducts (#2926)

* fix: keep Meta Boxes pane open by default for WooCommerce Builder products

Since WP 6.7 the iframed post editor renders meta boxes in a bottom drawer
that is collapsed unless the user previously opened it. Otter 3.2.0 bumped
all blocks to apiVersion 3, which switched WooCommerce Builder product edit
screens to the iframed canvas, hiding the WooCommerce Product data panel
(price, inventory, etc.) behind the collapsed drawer.

Default the drawer to open on builder-enabled product screens via the
preferences setDefaults API, which never overrides an explicit user choice.

Closes #2822

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: rescue Product data metabox from the side column + e2e coverage

The metabox Move-up/down arrows persist the order instantly and, at the
edge of an area, relocate a box into the adjacent area. From the block
editor (WooCommerce Builder products) one accidental click can strand
woocommerce-product-data in 'side', where it renders inside the ~280px
sidebar and its layout breaks. Correct it at read time via the
get_user_option_meta-box-order_product filter.

Adds Playwright coverage for the builder edit screen: drawer open by
default, side-area rescue, classic-editor scoping for non-builder
products, and explicit user preference winning over the new default.
WooCommerce (latest stable) joins .wp-env.json; the e2e bootstrap gains
product-creation and user-meta helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: address review, scope the rescue, and keep WooCommerce out of other suites

- Add the missing void return type that failed PHPStan.
- Scope restore_product_data_location() to builder-enabled products, so a
  layout saved on a classic product is left as the user arranged it.
- Add PHPUnit coverage for both guards: the inline meta-boxes-pane default
  (present for builder products, absent for plain products and non-product
  posts) and the metabox-order rescue.
- Mount WooCommerce as an inactive wp-env mapping from the copy composer
  already installs, and activate it only around the WooCommerce spec.
  Activating it for the whole environment changed editor load behavior and
  broke the performance suite.
- Reset the shared admin user's drawer preference and metabox order in
  beforeEach as well as afterEach, so the first test is not skewed by
  state left behind by an earlier run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: load a single WooCommerce copy in the PHPUnit bootstrap

Mounting WooCommerce at wp-content/plugins/woocommerce made the existing
activate_plugin() call in the bootstrap resolve — it had been a silent
no-op while that directory did not exist. activate_plugin() includes the
plugin file through plugin_sandbox_scrape(), so WooCommerce was loaded
via two different paths and PHP fataled on redeclaring the unguarded
WC() and wc_get_container() functions, failing the suite with exit 255
before any test ran.

Load the mounted copy when present so both includes resolve to the same
path, keeping the vendor copy as a fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: delete the products the WooCommerce spec creates

Each test published a product and teardown only reset user metadata, so a
reused wp-env gained four products per run and later product suites could
run against that residue.

Track the ids the spec creates and hard-delete exactly those in afterEach,
while WooCommerce is still active so its product lookup tables are cleaned
too. The endpoint ignores ids that are not products, so a stale id cannot
remove unrelated content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: restore the script registry after the WooCommerce Builder test

Removing the dummy wp-edit-post handle in teardown left the shared
$wp_scripts registry without WordPress's own registration, so later tests
could fail depending on suite order. Discard the registry instead, which
makes the next wp_scripts() call rebuild every default registration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: run the suites against current WooCommerce

The composer constraint is open but the lock still pinned 10.1.2 from a
year ago, so mounting the Composer copy meant neither the new regression
suite nor the PHPUnit bootstrap exercised current WooCommerce, and the
pin also contradicted the php-stubs/woocommerce-stubs ^10.8 requirement.

Refresh the lock to 10.9.4, the version the regression was reproduced on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Marius Cristea <marius.cristea@vertistudio.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: pirate-bot <58979018+pirate-bot@users.noreply.github.com>
@pirate-bot

Copy link
Copy Markdown
Contributor Author

Bundle Size Diff

Package Old Size New Size Diff
Animations 178.33 KB 178.33 KB 0 B (0.00%)
Blocks 1.65 MB 1.65 MB 0 B (0.00%)
CSS 7.83 KB 7.83 KB 0 B (0.00%)
Dashboard 172.49 KB 172.49 KB 0 B (0.00%)
Onboarding 68.14 KB 68.14 KB 0 B (0.00%)
Export Import 4.73 KB 4.73 KB 0 B (0.00%)
Pro 439.82 KB 439.82 KB 0 B (0.00%)

@pirate-bot

pirate-bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Plugin build for b749aa4 is ready 🛎️!

@pirate-bot

pirate-bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

E2E Tests

Playwright Test Status: See serial and parallel matrix jobs

Performance Results serverResponse: {"q25":439.31,"q50":474.11,"q75":478.3,"cnt":10}, firstPaint: {"q25":1633.63,"q50":1784.74,"q75":2035.54,"cnt":10}, domContentLoaded: {"q25":4245.71,"q50":4311.11,"q75":4329.22,"cnt":10}, loaded: {"q25":4249.26,"q50":4314.7,"q75":4332.93,"cnt":10}, firstContentfulPaint: {"q25":2813.23,"q50":3233.74,"q75":3253.22,"cnt":10}, firstBlock: {"q25":19869.26,"q50":19985.17,"q75":20097.71,"cnt":10}, type: {"q25":30.85,"q50":34.82,"q75":37.87,"cnt":10}, typeWithoutInspector: {"q25":28.04,"q50":30.54,"q75":32.21,"cnt":10}, typeWithTopToolbar: {"q25":41,"q50":43.78,"q75":48.83,"cnt":10}, typeContainer: {"q25":17.41,"q50":18.27,"q75":19.46,"cnt":10}, focus: {"q25":158.97,"q50":163.15,"q75":172.13,"cnt":10}, inserterOpen: {"q25":50.24,"q50":53.48,"q75":55.13,"cnt":10}, inserterSearch: {"q25":31.92,"q50":33.48,"q75":34.97,"cnt":10}, inserterHover: {"q25":5.48,"q50":5.74,"q75":6.34,"cnt":20}, loadPatterns: {"q25":2098,"q50":2148.82,"q75":2205.52,"cnt":10}, listViewOpen: {"q25":275.55,"q50":278.97,"q75":282.99,"cnt":10}

@milicavs
milicavs merged commit f6a4709 into master Aug 20, 2026
20 checks passed
@pirate-bot

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 3.2.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@pirate-bot pirate-bot added the released Indicate that an issue has been resolved and released in a particular version of the product. label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released Indicate that an issue has been resolved and released in a particular version of the product.

Projects

None yet

5 participants