From 9e383a560acfb546c59971591bbd0ff4f7462deb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 22 Jul 2015 20:28:03 +0000 Subject: [PATCH] Customizer: Introduce `customize_nav_menu_available_item_types` and `customize_nav_menu_available_items` filters. Allows for new available menu item types/objects to be registered in addition to filtering the available items that are returned for each menu item type/object. Props valendesigns, imath, westonruter. See #32832. Fixes #32708. git-svn-id: https://develop.svn.wordpress.org/trunk@33366 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/js/customize-nav-menus.js | 79 +++-------- .../class-wp-customize-control.php | 2 +- .../class-wp-customize-nav-menus.php | 121 +++++++++-------- .../class-wp-customize-setting.php | 20 ++- tests/phpunit/tests/ajax/CustomizeMenus.php | 50 +++---- .../tests/customize/nav-menu-item-setting.php | 42 ++++++ tests/phpunit/tests/customize/nav-menus.php | 124 +++++++++++++----- 7 files changed, 266 insertions(+), 172 deletions(-) diff --git a/src/wp-admin/js/customize-nav-menus.js b/src/wp-admin/js/customize-nav-menus.js index 58c27a8d57..7b8fbf551a 100644 --- a/src/wp-admin/js/customize-nav-menus.js +++ b/src/wp-admin/js/customize-nav-menus.js @@ -18,10 +18,7 @@ // Link settings. api.Menus.data = { nonce: '', - itemTypes: { - taxonomies: {}, - postTypes: {} - }, + itemTypes: [], l10n: {}, menuItemTransport: 'postMessage', phpIntMax: 0, @@ -280,35 +277,29 @@ var self = this; // Render the template for each item by type. - _.each( api.Menus.data.itemTypes, function( typeObjects, type ) { - _.each( typeObjects, function( typeObject, slug ) { - if ( 'postTypes' === type ) { - type = 'post_type'; - } else if ( 'taxonomies' === type ) { - type = 'taxonomy'; - } - self.pages[ slug ] = 0; // @todo should prefix with type - self.loadItems( slug, type ); - } ); + _.each( api.Menus.data.itemTypes, function( itemType ) { + self.pages[ itemType.type + ':' + itemType.object ] = 0; + self.loadItems( itemType.type, itemType.object ); // @todo we need to combine these Ajax requests. } ); }, // Load available menu items. - loadItems: function( type, obj_type ) { - var self = this, params, request, itemTemplate; + loadItems: function( type, object ) { + var self = this, params, request, itemTemplate, availableMenuItemContainer; itemTemplate = wp.template( 'available-menu-item' ); - if ( 0 > self.pages[ type ] ) { + if ( -1 === self.pages[ type + ':' + object ] ) { return; } - $( '#available-menu-items-' + type + ' .accordion-section-title' ).addClass( 'loading' ); + availableMenuItemContainer = $( '#available-menu-items-' + type + '-' + object ); + availableMenuItemContainer.find( '.accordion-section-title' ).addClass( 'loading' ); self.loading = true; params = { 'customize-menus-nonce': api.Menus.data.nonce, 'wp_customize': 'on', 'type': type, - 'obj_type': obj_type, - 'page': self.pages[ type ] + 'object': object, + 'page': self.pages[ type + ':' + object ] }; request = wp.ajax.post( 'load-available-menu-items-customizer', params ); @@ -316,23 +307,23 @@ var items, typeInner; items = data.items; if ( 0 === items.length ) { - if ( 0 === self.pages[ type ] ) { - $( '#available-menu-items-' + type ) + if ( 0 === self.pages[ type + ':' + object ] ) { + availableMenuItemContainer .addClass( 'cannot-expand' ) .removeClass( 'loading' ) .find( '.accordion-section-title > button' ) .prop( 'tabIndex', -1 ); } - self.pages[ type ] = -1; + self.pages[ type + ':' + object ] = -1; return; } items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away? self.collection.add( items.models ); - typeInner = $( '#available-menu-items-' + type + ' .accordion-section-content' ); - items.each(function( menu_item ) { - typeInner.append( itemTemplate( menu_item.attributes ) ); + typeInner = availableMenuItemContainer.find( '.accordion-section-content' ); + items.each(function( menuItem ) { + typeInner.append( itemTemplate( menuItem.attributes ) ); }); - self.pages[ type ] = self.pages[ type ] + 1; + self.pages[ type + ':' + object ] += 1; }); request.fail(function( data ) { if ( typeof console !== 'undefined' && console.error ) { @@ -340,7 +331,7 @@ } }); request.always(function() { - $( '#available-menu-items-' + type + ' .accordion-section-title' ).removeClass( 'loading' ); + availableMenuItemContainer.find( '.accordion-section-title' ).removeClass( 'loading' ); self.loading = false; }); }, @@ -1275,7 +1266,7 @@ } control.params.el_classes = containerClasses.join( ' ' ); - control.params.item_type_label = api.Menus.getTypeLabel( settingValue.type, settingValue.object ); + control.params.item_type_label = settingValue.type_label; control.params.item_type = settingValue.type; control.params.url = settingValue.url; control.params.target = settingValue.target; @@ -2551,36 +2542,6 @@ return api.control( 'nav_menu[' + menuId + ']' ); }; - /** - * Given a menu item type & object, get the label associated with it. - * - * @param {string} type - * @param {string} object - * @return {string} - */ - api.Menus.getTypeLabel = function( type, object ) { - var label, - data = api.Menus.data; - - if ( 'post_type' === type ) { - if ( data.itemTypes.postTypes[ object ] ) { - label = data.itemTypes.postTypes[ object ].label; - } else { - label = data.l10n.postTypeLabel; - } - } else if ( 'taxonomy' === type ) { - if ( data.itemTypes.taxonomies[ object ] ) { - label = data.itemTypes.taxonomies[ object ].label; - } else { - label = data.l10n.taxonomyTermLabel; - } - } else { - label = data.l10n.custom_label; - } - - return label; - }; - /** * Given a menu item ID, get the control associated with it. * diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index cc9469e017..c67fb153d2 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -1739,7 +1739,7 @@ class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control {

true ), 'object' ); - if ( $post_types ) : - foreach ( $post_types as $type ) : - ?> -
-

label ); ?>

- -
+ foreach ( $this->available_item_types() as $available_item_type ) { + $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] ); + ?> +
+

+ +
true ), 'object' ); - if ( $taxonomies ) : - foreach ( $taxonomies as $tax ) : - ?> -
-

label ); ?>

- -
- value[ $property ] ); @@ -1143,8 +1142,25 @@ class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { } if ( ! isset( $post->type_label ) ) { - $post->type_label = null; + if ( 'post_type' === $post->type ) { + $object = get_post_type_object( $post->object ); + if ( $object ) { + $post->type_label = $object->labels->singular_name; + } else { + $post->type_label = $post->object; + } + } elseif ( 'taxonomy' == $post->type ) { + $object = get_taxonomy( $post->object ); + if ( $object ) { + $post->type_label = $object->labels->singular_name; + } else { + $post->type_label = $post->object; + } + } else { + $post->type_label = __( 'Custom Link' ); + } } + return $post; } diff --git a/tests/phpunit/tests/ajax/CustomizeMenus.php b/tests/phpunit/tests/ajax/CustomizeMenus.php index c39e9b591e..abf630093e 100644 --- a/tests/phpunit/tests/ajax/CustomizeMenus.php +++ b/tests/phpunit/tests/ajax/CustomizeMenus.php @@ -122,7 +122,7 @@ class Tests_Ajax_CustomizeMenus extends WP_Ajax_UnitTestCase { 'administrator', array( 'success' => false, - 'data' => 'nav_menus_missing_obj_type_or_type_parameter', + 'data' => 'nav_menus_missing_type_or_object_parameter', ), ), ); @@ -172,41 +172,41 @@ class Tests_Ajax_CustomizeMenus extends WP_Ajax_UnitTestCase { // Testing empty obj_type and type. array( array( - 'obj_type' => '', 'type' => '', + 'object' => '', ), array( 'success' => false, - 'data' => 'nav_menus_missing_obj_type_or_type_parameter', + 'data' => 'nav_menus_missing_type_or_object_parameter', ), ), // Testing empty obj_type. array( array( - 'obj_type' => '', - 'type' => 'post', + 'type' => '', + 'object' => 'post', ), array( 'success' => false, - 'data' => 'nav_menus_missing_obj_type_or_type_parameter', + 'data' => 'nav_menus_missing_type_or_object_parameter', ), ), // Testing empty type. array( array( - 'obj_type' => '', - 'type' => 'post', + 'type' => '', + 'object' => 'post', ), array( 'success' => false, - 'data' => 'nav_menus_missing_obj_type_or_type_parameter', + 'data' => 'nav_menus_missing_type_or_object_parameter', ), ), // Testing incorrect type option. array( array( - 'obj_type' => 'post_type', - 'type' => 'invalid', + 'type' => 'post_type', + 'object' => 'invalid', ), array( 'success' => false, @@ -259,29 +259,29 @@ class Tests_Ajax_CustomizeMenus extends WP_Ajax_UnitTestCase { return array( array( array( - 'obj_type' => 'post_type', - 'type' => 'post', + 'type' => 'post_type', + 'object' => 'post', ), true, ), array( array( - 'obj_type' => 'post_type', - 'type' => 'page', + 'type' => 'post_type', + 'object' => 'page', ), true, ), array( array( - 'obj_type' => 'post_type', - 'type' => 'custom', + 'type' => 'post_type', + 'object' => 'custom', ), false, ), array( array( - 'obj_type' => 'taxonomy', - 'type' => 'post_tag', + 'type' => 'taxonomy', + 'object' => 'post_tag', ), true, ), @@ -363,20 +363,20 @@ class Tests_Ajax_CustomizeMenus extends WP_Ajax_UnitTestCase { return array( array( array( - 'obj_type' => 'post_type', - 'type' => 'post', + 'type' => 'post_type', + 'object' => 'post', ), ), array( array( - 'obj_type' => 'post_type', - 'type' => 'page', + 'type' => 'post_type', + 'object' => 'page', ), ), array( array( - 'obj_type' => 'taxonomy', - 'type' => 'post_tag', + 'type' => 'taxonomy', + 'object' => 'post_tag', ), ), ); diff --git a/tests/phpunit/tests/customize/nav-menu-item-setting.php b/tests/phpunit/tests/customize/nav-menu-item-setting.php index f4325175cf..2e96570c3e 100644 --- a/tests/phpunit/tests/customize/nav-menu-item-setting.php +++ b/tests/phpunit/tests/customize/nav-menu-item-setting.php @@ -37,6 +37,20 @@ class Test_WP_Customize_Nav_Menu_Item_Setting extends WP_UnitTestCase { parent::clean_up_global_scope(); } + /** + * Filter to add a custom menu item type label. + * + * @param object $menu_item Menu item. + * @return object + */ + function filter_type_label( $menu_item ) { + if ( 'custom_type' === $menu_item->type ) { + $menu_item->type_label = 'Custom Label'; + } + + return $menu_item; + } + /** * Test constants and statics. */ @@ -205,6 +219,34 @@ class Test_WP_Customize_Nav_Menu_Item_Setting extends WP_UnitTestCase { $this->assertEquals( 'Salutations', $value['original_title'] ); } + /** + * Test value method with a custom object. + * + * @see WP_Customize_Nav_Menu_Item_Setting::value() + */ + function test_custom_type_label() { + do_action( 'customize_register', $this->wp_customize ); + add_filter( 'wp_setup_nav_menu_item', array( $this, 'filter_type_label' ) ); + + $menu_id = wp_create_nav_menu( 'Menu' ); + $item_id = wp_update_nav_menu_item( $menu_id, 0, array( + 'menu-item-type' => 'custom_type', + 'menu-item-object' => 'custom_object', + 'menu-item-title' => 'Cool beans', + 'menu-item-status' => 'publish', + ) ); + + $post = get_post( $item_id ); + $menu_item = wp_setup_nav_menu_item( $post ); + + $setting_id = "nav_menu_item[$item_id]"; + $setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id ); + + $value = $setting->value(); + $this->assertEquals( $menu_item->type_label, 'Custom Label' ); + $this->assertEquals( $menu_item->type_label, $value['type_label'] ); + } + /** * Test value method returns zero for nav_menu_term_id when previewing a new menu. * diff --git a/tests/phpunit/tests/customize/nav-menus.php b/tests/phpunit/tests/customize/nav-menus.php index 41991a82f0..2eba4b3c24 100644 --- a/tests/phpunit/tests/customize/nav-menus.php +++ b/tests/phpunit/tests/customize/nav-menus.php @@ -37,6 +37,44 @@ class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { parent::clean_up_global_scope(); } + /** + * Filter to add custom menu item types. + * + * @param array $items Menu item types. + * @return array Menu item types. + */ + function filter_item_types( $items ) { + $items[] = array( + 'title' => 'Custom', + 'type' => 'custom_type', + 'object' => 'custom_object', + ); + + return $items; + } + + /** + * Filter to add custom menu items. + * + * @param array $items The menu items. + * @param string $type The object type (e.g. taxonomy). + * @param string $object The object name (e.g. category). + * @return array Menu items. + */ + function filter_items( $items, $type, $object ) { + $items[] = array( + 'id' => 'custom-1', + 'title' => 'Cool beans', + 'type' => $type, + 'type_label' => 'Custom Label', + 'object' => $object, + 'url' => home_url( '/cool-beans/' ), + 'classes' => 'custom-menu-item cool-beans', + ); + + return $items; + } + /** * Test constructor. * @@ -206,6 +244,31 @@ class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { $this->assertContains( $expected, $items ); } + /** + * Test the load_available_items_query method returns custom item. + * + * @see WP_Customize_Nav_Menus::load_available_items_query() + */ + function test_load_available_items_query_returns_custom_item() { + add_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) ); + add_filter( 'customize_nav_menu_available_items', array( $this, 'filter_items' ), 10, 4 ); + $menus = new WP_Customize_Nav_Menus( $this->wp_customize ); + + // Expected menu item array. + $expected = array( + 'id' => 'custom-1', + 'title' => 'Cool beans', + 'type' => 'custom_type', + 'type_label' => 'Custom Label', + 'object' => 'custom_object', + 'url' => home_url( '/cool-beans/' ), + 'classes' => 'custom-menu-item cool-beans', + ); + + $items = $menus->load_available_items_query( 'custom_type', 'custom_object', 0 ); + $this->assertContains( $expected, $items ); + } + /** * Test the search_available_items_query method. * @@ -361,40 +424,31 @@ class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { function test_available_item_types() { $menus = new WP_Customize_Nav_Menus( $this->wp_customize ); + $expected = array( - 'postTypes' => array( - 'post' => array( 'label' => 'Post' ), - 'page' => array( 'label' => 'Page' ), - ), - 'taxonomies' => array( - 'category' => array( 'label' => 'Category' ), - 'post_tag' => array( 'label' => 'Tag' ), - ), + array( 'title' => 'Post', 'type' => 'post_type', 'object' => 'post' ), + array( 'title' => 'Page', 'type' => 'post_type', 'object' => 'page' ), + array( 'title' => 'Category', 'type' => 'taxonomy', 'object' => 'category' ), + array( 'title' => 'Tag', 'type' => 'taxonomy', 'object' => 'post_tag' ), ); + if ( current_theme_supports( 'post-formats' ) ) { - $expected['taxonomies']['post_format'] = array( 'label' => 'Format' ); + $expected[] = array( 'title' => 'Format', 'type' => 'taxonomy', 'object' => 'post_format' ); } + $this->assertEquals( $expected, $menus->available_item_types() ); register_taxonomy( 'wptests_tax', array( 'post' ), array( 'labels' => array( 'name' => 'Foo' ) ) ); - $expected = array( - 'postTypes' => array( - 'post' => array( 'label' => 'Post' ), - 'page' => array( 'label' => 'Page' ), - ), - 'taxonomies' => array( - 'category' => array( 'label' => 'Category' ), - 'post_tag' => array( 'label' => 'Tag' ), - 'wptests_tax' => array( 'label' => 'Foo' ), - ), - ); - if ( current_theme_supports( 'post-formats' ) ) { - $wptests_tax = array_pop( $expected['taxonomies'] ); - $expected['taxonomies']['post_format'] = array( 'label' => 'Format' ); - $expected['taxonomies']['wptests_tax'] = $wptests_tax; - } + $expected[] = array( 'title' => 'Foo', 'type' => 'taxonomy', 'object' => 'wptests_tax' ); + $this->assertEquals( $expected, $menus->available_item_types() ); + $expected[] = array( 'title' => 'Custom', 'type' => 'custom_type', 'object' => 'custom_object' ); + + add_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) ); + $this->assertEquals( $expected, $menus->available_item_types() ); + remove_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) ); + } /** @@ -427,6 +481,7 @@ class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { * @see WP_Customize_Nav_Menus::available_items_template() */ function test_available_items_template() { + add_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) ); do_action( 'customize_register', $this->wp_customize ); $menus = new WP_Customize_Nav_Menus( $this->wp_customize ); @@ -441,20 +496,27 @@ class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase { $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' ); if ( $post_types ) { foreach ( $post_types as $type ) { - $this->assertContains( 'available-menu-items-' . esc_attr( $type->name ), $template ); - $this->assertContains( '

' . esc_html( $type->label ), $template ); - $this->assertContains( 'data-type="' . esc_attr( $type->name ) . '" data-obj_type="post_type"', $template ); + $this->assertContains( 'available-menu-items-post_type-' . esc_attr( $type->name ), $template ); + $this->assertContains( '

' . esc_html( $type->labels->singular_name ), $template ); + $this->assertContains( 'data-type="post_type"', $template ); + $this->assertContains( 'data-object="' . esc_attr( $type->name ) . '"', $template ); } } $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' ); if ( $taxonomies ) { foreach ( $taxonomies as $tax ) { - $this->assertContains( 'available-menu-items-' . esc_attr( $tax->name ), $template ); - $this->assertContains( '

' . esc_html( $tax->label ), $template ); - $this->assertContains( 'data-type="' . esc_attr( $tax->name ) . '" data-obj_type="taxonomy"', $template ); + $this->assertContains( 'available-menu-items-taxonomy-' . esc_attr( $tax->name ), $template ); + $this->assertContains( '

' . esc_html( $tax->labels->singular_name ), $template ); + $this->assertContains( 'data-type="taxonomy"', $template ); + $this->assertContains( 'data-object="' . esc_attr( $tax->name ) . '"', $template ); } } + + $this->assertContains( 'available-menu-items-custom_type', $template ); + $this->assertContains( '

Custom', $template ); + $this->assertContains( 'data-type="custom_type"', $template ); + $this->assertContains( 'data-object="custom_object"', $template ); } /**