diff --git a/src/wp-includes/widgets.php b/src/wp-includes/widgets.php index 6b216c2d3c119..799a5973611ae 100644 --- a/src/wp-includes/widgets.php +++ b/src/wp-includes/widgets.php @@ -925,7 +925,7 @@ function is_active_widget( $callback = false, $widget_id = false, $id_base = fal if ( is_array( $widgets ) ) { foreach ( $widgets as $widget ) { if ( ( $callback && isset( $wp_registered_widgets[ $widget ]['callback'] ) && $wp_registered_widgets[ $widget ]['callback'] === $callback ) || ( $id_base && _get_widget_id_base( $widget ) === $id_base ) ) { - if ( ! $widget_id || $widget_id === $wp_registered_widgets[ $widget ]['id'] ) { + if ( ! $widget_id || ( isset( $wp_registered_widgets[ $widget ]['id'] ) && $widget_id === $wp_registered_widgets[ $widget ]['id'] ) ) { return $sidebar; } } diff --git a/tests/phpunit/tests/widgets.php b/tests/phpunit/tests/widgets.php index a8ebf7aaa4d4d..8eb5914c9400d 100644 --- a/tests/phpunit/tests/widgets.php +++ b/tests/phpunit/tests/widgets.php @@ -1412,4 +1412,45 @@ public function test_wp_map_sidebars_widgets_converts_null_sidebar_to_empty_arra $this->assertArrayHasKey( 'primary', $new_sidebars ); $this->assertSame( array(), $new_sidebars['primary'], 'Primary sidebar should be an empty array after normalization.' ); } + + /** + * Tests that is_active_widget() does not generate a PHP warning when + * a widget ID exists in sidebars_widgets but is not in $wp_registered_widgets, + * and the function is called with id_base and widget_id parameters. + * + * This can happen when a widget is saved to a sidebar but the widget class + * has not yet been registered (e.g., during early plugin/theme loading). + * + * @ticket 57518 + * @covers ::is_active_widget + */ + public function test_is_active_widget_with_unregistered_widget_and_id_base_match() { + global $wp_registered_widgets; + + // Set up a sidebar with a widget that is NOT registered in $wp_registered_widgets. + update_option( + 'sidebars_widgets', + array( + 'wp_inactive_widgets' => array(), + 'sidebar-1' => array( 'search-2' ), + 'array_version' => 3, + ) + ); + + // Ensure the widget is NOT in $wp_registered_widgets. + unset( $wp_registered_widgets['search-2'] ); + + /* + * Call is_active_widget() with id_base and widget_id parameters. + * This should NOT generate a PHP warning about accessing array offset on null. + * + * The bug occurs because when matching by id_base, the code checks + * _get_widget_id_base( $widget ) === $id_base without verifying + * $wp_registered_widgets[ $widget ] exists, then tries to access + * $wp_registered_widgets[ $widget ]['id'] on the next line. + */ + $result = is_active_widget( false, 'search-2', 'search', true ); + + $this->assertFalse( $result, 'The widget is not registered, so the function should return false.' ); + } } diff --git a/tests/phpunit/tests/widgets/wpBlockThemeRegisterClassicSidebar.php b/tests/phpunit/tests/widgets/wpBlockThemeRegisterClassicSidebar.php index f8f8c519ae639..1d65f2ac32fdd 100644 --- a/tests/phpunit/tests/widgets/wpBlockThemeRegisterClassicSidebar.php +++ b/tests/phpunit/tests/widgets/wpBlockThemeRegisterClassicSidebar.php @@ -32,6 +32,11 @@ public function tear_down() { public function test_a_sidebar_should_be_registered() { global $wp_registered_sidebars; + // Register a sidebar for testing if none exist. + if ( empty( $wp_registered_sidebars ) ) { + register_sidebar( array( 'id' => 'test-sidebar' ) ); + } + $sidebar_id = array_key_first( $wp_registered_sidebars ); $this->assertNotEmpty( $sidebar_id ); } @@ -42,6 +47,11 @@ public function test_a_sidebar_should_be_registered() { public function test_should_reregister_previous_theme_sidebar() { global $wp_registered_sidebars; + // Register a sidebar for testing if none exist. + if ( empty( $wp_registered_sidebars ) ) { + register_sidebar( array( 'id' => 'test-sidebar' ) ); + } + $sidebar_id = array_key_first( $wp_registered_sidebars ); switch_theme( 'block-theme' );