Skip to content
Closed
2 changes: 1 addition & 1 deletion src/wp-includes/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
41 changes: 41 additions & 0 deletions tests/phpunit/tests/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Member

Choose a reason for hiding this comment

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

Confirmed that this causes a warning when the fix is not applied:

There was 1 error:

1) Tests_Widgets::test_is_active_widget_with_unregistered_widget_and_id_base_match
Undefined array key "search-2"

/var/www/src/wp-includes/widgets.php:928
/var/www/tests/phpunit/tests/widgets.php:1452


$this->assertFalse( $result, 'The widget is not registered, so the function should return false.' );
}
}
10 changes: 10 additions & 0 deletions tests/phpunit/tests/widgets/wpBlockThemeRegisterClassicSidebar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
Expand All @@ -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' );
Expand Down
Loading