Make WordPress Core

Ticket #35362: 35362.2.diff

File 35362.2.diff, 13.2 KB (added by westonruter, 8 years ago)
  • src/wp-includes/class-wp-customize-nav-menus.php

    diff --git src/wp-includes/class-wp-customize-nav-menus.php src/wp-includes/class-wp-customize-nav-menus.php
    index 1197dc1..7d49456 100644
    final class WP_Customize_Nav_Menus { 
    822822        //
    823823
    824824        /**
     825         * Nav menu args used for each instance, keyed by the args HMAC.
     826         *
     827         * @since 4.3.0
     828         * @access public
     829         * @var array
     830         */
     831        public $preview_nav_menu_instance_args = array();
     832
     833        /**
    825834         * Filter arguments for dynamic nav_menu selective refresh partials.
    826835         *
    827836         * @since 4.5.0
    final class WP_Customize_Nav_Menus { 
    862871                add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
    863872                add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
    864873                add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
     874                add_filter( 'wp_footer', array( $this, 'export_preview_data' ), 1 );
    865875        }
    866876
    867877        /**
    final class WP_Customize_Nav_Menus { 
    881891                 * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be
    882892                 * selective refreshed if...
    883893                 */
    884                 $can_selective_refresh = (
     894                $can_partial_refresh = (
    885895                        // ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated),
    886896                        ! empty( $args['echo'] )
    887897                        &&
    final class WP_Customize_Nav_Menus { 
    904914                                ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) )
    905915                        )
    906916                );
    907 
    908                 if ( ! $can_selective_refresh ) {
    909                         return $args;
    910                 }
     917                $args['can_partial_refresh'] = $can_partial_refresh;
    911918
    912919                $exported_args = $args;
    913920
     921                // Empty out args which may not be JSON-serializable.
     922                if ( ! $can_partial_refresh ) {
     923                        $exported_args['fallback_cb'] = '';
     924                        $exported_args['walker'] = '';
     925                }
     926
    914927                /*
    915928                 * Replace object menu arg with a term_id menu arg, as this exports better
    916929                 * to JS and is easier to compare hashes.
    final class WP_Customize_Nav_Menus { 
    923936                $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args );
    924937
    925938                $args['customize_preview_nav_menus_args'] = $exported_args;
    926 
     939                $this->preview_nav_menu_instance_args[ $exported_args['args_hmac'] ] = $exported_args;
    927940                return $args;
    928941        }
    929942
    final class WP_Customize_Nav_Menus { 
    942955         * @return null
    943956         */
    944957        public function filter_wp_nav_menu( $nav_menu_content, $args ) {
    945                 if ( ! empty( $args->customize_preview_nav_menus_args ) ) {
     958                if ( isset( $args->customize_preview_nav_menus_args['can_partial_refresh'] ) && $args->customize_preview_nav_menus_args['can_partial_refresh'] ) {
    946959                        $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) );
    947960                        $attributes .= ' data-customize-partial-type="nav_menu_instance"';
    948961                        $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $args->customize_preview_nav_menus_args ) ) );
    final class WP_Customize_Nav_Menus { 
    984997        }
    985998
    986999        /**
    987          * Exports data from PHP to JS.
     1000         * Export data from PHP to JS.
    9881001         *
    9891002         * @since 4.3.0
    990          * @deprecated 4.5.0 Obsolete
    9911003         * @access public
    9921004         */
    9931005        public function export_preview_data() {
    994                 _deprecated_function( __METHOD__, '4.5.0' );
     1006
     1007                // Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
     1008                $exports = array(
     1009                        'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
     1010                );
     1011                printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
    9951012        }
    9961013
    9971014        /**
  • src/wp-includes/js/customize-preview-nav-menus.js

    diff --git src/wp-includes/js/customize-preview-nav-menus.js src/wp-includes/js/customize-preview-nav-menus.js
    index 1ba3c87..21ebbfa 100644
     
     1/* global _wpCustomizePreviewNavMenusExports */
    12wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( $, _, wp, api ) {
    23        'use strict';
    34
    4         var self = {};
     5        var self = {
     6                data: {
     7                        navMenuInstanceArgs: {}
     8                }
     9        };
     10        if ( 'undefined' !== typeof _wpCustomizePreviewNavMenusExports ) {
     11                _.extend( self.data, _wpCustomizePreviewNavMenusExports );
     12        }
    513
    614        /**
    715         * Initialize nav menus preview.
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    1018                var self = this;
    1119
    1220                if ( api.selectiveRefresh ) {
    13                         self.watchNavMenuLocationChanges();
     21                        api.each( function( setting ) {
     22                                self.bindSettingListener( setting );
     23                        } );
     24                        api.bind( 'add', function( setting ) {
     25                                self.bindSettingListener( setting );
     26                        } );
     27                        api.bind( 'remove', function( setting ) {
     28                                self.unbindSettingListener( setting );
     29                        } );
    1430                }
    1531
    1632                api.preview.bind( 'active', function() {
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    152168                api.selectiveRefresh.partialConstructor.nav_menu_instance = self.NavMenuInstancePartial;
    153169
    154170                /**
    155                  * Watch for changes to nav_menu_locations[] settings.
     171                 * Request full refresh if there are nav menu instances that lack partials which also match the supplied args.
    156172                 *
    157                  * Refresh partials associated with the given nav_menu_locations[] setting,
    158                  * or request an entire preview refresh if there are no containers in the
    159                  * document for a partial associated with the theme location.
     173                 * @param {object} navMenuInstanceArgs
     174                 */
     175                self.handleUnplacedNavMenuInstances = function( navMenuInstanceArgs ) {
     176                        var unplacedNavMenuInstances;
     177                        unplacedNavMenuInstances = _.filter( _.values( self.data.navMenuInstanceArgs ), function( args ) {
     178                                return ! api.selectiveRefresh.partial.has( 'nav_menu_instance[' + args.args_hmac + ']' );
     179                        } );
     180                        if ( _.findWhere( unplacedNavMenuInstances, navMenuInstanceArgs ) ) {
     181                                api.selectiveRefresh.requestFullRefresh();
     182                                return true;
     183                        }
     184                        return false;
     185                };
     186
     187                /**
     188                 * Add change listener for a nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
    160189                 *
    161190                 * @since 4.5.0
     191                 *
     192                 * @param {wp.customize.Value} setting
     193                 * @return {boolean} Whether the setting was bound.
    162194                 */
    163                 self.watchNavMenuLocationChanges = function() {
    164                         api.bind( 'change', function( setting ) {
    165                                 var themeLocation, themeLocationPartialFound = false, matches = setting.id.match( /^nav_menu_locations\[(.+)]$/ );
    166                                 if ( ! matches ) {
     195                self.bindSettingListener = function( setting ) {
     196                        var matches;
     197
     198                        matches = setting.id.match( /^nav_menu\[(-?\d+)]$/ );
     199                        if ( matches ) {
     200                                setting._navMenuId = parseInt( matches[1], 10 );
     201                                setting.bind( this.onChangeNavMenuSetting );
     202                                return true;
     203                        }
     204
     205                        matches = setting.id.match( /^nav_menu_item\[(-?\d+)]$/ );
     206                        if ( matches ) {
     207                                setting._navMenuItemId = parseInt( matches[1], 10 );
     208                                setting.bind( this.onChangeNavMenuItemSetting );
     209                                return true;
     210                        }
     211
     212                        matches = setting.id.match( /^nav_menu_locations\[(.+?)]/ );
     213                        if ( matches ) {
     214                                setting._navMenuThemeLocation = matches[1];
     215                                setting.bind( this.onChangeNavMenuLocationsSetting );
     216                                return true;
     217                        }
     218
     219                        return false;
     220                };
     221
     222                /**
     223                 * Remove change listeners for nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
     224                 *
     225                 * @since 4.5.0
     226                 *
     227                 * @param {wp.customize.Value} setting
     228                 */
     229                self.unbindSettingListener = function( setting ) {
     230                        setting.unbind( this.onChangeNavMenuSetting );
     231                        setting.unbind( this.onChangeNavMenuItemSetting );
     232                        setting.unbind( this.onChangeNavMenuLocationsSetting );
     233                };
     234
     235                /**
     236                 * Handle change for nav_menu[] setting for nav menu instances lacking partials.
     237                 *
     238                 * @since 4.5.0
     239                 *
     240                 * @this {wp.customize.Value}
     241                 */
     242                self.onChangeNavMenuSetting = function() {
     243                        var setting = this;
     244
     245                        self.handleUnplacedNavMenuInstances( {
     246                                menu: setting._navMenuId
     247                        } );
     248
     249                        // Ensure all nav menu instances with a theme_location assigned to this menu are handled.
     250                        api.each( function( otherSetting ) {
     251                                if ( ! otherSetting._navMenuThemeLocation ) {
    167252                                        return;
    168253                                }
    169                                 themeLocation = matches[1];
    170                                 api.selectiveRefresh.partial.each( function( partial ) {
    171                                         if ( partial.extended( self.NavMenuInstancePartial ) && partial.params.navMenuArgs.theme_location === themeLocation ) {
    172                                                 partial.refresh();
    173                                                 themeLocationPartialFound = true;
    174                                         }
    175                                 } );
    176 
    177                                 if ( ! themeLocationPartialFound ) {
    178                                         api.selectiveRefresh.requestFullRefresh();
     254                                if ( setting._navMenuId === otherSetting() ) {
     255                                        self.handleUnplacedNavMenuInstances( {
     256                                                theme_location: otherSetting._navMenuThemeLocation
     257                                        } );
    179258                                }
    180259                        } );
    181260                };
     261
     262                /**
     263                 * Handle change for nav_menu_item[] setting for nav menu instances lacking partials.
     264                 *
     265                 * @since 4.5.0
     266                 *
     267                 * @param {object} newItem New value for nav_menu_item[] setting.
     268                 * @param {object} oldItem Old value for nav_menu_item[] setting.
     269                 * @this {wp.customize.Value}
     270                 */
     271                self.onChangeNavMenuItemSetting = function( newItem, oldItem ) {
     272                        var item = newItem || oldItem, navMenuSetting;
     273                        navMenuSetting = api( 'nav_menu[' + String( item.nav_menu_term_id ) + ']' );
     274                        if ( navMenuSetting ) {
     275                                self.onChangeNavMenuSetting.call( navMenuSetting );
     276                        }
     277                };
     278
     279                /**
     280                 * Handle change for nav_menu_locations[] setting for nav menu instances lacking partials.
     281                 *
     282                 * @since 4.5.0
     283                 *
     284                 * @this {wp.customize.Value}
     285                 */
     286                self.onChangeNavMenuLocationsSetting = function() {
     287                        var setting = this;
     288                        self.handleUnplacedNavMenuInstances( {
     289                                theme_location: setting._navMenuThemeLocation
     290                        } );
     291                };
    182292        }
    183293
    184294        /**
  • tests/phpunit/tests/customize/nav-menus.php

    diff --git tests/phpunit/tests/customize/nav-menus.php tests/phpunit/tests/customize/nav-menus.php
    index fd380bc..f5d56b6 100644
    class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 
    617617        function test_filter_wp_nav_menu_args() {
    618618                do_action( 'customize_register', $this->wp_customize );
    619619                $menus = $this->wp_customize->nav_menus;
     620                $menu_id = wp_create_nav_menu( 'Foo' );
    620621
    621622                $results = $menus->filter_wp_nav_menu_args( array(
    622623                        'echo'            => true,
    623624                        'fallback_cb'     => 'wp_page_menu',
    624625                        'walker'          => '',
    625                         'menu'            => wp_create_nav_menu( 'Foo' ),
     626                        'menu'            => $menu_id,
    626627                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    627628                ) );
    628629                $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results );
     630                $this->assertTrue( $results['can_partial_refresh'] );
    629631
    630632                $results = $menus->filter_wp_nav_menu_args( array(
    631633                        'echo'            => false,
    class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 
    633635                        'walker'          => new Walker_Nav_Menu(),
    634636                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    635637                ) );
    636                 $this->assertArrayNotHasKey( 'customize_preview_nav_menus_args', $results );
     638                $this->assertFalse( $results['can_partial_refresh'] );
     639                $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results );
    637640                $this->assertEquals( 'wp_page_menu', $results['fallback_cb'] );
    638641
    639642                $nav_menu_term = get_term( wp_create_nav_menu( 'Bar' ) );
    class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 
    644647                        'menu'            => $nav_menu_term,
    645648                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    646649                ) );
     650                $this->assertTrue( $results['can_partial_refresh'] );
    647651                $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results );
    648652                $this->assertEquals( $nav_menu_term->term_id, $results['customize_preview_nav_menus_args']['menu'] );
     653
     654                $results = $menus->filter_wp_nav_menu_args( array(
     655                        'echo'            => true,
     656                        'fallback_cb'     => 'wp_page_menu',
     657                        'walker'          => '',
     658                        'menu'            => $menu_id,
     659                        'container'       => 'div',
     660                        'items_wrap'      => '%3$s',
     661                ) );
     662                $this->assertTrue( $results['can_partial_refresh'] );
     663
     664                $results = $menus->filter_wp_nav_menu_args( array(
     665                        'echo'            => true,
     666                        'fallback_cb'     => 'wp_page_menu',
     667                        'walker'          => '',
     668                        'menu'            => $menu_id,
     669                        'container'       => false,
     670                        'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
     671                ) );
     672                $this->assertTrue( $results['can_partial_refresh'] );
     673
     674                $results = $menus->filter_wp_nav_menu_args( array(
     675                        'echo'            => true,
     676                        'fallback_cb'     => 'wp_page_menu',
     677                        'walker'          => '',
     678                        'menu'            => $menu_id,
     679                        'container'       => false,
     680                        'items_wrap'      => '%3$s',
     681                ) );
     682                $this->assertFalse( $results['can_partial_refresh'] );
    649683        }
    650684
    651685        /**
    class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 
    691725        }
    692726
    693727        /**
     728         * Test WP_Customize_Nav_Menus::export_preview_data() method.
     729         *
     730         * @see WP_Customize_Nav_Menus::export_preview_data()
     731         */
     732        function test_export_preview_data() {
     733                ob_start();
     734                $this->wp_customize->nav_menus->export_preview_data();
     735                $html = ob_get_clean();
     736                $this->assertTrue( (bool) preg_match( '/_wpCustomizePreviewNavMenusExports = ({.+})/s', $html, $matches ) );
     737                $exported_data = json_decode( $matches[1], true );
     738                $this->assertArrayHasKey( 'navMenuInstanceArgs', $exported_data );
     739        }
     740
     741        /**
    694742         * Test WP_Customize_Nav_Menus::render_nav_menu_partial() method.
    695743         *
    696744         * @see WP_Customize_Nav_Menus::render_nav_menu_partial()