From 4415671cac3f9ec66f7a1b82c8403cd3b5c1b57f Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 14:24:00 +0300 Subject: [PATCH 01/12] Keep WooCommerce Product data panel visible with WooCommerce Builder Products using WooCommerce Builder by Otter are forced into the block editor, where WordPress 6.7+ collapses the Meta Boxes drawer by default. This hid the WooCommerce Product data metabox (price, inventory, etc.), leaving merchants unable to find their product options. Open the meta boxes panel by default on builder-enabled product edit screens via the core/edit-post metaBoxesMainIsOpen preference default, while still respecting an explicitly persisted user choice. Adds e2e coverage for the product edit screen with the builder enabled, disabled, and toggled, and adds the composer-vendored WooCommerce to the wp-env test environment. Fixes #2822 Co-Authored-By: Claude Fable 5 --- .wp-env.json | 1 + .../inc/plugins/class-woocommerce-builder.php | 36 ++++++ .../e2e/blocks/woocommerce-builder.spec.js | 109 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/blocks/test/e2e/blocks/woocommerce-builder.spec.js diff --git a/.wp-env.json b/.wp-env.json index ecc59bbe2..528441d8e 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -4,6 +4,7 @@ "testsEnvironment": false, "plugins": [ ".", + "./vendor/wp-content/plugins/woocommerce", "https://downloads.wordpress.org/plugin/ai-provider-for-openai.zip" ], "themes": [ "./test/emptytheme" ], diff --git a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php index cdfdf59e0..925801e40 100644 --- a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php +++ b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php @@ -26,6 +26,7 @@ class WooCommerce_Builder { */ public function init() { add_action( 'add_meta_boxes', array( $this, 'register_metabox' ) ); + add_action( 'enqueue_block_editor_assets', array( $this, 'ensure_metabox_panel_visible' ) ); add_filter( 'use_block_editor_for_post_type', array( $this, 'enable_block_editor' ), 11, 2 ); add_filter( 'wc_get_template_part', array( $this, 'wc_get_template_part' ), 1000, 3 ); add_action( 'otter_blocks_woocommerce_content', 'the_content' ); @@ -92,6 +93,41 @@ public function render_metabox( $post_type ) { } } + /** + * Keep the WooCommerce Product data metabox visible in the block editor. + * + * Products using the WooCommerce Builder are edited in the block editor, + * where WordPress 6.7+ collapses the meta boxes panel by default — hiding + * the Product data options (price, inventory, etc.). Open the panel by + * default; an explicit user preference still wins over the default. + * + * @access public + */ + public function ensure_metabox_panel_visible() { + $post = get_post(); + + if ( ! $post || 'product' !== $post->post_type ) { + return; + } + + if ( ! boolval( get_post_meta( $post->ID, '_themeisle_gutenberg_woo_builder', true ) ) ) { + return; + } + + wp_add_inline_script( + 'wp-edit-post', + "( function() { + if ( ! window.wp || ! wp.data || ! wp.domReady ) { + return; + } + + wp.domReady( function() { + wp.data.dispatch( 'core/preferences' ).setDefaults( 'core/edit-post', { metaBoxesMainIsOpen: true } ); + } ); + } )();" + ); + } + /** * Enable the Block Editfor * diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js new file mode 100644 index 000000000..025472b7c --- /dev/null +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -0,0 +1,109 @@ +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +/** + * External dependencies + */ +import { execSync } from 'child_process'; + +/** + * Regression tests for https://github.com/Codeinwp/otter-blocks/issues/2822 + * + * Enabling `WooCommerce Builder by Otter` on a product stores the + * `_themeisle_gutenberg_woo_builder` meta, and `WooCommerce_Builder::enable_block_editor()` + * then forces the block editor for that product. WooCommerce's `Product data` + * metabox (price fields included) must stay reachable on the product edit + * screen either way — merchants otherwise lose access to pricing options. + */ + +const runWpCli = ( command ) => execSync( `npx wp-env run cli -- ${ command }`, { encoding: 'utf8' }); + +const tryRunWpCli = ( command ) => { + try { + return runWpCli( command ); + } catch ( error ) { + return ''; + } +}; + +const createProduct = ( title ) => { + const output = runWpCli( `wp post create --post_type=product --post_status=publish --post_title="${ title }" --porcelain` ); + const match = output.match( /^\s*(\d+)\s*$/m ); + return Number( match[ 1 ]); +}; + +const attachScreenshot = async( testInfo, page, name ) => { + await testInfo.attach( name, { + body: await page.screenshot({ fullPage: true }), + contentType: 'image/png' + }); +}; + +test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { + let productId; + + test.beforeAll( () => { + + // Prevent the WooCommerce activation redirect from hijacking admin page loads. + tryRunWpCli( 'wp transient delete _wc_activation_redirect' ); + + // Drop persisted editor preferences so the assertions exercise the + // plugin-provided "Meta Boxes" panel default rather than a leftover choice. + tryRunWpCli( 'wp user meta delete 1 wp_persisted_preferences' ); + }); + + test.beforeEach( async({}, testInfo ) => { + productId = createProduct( `Woo Builder ${ testInfo.workerIndex }-${ testInfo.retry }-${ Date.now() }` ); + }); + + test.afterEach( () => { + tryRunWpCli( `wp post delete ${ productId } --force` ); + }); + + test( 'product edit screen shows the Product data panel and the builder toggle', async({ admin, page }) => { + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); + + await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); + await expect( page.locator( '#_regular_price' ) ).toBeVisible(); + + // The license is stubbed as active in e2e, so the Otter metabox must offer the toggle. + await expect( page.locator( '#otter_woo_builder' ) ).toBeVisible(); + await expect( page.locator( 'a#otter-woo-builder' ) ).toHaveText( /Enable WooCommerce Builder/ ); + }); + + test( 'Product data panel stays reachable after enabling the builder', async({ admin, page }, testInfo ) => { + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); + await page.locator( 'a#otter-woo-builder' ).click({ timeout: 120000 }); + await page.waitForLoadState( 'domcontentloaded', { timeout: 120000 }); + + // The toggle must persist the builder flag server-side. + expect( runWpCli( `wp post meta get ${ productId } _themeisle_gutenberg_woo_builder` ) ).toContain( '1' ); + + // Re-open the edit screen the way a merchant would after the toggle. + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); + + const isBlockEditor = ( await page.locator( 'body.block-editor-page' ).count() ) > 0; + testInfo.annotations.push({ type: 'editor-loaded', description: isBlockEditor ? 'block editor' : 'classic editor' }); + await attachScreenshot( testInfo, page, 'product-edit-screen-with-builder-enabled' ); + + // Whichever editor loads, the WooCommerce product options must stay editable. + await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); + await expect( page.locator( '#_regular_price' ) ).toBeVisible(); + }); + + test( 'disabling the builder restores the Product data panel', async({ admin, page }) => { + + // Enable, then disable through the same query-arg toggle the metabox buttons use. + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=1` ); + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=0` ); + + expect( tryRunWpCli( `wp post meta get ${ productId } _themeisle_gutenberg_woo_builder` ) ).not.toContain( '1' ); + + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); + + await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); + await expect( page.locator( '#_regular_price' ) ).toBeVisible(); + }); +}); From 7beb659c5d7d9ad684e94847bcde549f174e75cc Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 14:38:08 +0300 Subject: [PATCH 02/12] Fix CI: PHPStan return type and WooCommerce double-load in PHPUnit bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tests bootstrap required the composer-vendored woocommerce.php and then activate_plugin() include_once'd the same plugin from the plugins directory, which now exists since wp-env mounts it there — fataling with "Cannot redeclare WC()". Load the plugins-directory copy when present so both loads resolve to the same file. Also add the missing @return annotation flagged by PHPStan. Co-Authored-By: Claude Fable 5 --- .../inc/plugins/class-woocommerce-builder.php | 1 + tests/bootstrap.php | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php index 925801e40..5a2a71265 100644 --- a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php +++ b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php @@ -102,6 +102,7 @@ public function render_metabox( $post_type ) { * default; an explicit user preference still wins over the default. * * @access public + * @return void */ public function ensure_metabox_panel_visible() { $post = get_post(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 51701404f..4bc16848a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,7 +20,17 @@ function _manually_load_plugin() { require dirname( dirname( __FILE__ ) ) . '/otter-blocks.php'; - require dirname( dirname( __FILE__ ) ) . '/vendor/wp-content/plugins/woocommerce/woocommerce.php'; + + // Prefer the copy in the plugins directory (mounted there by wp-env) so the + // activate_plugin() call below resolves to the already-loaded file instead + // of redeclaring WooCommerce from the composer-vendored copy. + $woocommerce = WP_PLUGIN_DIR . '/woocommerce/woocommerce.php'; + + if ( ! file_exists( $woocommerce ) ) { + $woocommerce = dirname( dirname( __FILE__ ) ) . '/vendor/wp-content/plugins/woocommerce/woocommerce.php'; + } + + require $woocommerce; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); From b0b1774148758fa9ae537bbf9a8cbad9e36ab67d Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 15:07:55 +0300 Subject: [PATCH 03/12] Address review: serialize woo-builder spec and assert block editor loads The spec deletes the admin user's persisted preferences, so run it in the serial project to avoid racing parallel specs. Also assert the builder actually routes the product into the block editor so the metabox checks can't pass against the classic editor. Co-Authored-By: Claude Fable 5 --- src/blocks/test/e2e/blocks/woocommerce-builder.spec.js | 7 ++++--- src/blocks/test/e2e/playwright.config.js | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index 025472b7c..c13ccd1e9 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -84,11 +84,12 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { // Re-open the edit screen the way a merchant would after the toggle. await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); - const isBlockEditor = ( await page.locator( 'body.block-editor-page' ).count() ) > 0; - testInfo.annotations.push({ type: 'editor-loaded', description: isBlockEditor ? 'block editor' : 'classic editor' }); + // The builder must actually route the product into the block editor — + // otherwise the metabox assertions below pass without exercising it. + await expect( page.locator( 'body.block-editor-page' ) ).toBeVisible(); await attachScreenshot( testInfo, page, 'product-edit-screen-with-builder-enabled' ); - // Whichever editor loads, the WooCommerce product options must stay editable. + // The WooCommerce product options must stay editable in the block editor. await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); await expect( page.locator( '#_regular_price' ) ).toBeVisible(); }); diff --git a/src/blocks/test/e2e/playwright.config.js b/src/blocks/test/e2e/playwright.config.js index cac9dd4c7..1062c84f5 100644 --- a/src/blocks/test/e2e/playwright.config.js +++ b/src/blocks/test/e2e/playwright.config.js @@ -57,7 +57,10 @@ const SERIAL_SPECS = [ '**/blocks/design-library.spec.js', // Flips the site-wide atomic-wind blocks option. - '**/blocks/atomic-wind-list-view.spec.js' + '**/blocks/atomic-wind-list-view.spec.js', + + // Deletes the admin user's persisted editor preferences; must not race parallel specs. + '**/blocks/woocommerce-builder.spec.js' ]; const config = defineConfig({ From 03f1db824d12f0a5feb0b48197ba7b175099205f Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 15:08:21 +0300 Subject: [PATCH 04/12] Make the WordPress.org Plugin Check pass Fix the real violations: add ABSPATH direct-access guards to all pattern files and development.php, correct the outdated readfile phpcs ignore sniff name, and annotate the atomic-wind iframe script tags that cannot use wp_enqueue_script. Scope the check to shipped code: exclude src/packages sources and phpcs.xml.dist, and ignore the offloading rule flagged by the remote demo images referenced in registered patterns (product decision). Co-Authored-By: Claude Fable 5 --- .github/workflows/plugin-check.yml | 5 +++++ development.php | 4 ++++ inc/patterns/aw-bento-features.php | 4 ++++ inc/patterns/aw-blog-cards.php | 4 ++++ inc/patterns/aw-case-study-metrics.php | 4 ++++ inc/patterns/aw-cta-banner.php | 4 ++++ inc/patterns/aw-faq-cards.php | 4 ++++ inc/patterns/aw-feature-cards.php | 4 ++++ inc/patterns/aw-feature-split.php | 4 ++++ inc/patterns/aw-gallery-grid.php | 4 ++++ inc/patterns/aw-glow-cta.php | 4 ++++ inc/patterns/aw-hero-centered.php | 4 ++++ inc/patterns/aw-hero-split.php | 4 ++++ inc/patterns/aw-icon-grid.php | 4 ++++ inc/patterns/aw-portfolio-grid.php | 4 ++++ inc/patterns/aw-pricing-tiers.php | 4 ++++ inc/patterns/aw-quote-spotlight.php | 4 ++++ inc/patterns/aw-review-stars.php | 4 ++++ inc/patterns/aw-stats-banner.php | 4 ++++ inc/patterns/aw-team-grid.php | 4 ++++ inc/patterns/aw-testimonial-cards.php | 4 ++++ inc/patterns/aw-timeline-steps.php | 4 ++++ inc/patterns/aw-video-feature.php | 4 ++++ inc/patterns/blog-featured-list.php | 4 ++++ inc/patterns/bold-image-hero.php | 4 ++++ inc/patterns/cafe-about.php | 4 ++++ inc/patterns/cafe-contact.php | 4 ++++ inc/patterns/cafe-homepage.php | 4 ++++ inc/patterns/cafe-menu.php | 4 ++++ inc/patterns/contact-form-split.php | 4 ++++ inc/patterns/contact-info-cards.php | 4 ++++ inc/patterns/content-checklist.php | 4 ++++ inc/patterns/countdown-launch.php | 4 ++++ inc/patterns/dark-cta-band.php | 4 ++++ inc/patterns/faq-boxed-list.php | 4 ++++ inc/patterns/faq-two-column.php | 4 ++++ inc/patterns/form-glow-card.php | 4 ++++ inc/patterns/form-gradient-split.php | 4 ++++ inc/patterns/gallery-feature-split.php | 4 ++++ inc/patterns/gallery-overlay-grid.php | 4 ++++ inc/patterns/icon-feature-trio.php | 4 ++++ inc/patterns/icon-grid-bordered.php | 4 ++++ inc/patterns/maps-location.php | 4 ++++ inc/patterns/newsletter-inline-bar.php | 4 ++++ inc/patterns/newsletter-signup.php | 4 ++++ inc/patterns/portfolio-overlay-grid.php | 4 ++++ inc/patterns/pricing-columns.php | 4 ++++ inc/patterns/pricing-compare-simple.php | 4 ++++ inc/patterns/stats-row.php | 4 ++++ inc/patterns/team-cards.php | 4 ++++ inc/patterns/team-spotlight-lead.php | 4 ++++ inc/patterns/text-lead-statement.php | 4 ++++ inc/patterns/text-two-column-prose.php | 4 ++++ inc/patterns/timeline-horizontal.php | 4 ++++ inc/patterns/travel-about.php | 4 ++++ inc/patterns/travel-contact.php | 4 ++++ inc/patterns/travel-homepage.php | 4 ++++ inc/patterns/travel-tours.php | 4 ++++ inc/patterns/video-hero-play.php | 4 ++++ inc/patterns/waitlist-coming-soon.php | 4 ++++ inc/patterns/wellness-classes.php | 4 ++++ inc/patterns/wellness-contact.php | 4 ++++ inc/patterns/wellness-homepage.php | 4 ++++ inc/plugins/class-atomic-wind-blocks.php | 4 ++-- inc/server/class-dynamic-content-server.php | 4 ++-- 65 files changed, 257 insertions(+), 4 deletions(-) diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml index f07af1cdc..55f937d5c 100644 --- a/.github/workflows/plugin-check.yml +++ b/.github/workflows/plugin-check.yml @@ -48,7 +48,12 @@ jobs: tests bin .github + src + packages + exclude-files: | + phpcs.xml.dist ignore-codes: | + PluginCheck.CodeAnalysis.Offloading.OffloadedContent WordPress.WP.I18n.TextDomainMismatch textdomain_mismatch hidden_files diff --git a/development.php b/development.php index 689bd8b70..507b2de82 100644 --- a/development.php +++ b/development.php @@ -7,6 +7,10 @@ // phpcs:ignoreFile +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + if ( ! defined( 'ENABLE_OTTER_PRO_DEV' ) ) { define( 'ENABLE_OTTER_PRO_DEV', true ); } diff --git a/inc/patterns/aw-bento-features.php b/inc/patterns/aw-bento-features.php index 4eed6391f..403dae985 100644 --- a/inc/patterns/aw-bento-features.php +++ b/inc/patterns/aw-bento-features.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Bento Feature Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'features' ), diff --git a/inc/patterns/aw-blog-cards.php b/inc/patterns/aw-blog-cards.php index 434ef3fbf..237ce0258 100644 --- a/inc/patterns/aw-blog-cards.php +++ b/inc/patterns/aw-blog-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Blog Post Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'blog' ), diff --git a/inc/patterns/aw-case-study-metrics.php b/inc/patterns/aw-case-study-metrics.php index 780e7ae81..a041d2afa 100644 --- a/inc/patterns/aw-case-study-metrics.php +++ b/inc/patterns/aw-case-study-metrics.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Case Study With Metrics', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'portfolio' ), diff --git a/inc/patterns/aw-cta-banner.php b/inc/patterns/aw-cta-banner.php index d6e1bf484..7adc9f41f 100644 --- a/inc/patterns/aw-cta-banner.php +++ b/inc/patterns/aw-cta-banner.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'CTA Banner', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'call-to-action' ), diff --git a/inc/patterns/aw-faq-cards.php b/inc/patterns/aw-faq-cards.php index 61354fdf8..3fa2ffc6a 100644 --- a/inc/patterns/aw-faq-cards.php +++ b/inc/patterns/aw-faq-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'FAQ Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'faq' ), diff --git a/inc/patterns/aw-feature-cards.php b/inc/patterns/aw-feature-cards.php index 88889096e..5b1096301 100644 --- a/inc/patterns/aw-feature-cards.php +++ b/inc/patterns/aw-feature-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Feature Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'features' ), diff --git a/inc/patterns/aw-feature-split.php b/inc/patterns/aw-feature-split.php index 94f2a6369..b0272e2dc 100644 --- a/inc/patterns/aw-feature-split.php +++ b/inc/patterns/aw-feature-split.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Feature Split', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'features' ), diff --git a/inc/patterns/aw-gallery-grid.php b/inc/patterns/aw-gallery-grid.php index 6a55a4cc5..6597e8260 100644 --- a/inc/patterns/aw-gallery-grid.php +++ b/inc/patterns/aw-gallery-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Gallery Masonry Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'gallery' ), diff --git a/inc/patterns/aw-glow-cta.php b/inc/patterns/aw-glow-cta.php index ca0ccd48d..5506fa3cf 100644 --- a/inc/patterns/aw-glow-cta.php +++ b/inc/patterns/aw-glow-cta.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Glow CTA', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'call-to-action' ), diff --git a/inc/patterns/aw-hero-centered.php b/inc/patterns/aw-hero-centered.php index 981eedd53..df9a5e585 100644 --- a/inc/patterns/aw-hero-centered.php +++ b/inc/patterns/aw-hero-centered.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Centered Hero', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'header' ), diff --git a/inc/patterns/aw-hero-split.php b/inc/patterns/aw-hero-split.php index f9429b104..913b10b09 100644 --- a/inc/patterns/aw-hero-split.php +++ b/inc/patterns/aw-hero-split.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Split Hero', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'header' ), diff --git a/inc/patterns/aw-icon-grid.php b/inc/patterns/aw-icon-grid.php index 51f62372e..96e8b748e 100644 --- a/inc/patterns/aw-icon-grid.php +++ b/inc/patterns/aw-icon-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Icon Feature Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'icons' ), diff --git a/inc/patterns/aw-portfolio-grid.php b/inc/patterns/aw-portfolio-grid.php index c9a4bba75..83c095313 100644 --- a/inc/patterns/aw-portfolio-grid.php +++ b/inc/patterns/aw-portfolio-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Portfolio Showcase Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'portfolio' ), diff --git a/inc/patterns/aw-pricing-tiers.php b/inc/patterns/aw-pricing-tiers.php index a083ff481..609d2d4ed 100644 --- a/inc/patterns/aw-pricing-tiers.php +++ b/inc/patterns/aw-pricing-tiers.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Pricing Tiers', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pricing' ), diff --git a/inc/patterns/aw-quote-spotlight.php b/inc/patterns/aw-quote-spotlight.php index a03f899d9..4dc98480a 100644 --- a/inc/patterns/aw-quote-spotlight.php +++ b/inc/patterns/aw-quote-spotlight.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Spotlight Quote', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'testimonials' ), diff --git a/inc/patterns/aw-review-stars.php b/inc/patterns/aw-review-stars.php index a14169b97..b5419bf1e 100644 --- a/inc/patterns/aw-review-stars.php +++ b/inc/patterns/aw-review-stars.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Star Rating Reviews', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'testimonials' ), diff --git a/inc/patterns/aw-stats-banner.php b/inc/patterns/aw-stats-banner.php index 88f64dd8c..af32a5747 100644 --- a/inc/patterns/aw-stats-banner.php +++ b/inc/patterns/aw-stats-banner.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Gradient Stats Banner', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'stats' ), diff --git a/inc/patterns/aw-team-grid.php b/inc/patterns/aw-team-grid.php index 8ae6e45ad..cff82df04 100644 --- a/inc/patterns/aw-team-grid.php +++ b/inc/patterns/aw-team-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Team Grid with Hover Effect', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'team' ), diff --git a/inc/patterns/aw-testimonial-cards.php b/inc/patterns/aw-testimonial-cards.php index ee7820236..7ad93b2ed 100644 --- a/inc/patterns/aw-testimonial-cards.php +++ b/inc/patterns/aw-testimonial-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Testimonial Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'testimonials' ), diff --git a/inc/patterns/aw-timeline-steps.php b/inc/patterns/aw-timeline-steps.php index f150a509b..56d01b2a9 100644 --- a/inc/patterns/aw-timeline-steps.php +++ b/inc/patterns/aw-timeline-steps.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Timeline Steps', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'timeline' ), diff --git a/inc/patterns/aw-video-feature.php b/inc/patterns/aw-video-feature.php index acb5685d4..719659acb 100644 --- a/inc/patterns/aw-video-feature.php +++ b/inc/patterns/aw-video-feature.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Video Feature', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'video' ), diff --git a/inc/patterns/blog-featured-list.php b/inc/patterns/blog-featured-list.php index 6462781b5..3ff7d35e2 100644 --- a/inc/patterns/blog-featured-list.php +++ b/inc/patterns/blog-featured-list.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Blog Featured & List', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'blog' ), diff --git a/inc/patterns/bold-image-hero.php b/inc/patterns/bold-image-hero.php index 69d2fb937..5d0abbe4f 100644 --- a/inc/patterns/bold-image-hero.php +++ b/inc/patterns/bold-image-hero.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Bold Image Hero', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'header' ), diff --git a/inc/patterns/cafe-about.php b/inc/patterns/cafe-about.php index d44456748..feadb3b31 100644 --- a/inc/patterns/cafe-about.php +++ b/inc/patterns/cafe-about.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Cafe - About', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'cafe-pack' ), diff --git a/inc/patterns/cafe-contact.php b/inc/patterns/cafe-contact.php index 1f7fbcd2c..d44d247f6 100644 --- a/inc/patterns/cafe-contact.php +++ b/inc/patterns/cafe-contact.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Cafe - Contact', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'cafe-pack' ), diff --git a/inc/patterns/cafe-homepage.php b/inc/patterns/cafe-homepage.php index d4aa9e133..98ea5db3e 100644 --- a/inc/patterns/cafe-homepage.php +++ b/inc/patterns/cafe-homepage.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Cafe - Homepage', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'cafe-pack' ), diff --git a/inc/patterns/cafe-menu.php b/inc/patterns/cafe-menu.php index b9d2a04fc..c2539a1c5 100644 --- a/inc/patterns/cafe-menu.php +++ b/inc/patterns/cafe-menu.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Cafe - Menu', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'cafe-pack' ), diff --git a/inc/patterns/contact-form-split.php b/inc/patterns/contact-form-split.php index 49824d65c..cd70db049 100644 --- a/inc/patterns/contact-form-split.php +++ b/inc/patterns/contact-form-split.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Contact Form with Details', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'contact' ), diff --git a/inc/patterns/contact-info-cards.php b/inc/patterns/contact-info-cards.php index a7736ca4d..a2101d527 100644 --- a/inc/patterns/contact-info-cards.php +++ b/inc/patterns/contact-info-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Contact Info Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'contact' ), diff --git a/inc/patterns/content-checklist.php b/inc/patterns/content-checklist.php index 7bd7eabe0..759130c17 100644 --- a/inc/patterns/content-checklist.php +++ b/inc/patterns/content-checklist.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Content with Checklist', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'text' ), diff --git a/inc/patterns/countdown-launch.php b/inc/patterns/countdown-launch.php index c55eb03e9..ef5591ab3 100644 --- a/inc/patterns/countdown-launch.php +++ b/inc/patterns/countdown-launch.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Launch Countdown', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'waitlist' ), diff --git a/inc/patterns/dark-cta-band.php b/inc/patterns/dark-cta-band.php index 869ab409a..4dad6ca05 100644 --- a/inc/patterns/dark-cta-band.php +++ b/inc/patterns/dark-cta-band.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Indigo CTA Band', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'call-to-action' ), diff --git a/inc/patterns/faq-boxed-list.php b/inc/patterns/faq-boxed-list.php index 3ef4cc149..55a284dda 100644 --- a/inc/patterns/faq-boxed-list.php +++ b/inc/patterns/faq-boxed-list.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'FAQ Boxed List', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'faq' ), diff --git a/inc/patterns/faq-two-column.php b/inc/patterns/faq-two-column.php index 66f71817f..17743dff7 100644 --- a/inc/patterns/faq-two-column.php +++ b/inc/patterns/faq-two-column.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'FAQ Two Column', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'faq' ), diff --git a/inc/patterns/form-glow-card.php b/inc/patterns/form-glow-card.php index 03ca59e4e..b13272ade 100644 --- a/inc/patterns/form-glow-card.php +++ b/inc/patterns/form-glow-card.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Get Access Form on Dark', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'forms' ), diff --git a/inc/patterns/form-gradient-split.php b/inc/patterns/form-gradient-split.php index 6811ee13a..26341167d 100644 --- a/inc/patterns/form-gradient-split.php +++ b/inc/patterns/form-gradient-split.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Contact Form with Gradient Panel', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'forms' ), diff --git a/inc/patterns/gallery-feature-split.php b/inc/patterns/gallery-feature-split.php index 6ba9c8027..93c9c9b0e 100644 --- a/inc/patterns/gallery-feature-split.php +++ b/inc/patterns/gallery-feature-split.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Gallery Feature Split', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'gallery' ), diff --git a/inc/patterns/gallery-overlay-grid.php b/inc/patterns/gallery-overlay-grid.php index df580ff7c..dfb226af7 100644 --- a/inc/patterns/gallery-overlay-grid.php +++ b/inc/patterns/gallery-overlay-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Gallery Overlay Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'gallery' ), diff --git a/inc/patterns/icon-feature-trio.php b/inc/patterns/icon-feature-trio.php index 5b9df7993..16aedfef6 100644 --- a/inc/patterns/icon-feature-trio.php +++ b/inc/patterns/icon-feature-trio.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Icon Feature Trio', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'icons' ), diff --git a/inc/patterns/icon-grid-bordered.php b/inc/patterns/icon-grid-bordered.php index 4ea29c5eb..dec44f972 100644 --- a/inc/patterns/icon-grid-bordered.php +++ b/inc/patterns/icon-grid-bordered.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Bordered Icon Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'icons' ), diff --git a/inc/patterns/maps-location.php b/inc/patterns/maps-location.php index 6ba0d5970..5d9eefafa 100644 --- a/inc/patterns/maps-location.php +++ b/inc/patterns/maps-location.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Map And Location Details', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'contact' ), diff --git a/inc/patterns/newsletter-inline-bar.php b/inc/patterns/newsletter-inline-bar.php index ee2fad32f..828b217eb 100644 --- a/inc/patterns/newsletter-inline-bar.php +++ b/inc/patterns/newsletter-inline-bar.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Newsletter Inline Bar', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'newsletter' ), diff --git a/inc/patterns/newsletter-signup.php b/inc/patterns/newsletter-signup.php index d2497f6a4..7de3cb5d8 100644 --- a/inc/patterns/newsletter-signup.php +++ b/inc/patterns/newsletter-signup.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Newsletter Signup', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'newsletter' ), diff --git a/inc/patterns/portfolio-overlay-grid.php b/inc/patterns/portfolio-overlay-grid.php index df5d482c5..dc04881eb 100644 --- a/inc/patterns/portfolio-overlay-grid.php +++ b/inc/patterns/portfolio-overlay-grid.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Portfolio Overlay Grid', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'portfolio' ), diff --git a/inc/patterns/pricing-columns.php b/inc/patterns/pricing-columns.php index fe20c4a89..c01922a38 100644 --- a/inc/patterns/pricing-columns.php +++ b/inc/patterns/pricing-columns.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Pricing Columns', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pricing' ), diff --git a/inc/patterns/pricing-compare-simple.php b/inc/patterns/pricing-compare-simple.php index 8939e3326..c10e53d56 100644 --- a/inc/patterns/pricing-compare-simple.php +++ b/inc/patterns/pricing-compare-simple.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Simple Pricing Comparison', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pricing' ), diff --git a/inc/patterns/stats-row.php b/inc/patterns/stats-row.php index 1c9209c9f..d57f427c9 100644 --- a/inc/patterns/stats-row.php +++ b/inc/patterns/stats-row.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Stats Row', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'stats' ), diff --git a/inc/patterns/team-cards.php b/inc/patterns/team-cards.php index 873d5a3dd..eec97ece0 100644 --- a/inc/patterns/team-cards.php +++ b/inc/patterns/team-cards.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Team Cards', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'team' ), diff --git a/inc/patterns/team-spotlight-lead.php b/inc/patterns/team-spotlight-lead.php index b6476f1cf..85b1306fa 100644 --- a/inc/patterns/team-spotlight-lead.php +++ b/inc/patterns/team-spotlight-lead.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Team Spotlight Lead', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'team' ), diff --git a/inc/patterns/text-lead-statement.php b/inc/patterns/text-lead-statement.php index 908134b8f..72ad6c402 100644 --- a/inc/patterns/text-lead-statement.php +++ b/inc/patterns/text-lead-statement.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Text Lead Statement', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'text' ), diff --git a/inc/patterns/text-two-column-prose.php b/inc/patterns/text-two-column-prose.php index ec87f5fd4..b74059e14 100644 --- a/inc/patterns/text-two-column-prose.php +++ b/inc/patterns/text-two-column-prose.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Text Two Column Prose', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'text' ), diff --git a/inc/patterns/timeline-horizontal.php b/inc/patterns/timeline-horizontal.php index 6df8199ec..c186d1038 100644 --- a/inc/patterns/timeline-horizontal.php +++ b/inc/patterns/timeline-horizontal.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Horizontal Timeline', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'timeline' ), diff --git a/inc/patterns/travel-about.php b/inc/patterns/travel-about.php index 5597a9e87..c70d269f1 100644 --- a/inc/patterns/travel-about.php +++ b/inc/patterns/travel-about.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Travel - About', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'travel-pack' ), diff --git a/inc/patterns/travel-contact.php b/inc/patterns/travel-contact.php index 424ccdf0a..8bac4d770 100644 --- a/inc/patterns/travel-contact.php +++ b/inc/patterns/travel-contact.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Travel - Contact', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'travel-pack' ), diff --git a/inc/patterns/travel-homepage.php b/inc/patterns/travel-homepage.php index 2051e7c3f..c857818f9 100644 --- a/inc/patterns/travel-homepage.php +++ b/inc/patterns/travel-homepage.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Travel - Homepage', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'travel-pack' ), diff --git a/inc/patterns/travel-tours.php b/inc/patterns/travel-tours.php index 68ac18ff7..f41718162 100644 --- a/inc/patterns/travel-tours.php +++ b/inc/patterns/travel-tours.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Travel - Tours', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'travel-pack' ), diff --git a/inc/patterns/video-hero-play.php b/inc/patterns/video-hero-play.php index 1d5362c9c..93e18b136 100644 --- a/inc/patterns/video-hero-play.php +++ b/inc/patterns/video-hero-play.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Video Hero With Play', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'video' ), diff --git a/inc/patterns/waitlist-coming-soon.php b/inc/patterns/waitlist-coming-soon.php index cb30e0944..91c50b636 100644 --- a/inc/patterns/waitlist-coming-soon.php +++ b/inc/patterns/waitlist-coming-soon.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Coming Soon Waitlist', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'waitlist' ), diff --git a/inc/patterns/wellness-classes.php b/inc/patterns/wellness-classes.php index 7b6f5f4e6..d38c962a4 100644 --- a/inc/patterns/wellness-classes.php +++ b/inc/patterns/wellness-classes.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Wellness - Classes', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'wellness-pack' ), diff --git a/inc/patterns/wellness-contact.php b/inc/patterns/wellness-contact.php index 412e25ceb..9618d86ff 100644 --- a/inc/patterns/wellness-contact.php +++ b/inc/patterns/wellness-contact.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Wellness - Contact', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'wellness-pack' ), diff --git a/inc/patterns/wellness-homepage.php b/inc/patterns/wellness-homepage.php index 676186b7a..60dd147f3 100644 --- a/inc/patterns/wellness-homepage.php +++ b/inc/patterns/wellness-homepage.php @@ -5,6 +5,10 @@ * @package ThemeIsle */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + return array( 'title' => __( 'Wellness - Homepage', 'otter-blocks' ), 'categories' => array( 'otter-blocks', 'pages', 'wellness-pack' ), diff --git a/inc/plugins/class-atomic-wind-blocks.php b/inc/plugins/class-atomic-wind-blocks.php index 9f99f4746..4f5e89822 100644 --- a/inc/plugins/class-atomic-wind-blocks.php +++ b/inc/plugins/class-atomic-wind-blocks.php @@ -482,8 +482,8 @@ private function render_css_warm_page_html( \WP_Post $post_obj ) { $html .= ''; $html .= $content; $html .= ''; - $html .= ''; - $html .= ''; + $html .= ''; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- standalone iframe document, not a WordPress page. + $html .= ''; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- standalone iframe document, not a WordPress page. $html .= ''; $html .= ''; diff --git a/inc/server/class-dynamic-content-server.php b/inc/server/class-dynamic-content-server.php index 9864b2e46..034365805 100644 --- a/inc/server/class-dynamic-content-server.php +++ b/inc/server/class-dynamic-content-server.php @@ -261,7 +261,7 @@ public function get( $request ) { if ( $size = @getimagesize( $path ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure ob_start(); - readfile( $path ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile + readfile( $path ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile, WordPress.WP.AlternativeFunctions.file_system_operations_readfile $output = ob_get_contents(); if ( ! empty( $size['mime'] ) ) { @@ -271,7 +271,7 @@ public function get( $request ) { } ob_start(); - readfile( $path ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile + readfile( $path ); //phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile, WordPress.WP.AlternativeFunctions.file_system_operations_readfile $output = ob_get_contents(); if ( isset( $size['mime'] ) ) { From b4eaac6292a4e72042b23012863f43f730319a36 Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 15:50:17 +0300 Subject: [PATCH 05/12] Stabilize CI: surgical preference reset and visible perf failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deleting the admin user's entire persisted preferences in the serial phase clobbered state later parallel specs rely on (button-group started crashing right after). Unset only core/edit-post metaBoxesMainIsOpen — the single key the woo-builder assertions need cleared. Also keep the list reporter on CI for the performance suite: the custom reporter implements no onError, so global-setup failures exited with code 1 and zero output, which is why the Performance job has been failing silently. Co-Authored-By: Claude Fable 5 --- src/blocks/test/e2e/blocks/woocommerce-builder.spec.js | 7 ++++--- src/blocks/test/performance/playwright.config.ts | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index c13ccd1e9..ebfa48e93 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -49,9 +49,10 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { // Prevent the WooCommerce activation redirect from hijacking admin page loads. tryRunWpCli( 'wp transient delete _wc_activation_redirect' ); - // Drop persisted editor preferences so the assertions exercise the - // plugin-provided "Meta Boxes" panel default rather than a leftover choice. - tryRunWpCli( 'wp user meta delete 1 wp_persisted_preferences' ); + // Unset only the persisted "Meta Boxes" panel preference so the + // assertions exercise the plugin-provided default rather than a + // leftover choice, without clobbering preferences other specs rely on. + tryRunWpCli( 'wp user meta patch delete 1 wp_persisted_preferences core/edit-post metaBoxesMainIsOpen' ); }); test.beforeEach( async({}, testInfo ) => { diff --git a/src/blocks/test/performance/playwright.config.ts b/src/blocks/test/performance/playwright.config.ts index 2f50e5bd7..47d3873dd 100644 --- a/src/blocks/test/performance/playwright.config.ts +++ b/src/blocks/test/performance/playwright.config.ts @@ -77,9 +77,9 @@ const config = defineConfig({ use: { ...devices[ 'Desktop Chrome' ], video: 'off' } } ], - reporter: process.env.CI ? - './config/performance-reporter.ts' : - [[ 'list' ], [ './config/performance-reporter.ts' ]], + // Keep the list reporter on CI too — the custom reporter implements no + // onError, so without it global-setup and test failures exit silently. + reporter: [[ 'list' ], [ './config/performance-reporter.ts' ]], forbidOnly: !! process.env.CI, fullyParallel: false, retries: 0, From f4162f33157845366d5558c9f9310709e8e7478f Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Thu, 16 Jul 2026 16:35:35 +0300 Subject: [PATCH 06/12] Scope WooCommerce activation to the woo-builder spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activating WooCommerce site-wide in wp-env changed editor behavior for every e2e spec and broke unrelated tests (button-group global defaults crashes the editor with WooCommerce active — reproduced locally). Mount WooCommerce via mappings instead, and let the serial woo-builder spec activate it in beforeAll and deactivate it in afterAll, so the rest of the suite keeps its historical environment. Co-Authored-By: Claude Fable 5 --- .wp-env.json | 4 ++-- src/blocks/test/e2e/blocks/woocommerce-builder.spec.js | 9 +++++++++ src/blocks/test/e2e/playwright.config.js | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 528441d8e..59e766d46 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -4,7 +4,6 @@ "testsEnvironment": false, "plugins": [ ".", - "./vendor/wp-content/plugins/woocommerce", "https://downloads.wordpress.org/plugin/ai-provider-for-openai.zip" ], "themes": [ "./test/emptytheme" ], @@ -18,7 +17,8 @@ }, "mappings": { "wp-content/mu-plugins": "./packages/e2e-tests/mu-plugins", - "wp-content/themes/raft": "https://downloads.wordpress.org/theme/raft.zip" + "wp-content/themes/raft": "https://downloads.wordpress.org/theme/raft.zip", + "wp-content/plugins/woocommerce": "./vendor/wp-content/plugins/woocommerce" }, "lifecycleScripts": { "afterStart": "bash bin/e2e-tests.sh" diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index ebfa48e93..998f6f393 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -46,6 +46,11 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { test.beforeAll( () => { + // WooCommerce is mounted by wp-env but only activated for this spec — + // the rest of the suite runs without it, as its editor integrations + // change load behavior for every other test. Serial project only. + runWpCli( 'wp plugin activate woocommerce' ); + // Prevent the WooCommerce activation redirect from hijacking admin page loads. tryRunWpCli( 'wp transient delete _wc_activation_redirect' ); @@ -55,6 +60,10 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { tryRunWpCli( 'wp user meta patch delete 1 wp_persisted_preferences core/edit-post metaBoxesMainIsOpen' ); }); + test.afterAll( () => { + tryRunWpCli( 'wp plugin deactivate woocommerce' ); + }); + test.beforeEach( async({}, testInfo ) => { productId = createProduct( `Woo Builder ${ testInfo.workerIndex }-${ testInfo.retry }-${ Date.now() }` ); }); diff --git a/src/blocks/test/e2e/playwright.config.js b/src/blocks/test/e2e/playwright.config.js index 1062c84f5..5ddba014d 100644 --- a/src/blocks/test/e2e/playwright.config.js +++ b/src/blocks/test/e2e/playwright.config.js @@ -59,7 +59,7 @@ const SERIAL_SPECS = [ // Flips the site-wide atomic-wind blocks option. '**/blocks/atomic-wind-list-view.spec.js', - // Deletes the admin user's persisted editor preferences; must not race parallel specs. + // Activates/deactivates the WooCommerce plugin site-wide; must not race parallel specs. '**/blocks/woocommerce-builder.spec.js' ]; From d28970e0fd41e5c0091929061dea44e1ae0d7695 Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Tue, 21 Jul 2026 11:29:21 +0300 Subject: [PATCH 07/12] test: harden woo-builder disable-path assertion and cover explicit collapsed preference Co-authored-by: Cursor --- .../e2e/blocks/woocommerce-builder.spec.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index 998f6f393..90fb118e7 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -28,6 +28,11 @@ const tryRunWpCli = ( command ) => { } }; +// Reports whether a post meta key exists without leaking WP-CLI failures: +// runWpCli() propagates infrastructure errors, and the eval always prints 0/1. +const postMetaExists = ( postId, key ) => + runWpCli( `wp eval "echo metadata_exists( 'post', ${ postId }, '${ key }' ) ? 1 : 0;"` ).trim(); + const createProduct = ( title ) => { const output = runWpCli( `wp post create --post_type=product --post_status=publish --post_title="${ title }" --porcelain` ); const match = output.match( /^\s*(\d+)\s*$/m ); @@ -110,11 +115,29 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=1` ); await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=0` ); - expect( tryRunWpCli( `wp post meta get ${ productId } _themeisle_gutenberg_woo_builder` ) ).not.toContain( '1' ); + expect( postMetaExists( productId, '_themeisle_gutenberg_woo_builder' ) ).toBe( '0' ); await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); await expect( page.locator( '#_regular_price' ) ).toBeVisible(); }); + + test( 'an explicit collapsed preference still wins over the default', async({ admin, page }) => { + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=1` ); + + // A merchant who deliberately collapsed the Meta Boxes drawer keeps that + // choice: ensure_metabox_panel_visible() uses setDefaults(), which must + // never override a persisted preference. + runWpCli( `wp user meta update 1 wp_persisted_preferences '{"core/edit-post":{"metaBoxesMainIsOpen":false}}' --format=json` ); + + await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); + await expect( page.locator( 'body.block-editor-page' ) ).toBeVisible(); + + // Drawer stays collapsed, so the Product data metabox remains hidden. + await expect( page.locator( '#woocommerce-product-data' ) ).toBeHidden(); + + // Restore the cleared-default state for any following assertions. + tryRunWpCli( 'wp user meta patch delete 1 wp_persisted_preferences core/edit-post metaBoxesMainIsOpen' ); + }); }); From 985544c314d018a6145104e981de121ea9cac4f1 Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Tue, 21 Jul 2026 12:20:39 +0300 Subject: [PATCH 08/12] ci: remove plugin-check workflow to resolve conflict with development Co-authored-by: Cursor --- .github/workflows/plugin-check.yml | 237 ----------------------------- 1 file changed, 237 deletions(-) delete mode 100644 .github/workflows/plugin-check.yml diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml deleted file mode 100644 index 55f937d5c..000000000 --- a/.github/workflows/plugin-check.yml +++ /dev/null @@ -1,237 +0,0 @@ -name: WordPress Plugin Check - -on: - pull_request: - types: [opened, synchronize, reopened] - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - plugin-check: - name: WordPress.org Guidelines Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Neutralize wp-env override - run: | - echo '{}' > .wp-env.override.json - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 20 - cache: 'npm' - - - name: Install npm dependencies - run: npm ci - - - name: Build assets - run: npm run build - - - name: Install Composer dependencies - run: composer install --no-dev --optimize-autoloader - - - uses: wordpress/plugin-check-action@v1 - id: plugin-check - with: - categories: plugin_repo,security,general - exclude-directories: | - node_modules - vendor - build - tests - bin - .github - src - packages - exclude-files: | - phpcs.xml.dist - ignore-codes: | - PluginCheck.CodeAnalysis.Offloading.OffloadedContent - WordPress.WP.I18n.TextDomainMismatch - textdomain_mismatch - hidden_files - WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound - WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound - WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound - WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound - WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound - WordPress.PHP.DevelopmentFunctions.error_log_trigger_error - WordPress.WP.EnqueuedResourceParameters.MissingVersion - include-experimental: true - repo-token: '' - - - name: Plugin Check Summary - if: always() - env: - RESULTS_FILE: ${{ runner.temp }}/plugin-check-results.txt - run: | - echo "## WordPress Plugin Check Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -s "$RESULTS_FILE" ]; then - echo "No results file found or file is empty." >> $GITHUB_STEP_SUMMARY - echo "Check the action logs for details." >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - PARSED=$(RESULTS_FILE="$RESULTS_FILE" python3 << 'PYEOF' - import json, os, re - - results_path = os.environ["RESULTS_FILE"] - - high_risk_codes = [ - "plugin_updater", "code_obfuscation", "no_unfiltered_uploads", - "trademarked_term", "trademarks" - ] - high_risk_messages = [ - r"Plugin Updater detected", r"Missing.*License.*Plugin Header", - r"restricted term", r"Unescaped parameter.*\$wpdb", - r"Use placeholders and.*\$wpdb->prepare" - ] - medium_risk_codes = [ - "missing_direct_file_access_protection", "trunk_stable_tag", - "mismatched_plugin_name", "application_detected" - ] - medium_risk_messages = [ - r"Missing.*\$domain.*parameter", r"has been deprecated", - r"wp_get_sites", r"cURL functions is highly discouraged" - ] - - high, medium, other = [], [], [] - - try: - with open(results_path, "r") as f: - content = f.read().strip() - - all_issues = [] - try: - data = json.loads(content) - if isinstance(data, list): - all_issues = data - elif isinstance(data, dict): - for fp, issues in data.items(): - if isinstance(issues, list): - for issue in issues: - issue['_file'] = fp - all_issues.append(issue) - except json.JSONDecodeError: - for line in content.split('\n'): - line = line.strip() - if not line: - continue - try: - parsed = json.loads(line) - if isinstance(parsed, list): - all_issues.extend(parsed) - elif isinstance(parsed, dict): - all_issues.append(parsed) - except json.JSONDecodeError: - continue - - for issue in all_issues: - code = issue.get('code', '') - msg = issue.get('message', '') - itype = issue.get('type', 'ERROR') - line_num = issue.get('line', 0) - file_path = issue.get('_file', '') - - prefix = "❌" if itype == "ERROR" else "⚠️" - location = "" - if file_path: - location = f" ({file_path}" - if line_num and line_num > 0: - location += f", line {line_num}" - location += ")" - elif line_num and line_num > 0: - location = f" (line {line_num})" - - readable = f"{prefix} {msg}{location}" - - is_high = code in high_risk_codes - if not is_high: - for p in high_risk_messages: - if re.search(p, msg, re.IGNORECASE): - is_high = True - break - - is_medium = code in medium_risk_codes - if not is_medium and not is_high: - for p in medium_risk_messages: - if re.search(p, msg, re.IGNORECASE): - is_medium = True - break - - if is_high: - high.append(readable) - elif is_medium: - medium.append(readable) - else: - other.append(readable) - - def dedup(lst): - seen = set() - result = [] - for item in lst: - if item not in seen: - seen.add(item) - result.append(item) - return result - - high, medium, other = dedup(high), dedup(medium), dedup(other) - - print("---HIGH---") - for i in high: print(i) - print("---MEDIUM---") - for i in medium: print(i) - print("---OTHER---") - for i in other: print(i) - print("---COUNTS---") - print(f"{len(high)}|{len(medium)}|{len(other)}") - - except Exception as e: - print(f"Parse error: {e}", file=__import__('sys').stderr) - print("---HIGH---\n---MEDIUM---\n---OTHER---\n---COUNTS---\n0|0|0") - PYEOF - ) - - HIGH_SECTION=$(echo "$PARSED" | sed -n '/^---HIGH---$/,/^---MEDIUM---$/p' | sed '1d;$d') - MEDIUM_SECTION=$(echo "$PARSED" | sed -n '/^---MEDIUM---$/,/^---OTHER---$/p' | sed '1d;$d') - OTHER_SECTION=$(echo "$PARSED" | sed -n '/^---OTHER---$/,/^---COUNTS---$/p' | sed '1d;$d') - COUNTS=$(echo "$PARSED" | tail -1) - OTHER_COUNT=$(echo "$COUNTS" | cut -d'|' -f3) - - echo "### 🚨 HIGH RISK — Can cause plugin closure or suspension" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [ -n "$HIGH_SECTION" ]; then - echo "$HIGH_SECTION" >> $GITHUB_STEP_SUMMARY - else - echo "✅ No high-risk issues found." >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - echo "### ⚠️ MEDIUM RISK — Commonly flagged in wordpress.org reviews" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [ -n "$MEDIUM_SECTION" ]; then - echo "$MEDIUM_SECTION" >> $GITHUB_STEP_SUMMARY - else - echo "✅ No medium-risk issues found." >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - echo "
" >> $GITHUB_STEP_SUMMARY - echo "📋 Other issues ($OTHER_COUNT) — click to expand" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [ -n "$OTHER_SECTION" ]; then - echo "$OTHER_SECTION" >> $GITHUB_STEP_SUMMARY - else - echo "No other issues." >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY From 50dcf6a113142802077a2dd2a767bd62bad2074e Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Tue, 21 Jul 2026 12:36:50 +0300 Subject: [PATCH 09/12] fix: use single quotes for static wp-cli command to satisfy eslint Co-authored-by: Cursor --- src/blocks/test/e2e/blocks/woocommerce-builder.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index 90fb118e7..44dfed255 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -129,7 +129,7 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { // A merchant who deliberately collapsed the Meta Boxes drawer keeps that // choice: ensure_metabox_panel_visible() uses setDefaults(), which must // never override a persisted preference. - runWpCli( `wp user meta update 1 wp_persisted_preferences '{"core/edit-post":{"metaBoxesMainIsOpen":false}}' --format=json` ); + runWpCli( 'wp user meta update 1 wp_persisted_preferences \'{"core/edit-post":{"metaBoxesMainIsOpen":false}}\' --format=json' ); await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); await expect( page.locator( 'body.block-editor-page' ) ).toBeVisible(); From e3cd54b3193811a54fe38c29f6583cc79c729ecb Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Fri, 24 Jul 2026 16:41:06 +0300 Subject: [PATCH 10/12] refactor: drop metabox-panel-open workaround, focus e2e on Otter hooking Issue #2822 appears misreported: WooCommerce Product data visibility is not something Otter should force open. Remove ensure_metabox_panel_visible() and its enqueue_block_editor_assets hook, and refocus the e2e spec on verifying the Otter metabox hooks in and its enable/disable elements appear for the WooCommerce Builder. Co-authored-by: Cursor --- .../inc/plugins/class-woocommerce-builder.php | 37 ---------- .../e2e/blocks/woocommerce-builder.spec.js | 67 +++++-------------- 2 files changed, 16 insertions(+), 88 deletions(-) diff --git a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php index 5a2a71265..cdfdf59e0 100644 --- a/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php +++ b/plugins/otter-pro/inc/plugins/class-woocommerce-builder.php @@ -26,7 +26,6 @@ class WooCommerce_Builder { */ public function init() { add_action( 'add_meta_boxes', array( $this, 'register_metabox' ) ); - add_action( 'enqueue_block_editor_assets', array( $this, 'ensure_metabox_panel_visible' ) ); add_filter( 'use_block_editor_for_post_type', array( $this, 'enable_block_editor' ), 11, 2 ); add_filter( 'wc_get_template_part', array( $this, 'wc_get_template_part' ), 1000, 3 ); add_action( 'otter_blocks_woocommerce_content', 'the_content' ); @@ -93,42 +92,6 @@ public function render_metabox( $post_type ) { } } - /** - * Keep the WooCommerce Product data metabox visible in the block editor. - * - * Products using the WooCommerce Builder are edited in the block editor, - * where WordPress 6.7+ collapses the meta boxes panel by default — hiding - * the Product data options (price, inventory, etc.). Open the panel by - * default; an explicit user preference still wins over the default. - * - * @access public - * @return void - */ - public function ensure_metabox_panel_visible() { - $post = get_post(); - - if ( ! $post || 'product' !== $post->post_type ) { - return; - } - - if ( ! boolval( get_post_meta( $post->ID, '_themeisle_gutenberg_woo_builder', true ) ) ) { - return; - } - - wp_add_inline_script( - 'wp-edit-post', - "( function() { - if ( ! window.wp || ! wp.data || ! wp.domReady ) { - return; - } - - wp.domReady( function() { - wp.data.dispatch( 'core/preferences' ).setDefaults( 'core/edit-post', { metaBoxesMainIsOpen: true } ); - } ); - } )();" - ); - } - /** * Enable the Block Editfor * diff --git a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js index 44dfed255..dfe164e19 100644 --- a/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js +++ b/src/blocks/test/e2e/blocks/woocommerce-builder.spec.js @@ -9,13 +9,13 @@ import { test, expect } from '@wordpress/e2e-test-utils-playwright'; import { execSync } from 'child_process'; /** - * Regression tests for https://github.com/Codeinwp/otter-blocks/issues/2822 + * E2E coverage for the `WooCommerce Builder by Otter` integration (issue #2822). * - * Enabling `WooCommerce Builder by Otter` on a product stores the - * `_themeisle_gutenberg_woo_builder` meta, and `WooCommerce_Builder::enable_block_editor()` - * then forces the block editor for that product. WooCommerce's `Product data` - * metabox (price fields included) must stay reachable on the product edit - * screen either way — merchants otherwise lose access to pricing options. + * The Otter metabox hooks into the product edit screen and lets merchants + * toggle the builder. Enabling it stores the `_themeisle_gutenberg_woo_builder` + * meta, and `WooCommerce_Builder::enable_block_editor()` then routes the product + * into the block editor. These tests verify the hooking is wired correctly and + * that the Otter elements show up in the metabox for the WooCommerce Builder. */ const runWpCli = ( command ) => execSync( `npx wp-env run cli -- ${ command }`, { encoding: 'utf8' }); @@ -39,13 +39,6 @@ const createProduct = ( title ) => { return Number( match[ 1 ]); }; -const attachScreenshot = async( testInfo, page, name ) => { - await testInfo.attach( name, { - body: await page.screenshot({ fullPage: true }), - contentType: 'image/png' - }); -}; - test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { let productId; @@ -58,11 +51,6 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { // Prevent the WooCommerce activation redirect from hijacking admin page loads. tryRunWpCli( 'wp transient delete _wc_activation_redirect' ); - - // Unset only the persisted "Meta Boxes" panel preference so the - // assertions exercise the plugin-provided default rather than a - // leftover choice, without clobbering preferences other specs rely on. - tryRunWpCli( 'wp user meta patch delete 1 wp_persisted_preferences core/edit-post metaBoxesMainIsOpen' ); }); test.afterAll( () => { @@ -77,18 +65,15 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { tryRunWpCli( `wp post delete ${ productId } --force` ); }); - test( 'product edit screen shows the Product data panel and the builder toggle', async({ admin, page }) => { + test( 'the Otter metabox hooks into the product edit screen with the enable toggle', async({ admin, page }) => { await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); - await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); - await expect( page.locator( '#_regular_price' ) ).toBeVisible(); - - // The license is stubbed as active in e2e, so the Otter metabox must offer the toggle. + // The license is stubbed as active in e2e, so the Otter metabox must render. await expect( page.locator( '#otter_woo_builder' ) ).toBeVisible(); await expect( page.locator( 'a#otter-woo-builder' ) ).toHaveText( /Enable WooCommerce Builder/ ); }); - test( 'Product data panel stays reachable after enabling the builder', async({ admin, page }, testInfo ) => { + test( 'enabling the builder persists the flag and shows the disable toggle', async({ admin, page }) => { await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); await page.locator( 'a#otter-woo-builder' ).click({ timeout: 120000 }); await page.waitForLoadState( 'domcontentloaded', { timeout: 120000 }); @@ -99,17 +84,15 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { // Re-open the edit screen the way a merchant would after the toggle. await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); - // The builder must actually route the product into the block editor — - // otherwise the metabox assertions below pass without exercising it. + // The builder must actually route the product into the block editor. await expect( page.locator( 'body.block-editor-page' ) ).toBeVisible(); - await attachScreenshot( testInfo, page, 'product-edit-screen-with-builder-enabled' ); - // The WooCommerce product options must stay editable in the block editor. - await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); - await expect( page.locator( '#_regular_price' ) ).toBeVisible(); + // The Otter metabox stays hooked in and now offers the disable toggle. + await expect( page.locator( '#otter_woo_builder' ) ).toBeVisible(); + await expect( page.locator( 'a#otter-woo-builder' ) ).toHaveText( /Disable WooCommerce Builder/ ); }); - test( 'disabling the builder restores the Product data panel', async({ admin, page }) => { + test( 'disabling the builder removes the flag and restores the enable toggle', async({ admin, page }) => { // Enable, then disable through the same query-arg toggle the metabox buttons use. await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=1` ); @@ -119,25 +102,7 @@ test.describe( 'WooCommerce Builder product editing (issue #2822)', () => { await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); - await expect( page.locator( '#woocommerce-product-data' ) ).toBeVisible(); - await expect( page.locator( '#_regular_price' ) ).toBeVisible(); - }); - - test( 'an explicit collapsed preference still wins over the default', async({ admin, page }) => { - await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit&otter-woo-builder=1` ); - - // A merchant who deliberately collapsed the Meta Boxes drawer keeps that - // choice: ensure_metabox_panel_visible() uses setDefaults(), which must - // never override a persisted preference. - runWpCli( 'wp user meta update 1 wp_persisted_preferences \'{"core/edit-post":{"metaBoxesMainIsOpen":false}}\' --format=json' ); - - await admin.visitAdminPage( 'post.php', `post=${ productId }&action=edit` ); - await expect( page.locator( 'body.block-editor-page' ) ).toBeVisible(); - - // Drawer stays collapsed, so the Product data metabox remains hidden. - await expect( page.locator( '#woocommerce-product-data' ) ).toBeHidden(); - - // Restore the cleared-default state for any following assertions. - tryRunWpCli( 'wp user meta patch delete 1 wp_persisted_preferences core/edit-post metaBoxesMainIsOpen' ); + await expect( page.locator( '#otter_woo_builder' ) ).toBeVisible(); + await expect( page.locator( 'a#otter-woo-builder' ) ).toHaveText( /Enable WooCommerce Builder/ ); }); }); From 795fd4120b7280655a7ac0b5a277f9e86af30dfc Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Mon, 27 Jul 2026 10:31:45 +0300 Subject: [PATCH 11/12] ci: restore plugin-check.yml workflow Robert asked to keep the WordPress Plugin Check workflow; revert the earlier deletion so it stays part of the branch. Co-authored-by: Cursor --- .github/workflows/plugin-check.yml | Bin 0 -> 18242 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/plugin-check.yml diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml new file mode 100644 index 0000000000000000000000000000000000000000..72a8a30d2ea84993d5f9aca59d813c4e67018f67 GIT binary patch literal 18242 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 Date: Mon, 27 Jul 2026 10:57:37 +0300 Subject: [PATCH 12/12] Revert "ci: restore plugin-check.yml workflow" This reverts commit 795fd412. The workflow was removed from development on purpose in cc260409 ("chore: remove WordPress Plugin Check workflow"). This branch was not deleting it; 985544c3 only aligned the branch with that decision, which is what cleared the merge conflict reported earlier. Restoring the file here re-introduced the modify/delete conflict against development and blocked every pull_request-triggered check, since GitHub cannot build a merge ref for a conflicting PR. Re-adding Plugin Check belongs in its own PR against development, not in this branch. Co-authored-by: Cursor --- .github/workflows/plugin-check.yml | Bin 18242 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .github/workflows/plugin-check.yml diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml deleted file mode 100644 index 72a8a30d2ea84993d5f9aca59d813c4e67018f67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18242 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