Make WordPress Core

Ticket #35362: 35362.5.diff

File 35362.5.diff, 16.5 KB (added by westonruter, 8 years ago)

https://github.com/xwp/wordpress-develop/commit/75c8013ba9e5660d6c8bd0fb2908987ae78286f5

  • 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..cb8994c 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 );
     875                add_filter( 'customize_render_partials_response', array( $this, 'export_partial_rendered_nav_menu_instances' ) );
    865876        }
    866877
    867878        /**
    final class WP_Customize_Nav_Menus { 
    881892                 * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be
    882893                 * selective refreshed if...
    883894                 */
    884                 $can_selective_refresh = (
     895                $can_partial_refresh = (
    885896                        // ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated),
    886897                        ! empty( $args['echo'] )
    887898                        &&
    final class WP_Customize_Nav_Menus { 
    904915                                ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) )
    905916                        )
    906917                );
    907 
    908                 if ( ! $can_selective_refresh ) {
    909                         return $args;
    910                 }
     918                $args['can_partial_refresh'] = $can_partial_refresh;
    911919
    912920                $exported_args = $args;
    913921
     922                // Empty out args which may not be JSON-serializable.
     923                if ( ! $can_partial_refresh ) {
     924                        $exported_args['fallback_cb'] = '';
     925                        $exported_args['walker'] = '';
     926                }
     927
    914928                /*
    915929                 * Replace object menu arg with a term_id menu arg, as this exports better
    916930                 * to JS and is easier to compare hashes.
    final class WP_Customize_Nav_Menus { 
    923937                $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args );
    924938
    925939                $args['customize_preview_nav_menus_args'] = $exported_args;
    926 
     940                $this->preview_nav_menu_instance_args[ $exported_args['args_hmac'] ] = $exported_args;
    927941                return $args;
    928942        }
    929943
    final class WP_Customize_Nav_Menus { 
    942956         * @return null
    943957         */
    944958        public function filter_wp_nav_menu( $nav_menu_content, $args ) {
    945                 if ( ! empty( $args->customize_preview_nav_menus_args ) ) {
     959                if ( isset( $args->customize_preview_nav_menus_args['can_partial_refresh'] ) && $args->customize_preview_nav_menus_args['can_partial_refresh'] ) {
    946960                        $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) );
    947961                        $attributes .= ' data-customize-partial-type="nav_menu_instance"';
    948962                        $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 { 
    9871001         * Exports data from PHP to JS.
    9881002         *
    9891003         * @since 4.3.0
    990          * @deprecated 4.5.0 Obsolete
    9911004         * @access public
    9921005         */
    9931006        public function export_preview_data() {
    994                 _deprecated_function( __METHOD__, '4.5.0' );
     1007
     1008                // Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
     1009                $exports = array(
     1010                        'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
     1011                );
     1012                printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
     1013        }
     1014
     1015        /**
     1016         * Export any wp_nav_menu() calls during the rendering of any partials.
     1017         *
     1018         * @since 4.5.0
     1019         * @access public
     1020         *
     1021         * @param array $response Response.
     1022         * @return array Response.
     1023         */
     1024        public function export_partial_rendered_nav_menu_instances( $response ) {
     1025                $response['nav_menu_instance_args'] = $this->preview_nav_menu_instance_args;
     1026                return $response;
    9951027        }
    9961028
    9971029        /**
  • 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..bb4d9d2 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                        // Listen for changes to settings related to nav menus.
     22                        api.each( function( setting ) {
     23                                self.bindSettingListener( setting );
     24                        } );
     25                        api.bind( 'add', function( setting ) {
     26                                self.bindSettingListener( setting, { fire: true } );
     27                        } );
     28                        api.bind( 'remove', function( setting ) {
     29                                self.unbindSettingListener( setting );
     30                        } );
     31
     32                        /*
     33                         * Ensure that wp_nav_menu() instances nested inside of other partials
     34                         * will be recognized as being present on the page.
     35                         */
     36                        api.selectiveRefresh.bind( 'render-partials-response', function( response ) {
     37                                if ( response.nav_menu_instance_args ) {
     38                                        _.extend( self.data.navMenuInstanceArgs, response.nav_menu_instance_args );
     39                                }
     40                        } );
    1441                }
    1542
    1643                api.preview.bind( 'active', function() {
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    128155                        },
    129156
    130157                        /**
     158                         * Make sure that partial fallback behavior is invoked if there is no associated menu.
     159                         *
     160                         * @since 4.5.0
     161                         *
     162                         * @returns {Promise}
     163                         */
     164                        refresh: function() {
     165                                var partial = this, menuId, deferred = $.Deferred();
     166
     167                                // Make sure the fallback behavior is invoked when
     168                                if ( _.isNumber( partial.params.navMenuArgs.menu ) ) {
     169                                        menuId = partial.params.navMenuArgs.menu;
     170                                } else if ( partial.params.navMenuArgs.theme_location && api.has( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' ) ) {
     171                                        menuId = api( 'nav_menu_locations[' + partial.params.navMenuArgs.theme_location + ']' ).get();
     172                                }
     173                                if ( ! menuId ) {
     174                                        partial.fallback();
     175                                        deferred.reject();
     176                                        return deferred.promise();
     177                                }
     178
     179                                return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
     180                        },
     181
     182                        /**
    131183                         * Render content.
    132184                         *
    133185                         * @inheritdoc
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    135187                         */
    136188                        renderContent: function( placement ) {
    137189                                var partial = this, previousContainer = placement.container;
     190
     191                                // Do fallback behavior to refresh preview if menu is now empty.
     192                                if ( '' === placement.addedContent ) {
     193                                        placement.partial.fallback();
     194                                }
     195
    138196                                if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
    139197
    140198                                        // Trigger deprecated event.
    wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 
    152210                api.selectiveRefresh.partialConstructor.nav_menu_instance = self.NavMenuInstancePartial;
    153211
    154212                /**
    155                  * Watch for changes to nav_menu_locations[] settings.
     213                 * Request full refresh if there are nav menu instances that lack partials which also match the supplied args.
    156214                 *
    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.
     215                 * @param {object} navMenuInstanceArgs
     216                 */
     217                self.handleUnplacedNavMenuInstances = function( navMenuInstanceArgs ) {
     218                        var unplacedNavMenuInstances;
     219                        unplacedNavMenuInstances = _.filter( _.values( self.data.navMenuInstanceArgs ), function( args ) {
     220                                return ! api.selectiveRefresh.partial.has( 'nav_menu_instance[' + args.args_hmac + ']' );
     221                        } );
     222                        if ( _.findWhere( unplacedNavMenuInstances, navMenuInstanceArgs ) ) {
     223                                api.selectiveRefresh.requestFullRefresh();
     224                                return true;
     225                        }
     226                        return false;
     227                };
     228
     229                /**
     230                 * Add change listener for a nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
    160231                 *
    161232                 * @since 4.5.0
     233                 *
     234                 * @param {wp.customize.Value} setting
     235                 * @param {object}             [options]
     236                 * @param {boolean}            options.fire Whether to invoke the callback after binding.
     237                 *                                          This is used when a dynamic setting is added.
     238                 * @return {boolean} Whether the setting was bound.
    162239                 */
    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 ) {
    167                                         return;
     240                self.bindSettingListener = function( setting, options ) {
     241                        var matches;
     242                        options = options || {};
     243
     244                        matches = setting.id.match( /^nav_menu\[(-?\d+)]$/ );
     245                        if ( matches ) {
     246                                setting._navMenuId = parseInt( matches[1], 10 );
     247                                setting.bind( this.onChangeNavMenuSetting );
     248                                if ( options.fire ) {
     249                                        this.onChangeNavMenuSetting.call( setting, setting(), false );
    168250                                }
    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                                 } );
     251                                return true;
     252                        }
     253
     254                        matches = setting.id.match( /^nav_menu_item\[(-?\d+)]$/ );
     255                        if ( matches ) {
     256                                setting._navMenuItemId = parseInt( matches[1], 10 );
     257                                setting.bind( this.onChangeNavMenuItemSetting );
     258                                if ( options.fire ) {
     259                                        this.onChangeNavMenuItemSetting.call( setting, setting(), false );
     260                                }
     261                                return true;
     262                        }
     263
     264                        matches = setting.id.match( /^nav_menu_locations\[(.+?)]/ );
     265                        if ( matches ) {
     266                                setting._navMenuThemeLocation = matches[1];
     267                                setting.bind( this.onChangeNavMenuLocationsSetting );
     268                                if ( options.fire ) {
     269                                        this.onChangeNavMenuLocationsSetting.call( setting, setting(), false );
     270                                }
     271                                return true;
     272                        }
     273
     274                        return false;
     275                };
     276
     277                /**
     278                 * Remove change listeners for nav_menu[], nav_menu_item[], or nav_menu_locations[] setting.
     279                 *
     280                 * @since 4.5.0
     281                 *
     282                 * @param {wp.customize.Value} setting
     283                 */
     284                self.unbindSettingListener = function( setting ) {
     285                        setting.unbind( this.onChangeNavMenuSetting );
     286                        setting.unbind( this.onChangeNavMenuItemSetting );
     287                        setting.unbind( this.onChangeNavMenuLocationsSetting );
     288                };
     289
     290                /**
     291                 * Handle change for nav_menu[] setting for nav menu instances lacking partials.
     292                 *
     293                 * @since 4.5.0
     294                 *
     295                 * @this {wp.customize.Value}
     296                 */
     297                self.onChangeNavMenuSetting = function() {
     298                        var setting = this;
    176299
    177                                 if ( ! themeLocationPartialFound ) {
    178                                         api.selectiveRefresh.requestFullRefresh();
     300                        self.handleUnplacedNavMenuInstances( {
     301                                menu: setting._navMenuId
     302                        } );
     303
     304                        // Ensure all nav menu instances with a theme_location assigned to this menu are handled.
     305                        api.each( function( otherSetting ) {
     306                                if ( ! otherSetting._navMenuThemeLocation ) {
     307                                        return;
    179308                                }
     309                                if ( setting._navMenuId === otherSetting() ) {
     310                                        self.handleUnplacedNavMenuInstances( {
     311                                                theme_location: otherSetting._navMenuThemeLocation
     312                                        } );
     313                                }
     314                        } );
     315                };
     316
     317                /**
     318                 * Handle change for nav_menu_item[] setting for nav menu instances lacking partials.
     319                 *
     320                 * @since 4.5.0
     321                 *
     322                 * @param {object} newItem New value for nav_menu_item[] setting.
     323                 * @param {object} oldItem Old value for nav_menu_item[] setting.
     324                 * @this {wp.customize.Value}
     325                 */
     326                self.onChangeNavMenuItemSetting = function( newItem, oldItem ) {
     327                        var item = newItem || oldItem, navMenuSetting;
     328                        navMenuSetting = api( 'nav_menu[' + String( item.nav_menu_term_id ) + ']' );
     329                        if ( navMenuSetting ) {
     330                                self.onChangeNavMenuSetting.call( navMenuSetting );
     331                        }
     332                };
     333
     334                /**
     335                 * Handle change for nav_menu_locations[] setting for nav menu instances lacking partials.
     336                 *
     337                 * @since 4.5.0
     338                 *
     339                 * @this {wp.customize.Value}
     340                 */
     341                self.onChangeNavMenuLocationsSetting = function() {
     342                        var setting = this, hasNavMenuInstance;
     343                        self.handleUnplacedNavMenuInstances( {
     344                                theme_location: setting._navMenuThemeLocation
    180345                        } );
     346
     347                        // If there are no wp_nav_menu() instances that refer to the theme location, do full refresh.
     348                        hasNavMenuInstance = !! _.findWhere( _.values( self.data.navMenuInstanceArgs ), {
     349                                theme_location: setting._navMenuThemeLocation
     350                        } );
     351                        if ( ! hasNavMenuInstance ) {
     352                                api.selectiveRefresh.requestFullRefresh();
     353                        }
    181354                };
    182355        }
    183356
  • 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()