<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[41555] trunk: Widgets: Improved sidebar mapping on theme switch</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/41555">41555</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/41555","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>obenland</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2017-09-21 18:45:03 +0000 (Thu, 21 Sep 2017)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Widgets: Improved sidebar mapping on theme switch

Builds on efforts brought forward in <a href="https://core.trac.wordpress.org/ticket/17979">#17979</a>.

This will send sidebars through three levels of mapping:?\226?\128?\168
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 <a href="https://core.trac.wordpress.org/ticket/39693">#39693</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesthemephp">trunk/src/wp-includes/theme.php</a></li>
<li><a href="#trunksrcwpincludeswidgetsphp">trunk/src/wp-includes/widgets.php</a></li>
<li><a href="#trunktestsphpunittestswidgetsphp">trunk/tests/phpunit/tests/widgets.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/theme.php   2017-09-21 16:34:31 UTC (rev 41554)
+++ trunk/src/wp-includes/theme.php     2017-09-21 18:45:03 UTC (rev 41555)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -681,13 +681,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $_sidebars_widgets = null;
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'wp_ajax_customize_save' === current_action() ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $_sidebars_widgets = $wp_customize->post_value( $wp_customize->get_setting( 'old_sidebars_widgets_data' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $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 );
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         } elseif ( is_array( $sidebars_widgets ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $_sidebars_widgets = $sidebars_widgets;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( is_array( $_sidebars_widgets ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $_sidebars_widgets ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         set_theme_mod( 'sidebars_widgets', $_sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
</span></span></pre></div>
<a id="trunksrcwpincludeswidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/widgets.php 2017-09-21 16:34:31 UTC (rev 41554)
+++ trunk/src/wp-includes/widgets.php   2017-09-21 18:45:03 UTC (rev 41555)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -919,11 +919,19 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.2.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @global array $_wp_sidebars_widgets
</ins><span class="cx" style="display: block; padding: 0 10px">  * @param array $sidebars_widgets Sidebar widgets and their settings.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function wp_set_sidebars_widgets( $sidebars_widgets ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( !isset( $sidebars_widgets['array_version'] ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ global $_wp_sidebars_widgets;
+
+       // Clear cached value used in wp_get_sidebars_widgets().
+       $_wp_sidebars_widgets = null;
+
+       if ( ! isset( $sidebars_widgets['array_version'] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $sidebars_widgets['array_version'] = 3;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         update_option( 'sidebars_widgets', $sidebars_widgets );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1113,109 +1121,250 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string|bool $theme_changed Whether the theme was changed as a boolean. A value
</span><span class="cx" style="display: block; padding: 0 10px">  *                                   of 'customize' defers updates for the Customizer.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return array|void
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @return array Updated sidebars widgets.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function retrieve_widgets( $theme_changed = false ) {
</span><span class="cx" style="display: block; padding: 0 10px">        global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $registered_sidebar_keys = array_keys( $wp_registered_sidebars );
-       $orphaned = 0;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $registered_sidebars_keys = array_keys( $wp_registered_sidebars );
+       $registered_widgets_ids   = array_keys( $wp_registered_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $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'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! is_array( get_theme_mod( 'sidebars_widgets' ) ) )  {
+               if ( empty( $sidebars_widgets ) ) {
+                       return array();
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 'customize' !== $theme_changed ) {
-                       remove_theme_mod( 'sidebars_widgets' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         unset( $sidebars_widgets['array_version'] );
+
+               $sidebars_widgets_keys = array_keys( $sidebars_widgets );
+               sort( $sidebars_widgets_keys );
+               sort( $registered_sidebars_keys );
+
+               if ( $sidebars_widgets_keys === $registered_sidebars_keys ) {
+                       $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids );
+
+                       return $sidebars_widgets;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
-                       if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
-                               continue;
-                       }
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( !in_array( $sidebar, $registered_sidebar_keys ) ) {
-                               $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $widgets;
-                               unset( $_sidebars_widgets[$sidebar] );
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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 ] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        } else {
-               if ( empty( $sidebars_widgets ) )
-                       return;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ }
+       $sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                unset( $sidebars_widgets['array_version'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'customize' !== $theme_changed ) {
+               wp_set_sidebars_widgets( $sidebars_widgets );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $old = array_keys($sidebars_widgets);
-               sort($old);
-               sort($registered_sidebar_keys);
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return $sidebars_widgets;
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $old == $registered_sidebar_keys )
-                       return;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * 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;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $_sidebars_widgets = array(
-                       'wp_inactive_widgets' => !empty( $sidebars_widgets['wp_inactive_widgets'] ) ? $sidebars_widgets['wp_inactive_widgets'] : array()
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $new_sidebars_widgets = array(
+               'wp_inactive_widgets' => array(),
+       );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                unset( $sidebars_widgets['wp_inactive_widgets'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Short-circuit if there are no sidebars to map.
+       if ( ! is_array( $existing_sidebars_widgets ) || empty( $existing_sidebars_widgets ) ) {
+               return $new_sidebars_widgets;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                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] );
-                               }
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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 ] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                foreach ( $sidebars_widgets as $val ) {
-                       if ( is_array($val) && ! empty( $val ) )
-                               $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $val;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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();
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // discard invalid, theme-specific widgets from sidebars
-       $shown_widgets = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // If there are no old sidebars left, then we're done.
+       if ( empty( $existing_sidebars_widgets ) ) {
+               return $new_sidebars_widgets;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
-               if ( !is_array($widgets) )
-                       continue;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+        * 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' ),
+       );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $_widgets = array();
-               foreach ( $widgets as $widget ) {
-                       if ( isset($wp_registered_widgets[$widget]) )
-                               $_widgets[] = $widget;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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 );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               $_sidebars_widgets[$sidebar] = $_widgets;
-               $shown_widgets = array_merge($shown_widgets, $_widgets);
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $sidebars_widgets = $_sidebars_widgets;
-       unset($_sidebars_widgets, $_widgets);
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Sidebars_widgets settings from when this theme was previously active.
+       $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // find hidden/lost multi-widget instances
-       $lost_widgets = array();
-       foreach ( $wp_registered_widgets as $key => $val ) {
-               if ( in_array($key, $shown_widgets, true) )
-                       continue;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_array( $old_sidebars_widgets ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $number = preg_replace('/.+?-([0-9]+)$/', '$1', $key);
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // 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 ] );
+                       }
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 2 > (int) $number )
-                       continue;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // 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 ] );
+                       }
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $lost_widgets[] = $key;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $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 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return $new_sidebars_widgets;
+}
+
+/**
+ * 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'] );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        foreach ( $sidebars_widgets as $sidebar => $widgets ) {
+               if ( is_array( $widgets ) ) {
+                       $sidebars_widgets[ $sidebar ] = array_intersect( $widgets, $whitelist );
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         return $sidebars_widgets;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunktestsphpunittestswidgetsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/widgets.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/widgets.php     2017-09-21 16:34:31 UTC (rev 41554)
+++ trunk/tests/phpunit/tests/widgets.php       2017-09-21 18:45:03 UTC (rev 41555)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -709,24 +709,27 @@
</span><span class="cx" style="display: block; padding: 0 10px">                global $sidebars_widgets, $_wp_sidebars_widgets;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                wp_widgets_init();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->register_sidebars( array( 'sidebar-1', 'sidebar-2','sidebar-3', 'wp_inactive_widgets' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->register_sidebars( array( 'sidebar-1', 'sidebar-2', 'sidebar-3', 'wp_inactive_widgets' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Test restoring sidebars widgets when previously activated.
</ins><span class="cx" style="display: block; padding: 0 10px">                 set_theme_mod( 'sidebars_widgets', array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        '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(),
-                       ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 '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' ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $sidebars_widgets = array(
+                       'sidebar-1' => array( 'tag_cloud-1' ),
+                       'sidebar-2' => array( 'text-1' ),
+                       'fantasy'   => array( 'archives-2' ),
+                       'wp_inactive_widgets' => array(),
+               );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $result = retrieve_widgets( true );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $_wp_sidebars_widgets = array();
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->assertInternalType( 'array', $result );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNotEmpty( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $result, $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $sidebars_widgets as $widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertInternalType( 'array', $widgets );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -734,22 +737,22 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertContains( 'media_image-1', $sidebars_widgets['sidebar-3'] );
+               $this->assertArrayNotHasKey( 'orphaned_widgets_1', $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Unregistered widget should be filtered out.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertEmpty( $sidebars_widgets['sidebar-3'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertNotContains( 'unregistered_widget-1', $sidebars_widgets['sidebar-3'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // 6 default widgets - 1 active text widget = 5.
-               $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // 6 default widgets - 1 active text widget + 1 orphaned widget = 6.
+               $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertContains( 'meta-2',            $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'search-2',          $sidebars_widgets['wp_inactive_widgets'] );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->assertContains( 'archives-2',        $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertContains( 'categories-2',      $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-posts-2',    $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Theme mode with previous widgets was removed.
-               $this->assertFalse( get_theme_mod( 'sidebars_widgets' ) );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 // Sidebar_widgets option was updated.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEquals( $sidebars_widgets, wp_get_sidebars_widgets() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -775,7 +778,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $result = retrieve_widgets( true );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // $sidebars_widgets matches registered sidebars.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNull( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertInternalType( 'array', $result );
+               $this->assertEquals( $result, $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $sidebars_widgets as $widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertInternalType( 'array', $widgets );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -784,8 +788,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // No widget validity check when $sidebars_widgets matches registered sidebars.
-               $this->assertContains( 'custom_widget-1', $sidebars_widgets['sidebar-3'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Invalid widget removed, even when $sidebars_widgets matches registered sidebars.
+               $this->assertEmpty( $sidebars_widgets['sidebar-3'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // No lost widgets when $sidebars_widgets matches registered sidebars.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEmpty( $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -814,7 +818,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $_wp_sidebars_widgets = array();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertInternalType( 'array', $result );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNotEmpty( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $result, $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $sidebars_widgets as $widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertInternalType( 'array', $widgets );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -857,29 +861,26 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $_wp_sidebars_widgets = array();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertInternalType( 'array', $result );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNotEmpty( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $result, $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $sidebars_widgets as $widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertInternalType( 'array', $widgets );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /*
-                * Only returns intersection of registered sidebars and saved sidebars,
-                * so neither fantasy-sidebar nor sidebar-3 will make the cut.
-                */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // This sidebar is not registered anymore.
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertArrayNotHasKey( 'fantasy', $sidebars_widgets );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertArrayNotHasKey( 'sidebar-3', $sidebars_widgets );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // archives-2 ends up as an orphan because of the above behavior.
-               $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // 6 default widgets - 1 active text widget = 5.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'meta-2',            $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'search-2',          $sidebars_widgets['wp_inactive_widgets'] );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // archives-2 ends up as inactive because fantasy sidebar doesn't exist.
+               $this->assertContains( 'archives-2',        $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertContains( 'categories-2',      $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-posts-2',    $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -900,14 +901,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->register_sidebars( array( 'sidebar-1', 'sidebar-2','sidebar-3', 'wp_inactive_widgets' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $old_sidebars_widgets = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        '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(),
-                       ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 '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(),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                set_theme_mod( 'sidebars_widgets', $old_sidebars_widgets );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -915,7 +913,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $_wp_sidebars_widgets = array();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertInternalType( 'array', $result );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertNotEmpty( $result );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertEquals( $result, $sidebars_widgets );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $sidebars_widgets as $widgets ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertInternalType( 'array', $widgets );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -923,13 +921,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] );
</del><span class="cx" style="display: block; padding: 0 10px">                 $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertEmpty( $sidebars_widgets['sidebar-3'] );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'meta-2',            $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'search-2',          $sidebars_widgets['wp_inactive_widgets'] );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->assertContains( 'archives-2',        $sidebars_widgets['wp_inactive_widgets'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertContains( 'categories-2',      $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-posts-2',    $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertContains( 'recent-comments-2', $sidebars_widgets['wp_inactive_widgets'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -940,4 +938,161 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Sidebar_widgets option was not updated.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNotEquals( $sidebars_widgets, wp_get_sidebars_widgets() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       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 );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>