Ticket #35362: 35362.6.diff
File 35362.6.diff, 16.6 KB (added by , 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..cb8994c 100644
final class WP_Customize_Nav_Menus { 822 822 // 823 823 824 824 /** 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 /** 825 834 * Filter arguments for dynamic nav_menu selective refresh partials. 826 835 * 827 836 * @since 4.5.0 … … final class WP_Customize_Nav_Menus { 862 871 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) ); 863 872 add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 ); 864 873 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' ) ); 865 876 } 866 877 867 878 /** … … final class WP_Customize_Nav_Menus { 881 892 * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be 882 893 * selective refreshed if... 883 894 */ 884 $can_ selective_refresh = (895 $can_partial_refresh = ( 885 896 // ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated), 886 897 ! empty( $args['echo'] ) 887 898 && … … final class WP_Customize_Nav_Menus { 904 915 ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) ) 905 916 ) 906 917 ); 907 908 if ( ! $can_selective_refresh ) { 909 return $args; 910 } 918 $args['can_partial_refresh'] = $can_partial_refresh; 911 919 912 920 $exported_args = $args; 913 921 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 914 928 /* 915 929 * Replace object menu arg with a term_id menu arg, as this exports better 916 930 * to JS and is easier to compare hashes. … … final class WP_Customize_Nav_Menus { 923 937 $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args ); 924 938 925 939 $args['customize_preview_nav_menus_args'] = $exported_args; 926 940 $this->preview_nav_menu_instance_args[ $exported_args['args_hmac'] ] = $exported_args; 927 941 return $args; 928 942 } 929 943 … … final class WP_Customize_Nav_Menus { 942 956 * @return null 943 957 */ 944 958 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'] ) { 946 960 $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) ); 947 961 $attributes .= ' data-customize-partial-type="nav_menu_instance"'; 948 962 $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 { 987 1001 * Exports data from PHP to JS. 988 1002 * 989 1003 * @since 4.3.0 990 * @deprecated 4.5.0 Obsolete991 1004 * @access public 992 1005 */ 993 1006 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; 995 1027 } 996 1028 997 1029 /** -
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..bd42f5e 100644
1 /* global _wpCustomizePreviewNavMenusExports */ 1 2 wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( $, _, wp, api ) { 2 3 'use strict'; 3 4 4 var self = {}; 5 var self = { 6 data: { 7 navMenuInstanceArgs: {} 8 } 9 }; 10 if ( 'undefined' !== typeof _wpCustomizePreviewNavMenusExports ) { 11 _.extend( self.data, _wpCustomizePreviewNavMenusExports ); 12 } 5 13 6 14 /** 7 15 * Initialize nav menus preview. … … wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 10 18 var self = this; 11 19 12 20 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 } ); 14 41 } 15 42 16 43 api.preview.bind( 'active', function() { … … wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 128 155 }, 129 156 130 157 /** 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 the partial is no longer associated with a menu. 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 /** 131 183 * Render content. 132 184 * 133 185 * @inheritdoc … … wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 135 187 */ 136 188 renderContent: function( placement ) { 137 189 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 138 196 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { 139 197 140 198 // Trigger deprecated event. … … wp.customize.navMenusPreview = wp.customize.MenusCustomizerPreview = ( function( 152 210 api.selectiveRefresh.partialConstructor.nav_menu_instance = self.NavMenuInstancePartial; 153 211 154 212 /** 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. 156 214 * 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. 160 231 * 161 232 * @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. 162 239 */ 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 ); 168 250 } 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; 176 299 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; 179 308 } 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 180 345 } ); 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 } 181 354 }; 182 355 } 183 356 -
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 { 617 617 function test_filter_wp_nav_menu_args() { 618 618 do_action( 'customize_register', $this->wp_customize ); 619 619 $menus = $this->wp_customize->nav_menus; 620 $menu_id = wp_create_nav_menu( 'Foo' ); 620 621 621 622 $results = $menus->filter_wp_nav_menu_args( array( 622 623 'echo' => true, 623 624 'fallback_cb' => 'wp_page_menu', 624 625 'walker' => '', 625 'menu' => wp_create_nav_menu( 'Foo' ),626 'menu' => $menu_id, 626 627 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', 627 628 ) ); 628 629 $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results ); 630 $this->assertTrue( $results['can_partial_refresh'] ); 629 631 630 632 $results = $menus->filter_wp_nav_menu_args( array( 631 633 'echo' => false, … … class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 633 635 'walker' => new Walker_Nav_Menu(), 634 636 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', 635 637 ) ); 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 ); 637 640 $this->assertEquals( 'wp_page_menu', $results['fallback_cb'] ); 638 641 639 642 $nav_menu_term = get_term( wp_create_nav_menu( 'Bar' ) ); … … class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 644 647 'menu' => $nav_menu_term, 645 648 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', 646 649 ) ); 650 $this->assertTrue( $results['can_partial_refresh'] ); 647 651 $this->assertArrayHasKey( 'customize_preview_nav_menus_args', $results ); 648 652 $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'] ); 649 683 } 650 684 651 685 /** … … class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { 691 725 } 692 726 693 727 /** 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 /** 694 742 * Test WP_Customize_Nav_Menus::render_nav_menu_partial() method. 695 743 * 696 744 * @see WP_Customize_Nav_Menus::render_nav_menu_partial()