Make WordPress Core


Ignore:
Timestamp:
09/21/2017 06:45:03 PM (7 years ago)
Author:
obenland
Message:

Widgets: Improved sidebar mapping on theme switch

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/widgets.php

    r41340 r41555  
    920920 * @access private
    921921 *
     922 * @global array $_wp_sidebars_widgets
    922923 * @param array $sidebars_widgets Sidebar widgets and their settings.
    923924 */
    924925function wp_set_sidebars_widgets( $sidebars_widgets ) {
    925     if ( !isset( $sidebars_widgets['array_version'] ) )
     926    global $_wp_sidebars_widgets;
     927
     928    // Clear cached value used in wp_get_sidebars_widgets().
     929    $_wp_sidebars_widgets = null;
     930
     931    if ( ! isset( $sidebars_widgets['array_version'] ) ) {
    926932        $sidebars_widgets['array_version'] = 3;
     933    }
     934
    927935    update_option( 'sidebars_widgets', $sidebars_widgets );
    928936}
     
    11141122 * @param string|bool $theme_changed Whether the theme was changed as a boolean. A value
    11151123 *                                   of 'customize' defers updates for the Customizer.
    1116  * @return array|void
     1124 * @return array Updated sidebars widgets.
    11171125 */
    11181126function retrieve_widgets( $theme_changed = false ) {
    11191127    global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets;
    11201128
    1121     $registered_sidebar_keys = array_keys( $wp_registered_sidebars );
    1122     $orphaned = 0;
    1123 
    1124     $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' );
    1125     if ( is_array( $old_sidebars_widgets ) ) {
    1126         // time() that sidebars were stored is in $old_sidebars_widgets['time']
    1127         $_sidebars_widgets = $old_sidebars_widgets['data'];
    1128 
    1129         if ( 'customize' !== $theme_changed ) {
    1130             remove_theme_mod( 'sidebars_widgets' );
    1131         }
    1132 
    1133         foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
    1134             if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
    1135                 continue;
    1136             }
    1137 
    1138             if ( !in_array( $sidebar, $registered_sidebar_keys ) ) {
    1139                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $widgets;
    1140                 unset( $_sidebars_widgets[$sidebar] );
    1141             }
    1142         }
    1143     } else {
    1144         if ( empty( $sidebars_widgets ) )
    1145             return;
     1129    $registered_sidebars_keys = array_keys( $wp_registered_sidebars );
     1130    $registered_widgets_ids   = array_keys( $wp_registered_widgets );
     1131
     1132    if ( ! is_array( get_theme_mod( 'sidebars_widgets' ) ) )  {
     1133        if ( empty( $sidebars_widgets ) ) {
     1134            return array();
     1135        }
    11461136
    11471137        unset( $sidebars_widgets['array_version'] );
    11481138
    1149         $old = array_keys($sidebars_widgets);
    1150         sort($old);
    1151         sort($registered_sidebar_keys);
    1152 
    1153         if ( $old == $registered_sidebar_keys )
    1154             return;
    1155 
    1156         $_sidebars_widgets = array(
    1157             'wp_inactive_widgets' => !empty( $sidebars_widgets['wp_inactive_widgets'] ) ? $sidebars_widgets['wp_inactive_widgets'] : array()
    1158         );
    1159 
    1160         unset( $sidebars_widgets['wp_inactive_widgets'] );
    1161 
    1162         foreach ( $wp_registered_sidebars as $id => $settings ) {
    1163             if ( $theme_changed ) {
    1164                 $_sidebars_widgets[$id] = array_shift( $sidebars_widgets );
    1165             } else {
    1166                 // no theme change, grab only sidebars that are currently registered
    1167                 if ( isset( $sidebars_widgets[$id] ) ) {
    1168                     $_sidebars_widgets[$id] = $sidebars_widgets[$id];
    1169                     unset( $sidebars_widgets[$id] );
    1170                 }
    1171             }
    1172         }
    1173 
    1174         foreach ( $sidebars_widgets as $val ) {
    1175             if ( is_array($val) && ! empty( $val ) )
    1176                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $val;
    1177         }
    1178     }
    1179 
    1180     // discard invalid, theme-specific widgets from sidebars
    1181     $shown_widgets = array();
    1182 
    1183     foreach ( $_sidebars_widgets as $sidebar => $widgets ) {
    1184         if ( !is_array($widgets) )
    1185             continue;
    1186 
    1187         $_widgets = array();
    1188         foreach ( $widgets as $widget ) {
    1189             if ( isset($wp_registered_widgets[$widget]) )
    1190                 $_widgets[] = $widget;
    1191         }
    1192 
    1193         $_sidebars_widgets[$sidebar] = $_widgets;
    1194         $shown_widgets = array_merge($shown_widgets, $_widgets);
    1195     }
    1196 
    1197     $sidebars_widgets = $_sidebars_widgets;
    1198     unset($_sidebars_widgets, $_widgets);
    1199 
    1200     // find hidden/lost multi-widget instances
    1201     $lost_widgets = array();
    1202     foreach ( $wp_registered_widgets as $key => $val ) {
    1203         if ( in_array($key, $shown_widgets, true) )
    1204             continue;
    1205 
    1206         $number = preg_replace('/.+?-([0-9]+)$/', '$1', $key);
    1207 
    1208         if ( 2 > (int) $number )
    1209             continue;
    1210 
    1211         $lost_widgets[] = $key;
    1212     }
    1213 
    1214     $sidebars_widgets['wp_inactive_widgets'] = array_merge($lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets']);
     1139        $sidebars_widgets_keys = array_keys( $sidebars_widgets );
     1140        sort( $sidebars_widgets_keys );
     1141        sort( $registered_sidebars_keys );
     1142
     1143        if ( $sidebars_widgets_keys === $registered_sidebars_keys ) {
     1144            $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids );
     1145
     1146            return $sidebars_widgets;
     1147        }
     1148    }
     1149
     1150
     1151    // Discard invalid, theme-specific widgets from sidebars.
     1152    $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids );
     1153    $sidebars_widgets = wp_map_sidebars_widgets( $sidebars_widgets );
     1154
     1155    // Find hidden/lost multi-widget instances.
     1156    $shown_widgets = call_user_func_array( 'array_merge', $sidebars_widgets );
     1157    $lost_widgets  = array_diff( $registered_widgets_ids, $shown_widgets );
     1158
     1159    foreach ( $lost_widgets as $key => $widget_id ) {
     1160        $number = preg_replace( '/.+?-([0-9]+)$/', '$1', $widget_id );
     1161
     1162        // Only keep active and default widgets.
     1163        if ( is_numeric( $number ) && (int) $number < 2 ) {
     1164            unset( $lost_widgets[ $key ] );
     1165        }
     1166    }
     1167    $sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] );
     1168
    12151169    if ( 'customize' !== $theme_changed ) {
    12161170        wp_set_sidebars_widgets( $sidebars_widgets );
     1171    }
     1172
     1173    return $sidebars_widgets;
     1174}
     1175
     1176/**
     1177 * Compares a list of sidebars with their widgets against a whitelist.
     1178 *
     1179 * @since 4.9.0
     1180 *
     1181 * @param array $existing_sidebars_widgets List of sidebars and their widget instance IDs.
     1182 * @return array Mapped sidebars widgets.
     1183 */
     1184function wp_map_sidebars_widgets( $existing_sidebars_widgets ) {
     1185    global $wp_registered_sidebars;
     1186
     1187    $new_sidebars_widgets = array(
     1188        'wp_inactive_widgets' => array(),
     1189    );
     1190
     1191    // Short-circuit if there are no sidebars to map.
     1192    if ( ! is_array( $existing_sidebars_widgets ) || empty( $existing_sidebars_widgets ) ) {
     1193        return $new_sidebars_widgets;
     1194    }
     1195
     1196    foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) {
     1197        if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
     1198            $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], (array) $widgets );
     1199            unset( $existing_sidebars_widgets[ $sidebar ] );
     1200        }
     1201    }
     1202
     1203    // If old and new theme have just one sidebar, map it and we're done.
     1204    if ( 1 === count( $existing_sidebars_widgets ) && 1 === count( $wp_registered_sidebars ) ) {
     1205        $new_sidebars_widgets[ key( $wp_registered_sidebars ) ] = array_pop( $existing_sidebars_widgets );
     1206
     1207        return $new_sidebars_widgets;
     1208    }
     1209
     1210    // Map locations with the same slug.
     1211    $existing_sidebars = array_keys( $existing_sidebars_widgets );
     1212
     1213    foreach ( $wp_registered_sidebars as $sidebar => $name ) {
     1214        if ( in_array( $sidebar, $existing_sidebars, true ) ) {
     1215            $new_sidebars_widgets[ $sidebar ] = $existing_sidebars_widgets[ $sidebar ];
     1216            unset( $existing_sidebars_widgets[ $sidebar ] );
     1217        } else {
     1218            $new_sidebars_widgets[ $sidebar ] = array();
     1219        }
     1220    }
     1221
     1222    // If there are no old sidebars left, then we're done.
     1223    if ( empty( $existing_sidebars_widgets ) ) {
     1224        return $new_sidebars_widgets;
     1225    }
     1226
     1227    /*
     1228     * If old and new theme both have sidebars that contain phrases
     1229     * from within the same group, make an educated guess and map it.
     1230     */
     1231    $common_slug_groups = array(
     1232        array( 'sidebar', 'primary', 'main', 'right' ),
     1233        array( 'second', 'left' ),
     1234        array( 'sidebar-2', 'footer', 'bottom' ),
     1235        array( 'header', 'top' ),
     1236    );
     1237
     1238    // Go through each group...
     1239    foreach ( $common_slug_groups as $slug_group ) {
     1240
     1241        // ...and see if any of these slugs...
     1242        foreach ( $slug_group as $slug ) {
     1243
     1244            // ...and any of the new sidebars...
     1245            foreach ( $wp_registered_sidebars as $new_sidebar => $args ) {
     1246
     1247                // ...actually match!
     1248                if ( false === stripos( $new_sidebar, $slug ) && false === stripos( $slug, $new_sidebar ) ) {
     1249                    continue;
     1250                }
     1251
     1252                // Then see if any of the existing sidebars...
     1253                foreach ( $existing_sidebars_widgets as $sidebar => $widgets ) {
     1254
     1255                    // ...and any slug in the same group...
     1256                    foreach ( $slug_group as $slug ) {
     1257
     1258                        // ... have a match as well.
     1259                        if ( false === stripos( $sidebar, $slug ) && false === stripos( $slug, $sidebar ) ) {
     1260                            continue;
     1261                        }
     1262
     1263                        // Make sure this sidebar wasn't mapped and removed previously.
     1264                        if ( ! empty( $existing_sidebars_widgets[ $sidebar ] ) ) {
     1265
     1266                            // We have a match that can be mapped!
     1267                            $new_sidebars_widgets[ $new_sidebar ] = array_merge( $new_sidebars_widgets[ $new_sidebar ], $existing_sidebars_widgets[ $sidebar ] );
     1268
     1269                            // Remove the mapped sidebar so it can't be mapped again.
     1270                            unset( $existing_sidebars_widgets[ $sidebar ] );
     1271
     1272                            // Go back and check the next new sidebar.
     1273                            continue 3;
     1274                        }
     1275                    } // endforeach ( $slug_group as $slug )
     1276                } // endforeach ( $existing_sidebars_widgets as $sidebar => $widgets )
     1277            } // endforeach foreach ( $wp_registered_sidebars as $new_sidebar => $args )
     1278        } // endforeach ( $slug_group as $slug )
     1279    } // endforeach ( $common_slug_groups as $slug_group )
     1280
     1281    // Move any left over widgets to inactive sidebar.
     1282    foreach ( $existing_sidebars_widgets as $widgets ) {
     1283        if ( is_array( $widgets ) && ! empty( $widgets ) ) {
     1284            $new_sidebars_widgets['wp_inactive_widgets'] = array_merge( $new_sidebars_widgets['wp_inactive_widgets'], $widgets );
     1285        }
     1286    }
     1287
     1288    // Sidebars_widgets settings from when this theme was previously active.
     1289    $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' );
     1290
     1291    if ( is_array( $old_sidebars_widgets ) ) {
     1292
     1293        // Only check sidebars that are empty or have not been mapped to yet.
     1294        foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) {
     1295            if ( array_key_exists( $new_sidebar, $old_sidebars_widgets ) && ! empty( $new_widgets ) ) {
     1296                unset( $old_sidebars_widgets[ $new_sidebar ] );
     1297            }
     1298        }
     1299
     1300        // Remove orphaned widgets, we're only interested in previously active sidebars.
     1301        foreach ( $old_sidebars_widgets as $sidebar => $widgets ) {
     1302            if ( 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
     1303                unset( $old_sidebars_widgets[ $sidebar ] );
     1304            }
     1305        }
     1306
     1307        $old_sidebars_widgets = _wp_remove_unregistered_widgets( $old_sidebars_widgets );
     1308
     1309        if ( ! empty( $old_sidebars_widgets ) ) {
     1310
     1311            // Go through each remaining sidebar...
     1312            foreach ( $old_sidebars_widgets as $old_sidebar => $old_widgets ) {
     1313
     1314                // ...and check every new sidebar...
     1315                foreach ( $new_sidebars_widgets as $new_sidebar => $new_widgets ) {
     1316
     1317                    // ...for every widget we're trying to revive.
     1318                    foreach ( $old_widgets as $key => $widget_id ) {
     1319                        $active_key = array_search( $widget_id, $new_widgets, true );
     1320
     1321                        // If the widget is used elsewhere...
     1322                        if ( false !== $active_key ) {
     1323
     1324                            // ...and that elsewhere is inactive widgets...
     1325                            if ( 'wp_inactive_widgets' === $new_sidebar ) {
     1326
     1327                                // ...remove it from there and keep the active version...
     1328                                unset( $new_sidebars_widgets['wp_inactive_widgets'][ $active_key ] );
     1329                            } else {
     1330
     1331                                // ...otherwise remove it from the old sidebar and keep it in the new one.
     1332                                unset( $old_sidebars_widgets[ $old_sidebar ][ $key ] );
     1333                            }
     1334                        } // endif ( $active_key )
     1335                    } // endforeach ( $old_widgets as $key => $widget_id )
     1336                } // endforeach ( $new_sidebars_widgets as $new_sidebar => $new_widgets )
     1337            } // endforeach ( $old_sidebars_widgets as $old_sidebar => $old_widgets )
     1338        } // endif ( ! empty( $old_sidebars_widgets ) )
     1339
     1340
     1341        // Restore widget settings from when theme was previously active.
     1342        $new_sidebars_widgets = array_merge( $new_sidebars_widgets, $old_sidebars_widgets );
     1343    }
     1344
     1345    return $new_sidebars_widgets;
     1346}
     1347
     1348/**
     1349 * Compares a list of sidebars with their widgets against a whitelist.
     1350 *
     1351 * @since 4.9.0
     1352 *
     1353 * @param array $sidebars_widgets List of sidebars and their widget instance IDs.
     1354 * @param array $whitelist        Optional. List of widget IDs to compare against. Default: Registered widgets.
     1355 * @return array Sidebars with whitelisted widgets.
     1356 */
     1357function _wp_remove_unregistered_widgets( $sidebars_widgets, $whitelist = array() ) {
     1358    if ( empty( $whitelist ) ) {
     1359        $whitelist = array_keys( $GLOBALS['wp_registered_widgets'] );
     1360    }
     1361
     1362    foreach ( $sidebars_widgets as $sidebar => $widgets ) {
     1363        if ( is_array( $widgets ) ) {
     1364            $sidebars_widgets[ $sidebar ] = array_intersect( $widgets, $whitelist );
     1365        }
    12171366    }
    12181367
Note: See TracChangeset for help on using the changeset viewer.