Commit 295dc0b2bf6 for woocommerce
commit 295dc0b2bf6ed9d3d893e84acff7af299ab75b99
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Mon Jun 8 18:04:39 2026 +0100
Surface settings UI fallback through wc_doing_it_wrong (#65499)
* dev: surface settings ui fallback notices
* Fix settings UI fallback notice version
* Revert "Fix settings UI fallback notice version"
This reverts commit 73fa8de992822137ded2a1bee0d6483fc8a3e560.
* Polish settings UI fallback handling
* Log settings UI script handle errors
* Log settings UI fallback debug details
diff --git a/plugins/woocommerce/changelog/fix-settings-ui-fallback-doing-it-wrong b/plugins/woocommerce/changelog/fix-settings-ui-fallback-doing-it-wrong
new file mode 100644
index 00000000000..6fa780347ac
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-settings-ui-fallback-doing-it-wrong
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Surface Settings UI fallback through wc_doing_it_wrong.
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 82b308f8690..cc847268411 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
@@ -148,6 +148,29 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
return "$classes woocommerce-settings-ui-page";
}
+ /**
+ * Log a developer-facing notice when settings UI rendering falls back to the legacy renderer.
+ *
+ * @since 10.9.0
+ *
+ * @param SettingsUIPageInterface $settings_ui_page Settings UI page adapter.
+ * @param string $section_id Section id.
+ * @param string $reason Fallback reason.
+ */
+ private function log_settings_ui_fallback( SettingsUIPageInterface $settings_ui_page, string $section_id, string $reason ): void {
+ wc_doing_it_wrong(
+ 'WC_Settings_Page::output',
+ sprintf(
+ /* translators: 1: settings page id, 2: settings section id, 3: fallback reason. */
+ __( 'Settings UI rendering for page "%1$s" section "%2$s" fell back to the legacy settings renderer. Reason: %3$s', 'woocommerce' ),
+ $settings_ui_page->get_page_id(),
+ '' === $section_id ? 'default' : $section_id,
+ $reason
+ ),
+ '10.9.0'
+ );
+ }
+
/**
* Creates the React mount point for settings slot.
*/
@@ -313,41 +336,68 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
$page_id = $settings_ui_page instanceof SettingsUIPageInterface ? $settings_ui_page->get_page_id() : '';
$schema_failed = ! empty( $GLOBALS['wc_settings_ui_schema_failed'][ $page_id ][ $section_key ] );
- if ( Features::is_enabled( 'settings-ui' ) && $settings_ui_page instanceof SettingsUIPageInterface && ! $schema_failed ) {
- $render_settings_ui = true;
-
- try {
- $script_handles = $settings_ui_page->get_script_handles( $current_section );
- } catch ( \Throwable $e ) {
- $script_handles = array();
- $render_settings_ui = false;
+ if ( Features::is_enabled( 'settings-ui' ) && $settings_ui_page instanceof SettingsUIPageInterface ) {
+ if ( $schema_failed ) {
+ $this->log_settings_ui_fallback(
+ $settings_ui_page,
+ $current_section,
+ __( 'Settings UI schema generation failed.', 'woocommerce' )
+ );
+ } else {
+ $render_settings_ui = true;
+
+ try {
+ $script_handles = $settings_ui_page->get_script_handles( $current_section );
+ } catch ( \Throwable $e ) {
+ $script_handles = array();
+ $render_settings_ui = false;
+ $reason = __( 'Settings UI script handles could not be resolved.', 'woocommerce' );
+
+ wc_get_logger()->debug(
+ sprintf(
+ 'Settings UI script handles could not be resolved for page "%1$s" section "%2$s": %3$s: %4$s',
+ $settings_ui_page->get_page_id(),
+ '' === $current_section ? 'default' : $current_section,
+ get_class( $e ),
+ $e->getMessage()
+ ),
+ array( 'source' => 'settings-ui' )
+ );
+
+ if ( $e instanceof \Exception ) {
+ $reason = sprintf(
+ /* translators: %s: exception message. */
+ __( 'Settings UI script handles could not be resolved: %s', 'woocommerce' ),
+ $e->getMessage()
+ );
+ wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__ );
+ }
- if ( $e instanceof \Exception ) {
- wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__ );
+ $this->log_settings_ui_fallback( $settings_ui_page, $current_section, $reason );
}
- }
- if ( $render_settings_ui ) {
- /**
- * Extension-provided handles may violate the interface contract.
- *
- * @var mixed[] $script_handles
- */
- foreach ( $script_handles as $script_handle ) {
- if ( is_string( $script_handle ) && '' !== $script_handle ) {
- wp_enqueue_script( $script_handle );
+ if ( $render_settings_ui ) {
+ /**
+ * Extension-provided handles may violate the interface contract.
+ *
+ * @var mixed[] $script_handles
+ */
+ foreach ( $script_handles as $script_handle ) {
+ if ( is_string( $script_handle ) && '' !== $script_handle ) {
+ wp_enqueue_script( $script_handle );
+ }
}
- }
- $GLOBALS['hide_save_button'] = true;
+ $GLOBALS['hide_save_button'] = true;
- printf(
- '<div id="%1$s" data-wc-settings-ui="1" data-wc-settings-page="%2$s" data-wc-settings-section="%3$s"></div>',
- esc_attr( 'wc_settings_ui_' . sanitize_html_class( $this->id ) . '_' . sanitize_html_class( '' === $current_section ? 'default' : $current_section ) ),
- esc_attr( $settings_ui_page->get_page_id() ),
- esc_attr( $current_section )
- );
- return;
+ printf(
+ '<div id="%1$s" data-wc-settings-ui="1" data-wc-settings-page="%2$s" data-wc-settings-section="%3$s"></div>',
+ esc_attr( 'wc_settings_ui_' . sanitize_html_class( $this->id ) . '_' . sanitize_html_class( '' === $current_section ? 'default' : $current_section ) ),
+ esc_attr( $settings_ui_page->get_page_id() ),
+ esc_attr( $current_section )
+ );
+ return;
+ }
}
}
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 a3141a1c03a..fef3cab7598 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
@@ -133,6 +133,93 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
$this->assertTrue( $GLOBALS['hide_save_button'] );
}
+ /**
+ * It emits developer feedback when settings UI rendering falls back to legacy output.
+ */
+ public function test_settings_ui_fallback_emits_doing_it_wrong_notice(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+ add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
+ $this->setExpectedIncorrectUsage( 'WC_Settings_Page::output' );
+
+ $notices = array();
+ $action = function ( $function_name, $message, $version ) use ( &$notices ) {
+ $notices[] = array(
+ 'function_name' => $function_name,
+ 'message' => $message,
+ 'version' => $version,
+ );
+ };
+ add_action( 'doing_it_wrong_run', $action, 10, 3 );
+
+ global $current_section;
+ $current_section = 'advanced';
+ $page = $this->get_settings_ui_test_page_with_failing_script_handles();
+
+ try {
+ ob_start();
+ $page->output();
+ $output = ob_get_clean();
+ } finally {
+ remove_action( 'doing_it_wrong_run', $action, 10 );
+ remove_filter( 'doing_it_wrong_trigger_error', '__return_false' );
+ }
+
+ $settings_page_notices = $this->get_settings_page_output_notices( $notices );
+
+ $this->assertStringContainsString( 'name="woocommerce_settings_ui_flag_test"', $output );
+ $this->assertStringNotContainsString( 'data-wc-settings-ui="1"', $output );
+ $this->assertNotEmpty( $settings_page_notices );
+ $this->assertSame( '10.9.0', $settings_page_notices[0]['version'] );
+ $this->assertStringContainsString( 'settings_ui_flag_test', $settings_page_notices[0]['message'] );
+ $this->assertStringContainsString( 'advanced', $settings_page_notices[0]['message'] );
+ $this->assertStringContainsString( 'Unable to load extension script handles.', $settings_page_notices[0]['message'] );
+ }
+
+ /**
+ * It emits developer feedback when settings UI schema generation has failed.
+ */
+ public function test_settings_ui_schema_failure_fallback_emits_doing_it_wrong_notice(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+ add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
+ $this->setExpectedIncorrectUsage( 'WC_Settings_Page::output' );
+
+ $notices = array();
+ $action = function ( $function_name, $message, $version ) use ( &$notices ) {
+ $notices[] = array(
+ 'function_name' => $function_name,
+ 'message' => $message,
+ 'version' => $version,
+ );
+ };
+ add_action( 'doing_it_wrong_run', $action, 10, 3 );
+
+ global $current_section;
+ $current_section = 'advanced';
+ $page = $this->get_settings_ui_test_page_with_failing_script_handles();
+
+ try {
+ $GLOBALS['wc_settings_ui_schema_failed']['settings_ui_flag_test']['advanced'] = true;
+
+ ob_start();
+ $page->output();
+ $output = ob_get_clean();
+ } finally {
+ unset( $GLOBALS['wc_settings_ui_schema_failed']['settings_ui_flag_test']['advanced'] );
+ remove_action( 'doing_it_wrong_run', $action, 10 );
+ remove_filter( 'doing_it_wrong_trigger_error', '__return_false' );
+ }
+
+ $settings_page_notices = $this->get_settings_page_output_notices( $notices );
+
+ $this->assertStringContainsString( 'name="woocommerce_settings_ui_flag_test"', $output );
+ $this->assertStringNotContainsString( 'data-wc-settings-ui="1"', $output );
+ $this->assertNotEmpty( $settings_page_notices );
+ $this->assertSame( '10.9.0', $settings_page_notices[0]['version'] );
+ $this->assertStringContainsString( 'settings_ui_flag_test', $settings_page_notices[0]['message'] );
+ $this->assertStringContainsString( 'advanced', $settings_page_notices[0]['message'] );
+ $this->assertStringContainsString( 'Settings UI schema generation failed.', $settings_page_notices[0]['message'] );
+ }
+
/**
* It exposes section navigation metadata from legacy settings pages.
*/
@@ -270,6 +357,79 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
};
}
+ /**
+ * Get captured doing-it-wrong notices emitted by the settings page output method.
+ *
+ * @param array $notices Captured doing-it-wrong notices.
+ * @return array
+ */
+ private function get_settings_page_output_notices( array $notices ): array {
+ return array_values(
+ array_filter(
+ $notices,
+ static function ( array $notice ): bool {
+ return 'WC_Settings_Page::output' === $notice['function_name'];
+ }
+ )
+ );
+ }
+
+ /**
+ * Build a settings page whose settings UI adapter cannot provide script handles.
+ *
+ * @return \WC_Settings_Page
+ */
+ private function get_settings_ui_test_page_with_failing_script_handles(): \WC_Settings_Page {
+ return new class() extends \WC_Settings_Page {
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->id = 'settings_ui_flag_test';
+ $this->label = 'Settings UI flag test';
+ }
+
+ /**
+ * Get the settings UI page adapter.
+ *
+ * @return \Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface|null
+ */
+ public function get_settings_ui_page(): ?\Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface {
+ return new class( $this ) extends \Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter {
+ /**
+ * Get script handles.
+ *
+ * @param string $section_id Section id.
+ * @return array
+ */
+ public function get_script_handles( string $section_id ): array {
+ if ( 'advanced' === $section_id ) {
+ throw new \RuntimeException( 'Unable to load extension script handles.' );
+ }
+
+ return array();
+ }
+ };
+ }
+
+ /**
+ * Get settings for a section.
+ *
+ * @param string $section_id Section id.
+ * @return array
+ */
+ protected function get_settings_for_section_core( $section_id ) {
+ return array(
+ array(
+ 'id' => 'woocommerce_settings_ui_flag_test',
+ 'type' => 'text',
+ 'title' => 'Settings UI flag test',
+ ),
+ );
+ }
+ };
+ }
+
/**
* Build a settings page with multiple sections.
*