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 ) :
- ?>
-
+ 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 ) :
- ?>
-
-
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 );
}
/**