From 9b7c97af2695e65386e4e3e96071764deb307908 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 21 Sep 2017 18:45:03 +0000 Subject: [PATCH] Widgets: Improved sidebar mapping on theme switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds on efforts brought forward in #17979. This will send sidebars through three levels of mapping:
 1. If both themes have only one sidebar, that gets mapped. 2. If both themes have sidebars with the same slug, they get mapped. 3. Sidebars that (even partially) match slugs from a similar kind of sidebar will get mapped. Finally, if the theme has previously been active and we have a record of its sidebar configuration then, any unmapped sidebar will be restored to its previous state. Props westonruter, obenland, alexvorn2, timmydcrawford. See #39693. git-svn-id: https://develop.svn.wordpress.org/trunk@41555 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/theme.php | 7 +- src/wp-includes/widgets.php | 305 ++++++++++++++++++++++++-------- tests/phpunit/tests/widgets.php | 239 ++++++++++++++++++++----- 3 files changed, 429 insertions(+), 122 deletions(-) diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 8a51d7e014..ad5b12248e 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -681,13 +681,16 @@ function switch_theme( $stylesheet ) { $_sidebars_widgets = null; if ( 'wp_ajax_customize_save' === current_action() ) { - $_sidebars_widgets = $wp_customize->post_value( $wp_customize->get_setting( 'old_sidebars_widgets_data' ) ); + $old_sidebars_widgets_data_setting = $wp_customize->get_setting( 'old_sidebars_widgets_data' ); + if ( $old_sidebars_widgets_data_setting ) { + $_sidebars_widgets = $wp_customize->post_value( $old_sidebars_widgets_data_setting ); + } } elseif ( is_array( $sidebars_widgets ) ) { $_sidebars_widgets = $sidebars_widgets; } if ( is_array( $_sidebars_widgets ) ) { - set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $_sidebars_widgets ) ); + set_theme_mod( 'sidebars_widgets', $_sidebars_widgets ); } $nav_menu_locations = get_theme_mod( 'nav_menu_locations' ); diff --git a/src/wp-includes/widgets.php b/src/wp-includes/widgets.php index 719b273f73..fe0e058d9a 100644 --- a/src/wp-includes/widgets.php +++ b/src/wp-includes/widgets.php @@ -919,11 +919,19 @@ function wp_get_sidebars_widgets( $deprecated = true ) { * @since 2.2.0 * @access private * + * @global array $_wp_sidebars_widgets * @param array $sidebars_widgets Sidebar widgets and their settings. */ function wp_set_sidebars_widgets( $sidebars_widgets ) { - if ( !isset( $sidebars_widgets['array_version'] ) ) + global $_wp_sidebars_widgets; + + // Clear cached value used in wp_get_sidebars_widgets(). + $_wp_sidebars_widgets = null; + + if ( ! isset( $sidebars_widgets['array_version'] ) ) { $sidebars_widgets['array_version'] = 3; + } + update_option( 'sidebars_widgets', $sidebars_widgets ); } @@ -1113,107 +1121,248 @@ function _wp_sidebars_changed() { * * @param string|bool $theme_changed Whether the theme was changed as a boolean. A value * of 'customize' defers updates for the Customizer. - * @return array|void + * @return array Updated sidebars widgets. */ function retrieve_widgets( $theme_changed = false ) { global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets; - $registered_sidebar_keys = array_keys( $wp_registered_sidebars ); - $orphaned = 0; + $registered_sidebars_keys = array_keys( $wp_registered_sidebars ); + $registered_widgets_ids = array_keys( $wp_registered_widgets ); - $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' ); - if ( is_array( $old_sidebars_widgets ) ) { - // time() that sidebars were stored is in $old_sidebars_widgets['time'] - $_sidebars_widgets = $old_sidebars_widgets['data']; - - if ( 'customize' !== $theme_changed ) { - remove_theme_mod( 'sidebars_widgets' ); + if ( ! is_array( get_theme_mod( 'sidebars_widgets' ) ) ) { + if ( empty( $sidebars_widgets ) ) { + return array(); } - foreach ( $_sidebars_widgets as $sidebar => $widgets ) { - if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) { - continue; - } - - if ( !in_array( $sidebar, $registered_sidebar_keys ) ) { - $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $widgets; - unset( $_sidebars_widgets[$sidebar] ); - } - } - } else { - if ( empty( $sidebars_widgets ) ) - return; - unset( $sidebars_widgets['array_version'] ); - $old = array_keys($sidebars_widgets); - sort($old); - sort($registered_sidebar_keys); + $sidebars_widgets_keys = array_keys( $sidebars_widgets ); + sort( $sidebars_widgets_keys ); + sort( $registered_sidebars_keys ); - if ( $old == $registered_sidebar_keys ) - return; + if ( $sidebars_widgets_keys === $registered_sidebars_keys ) { + $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); - $_sidebars_widgets = array( - 'wp_inactive_widgets' => !empty( $sidebars_widgets['wp_inactive_widgets'] ) ? $sidebars_widgets['wp_inactive_widgets'] : array() - ); + return $sidebars_widgets; + } + } - unset( $sidebars_widgets['wp_inactive_widgets'] ); - foreach ( $wp_registered_sidebars as $id => $settings ) { - if ( $theme_changed ) { - $_sidebars_widgets[$id] = array_shift( $sidebars_widgets ); - } else { - // no theme change, grab only sidebars that are currently registered - if ( isset( $sidebars_widgets[$id] ) ) { - $_sidebars_widgets[$id] = $sidebars_widgets[$id]; - unset( $sidebars_widgets[$id] ); + // Discard invalid, theme-specific widgets from sidebars. + $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); + $sidebars_widgets = wp_map_sidebars_widgets( $sidebars_widgets ); + + // Find hidden/lost multi-widget instances. + $shown_widgets = call_user_func_array( 'array_merge', $sidebars_widgets ); + $lost_widgets = array_diff( $registered_widgets_ids, $shown_widgets ); + + foreach ( $lost_widgets as $key => $widget_id ) { + $number = preg_replace( '/.+?-([0-9]+)$/', '$1', $widget_id ); + + // Only keep active and default widgets. + if ( is_numeric( $number ) && (int) $number < 2 ) { + unset( $lost_widgets[ $key ] ); + } + } + $sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] ); + + if ( 'customize' !== $theme_changed ) { + wp_set_sidebars_widgets( $sidebars_widgets ); + } + + return $sidebars_widgets; +} + +/** + * Compares a list of sidebars with their widgets against a whitelist. + * + * @since 4.9.0 + * + * @param array $existing_sidebars_widgets List of sidebars and their widget instance IDs. + * @return array Mapped sidebars widgets. + */ +function wp_map_sidebars_widgets( $existing_sidebars_widgets ) { + global $wp_registered_sidebars; + + $new_sidebars_widgets = array( + 'wp_inactive_widgets' => array(), + ); + + // Short-circuit if there are no sidebars to map. + if ( ! is_array( $existing_sidebars_widgets ) || empty( $existing_sidebars_widgets ) ) { + return $new_sidebars_widgets; + } + + foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) { + if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) { + $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], (array) $widgets ); + unset( $existing_sidebars_widgets[ $sidebar ] ); + } + } + + // If old and new theme have just one sidebar, map it and we're done. + if ( 1 === count( $existing_sidebars_widgets ) && 1 === count( $wp_registered_sidebars ) ) { + $new_sidebars_widgets[ key( $wp_registered_sidebars ) ] = array_pop( $existing_sidebars_widgets ); + + return $new_sidebars_widgets; + } + + // Map locations with the same slug. + $existing_sidebars = array_keys( $existing_sidebars_widgets ); + + foreach ( $wp_registered_sidebars as $sidebar => $name ) { + if ( in_array( $sidebar, $existing_sidebars, true ) ) { + $new_sidebars_widgets[ $sidebar ] = $existing_sidebars_widgets[ $sidebar ]; + unset( $existing_sidebars_widgets[ $sidebar ] ); + } else { + $new_sidebars_widgets[ $sidebar ] = array(); + } + } + + // If there are no old sidebars left, then we're done. + if ( empty( $existing_sidebars_widgets ) ) { + return $new_sidebars_widgets; + } + + /* + * If old and new theme both have sidebars that contain phrases + * from within the same group, make an educated guess and map it. + */ + $common_slug_groups = array( + array( 'sidebar', 'primary', 'main', 'right' ), + array( 'second', 'left' ), + array( 'sidebar-2', 'footer', 'bottom' ), + array( 'header', 'top' ), + ); + + // Go through each group... + foreach ( $common_slug_groups as $slug_group ) { + + // ...and see if any of these slugs... + foreach ( $slug_group as $slug ) { + + // ...and any of the new sidebars... + foreach ( $wp_registered_sidebars as $new_sidebar => $args ) { + + // ...actually match! + if ( false === stripos( $new_sidebar, $slug ) && false === stripos( $slug, $new_sidebar ) ) { + continue; } + + // Then see if any of the existing sidebars... + foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) { + + // ...and any slug in the same group... + foreach ( $slug_group as $slug ) { + + // ... have a match as well. + if ( false === stripos( $sidebar, $slug ) && false === stripos( $slug, $sidebar ) ) { + continue; + } + + // Make sure this sidebar wasn't mapped and removed previously. + if ( ! empty( $existing_sidebars_widgets[ $sidebar ] ) ) { + + // We have a match that can be mapped! + $new_sidebars_widgets[ $new_sidebar ] = array_merge( $new_sidebars_widgets[ $new_sidebar ], $existing_sidebars_widgets[ $sidebar ] ); + + // Remove the mapped sidebar so it can't be mapped again. + unset( $existing_sidebars_widgets[ $sidebar ] ); + + // Go back and check the next new sidebar. + continue 3; + } + } // endforeach ( $slug_group as $slug ) + } // endforeach ( $existing_sidebars_widgets as $sidebar => $widgets ) + } // endforeach foreach ( $wp_registered_sidebars as $new_sidebar => $args ) + } // endforeach ( $slug_group as $slug ) + } // endforeach ( $common_slug_groups as $slug_group ) + + // Move any left over widgets to inactive sidebar. + foreach ( $existing_sidebars_widgets as $widgets ) { + if ( is_array( $widgets ) && ! empty( $widgets ) ) { + $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], $widgets ); + } + } + + // Sidebars_widgets settings from when this theme was previously active. + $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' ); + + if ( is_array( $old_sidebars_widgets ) ) { + + // Only check sidebars that are empty or have not been mapped to yet. + foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) { + if ( array_key_exists( $new_sidebar, $old_sidebars_widgets ) && ! empty( $new_widgets ) ) { + unset( $old_sidebars_widgets[ $new_sidebar ] ); } } - foreach ( $sidebars_widgets as $val ) { - if ( is_array($val) && ! empty( $val ) ) - $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $val; - } - } - - // discard invalid, theme-specific widgets from sidebars - $shown_widgets = array(); - - foreach ( $_sidebars_widgets as $sidebar => $widgets ) { - if ( !is_array($widgets) ) - continue; - - $_widgets = array(); - foreach ( $widgets as $widget ) { - if ( isset($wp_registered_widgets[$widget]) ) - $_widgets[] = $widget; + // Remove orphaned widgets, we're only interested in previously active sidebars. + foreach ( $old_sidebars_widgets as $sidebar => $widgets ) { + if ( 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) { + unset( $old_sidebars_widgets[ $sidebar ] ); + } } - $_sidebars_widgets[$sidebar] = $_widgets; - $shown_widgets = array_merge($shown_widgets, $_widgets); + $old_sidebars_widgets = _wp_remove_unregistered_widgets( $old_sidebars_widgets ); + + if ( ! empty( $old_sidebars_widgets ) ) { + + // Go through each remaining sidebar... + foreach ( $old_sidebars_widgets as $old_sidebar => $old_widgets ) { + + // ...and check every new sidebar... + foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) { + + // ...for every widget we're trying to revive. + foreach ( $old_widgets as $key => $widget_id ) { + $active_key = array_search( $widget_id, $new_widgets, true ); + + // If the widget is used elsewhere... + if ( false !== $active_key ) { + + // ...and that elsewhere is inactive widgets... + if ( 'wp_inactive_widgets' === $new_sidebar ) { + + // ...remove it from there and keep the active version... + unset( $new_sidebars_widgets['wp_inactive_widgets'][ $active_key ] ); + } else { + + // ...otherwise remove it from the old sidebar and keep it in the new one. + unset( $old_sidebars_widgets[ $old_sidebar ][ $key ] ); + } + } // endif ( $active_key ) + } // endforeach ( $old_widgets as $key => $widget_id ) + } // endforeach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) + } // endforeach ( $old_sidebars_widgets as $old_sidebar => $old_widgets ) + } // endif ( ! empty( $old_sidebars_widgets ) ) + + + // Restore widget settings from when theme was previously active. + $new_sidebars_widgets = array_merge( $new_sidebars_widgets, $old_sidebars_widgets ); } - $sidebars_widgets = $_sidebars_widgets; - unset($_sidebars_widgets, $_widgets); + return $new_sidebars_widgets; +} - // find hidden/lost multi-widget instances - $lost_widgets = array(); - foreach ( $wp_registered_widgets as $key => $val ) { - if ( in_array($key, $shown_widgets, true) ) - continue; - - $number = preg_replace('/.+?-([0-9]+)$/', '$1', $key); - - if ( 2 > (int) $number ) - continue; - - $lost_widgets[] = $key; +/** + * Compares a list of sidebars with their widgets against a whitelist. + * + * @since 4.9.0 + * + * @param array $sidebars_widgets List of sidebars and their widget instance IDs. + * @param array $whitelist Optional. List of widget IDs to compare against. Default: Registered widgets. + * @return array Sidebars with whitelisted widgets. + */ +function _wp_remove_unregistered_widgets( $sidebars_widgets, $whitelist = array() ) { + if ( empty( $whitelist ) ) { + $whitelist = array_keys( $GLOBALS['wp_registered_widgets'] ); } - $sidebars_widgets['wp_inactive_widgets'] = array_merge($lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets']); - if ( 'customize' !== $theme_changed ) { - wp_set_sidebars_widgets( $sidebars_widgets ); + foreach ( $sidebars_widgets as $sidebar => $widgets ) { + if ( is_array( $widgets ) ) { + $sidebars_widgets[ $sidebar ] = array_intersect( $widgets, $whitelist ); + } } return $sidebars_widgets; diff --git a/tests/phpunit/tests/widgets.php b/tests/phpunit/tests/widgets.php index 83c1d3bcfa..aa1ba6c493 100644 --- a/tests/phpunit/tests/widgets.php +++ b/tests/phpunit/tests/widgets.php @@ -709,24 +709,27 @@ class Tests_Widgets extends WP_UnitTestCase { global $sidebars_widgets, $_wp_sidebars_widgets; wp_widgets_init(); - $this->register_sidebars( array( 'sidebar-1', 'sidebar-2','sidebar-3', 'wp_inactive_widgets' ) ); + $this->register_sidebars( array( 'sidebar-1', 'sidebar-2', 'sidebar-3', 'wp_inactive_widgets' ) ); + // Test restoring sidebars widgets when previously activated. set_theme_mod( 'sidebars_widgets', array( - 'time' => time(), - 'data' => array( - 'sidebar-1' => array( 'tag_cloud-1' ), - 'sidebar-2' => array( 'text-1' ), - 'sidebar-3' => array( 'unregistered_widget-1' ), - 'fantasy' => array( 'archives-2' ), - 'wp_inactive_widgets' => array(), - ), + 'sidebar-1' => array( 'tag_cloud-1' ), + 'sidebar-2' => array(), + 'sidebar-3' => array( 'unregistered_widget-1', 'text-1', 'media_image-1' ), + 'orphaned_widgets_1' => array( 'media_video-2' ), ) ); + $sidebars_widgets = array( + 'sidebar-1' => array( 'tag_cloud-1' ), + 'sidebar-2' => array( 'text-1' ), + 'fantasy' => array( 'archives-2' ), + 'wp_inactive_widgets' => array(), + ); + $result = retrieve_widgets( true ); - $_wp_sidebars_widgets = array(); $this->assertInternalType( 'array', $result ); - $this->assertNotEmpty( $result ); + $this->assertEquals( $result, $sidebars_widgets ); foreach ( $sidebars_widgets as $widgets ) { $this->assertInternalType( 'array', $widgets ); @@ -734,22 +737,22 @@ class Tests_Widgets extends WP_UnitTestCase { $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); - $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); + $this->assertContains( 'media_image-1', $sidebars_widgets['sidebar-3'] ); + $this->assertArrayNotHasKey( 'orphaned_widgets_1', $sidebars_widgets ); // Unregistered widget should be filtered out. - $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); + $this->assertNotContains( 'unregistered_widget-1', $sidebars_widgets['sidebar-3'] ); + + // 6 default widgets - 1 active text widget + 1 orphaned widget = 6. + $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] ); - // 6 default widgets - 1 active text widget = 5. - $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'meta-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'search-2', $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertContains( 'archives-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'categories-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-posts-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] ); - // Theme mode with previous widgets was removed. - $this->assertFalse( get_theme_mod( 'sidebars_widgets' ) ); - // Sidebar_widgets option was updated. $this->assertEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); } @@ -775,7 +778,8 @@ class Tests_Widgets extends WP_UnitTestCase { $result = retrieve_widgets( true ); // $sidebars_widgets matches registered sidebars. - $this->assertNull( $result ); + $this->assertInternalType( 'array', $result ); + $this->assertEquals( $result, $sidebars_widgets ); foreach ( $sidebars_widgets as $widgets ) { $this->assertInternalType( 'array', $widgets ); @@ -784,8 +788,8 @@ class Tests_Widgets extends WP_UnitTestCase { $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); - // No widget validity check when $sidebars_widgets matches registered sidebars. - $this->assertContains( 'custom_widget-1', $sidebars_widgets['sidebar-3'] ); + // Invalid widget removed, even when $sidebars_widgets matches registered sidebars. + $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); // No lost widgets when $sidebars_widgets matches registered sidebars. $this->assertEmpty( $sidebars_widgets['wp_inactive_widgets'] ); @@ -814,7 +818,7 @@ class Tests_Widgets extends WP_UnitTestCase { $_wp_sidebars_widgets = array(); $this->assertInternalType( 'array', $result ); - $this->assertNotEmpty( $result ); + $this->assertEquals( $result, $sidebars_widgets ); foreach ( $sidebars_widgets as $widgets ) { $this->assertInternalType( 'array', $widgets ); @@ -857,29 +861,26 @@ class Tests_Widgets extends WP_UnitTestCase { $_wp_sidebars_widgets = array(); $this->assertInternalType( 'array', $result ); - $this->assertNotEmpty( $result ); + $this->assertEquals( $result, $sidebars_widgets ); foreach ( $sidebars_widgets as $widgets ) { $this->assertInternalType( 'array', $widgets ); } - /* - * Only returns intersection of registered sidebars and saved sidebars, - * so neither fantasy-sidebar nor sidebar-3 will make the cut. - */ + // This sidebar is not registered anymore. $this->assertArrayNotHasKey( 'fantasy', $sidebars_widgets ); - $this->assertArrayNotHasKey( 'sidebar-3', $sidebars_widgets ); + $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets ); - // archives-2 ends up as an orphan because of the above behavior. - $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); // 6 default widgets - 1 active text widget = 5. - $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'meta-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'search-2', $sidebars_widgets['wp_inactive_widgets'] ); + // archives-2 ends up as inactive because fantasy sidebar doesn't exist. + $this->assertContains( 'archives-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'categories-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-posts-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] ); @@ -900,14 +901,11 @@ class Tests_Widgets extends WP_UnitTestCase { $this->register_sidebars( array( 'sidebar-1', 'sidebar-2','sidebar-3', 'wp_inactive_widgets' ) ); $old_sidebars_widgets = array( - 'time' => time(), - 'data' => array( - 'sidebar-1' => array( 'tag_cloud-1' ), - 'sidebar-2' => array( 'text-1' ), - 'sidebar-3' => array( 'unregistered_widget-1' ), - 'fantasy' => array( 'archives-2' ), - 'wp_inactive_widgets' => array(), - ), + 'sidebar-1' => array( 'tag_cloud-1' ), + 'sidebar-2' => array( 'text-1' ), + 'sidebar-3' => array( 'unregistered_widget-1' ), + 'fantasy' => array( 'archives-2' ), + 'wp_inactive_widgets' => array(), ); set_theme_mod( 'sidebars_widgets', $old_sidebars_widgets ); @@ -915,7 +913,7 @@ class Tests_Widgets extends WP_UnitTestCase { $_wp_sidebars_widgets = array(); $this->assertInternalType( 'array', $result ); - $this->assertNotEmpty( $result ); + $this->assertEquals( $result, $sidebars_widgets ); foreach ( $sidebars_widgets as $widgets ) { $this->assertInternalType( 'array', $widgets ); @@ -923,13 +921,13 @@ class Tests_Widgets extends WP_UnitTestCase { $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); - $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets ); $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); - $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'meta-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'search-2', $sidebars_widgets['wp_inactive_widgets'] ); + $this->assertContains( 'archives-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'categories-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-posts-2', $sidebars_widgets['wp_inactive_widgets'] ); $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] ); @@ -940,4 +938,161 @@ class Tests_Widgets extends WP_UnitTestCase { // Sidebar_widgets option was not updated. $this->assertNotEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); } + + function test_retreive_widgets_with_single_widget() { + global $sidebars_widgets; + + wp_widgets_init(); + + // Register single-dimension widget. + wp_register_sidebar_widget( 'single', 'Single', '__return_false', array(), array() ); + wp_register_widget_control( 'single', 'Single', '__return_false', array(), array() ); + + $this->register_sidebars( array( 'sidebar-1', 'sidebar-2', 'wp_inactive_widgets' ) ); + + $sidebars_widgets = array( + 'sidebar-1' => array( 'tag_cloud-1' ), + 'wp_inactive_widgets' => array(), + ); + + // Theme changed. + $result = retrieve_widgets( true ); + + $this->assertContains( 'single', $result['wp_inactive_widgets'] ); + } + + /** + * Test _wp_remove_unregistered_widgets. + * + * @covers _wp_remove_unregistered_widgets() + */ + public function test__wp_remove_unregistered_widgets() { + $widgets = array( + 'sidebar-1' => array( 'tag_cloud-1' ), + 'sidebar-2' => array( 'text-1' ), + 'fantasy' => array( 'archives-2' ), + 'wp_inactive_widgets' => array(), + 'array_version' => 3, + ); + + $whitelist = array( 'tag_cloud-1', 'text-1' ); + + $filtered_widgets = _wp_remove_unregistered_widgets( $widgets, $whitelist ); + + $this->assertInternalType( 'array', $filtered_widgets ); + $this->assertArrayHasKey( 'fantasy', $filtered_widgets ); + $this->assertEmpty( $filtered_widgets['fantasy'] ); + $this->assertArrayHasKey( 'array_version', $filtered_widgets ); + $this->assertEquals( 3, $filtered_widgets['array_version'] ); + $this->assertInternalType( 'integer', $filtered_widgets['array_version'] ); + } + + /** + * wp_map_sidebars_widgets Tests. + */ + + /** + * Two themes with one sidebar each should just map, switching to a theme not previously-active. + * + * @covers wp_map_sidebars_widgets() + */ + public function test_one_sidebar_each() { + $this->register_sidebars( array( 'primary' ) ); + $prev_theme_sidebars = array( + 'unique-slug' => 1, + ); + + $new_next_theme_sidebars = wp_map_sidebars_widgets( $prev_theme_sidebars ); + + $expected_sidebars = array( + 'primary' => 1, + 'wp_inactive_widgets' => array(), + ); + $this->assertEquals( $expected_sidebars, $new_next_theme_sidebars ); + } + + /** + * Sidebars with the same name should map, switching to a theme not previously-active. + * + * @covers wp_map_sidebars_widgets() + */ + public function test_sidebars_with_same_slug() { + $this->register_sidebars( array( 'primary', 'secondary' ) ); + $prev_theme_sidebars = array( + 'primary' => 1, + 'secondary' => 2, + 'wp_inactive_widgets' => array(), + ); + + $new_next_theme_sidebars = wp_map_sidebars_widgets( $prev_theme_sidebars ); + + $this->assertEquals( $prev_theme_sidebars, $new_next_theme_sidebars ); + } + + /** + * Make educated guesses on theme sidebars. + * + * @covers wp_map_sidebars_widgets() + */ + public function test_sidebar_guessing() { + $this->register_sidebars( array( 'primary', 'secondary' ) ); + + $prev_theme_sidebars = array( + 'header' => array(), + 'footer' => array(), + ); + + $new_next_theme_sidebars = wp_map_sidebars_widgets( $prev_theme_sidebars ); + + $expected_sidebars = array( + 'primary' => array(), + 'secondary' => array(), + 'wp_inactive_widgets' => array(), + ); + $this->assertEquals( $expected_sidebars, $new_next_theme_sidebars ); + } + + /** + * Make sure two sidebars that fall in the same group don't get the same menu assigned. + * + * @covers wp_map_sidebars_widgets() + */ + public function test_sidebar_guessing_one_menu_per_group() { + $this->register_sidebars( array( 'primary' ) ); + $prev_theme_sidebars = array( + 'top-menu' => array(), + 'secondary' => array(), + ); + + $new_next_theme_sidebars = wp_map_sidebars_widgets( $prev_theme_sidebars ); + + $expected_sidebars = array( + 'main' => array(), + 'wp_inactive_widgets' => array(), + ); + $this->assertEqualSets( $expected_sidebars, $new_next_theme_sidebars ); + } + + /** + * Make sure two sidebars that fall in the same group get menus assigned from the same group. + * + * @covers wp_map_sidebars_widgets() + */ + public function test_sidebar_guessing_one_menu_per_sidebar() { + $this->register_sidebars( array( 'primary', 'main' ) ); + + $prev_theme_sidebars = array( + 'navigation-menu' => array(), + 'top-menu' => array(), + ); + + $new_next_theme_sidebars = wp_map_sidebars_widgets( $prev_theme_sidebars ); + + $expected_sidebars = array( + 'main' => array(), + 'primary' => array(), + 'wp_inactive_widgets' => array(), + ); + $this->assertEquals( $expected_sidebars, $new_next_theme_sidebars ); + } }