From 5a4778859a78bd68b42792872faff1eb71f311e8 Mon Sep 17 00:00:00 2001 From: Luca Dobrescu Date: Fri, 31 Jul 2026 10:50:26 +0300 Subject: [PATCH] fix: guard widgets CSS writability check against missing WP_Filesystem() 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: Claude Fable 5 --- .wp-env.json | 5 +- inc/css/class-css-handler.php | 9 +- .../mu-plugins/otter-e2e-bootstrap.php | 167 ++++++++++++++++++ .../e2e/blocks/widgets-css-frontend.spec.js | 94 ++++++++++ src/blocks/test/e2e/fixtures.ts | 15 ++ src/blocks/test/e2e/playwright.config.js | 5 +- tests/php/is-writable-sandbox.php | 65 +++++++ tests/test-css-handler.php | 50 ++++++ 8 files changed, 403 insertions(+), 7 deletions(-) create mode 100644 src/blocks/test/e2e/blocks/widgets-css-frontend.spec.js create mode 100644 tests/php/is-writable-sandbox.php create mode 100644 tests/test-css-handler.php diff --git a/.wp-env.json b/.wp-env.json index ecc59bbe2..baf5a856f 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -6,7 +6,10 @@ ".", "https://downloads.wordpress.org/plugin/ai-provider-for-openai.zip" ], - "themes": [ "./test/emptytheme" ], + "themes": [ + "./test/emptytheme", + "https://downloads.wordpress.org/theme/twentytwentyone.zip" + ], "config": { "WP_DEBUG": true, "WP_DEBUG_LOG": true, diff --git a/inc/css/class-css-handler.php b/inc/css/class-css-handler.php index db96b5832..41afb4ecd 100644 --- a/inc/css/class-css-handler.php +++ b/inc/css/class-css-handler.php @@ -496,18 +496,17 @@ public static function save_widgets_styles() { public static function is_writable() { global $wp_filesystem; include_once ABSPATH . 'wp-admin/includes/file.php'; - WP_Filesystem(); - - $wp_upload_dir = wp_upload_dir( null, false ); - $upload_dir = $wp_upload_dir['basedir']; if ( ! function_exists( 'WP_Filesystem' ) ) { return false; } + $wp_upload_dir = wp_upload_dir( null, false ); + $upload_dir = $wp_upload_dir['basedir']; + $writable = WP_Filesystem( false, $upload_dir ); - return $writable && 'direct' === $wp_filesystem->method; + return $writable && $wp_filesystem instanceof \WP_Filesystem_Base && 'direct' === $wp_filesystem->method; } /** diff --git a/packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php b/packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php index 882241e0a..6288186ff 100644 --- a/packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php +++ b/packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php @@ -94,6 +94,19 @@ */ const OPENAI_STUB_OPTION = 'otter_e2e_openai_stub'; +/** + * When truthy, get_filesystem_method() reports a bogus method so WP_Filesystem() + * fails to initialize. Simulates hosts where the filesystem API is unavailable + * on the frontend (issue #2937) — CSS_Handler::is_writable() must degrade to + * `false` and the widgets CSS must fall back to an inline