Make WordPress Core

Ticket #35362: 35362.3.diff

File 35362.3.diff, 15.0 KB (added by westonruter, 9 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..c85a9eb 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 { 
    9871000         * Exports 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..ed5a91c 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( 
    128144                        },
    129145
    130146                        /**
     147                         * Make sure that partial fallback behavior is invoked if there is no associated menu.
     148                         *
     149                         * @since 4.5.0
     150                         *
     151                         * @returns {Promise}
     152                         */
     153                        refresh: function() {
     154                                var partial = this, menuId, deferred = $.Deferred();
     155
     156                                // Make sure the fallback behavior is invoked when
     157                                if ( _.isNumber( partial.params.navMenuArgs.menu ) ) {
     158                                        menuId = partial.params.navMenuArgs.menu;
     159                                } else if ( partial.params.navMenuArgs.theme_location && api.has( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' ) ) {
     160                                        menuId = api( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' ).get();
     161                                }
     162                                if ( ! menuId ) {
     163                                        partial.fallback();
     164                                        deferred.reject();
     165                                        return deferred.promise();
     166                                }
     167
     168                                return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
     169                        },
     170
     171                        /**
    131172                         * Render content.
    132173                         *
    133174                         * @inheritdoc
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    135176                         */
    136177                        renderContent: function( placement ) {
    137178                                var partial = this, previousContainer = placement.container;
     179
     180                                // Do fallback behavior to refresh preview if menu is now empty.
     181                                if ( '' === placement.addedContent ) {
     182                                        placement.partial.fallback();
     183                                }
     184
    138185                                if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
    139186
    140187                                        // Trigger deprecated event.
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    152199                api.selectiveRefresh.partialConstructor.nav_menu_instance = self.NavMenuInstancePartial;
    153200
    154201                /**
    155                  * Watch for changes to nav_menu_locations[] settings.
     202                 * Request full refresh if there are nav menu instances that lack partials which also match the supplied args.
    156203                 *
    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.
     204                 * @param {object} navMenuInstanceArgs
     205                 */
     206                self.handleUnplacedNavMenuInstances = function( navMenuInstanceArgs ) {
     207                        var unplacedNavMenuInstances;
     208                        unplacedNavMenuInstances = _.filter( _.values( self.data.navMenuInstanceArgs ), function( args ) {
     209                                return ! api.selectiveRefresh.partial.has( 'nav_menu_instance[' + args.args_hmac + ']' );
     210                        } );
     211                        if ( _.findWhere( unplacedNavMenuInstances, navMenuInstanceArgs ) ) {
     212                                api.selectiveRefresh.requestFullRefresh();
     213                                return true;
     214                        }
     215                        return false;
     216                };
     217
     218                /**
     219                 * Add change listener for a nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
    160220                 *
    161221                 * @since 4.5.0
     222                 *
     223                 * @param {wp.customize.Value} setting
     224                 * @return {boolean} Whether the setting was bound.
    162225                 */
    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 ) {
     226                self.bindSettingListener = function( setting ) {
     227                        var matches;
     228
     229                        matches = setting.id.match( /^nav_menu\[(-?\d+)]$/ );
     230                        if ( matches ) {
     231                                setting._navMenuId = parseInt( matches[1], 10 );
     232                                setting.bind( this.onChangeNavMenuSetting );
     233                                return true;
     234                        }
     235
     236                        matches = setting.id.match( /^nav_menu_item\[(-?\d+)]$/ );
     237                        if ( matches ) {
     238                                setting._navMenuItemId = parseInt( matches[1], 10 );
     239                                setting.bind( this.onChangeNavMenuItemSetting );
     240                                return true;
     241                        }
     242
     243                        matches = setting.id.match( /^nav_menu_locations\[(.+?)]/ );
     244                        if ( matches ) {
     245                                setting._navMenuThemeLocation = matches[1];
     246                                setting.bind( this.onChangeNavMenuLocationsSetting );
     247                                return true;
     248                        }
     249
     250                        return false;
     251                };
     252
     253                /**
     254                 * Remove change listeners for nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
     255                 *
     256                 * @since 4.5.0
     257                 *
     258                 * @param {wp.customize.Value} setting
     259                 */
     260                self.unbindSettingListener = function( setting ) {
     261                        setting.unbind( this.onChangeNavMenuSetting );
     262                        setting.unbind( this.onChangeNavMenuItemSetting );
     263                        setting.unbind( this.onChangeNavMenuLocationsSetting );
     264                };
     265
     266                /**
     267                 * Handle change for nav_menu[] setting for nav menu instances lacking partials.
     268                 *
     269                 * @since 4.5.0
     270                 *
     271                 * @this {wp.customize.Value}
     272                 */
     273                self.onChangeNavMenuSetting = function() {
     274                        var setting = this;
     275
     276                        self.handleUnplacedNavMenuInstances( {
     277                                menu: setting._navMenuId
     278                        } );
     279
     280                        // Ensure all nav menu instances with a theme_location assigned to this menu are handled.
     281                        api.each( function( otherSetting ) {
     282                                if ( ! otherSetting._navMenuThemeLocation ) {
    167283                                        return;
    168284                                }
    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();
     285                                if ( setting._navMenuId === otherSetting() ) {
     286                                        self.handleUnplacedNavMenuInstances( {
     287                                                theme_location: otherSetting._navMenuThemeLocation
     288                                        } );
    179289                                }
    180290                        } );
    181291                };
     292
     293                /**
     294                 * Handle change for nav_menu_item[] setting for nav menu instances lacking partials.
     295                 *
     296                 * @since 4.5.0
     297                 *
     298                 * @param {object} newItem New value for nav_menu_item[] setting.
     299                 * @param {object} oldItem Old value for nav_menu_item[] setting.
     300                 * @this {wp.customize.Value}
     301                 */
     302                self.onChangeNavMenuItemSetting = function( newItem, oldItem ) {
     303                        var item = newItem || oldItem, navMenuSetting;
     304                        navMenuSetting = api( 'nav_menu[' + String( item.nav_menu_term_id ) + ']' );
     305                        if ( navMenuSetting ) {
     306                                self.onChangeNavMenuSetting.call( navMenuSetting );
     307                        }
     308                };
     309
     310                /**
     311                 * Handle change for nav_menu_locations[] setting for nav menu instances lacking partials.
     312                 *
     313                 * @since 4.5.0
     314                 *
     315                 * @this {wp.customize.Value}
     316                 */
     317                self.onChangeNavMenuLocationsSetting = function() {
     318                        var setting = this, hasNavMenuInstance;
     319                        self.handleUnplacedNavMenuInstances( {
     320                                theme_location: setting._navMenuThemeLocation
     321                        } );
     322
     323                        // If there are no wp_nav_menu() instances that refer to the theme location, do full refresh.
     324                        hasNavMenuInstance = !! _.findWhere( _.values( self.data.navMenuInstanceArgs ), {
     325                                theme_location: setting._navMenuThemeLocation
     326                        } );
     327                        if ( ! hasNavMenuInstance ) {
     328                                api.selectiveRefresh.requestFullRefresh();
     329                        }
     330                };
    182331        }
    183332
    184333        /**
  • 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()