diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 2af08fba70af9..935902553fc12 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -2489,19 +2489,8 @@ function wp_ajax_delete_inactive_widgets() { /** This action is documented in wp-admin/widgets-form.php */ do_action( 'sidebar_admin_setup' ); - $sidebars_widgets = wp_get_sidebars_widgets(); - - foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) { - $pieces = explode( '-', $widget_id ); - $multi_number = array_pop( $pieces ); - $id_base = implode( '-', $pieces ); - $widget = get_option( 'widget_' . $id_base ); - unset( $widget[ $multi_number ] ); - update_option( 'widget_' . $id_base, $widget ); - unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] ); - } - - wp_set_sidebars_widgets( $sidebars_widgets ); + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + wp_delete_inactive_widgets(); wp_die(); } diff --git a/src/wp-admin/includes/widgets.php b/src/wp-admin/includes/widgets.php index e751602866b0d..5d93d2a4b9eb8 100644 --- a/src/wp-admin/includes/widgets.php +++ b/src/wp-admin/includes/widgets.php @@ -326,3 +326,32 @@ function wp_widget_control( $sidebar_args ) { function wp_widgets_access_body_class( $classes ) { return "$classes widgets_access "; } + +/** + * Removes all widgets from the inactive widgets sidebar and their corresponding settings. + * + * @since 7.1.0 + */ +function wp_delete_inactive_widgets(): void { + $sidebars_widgets = wp_get_sidebars_widgets(); + + $inactive_widgets = $sidebars_widgets['wp_inactive_widgets'] ?? array(); + if ( ! is_array( $inactive_widgets ) || 0 === count( $inactive_widgets ) ) { + return; + } + + foreach ( $inactive_widgets as $widget_id ) { + $pieces = explode( '-', $widget_id ); + $multi_number = array_pop( $pieces ); + $id_base = implode( '-', $pieces ); + $widget = get_option( 'widget_' . $id_base ); + + if ( is_array( $widget ) ) { + unset( $widget[ $multi_number ] ); + update_option( 'widget_' . $id_base, $widget ); + } + } + + $sidebars_widgets['wp_inactive_widgets'] = array(); + wp_set_sidebars_widgets( $sidebars_widgets ); +} diff --git a/tests/phpunit/tests/widgets.php b/tests/phpunit/tests/widgets.php index 8eb5914c9400d..6d2bda524d149 100644 --- a/tests/phpunit/tests/widgets.php +++ b/tests/phpunit/tests/widgets.php @@ -866,6 +866,136 @@ public function test_wp_widget_control() { } } + /** + * @covers ::wp_delete_inactive_widgets + * @ticket 65414 + */ + public function test_wp_delete_inactive_widgets_removes_settings_and_empties_sidebar() { + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + + update_option( + 'widget_search', + array( + 2 => array( 'title' => 'Search 2' ), + 3 => array( 'title' => 'Search 3' ), + '_multiwidget' => 1, + ) + ); + update_option( + 'widget_text', + array( + 5 => array( 'text' => 'Text 5' ), + '_multiwidget' => 1, + ) + ); + + wp_set_sidebars_widgets( + array( + 'sidebar-1' => array( 'search-3' ), + 'wp_inactive_widgets' => array( 'search-2', 'text-5' ), + ) + ); + + wp_delete_inactive_widgets(); + + $sidebars_widgets = wp_get_sidebars_widgets(); + $this->assertSame( array(), $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertSame( array( 'search-3' ), $sidebars_widgets['sidebar-1'] ); + + $search_settings = get_option( 'widget_search' ); + $this->assertArrayNotHasKey( 2, $search_settings ); + $this->assertArrayHasKey( 3, $search_settings ); + + $text_settings = get_option( 'widget_text' ); + $this->assertArrayNotHasKey( 5, $text_settings ); + } + + /** + * @covers ::wp_delete_inactive_widgets + * @ticket 65414 + */ + public function test_wp_delete_inactive_widgets_with_no_inactive_widgets_key() { + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + + wp_set_sidebars_widgets( + array( + 'sidebar-1' => array( 'search-2' ), + ) + ); + + wp_delete_inactive_widgets(); + + $sidebars_widgets = wp_get_sidebars_widgets(); + $this->assertArrayNotHasKey( 'wp_inactive_widgets', $sidebars_widgets ); + $this->assertSame( array( 'search-2' ), $sidebars_widgets['sidebar-1'] ); + } + + /** + * @covers ::wp_delete_inactive_widgets + * @ticket 65414 + */ + public function test_wp_delete_inactive_widgets_with_empty_inactive_widgets() { + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + + wp_set_sidebars_widgets( + array( + 'sidebar-1' => array( 'search-2' ), + 'wp_inactive_widgets' => array(), + ) + ); + + wp_delete_inactive_widgets(); + + $sidebars_widgets = wp_get_sidebars_widgets(); + $this->assertSame( array(), $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertSame( array( 'search-2' ), $sidebars_widgets['sidebar-1'] ); + } + + /** + * A corrupted `wp_inactive_widgets` entry (not an array) must not raise a + * TypeError from count() on PHP 8. + * + * @covers ::wp_delete_inactive_widgets + * @ticket 65414 + */ + public function test_wp_delete_inactive_widgets_with_non_array_inactive_widgets_does_not_throw() { + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + + wp_set_sidebars_widgets( + array( + 'sidebar-1' => array( 'search-2' ), + 'wp_inactive_widgets' => 'not-an-array', + ) + ); + + wp_delete_inactive_widgets(); + + $sidebars_widgets = wp_get_sidebars_widgets(); + $this->assertSame( 'not-an-array', $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertSame( array( 'search-2' ), $sidebars_widgets['sidebar-1'] ); + } + + /** + * @covers ::wp_delete_inactive_widgets + * @ticket 65414 + */ + public function test_wp_delete_inactive_widgets_skips_non_array_widget_option() { + require_once ABSPATH . 'wp-admin/includes/widgets.php'; + + update_option( 'widget_search', 'corrupted-data' ); + + wp_set_sidebars_widgets( + array( + 'wp_inactive_widgets' => array( 'search-2' ), + ) + ); + + wp_delete_inactive_widgets(); + + $this->assertSame( 'corrupted-data', get_option( 'widget_search' ) ); + $this->assertSame( array(), wp_get_sidebars_widgets()['wp_inactive_widgets'] ); + } + public function test_the_widget_custom_before_title_arg() { register_widget( 'WP_Widget_Text' );