WordPress.org

Make WordPress Core

Ticket #39693: 39693.diff

File 39693.diff, 16.8 KB (added by obenland, 5 months ago)
  • src/wp-includes/widgets.php

     
    11071107 * 
    11081108 * @param string|bool $theme_changed Whether the theme was changed as a boolean. A value 
    11091109 *                                   of 'customize' defers updates for the Customizer. 
    1110  * @return array|void 
     1110 * @return array 
    11111111 */ 
    11121112function retrieve_widgets( $theme_changed = false ) { 
    11131113        global $wp_registered_sidebars, $sidebars_widgets, $wp_registered_widgets; 
    11141114 
    1115         $registered_sidebar_keys = array_keys( $wp_registered_sidebars ); 
    1116         $orphaned = 0; 
     1115        $registered_sidebars_keys = array_keys( $wp_registered_sidebars ); 
     1116        $registered_widgets_ids   = array_keys( $wp_registered_widgets ); 
     1117        $old_sidebars_widgets     = get_theme_mod( 'sidebars_widgets' ); 
    11171118 
    1118         $old_sidebars_widgets = get_theme_mod( 'sidebars_widgets' ); 
    11191119        if ( is_array( $old_sidebars_widgets ) ) { 
    11201120                // time() that sidebars were stored is in $old_sidebars_widgets['time'] 
    1121                 $_sidebars_widgets = $old_sidebars_widgets['data']; 
     1121                $sidebars_widgets = $old_sidebars_widgets['data']; 
    11221122 
    11231123                if ( 'customize' !== $theme_changed ) { 
    11241124                        remove_theme_mod( 'sidebars_widgets' ); 
    11251125                } 
     1126        } else { 
     1127                if ( empty( $sidebars_widgets ) ) { 
     1128                        return array(); 
     1129                } 
    11261130 
    1127                 foreach ( $_sidebars_widgets as $sidebar => $widgets ) { 
    1128                         if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) { 
    1129                                 continue; 
    1130                         } 
     1131                unset( $sidebars_widgets['array_version'] ); 
    11311132 
    1132                         if ( !in_array( $sidebar, $registered_sidebar_keys ) ) { 
    1133                                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $widgets; 
    1134                                 unset( $_sidebars_widgets[$sidebar] ); 
    1135                         } 
     1133                $sidebars_widgets_keys = array_keys( $sidebars_widgets ); 
     1134                sort( $sidebars_widgets_keys ); 
     1135                sort( $registered_sidebars_keys ); 
     1136 
     1137                if ( $sidebars_widgets_keys == $registered_sidebars_keys ) { 
     1138                        return _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); 
    11361139                } 
    1137         } else { 
    1138                 if ( empty( $sidebars_widgets ) ) 
    1139                         return; 
     1140        } 
    11401141 
    1141                 unset( $sidebars_widgets['array_version'] ); 
     1142        // Discard invalid, theme-specific widgets from sidebars. 
     1143        $sidebars_widgets = _wp_remove_unregistered_widgets( $sidebars_widgets, $registered_widgets_ids ); 
     1144        $sidebars_widgets = _wp_map_sidebars( $sidebars_widgets ); 
    11421145 
    1143                 $old = array_keys($sidebars_widgets); 
    1144                 sort($old); 
    1145                 sort($registered_sidebar_keys); 
    1146  
    1147                 if ( $old == $registered_sidebar_keys ) 
    1148                         return; 
    1149  
    1150                 $_sidebars_widgets = array( 
    1151                         'wp_inactive_widgets' => !empty( $sidebars_widgets['wp_inactive_widgets'] ) ? $sidebars_widgets['wp_inactive_widgets'] : array() 
    1152                 ); 
    1153  
    1154                 unset( $sidebars_widgets['wp_inactive_widgets'] ); 
    1155  
    1156                 foreach ( $wp_registered_sidebars as $id => $settings ) { 
    1157                         if ( $theme_changed ) { 
    1158                                 $_sidebars_widgets[$id] = array_shift( $sidebars_widgets ); 
    1159                         } else { 
    1160                                 // no theme change, grab only sidebars that are currently registered 
    1161                                 if ( isset( $sidebars_widgets[$id] ) ) { 
    1162                                         $_sidebars_widgets[$id] = $sidebars_widgets[$id]; 
    1163                                         unset( $sidebars_widgets[$id] ); 
    1164                                 } 
    1165                         } 
     1146        // Find hidden/lost multi-widget instances. 
     1147        $shown_widgets = call_user_func_array( 'array_merge', array_filter( $sidebars_widgets ) ); 
     1148        $lost_widgets  = array_diff( $registered_widgets_ids, $shown_widgets ); 
     1149 
     1150        foreach ( $lost_widgets as $key => $widget_id ) { 
     1151                $number = preg_replace( '/.+?-([0-9]+)$/', '$1', $widget_id ); 
     1152 
     1153                if ( (int) $number < 2 ) { 
     1154                        unset( $lost_widgets[ $key ] ); 
    11661155                } 
     1156        } 
     1157        $sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] ); 
     1158 
     1159        if ( 'customize' !== $theme_changed ) { 
     1160                wp_set_sidebars_widgets( $sidebars_widgets ); 
     1161        } 
     1162 
     1163        return $sidebars_widgets; 
     1164} 
     1165 
     1166/** 
     1167 * Compares a list of sidebars with their widgets against a whitelist. 
     1168 * 
     1169 * @since 4.9.0 
     1170 * 
     1171 * @param array $old_sidebars_widgets List of sidebars and their widget instance IDs. 
     1172 * @return array Mapped sidebars widgets. 
     1173 */ 
     1174function _wp_map_sidebars( $old_sidebars_widgets ) { 
     1175        global $wp_registered_sidebars; 
     1176 
     1177        $new_sidebars_widgets = array(); 
     1178 
     1179        // Short-circuit if there are no sidebars to map. 
     1180        if ( empty( $old_sidebars_widgets ) ) { 
     1181                return $new_sidebars_widgets; 
     1182        } 
    11671183 
    1168                 foreach ( $sidebars_widgets as $val ) { 
    1169                         if ( is_array($val) && ! empty( $val ) ) 
    1170                                 $_sidebars_widgets['orphaned_widgets_' . ++$orphaned] = $val; 
     1184        foreach ( $old_sidebars_widgets as $sidebar => $widgets ) { 
     1185                if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) { 
     1186                        $new_sidebars_widgets[ $sidebar ] = $widgets; 
     1187                        unset( $old_sidebars_widgets[ $sidebar ] ); 
    11711188                } 
    11721189        } 
    11731190 
    1174         // discard invalid, theme-specific widgets from sidebars 
    1175         $shown_widgets = array(); 
     1191        // If old and new theme have just one sidebar, map it and we're done. 
     1192        if ( 1 === count( $old_sidebars_widgets ) && 1 === count( $wp_registered_sidebars ) ) { 
     1193                $new_sidebars_widgets[ key( $wp_registered_sidebars ) ] = array_pop( $old_sidebars_widgets ); 
     1194 
     1195                return $new_sidebars_widgets; 
     1196        } 
    11761197 
    1177         foreach ( $_sidebars_widgets as $sidebar => $widgets ) { 
    1178                 if ( !is_array($widgets) ) 
    1179                         continue; 
     1198        // Map locations with the same slug. 
     1199        $old_sidebars = array_keys( $old_sidebars_widgets ); 
    11801200 
    1181                 $_widgets = array(); 
    1182                 foreach ( $widgets as $widget ) { 
    1183                         if ( isset($wp_registered_widgets[$widget]) ) 
    1184                                 $_widgets[] = $widget; 
     1201        foreach ( $wp_registered_sidebars as $sidebar => $name ) { 
     1202                if ( in_array( $sidebar, $old_sidebars, true ) ) { 
     1203                        $new_sidebars_widgets[ $sidebar ] = $old_sidebars_widgets[ $sidebar ]; 
     1204                        unset( $old_sidebars_widgets[ $sidebar ] ); 
     1205                } else { 
     1206                        $new_sidebars_widgets[ $sidebar ] = array(); 
    11851207                } 
     1208        } 
    11861209 
    1187                 $_sidebars_widgets[$sidebar] = $_widgets; 
    1188                 $shown_widgets = array_merge($shown_widgets, $_widgets); 
     1210        // If there are no old sidebars left, then we're done. 
     1211        if ( empty( $old_sidebars_widgets ) ) { 
     1212                return $new_sidebars_widgets; 
    11891213        } 
    11901214 
    1191         $sidebars_widgets = $_sidebars_widgets; 
    1192         unset($_sidebars_widgets, $_widgets); 
     1215        /* 
     1216         * If old and new theme both have sidebars that contain phrases 
     1217         * from within the same group, make an educated guess and map it. 
     1218         */ 
     1219        $common_slug_groups = array( 
     1220                array( 'sidebar', 'primary', 'main', 'right' ), 
     1221                array( 'second', 'left' ), 
     1222                array( 'footer', 'bottom' ), 
     1223                array( 'header', 'top' ), 
     1224        ); 
     1225 
     1226        // Go through each group... 
     1227        foreach ( $common_slug_groups as $slug_group ) { 
     1228 
     1229                // ...and see if any of these slugs... 
     1230                foreach ( $slug_group as $slug ) { 
     1231 
     1232                        // ...and any of the new sidebars... 
     1233                        foreach ( $wp_registered_sidebars as $new_sidebar => $args ) { 
     1234 
     1235                                // ...actually match! 
     1236                                if ( false === stripos( $new_sidebar, $slug ) && false === stripos( $slug, $new_sidebar ) ) { 
     1237                                        continue; 
     1238                                } 
    11931239 
    1194         // find hidden/lost multi-widget instances 
    1195         $lost_widgets = array(); 
    1196         foreach ( $wp_registered_widgets as $key => $val ) { 
    1197                 if ( in_array($key, $shown_widgets, true) ) 
    1198                         continue; 
     1240                                // Then see if any of the old sidebars... 
     1241                                foreach ( $old_sidebars_widgets as $sidebar => $widgets ) { 
    11991242 
    1200                 $number = preg_replace('/.+?-([0-9]+)$/', '$1', $key); 
     1243                                        // ...and any slug in the same group... 
     1244                                        foreach ( $slug_group as $slug ) { 
    12011245 
    1202                 if ( 2 > (int) $number ) 
    1203                         continue; 
     1246                                                // ... have a match as well. 
     1247                                                if ( false === stripos( $sidebar, $slug ) && false === stripos( $slug, $sidebar ) ) { 
     1248                                                        continue; 
     1249                                                } 
     1250 
     1251                                                // Make sure this sidebar wasn't mapped and removed previously. 
     1252                                                if ( ! empty( $old_sidebars_widgets[ $sidebar ] ) ) { 
     1253 
     1254                                                        // We have a match that can be mapped! 
     1255                                                        $new_sidebars_widgets[ $new_sidebar ] = $old_sidebars_widgets[ $sidebar ]; 
     1256 
     1257                                                        // Remove the mapped sidebar so it can't be mapped again. 
     1258                                                        unset( $old_sidebars_widgets[ $sidebar ] ); 
     1259 
     1260                                                        // Go back and check the next new sidebar. 
     1261                                                        continue 3; 
     1262                                                } 
     1263                                        } // endforeach ( $slug_group as $slug ) 
     1264                                } // endforeach ( $old_sidebars_widgets as $sidebar => $menu_id ) 
     1265                        } // endforeach foreach ( $wp_registered_sidebars as $new_sidebar => $name ) 
     1266                } // endforeach ( $slug_group as $slug ) 
     1267        } // endforeach ( $common_slug_groups as $slug_group ) 
    12041268 
    1205                 $lost_widgets[] = $key; 
     1269        $orphaned = 0; 
     1270        foreach ( $old_sidebars_widgets as $widgets ) { 
     1271                if ( is_array( $widgets ) && ! empty( $widgets ) ) { 
     1272                        $new_sidebars_widgets[ 'orphaned_widgets_' . ++$orphaned ] = $widgets; 
     1273                } 
    12061274        } 
    12071275 
    1208         $sidebars_widgets['wp_inactive_widgets'] = array_merge($lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets']); 
    1209         if ( 'customize' !== $theme_changed ) { 
    1210                 wp_set_sidebars_widgets( $sidebars_widgets ); 
     1276        return $new_sidebars_widgets; 
     1277} 
     1278 
     1279/** 
     1280 * Compares a list of sidebars with their widgets against a whitelist. 
     1281 * 
     1282 * @since 4.9.0 
     1283 * 
     1284 * @param array $sidebars_widgets List of sidebars and their widget instance IDs. 
     1285 * @param array $whitelist        List of widget IDs to compare against. 
     1286 * @return array Sidebars with whitelisted widgets. 
     1287 */ 
     1288function _wp_remove_unregistered_widgets( $sidebars_widgets, $whitelist ) { 
     1289        foreach ( $sidebars_widgets as $sidebar => $widgets ) { 
     1290                if ( is_array( $widgets ) ) { 
     1291                        $sidebars_widgets[ $sidebar ] = array_intersect( $widgets, $whitelist ); 
     1292                } 
    12111293        } 
    12121294 
    12131295        return $sidebars_widgets; 
  • tests/phpunit/tests/widgets.php

     
    678678 
    679679        } 
    680680 
     681        function test_retrieve_widgets_with_theme_mod() { 
     682                global $sidebars_widgets, $_wp_sidebars_widgets; 
     683 
     684                wp_widgets_init(); 
     685 
     686                register_sidebar( array( 
     687                        'name' => 'Primary Sidebar', 
     688                        'id'   => 'sidebar-1', 
     689                ) ); 
     690                register_sidebar( array( 
     691                        'name' => 'Content Sidebar', 
     692                        'id'   => 'sidebar-2', 
     693                ) ); 
     694                register_sidebar( array( 
     695                        'name' => 'Footer Widget Area', 
     696                        'id'   => 'sidebar-3', 
     697                ) ); 
     698                register_sidebar( array( 'id' => 'wp_inactive_widgets' ) ); 
     699 
     700                set_theme_mod( 'sidebars_widgets', array( 
     701                        'time' => time(), 
     702                        'data' => array( 
     703                                'sidebar-1' => array( 'tag_cloud-1' ), 
     704                                'sidebar-2' => array( 'text-1' ), 
     705                                'sidebar-3' => array( 'unregistered_widget-1' ), 
     706                                'fantasy-sidebar'     => array( 'archives-2' ), 
     707                                'wp_inactive_widgets' => array(), 
     708                        ), 
     709                ) ); 
     710 
     711                $result = retrieve_widgets( true ); 
     712 
     713                $_wp_sidebars_widgets = array(); 
     714                $this->assertInternalType( 'array', $result ); 
     715                $this->assertEquals( $result, $sidebars_widgets ); 
     716                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); 
     717                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); 
     718                $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); 
     719 
     720                // Unregistered widget should be filtered out. 
     721                $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); 
     722 
     723                // 6 default widgets - 1 active text widget = 5. 
     724                $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); 
     725 
     726                // Theme mode with previous widgets was removed. 
     727                $this->assertFalse( get_theme_mod( 'sidebars_widgets' ) ); 
     728 
     729                // Sidebar_widgets option was updated. 
     730                $this->assertEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); 
     731        } 
     732 
     733        function test_retrieve_widgets_with_sidebars_widgets_matching_registered_sidebars() { 
     734                global $sidebars_widgets; 
     735 
     736                wp_widgets_init(); 
     737 
     738                register_sidebar( array( 
     739                        'name' => 'Primary Sidebar', 
     740                        'id'   => 'sidebar-1', 
     741                ) ); 
     742                register_sidebar( array( 
     743                        'name' => 'Content Sidebar', 
     744                        'id'   => 'sidebar-2', 
     745                ) ); 
     746                register_sidebar( array( 
     747                        'name' => 'Footer Widget Area', 
     748                        'id'   => 'sidebar-3', 
     749                ) ); 
     750                register_sidebar( array( 'id' => 'wp_inactive_widgets' ) ); 
     751 
     752                $sidebars_widgets = array( 
     753                        'sidebar-1' => array( 'tag_cloud-1' ), 
     754                        'sidebar-2' => array( 'text-1' ), 
     755                        'sidebar-3' => array( 'custom_widget-1' ), 
     756                        'wp_inactive_widgets' => array(), 
     757                ); 
     758 
     759                $result = retrieve_widgets( true ); 
     760 
     761                // $sidebars_widgets matches registered sidebars. 
     762                $this->assertInternalType( 'array', $result ); 
     763                $this->assertEquals( $result, $sidebars_widgets ); 
     764                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); 
     765                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); 
     766 
     767                // Invalid widget removed, even when $sidebars_widgets matches registered sidebars. 
     768                $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); 
     769 
     770                // No lost widgets when $sidebars_widgets matches registered sidebars. 
     771                $this->assertEmpty( $sidebars_widgets['wp_inactive_widgets'] ); 
     772        } 
     773 
     774        function test_retrieve_widgets_with_sidebars_widgets_not_matching_registered_sidebars() { 
     775                global $sidebars_widgets, $_wp_sidebars_widgets; 
     776 
     777                wp_widgets_init(); 
     778 
     779                register_sidebar( array( 
     780                        'name' => 'Primary Sidebar', 
     781                        'id'   => 'sidebar-1', 
     782                ) ); 
     783                register_sidebar( array( 
     784                        'name' => 'Content Sidebar', 
     785                        'id'   => 'sidebar-2', 
     786                ) ); 
     787                register_sidebar( array( 
     788                        'name' => 'Footer Widget Area', 
     789                        'id'   => 'sidebar-3', 
     790                ) ); 
     791                register_sidebar( array( 'id' => 'wp_inactive_widgets' ) ); 
     792 
     793                $sidebars_widgets = array( 
     794                        'sidebar-1'           => array( 'tag_cloud-1' ), 
     795                        'sidebar-2'           => array( 'text-1' ), 
     796                        'fantasy-sidebar'     => array( 'unregistered_widget-1' ), 
     797                        'wp_inactive_widgets' => array(), 
     798                ); 
     799 
     800                // Theme changed. 
     801                $result = retrieve_widgets( true ); 
     802 
     803                $_wp_sidebars_widgets = array(); 
     804                $this->assertInternalType( 'array', $result ); 
     805                $this->assertEquals( $result, $sidebars_widgets ); 
     806 
     807                // Current theme doesn't have a fantasy-sidebar. 
     808                $this->assertArrayNotHasKey( 'fantasy-sidebar', $sidebars_widgets ); 
     809                $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets ); 
     810 
     811                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); 
     812                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); 
     813 
     814                // We should not have orphaned widgets, because widget was not registered. 
     815                $this->assertArrayNotHasKey( 'orphaned_widgets_1', $sidebars_widgets ); 
     816 
     817                // 6 default widgets. 
     818                $this->assertCount( 6, $sidebars_widgets['wp_inactive_widgets'] ); 
     819 
     820                // Sidebar_widgets option was updated. 
     821                $this->assertEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); 
     822 
     823                // Reset. 
     824                $sidebars_widgets = array( 
     825                        'sidebar-1'           => array( 'tag_cloud-1' ), 
     826                        'sidebar-2'           => array( 'text-1' ), 
     827                        'fantasy-sidebar'     => array( 'archives-2' ), 
     828                        'wp_inactive_widgets' => array(), 
     829                ); 
     830 
     831                // Theme did not change. 
     832                $result = retrieve_widgets(); 
     833 
     834                $_wp_sidebars_widgets = array(); 
     835                $this->assertInternalType( 'array', $result ); 
     836                $this->assertEquals( $result, $sidebars_widgets ); 
     837 
     838                // This sidebar is not registered anymore. 
     839                $this->assertArrayNotHasKey( 'fantasy-sidebar', $sidebars_widgets ); 
     840                $this->assertArrayHasKey( 'sidebar-3', $sidebars_widgets ); 
     841 
     842                // archives-2 ends up as an orphan because of the above behavior. 
     843                $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); 
     844                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); 
     845                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); 
     846 
     847                // 6 default widgets - 1 active text widget = 5. 
     848                $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); 
     849 
     850                // Sidebar_widgets option was updated. 
     851                $this->assertEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); 
     852        } 
     853 
     854        function test_retrieve_widgets_for_customizer() { 
     855                global $sidebars_widgets, $_wp_sidebars_widgets; 
     856 
     857                wp_widgets_init(); 
     858 
     859                register_sidebar( array( 
     860                        'name' => 'Primary Sidebar', 
     861                        'id'   => 'sidebar-1', 
     862                ) ); 
     863                register_sidebar( array( 
     864                        'name' => 'Content Sidebar', 
     865                        'id'   => 'sidebar-2', 
     866                ) ); 
     867                register_sidebar( array( 
     868                        'name' => 'Footer Widget Area', 
     869                        'id'   => 'sidebar-3', 
     870                ) ); 
     871                register_sidebar( array( 'id' => 'wp_inactive_widgets' ) ); 
     872 
     873                $old_sidebars_widgets = array( 
     874                        'time' => time(), 
     875                        'data' => array( 
     876                                'sidebar-1' => array( 'tag_cloud-1' ), 
     877                                'sidebar-2' => array( 'text-1' ), 
     878                                'sidebar-3' => array( 'unregistered_widget-1' ), 
     879                                'fantasy-sidebar'     => array( 'archives-2' ), 
     880                                'wp_inactive_widgets' => array(), 
     881                        ), 
     882                ); 
     883                set_theme_mod( 'sidebars_widgets', $old_sidebars_widgets ); 
     884 
     885                $result = retrieve_widgets( 'customize' ); 
     886 
     887                $_wp_sidebars_widgets = array(); 
     888                $this->assertInternalType( 'array', $result ); 
     889                $this->assertEquals( $result, $sidebars_widgets ); 
     890                $this->assertContains( 'tag_cloud-1', $sidebars_widgets['sidebar-1'] ); 
     891                $this->assertContains( 'text-1', $sidebars_widgets['sidebar-2'] ); 
     892                $this->assertContains( 'archives-2', $sidebars_widgets['orphaned_widgets_1'] ); 
     893                $this->assertEmpty( $sidebars_widgets['sidebar-3'] ); 
     894                $this->assertCount( 5, $sidebars_widgets['wp_inactive_widgets'] ); 
     895 
     896                // Theme mod with previous widgets was not removed. 
     897                $this->assertEqualSets( $old_sidebars_widgets, get_theme_mod( 'sidebars_widgets' ) ); 
     898 
     899                // Sidebar_widgets option was not updated. 
     900                $this->assertNotEquals( $sidebars_widgets, wp_get_sidebars_widgets() ); 
     901        } 
    681902}