Skip to content

fix: widget CSS writability check fatals when WP_Filesystem() is unavailable - #2938

Open
lucadobrescu wants to merge 1 commit into
developmentfrom
fix/2937-wp-filesystem-guard
Open

fix: widget CSS writability check fatals when WP_Filesystem() is unavailable#2938
lucadobrescu wants to merge 1 commit into
developmentfrom
fix/2937-wp-filesystem-guard

Conversation

@lucadobrescu

Copy link
Copy Markdown
Contributor

A frontend request with an active sidebar and no generated widget stylesheet fataled with Call to undefined function WP_Filesystem() during wp_footer (production crash telemetry on Otter 3.2.0, issue #2937). CSS_Handler::is_writable() now checks that the function exists before it calls it, so the request degrades to the inline widget CSS fallback instead of dying.

What changed

  • CSS_Handler::is_writable() — the function_exists( 'WP_Filesystem' ) guard now runs before any call. Before → the guard sat after an unguarded WP_Filesystem() call at inc/css/class-css-handler.php:499 and never protected anything. After → the method returns false and the page renders with inline widget CSS. The return also verifies the initialized $wp_filesystem instance before it reads ->method.

  • PHPUnit regression testtests/test-css-handler.php runs is_writable() in a separate PHP process against a fake ABSPATH whose wp-admin/includes/file.php defines nothing. On the old code the test fails with the exact production fatal; with the fix it asserts a clean false.

  • E2E specsrc/blocks/test/e2e/blocks/widgets-css-frontend.spec.js (serial project) covers the crash path end to end: classic theme, Otter block in a sidebar, no generated stylesheet. With the filesystem blocked the page must finish rendering and serve the widget CSS inline; with the filesystem available a stylesheet file is written and enqueued on the next request.

  • E2E infrastructure — the test mu-plugin gains /otter-e2e/v1/filesystem (make WP_Filesystem() fail to initialize), /otter-e2e/v1/widgets/seed, and /otter-e2e/v1/widgets/cleanup; .wp-env.json installs Twenty Twenty-One because block themes register no sidebars, so the widgets-CSS path is unreachable without a classic theme.

Note

The exact production condition — WP_Filesystem() still undefined after the include — cannot exist inside a loaded WordPress test site. The PHPUnit sandbox reproduces it in an isolated process; the e2e spec blocks the filesystem at the get_filesystem_method() level, which drives the same is_writable() → false branch.

Widgets CSS flow at wp_footer

flowchart LR
    A[wp_footer] --> B{Active<br/>sidebar?}
    B -- no --> Z[Done]
    B -- yes --> C{Generated CSS<br/>file exists?}
    C -- yes --> D[Enqueue stylesheet]
    C -- no --> E{Changed:<br/>filesystem usable?}:::changed
    E -- yes --> F[Write CSS file] --> G[Echo inline CSS]
    E -- no --> G
    classDef changed fill:#9a6700,color:#fff,stroke:#5c3d00,stroke-width:3px,stroke-dasharray:6 3
Loading

Before this change, the "filesystem usable?" check itself fataled when WP_Filesystem() was unavailable, so the request never reached either branch.

QA

  1. Activate a classic theme: go to WP Admin → Appearance → Themes and activate Twenty Twenty-One.

  2. Go to WP Admin → Appearance → Widgets. Add an Otter block (for example Progress Bar) to the Footer widget area and save.

  3. Simulate an unusable filesystem and clear the generated stylesheet:

    wp option delete themeisle_blocks_widgets_css_file themeisle_blocks_widgets_css
    # mu-plugin: force WP_Filesystem() initialization to fail
    echo '<?php add_filter( "filesystem_method", function () { return "blocked"; } );' > wp-content/mu-plugins/block-fs.php
  4. Load any frontend page.

    Expect: the page renders to the end, the widget is styled, and an inline <style> with the block CSS sits in the footer. No fatal appears in debug.log.

  5. Remove wp-content/mu-plugins/block-fs.php and load a frontend page twice.

    Expect: the second load enqueues link#otter-widgets-css pointing into uploads/themeisle-gutenberg/.

Issue: #2937 (this PR targets development, so GitHub does not auto-close on merge — link it via the Development sidebar).

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@lucadobrescu
lucadobrescu requested a review from Copilot July 31, 2026 07:53
@lucadobrescu lucadobrescu self-assigned this Jul 31, 2026
@pirate-bot

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

Plugin build for 5a47788 is ready 🛎️!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents frontend widget CSS generation from fatally calling an unavailable WordPress filesystem API.

Changes:

  • Guards and validates filesystem initialization.
  • Adds PHPUnit and E2E regression coverage.
  • Extends E2E fixtures and classic-theme setup.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
inc/css/class-css-handler.php Safely checks filesystem availability.
tests/test-css-handler.php Adds filesystem regression tests.
tests/php/is-writable-sandbox.php Reproduces the missing-function condition.
src/blocks/test/e2e/blocks/widgets-css-frontend.spec.js Tests inline fallback and file generation.
src/blocks/test/e2e/fixtures.ts Adds widget/filesystem helpers.
src/blocks/test/e2e/playwright.config.js Serializes the stateful E2E spec.
packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php Adds E2E state-management endpoints.
.wp-env.json Installs a classic theme with sidebars.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pirate-bot

Copy link
Copy Markdown
Contributor

E2E Tests

Playwright Test Status: See serial and parallel matrix jobs

Performance Results serverResponse: {"q25":377.2,"q50":395.65,"q75":412.6,"cnt":10}, firstPaint: {"q25":1344.2,"q50":1445.35,"q75":1661.7,"cnt":10}, domContentLoaded: {"q25":3662.9,"q50":3709.25,"q75":3764.3,"cnt":10}, loaded: {"q25":3664.8,"q50":3711.2,"q75":3766.4,"cnt":10}, firstContentfulPaint: {"q25":4172.2,"q50":4198,"q75":4260.5,"cnt":10}, firstBlock: {"q25":14751.2,"q50":14919.9,"q75":15022.5,"cnt":10}, type: {"q25":30.15,"q50":31.11,"q75":31.77,"cnt":10}, typeWithoutInspector: {"q25":26.19,"q50":27.91,"q75":29.28,"cnt":10}, typeWithTopToolbar: {"q25":33.21,"q50":35.21,"q75":41.03,"cnt":10}, typeContainer: {"q25":16.56,"q50":18.57,"q75":19.92,"cnt":10}, focus: {"q25":138.43,"q50":147.76,"q75":151.71,"cnt":10}, inserterOpen: {"q25":46.76,"q50":47.85,"q75":49.41,"cnt":10}, inserterSearch: {"q25":17.32,"q50":19.34,"q75":20.65,"cnt":10}, inserterHover: {"q25":5.97,"q50":6.4,"q75":6.82,"cnt":20}, loadPatterns: {"q25":1831.26,"q50":1845.7,"q75":1895.2,"cnt":10}, listViewOpen: {"q25":236.75,"q50":246.18,"q75":262,"cnt":10}

@lucadobrescu
lucadobrescu requested a review from Copilot July 31, 2026 11:25
@lucadobrescu lucadobrescu added the pr-checklist-skip Allow this Pull Request to skip checklist. label Jul 31, 2026
@pirate-bot pirate-bot added the pr-checklist-complete The Pull Request checklist is complete. (automatic label) label Jul 31, 2026
@lucadobrescu

Copy link
Copy Markdown
Contributor Author

Copilot reviewed all 8 changed files and raised no comments, and every CI check is green — UnitTests, PHPStan, Phplint, JS Test, Performance, and both Playwright jobs (the new widgets-css-frontend.spec.js runs in the serial matrix).

Verification recap:

  • The new PHPUnit regression test was confirmed red on the unfixed code (it reproduces the exact production fatal from Widget CSS writability check fatals when WP_Filesystem() is unavailable #2937, Call to undefined function WP_Filesystem() at inc/css/class-css-handler.php:499) and green with the fix.
  • The e2e spec passes both scenarios: blocked filesystem → page completes with inline widget CSS; filesystem available → stylesheet file written and enqueued on the next request.

No further changes needed from the review.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-checklist-complete The Pull Request checklist is complete. (automatic label) pr-checklist-skip Allow this Pull Request to skip checklist.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants