Commit 9082fd51cca for woocommerce
commit 9082fd51cca2d541adb1f77c0bfbd55da1b9f1ae
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Fri Jul 24 14:40:49 2026 +0100
Fix hidden Save button on Settings UI fallback pages (#66958)
fix(settings): Keep classic body classes when settings UI falls back
The woocommerce-settings-ui-page body class was added whenever the
feature flag was on and the page had a Settings UI adapter, even when
schema or script handle resolution had failed and output() had fallen
back to the legacy renderer. The settings UI stylesheet hides
`#mainform > .submit` under that class because the shell brings its own
save UI, so the fallback page rendered navigation and legacy fields
with an invisible Save button: merchants could view but not save.
Skip all settings UI body classes when the request context reports a
schema or script handle failure, using the same gate the drill-down
class already applied. The fallback page keeps the full classic
styling, including the visible Save button. Schema resolution now
happens at admin_body_class time for top-level pages too; it is
memoized in the request context, so output() reuses the result.
Refs #66908
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class b/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class
new file mode 100644
index 00000000000..c3cd7c7ee41
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show the Save button when an experimental Settings UI page falls back to the legacy renderer.
diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
index 5573e5b6d7d..5e5ad88b8e6 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
@@ -146,9 +146,13 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
return $classes;
}
- $is_rendering_drill_down = $context->is_drill_down()
- && ! $context->has_schema_failed()
- && ! $context->has_script_handles_failed();
+ // The legacy fallback renderer needs the classic styling: the settings UI
+ // body class hides the legacy Save button via CSS.
+ if ( $context->has_schema_failed() || $context->has_script_handles_failed() ) {
+ return $classes;
+ }
+
+ $is_rendering_drill_down = $context->is_drill_down();
} catch ( \Throwable $e ) {
return $classes;
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
index f73398752d8..70afbd50eca 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
@@ -462,9 +462,9 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
}
/**
- * @testdox Should not add the drill-down body class when schema generation falls back to legacy rendering.
+ * @testdox Should not add the Settings UI body classes when schema generation falls back to legacy rendering.
*/
- public function test_settings_ui_drill_down_body_class_is_not_added_when_schema_generation_fails(): void {
+ public function test_settings_ui_body_classes_are_not_added_when_schema_generation_fails(): void {
add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
global $current_section, $current_tab;
@@ -474,14 +474,13 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
$classes = $page->add_settings_ui_body_class( 'existing-class' );
- $this->assertStringContainsString( 'woocommerce-settings-ui-page', $classes );
- $this->assertStringNotContainsString( 'woocommerce-settings-ui-drill-down', $classes );
+ $this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
}
/**
- * @testdox Should not add the drill-down body class when script handle resolution falls back to legacy rendering.
+ * @testdox Should not add the Settings UI body classes when script handle resolution falls back to legacy rendering.
*/
- public function test_settings_ui_drill_down_body_class_is_not_added_when_script_handle_resolution_fails(): void {
+ public function test_settings_ui_body_classes_are_not_added_when_script_handle_resolution_fails(): void {
add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
global $current_section, $current_tab;
@@ -491,8 +490,23 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
$classes = $page->add_settings_ui_body_class( 'existing-class' );
- $this->assertStringContainsString( 'woocommerce-settings-ui-page', $classes );
- $this->assertStringNotContainsString( 'woocommerce-settings-ui-drill-down', $classes );
+ $this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
+ }
+
+ /**
+ * @testdox Should not add the Settings UI body class when a top-level page falls back to legacy rendering.
+ */
+ public function test_settings_ui_body_class_is_not_added_when_a_top_level_page_falls_back(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+
+ global $current_section, $current_tab;
+ $current_section = 'failing_section';
+ $current_tab = 'settings_ui_flag_test';
+ $page = $this->get_settings_ui_test_page_with_failing_schema();
+
+ $classes = $page->add_settings_ui_body_class( 'existing-class' );
+
+ $this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
}
/**