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.

Built from https://develop.svn.wordpress.org/trunk@33366


git-svn-id: http://core.svn.wordpress.org/trunk@33338 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter 2015-07-22 20:29:25 +00:00
parent 31ca0cd939
commit d5ce83c94c
6 changed files with 109 additions and 119 deletions

View File

@ -18,10 +18,7 @@
// Link settings. // Link settings.
api.Menus.data = { api.Menus.data = {
nonce: '', nonce: '',
itemTypes: { itemTypes: [],
taxonomies: {},
postTypes: {}
},
l10n: {}, l10n: {},
menuItemTransport: 'postMessage', menuItemTransport: 'postMessage',
phpIntMax: 0, phpIntMax: 0,
@ -280,35 +277,29 @@
var self = this; var self = this;
// Render the template for each item by type. // Render the template for each item by type.
_.each( api.Menus.data.itemTypes, function( typeObjects, type ) { _.each( api.Menus.data.itemTypes, function( itemType ) {
_.each( typeObjects, function( typeObject, slug ) { self.pages[ itemType.type + ':' + itemType.object ] = 0;
if ( 'postTypes' === type ) { self.loadItems( itemType.type, itemType.object ); // @todo we need to combine these Ajax requests.
type = 'post_type';
} else if ( 'taxonomies' === type ) {
type = 'taxonomy';
}
self.pages[ slug ] = 0; // @todo should prefix with type
self.loadItems( slug, type );
} );
} ); } );
}, },
// Load available menu items. // Load available menu items.
loadItems: function( type, obj_type ) { loadItems: function( type, object ) {
var self = this, params, request, itemTemplate; var self = this, params, request, itemTemplate, availableMenuItemContainer;
itemTemplate = wp.template( 'available-menu-item' ); itemTemplate = wp.template( 'available-menu-item' );
if ( 0 > self.pages[ type ] ) { if ( -1 === self.pages[ type + ':' + object ] ) {
return; 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; self.loading = true;
params = { params = {
'customize-menus-nonce': api.Menus.data.nonce, 'customize-menus-nonce': api.Menus.data.nonce,
'wp_customize': 'on', 'wp_customize': 'on',
'type': type, 'type': type,
'obj_type': obj_type, 'object': object,
'page': self.pages[ type ] 'page': self.pages[ type + ':' + object ]
}; };
request = wp.ajax.post( 'load-available-menu-items-customizer', params ); request = wp.ajax.post( 'load-available-menu-items-customizer', params );
@ -316,23 +307,23 @@
var items, typeInner; var items, typeInner;
items = data.items; items = data.items;
if ( 0 === items.length ) { if ( 0 === items.length ) {
if ( 0 === self.pages[ type ] ) { if ( 0 === self.pages[ type + ':' + object ] ) {
$( '#available-menu-items-' + type ) availableMenuItemContainer
.addClass( 'cannot-expand' ) .addClass( 'cannot-expand' )
.removeClass( 'loading' ) .removeClass( 'loading' )
.find( '.accordion-section-title > button' ) .find( '.accordion-section-title > button' )
.prop( 'tabIndex', -1 ); .prop( 'tabIndex', -1 );
} }
self.pages[ type ] = -1; self.pages[ type + ':' + object ] = -1;
return; return;
} }
items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away? items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away?
self.collection.add( items.models ); self.collection.add( items.models );
typeInner = $( '#available-menu-items-' + type + ' .accordion-section-content' ); typeInner = availableMenuItemContainer.find( '.accordion-section-content' );
items.each(function( menu_item ) { items.each(function( menuItem ) {
typeInner.append( itemTemplate( menu_item.attributes ) ); typeInner.append( itemTemplate( menuItem.attributes ) );
}); });
self.pages[ type ] = self.pages[ type ] + 1; self.pages[ type + ':' + object ] += 1;
}); });
request.fail(function( data ) { request.fail(function( data ) {
if ( typeof console !== 'undefined' && console.error ) { if ( typeof console !== 'undefined' && console.error ) {
@ -340,7 +331,7 @@
} }
}); });
request.always(function() { request.always(function() {
$( '#available-menu-items-' + type + ' .accordion-section-title' ).removeClass( 'loading' ); availableMenuItemContainer.find( '.accordion-section-title' ).removeClass( 'loading' );
self.loading = false; self.loading = false;
}); });
}, },
@ -1275,7 +1266,7 @@
} }
control.params.el_classes = containerClasses.join( ' ' ); 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.item_type = settingValue.type;
control.params.url = settingValue.url; control.params.url = settingValue.url;
control.params.target = settingValue.target; control.params.target = settingValue.target;
@ -2551,36 +2542,6 @@
return api.control( 'nav_menu[' + menuId + ']' ); 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. * Given a menu item ID, get the control associated with it.
* *

File diff suppressed because one or more lines are too long

View File

@ -1739,7 +1739,7 @@ class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control {
</p> </p>
<div class="menu-item-actions description-thin submitbox"> <div class="menu-item-actions description-thin submitbox">
<# if ( 'custom' != data.item_type && '' != data.original_title ) { #> <# if ( ( 'post_type' === data.item_type || 'taxonomy' === data.item_type ) && '' !== data.original_title ) { #>
<p class="link-to-original"> <p class="link-to-original">
<?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{ data.original_title }}</a>' ); ?> <?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{ data.original_title }}</a>' ); ?>
</p> </p>

View File

@ -75,14 +75,14 @@ final class WP_Customize_Nav_Menus {
wp_die( -1 ); wp_die( -1 );
} }
if ( empty( $_POST['obj_type'] ) || empty( $_POST['type'] ) ) { if ( empty( $_POST['type'] ) || empty( $_POST['object'] ) ) {
wp_send_json_error( 'nav_menus_missing_obj_type_or_type_parameter' ); wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' );
} }
$obj_type = sanitize_key( $_POST['obj_type'] ); $type = sanitize_key( $_POST['type'] );
$obj_name = sanitize_key( $_POST['type'] ); $object = sanitize_key( $_POST['object'] );
$page = empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] ); $page = empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] );
$items = $this->load_available_items_query( $obj_type, $obj_name, $page ); $items = $this->load_available_items_query( $type, $object, $page );
if ( is_wp_error( $items ) ) { if ( is_wp_error( $items ) ) {
wp_send_json_error( $items->get_error_code() ); wp_send_json_error( $items->get_error_code() );
@ -97,21 +97,21 @@ final class WP_Customize_Nav_Menus {
* @since 4.3.0 * @since 4.3.0
* @access public * @access public
* *
* @param string $obj_type Optional. Accepts any custom object type and has built-in support for * @param string $type Optional. Accepts any custom object type and has built-in support for
* 'post_type' and 'taxonomy'. Default is 'post_type'. * 'post_type' and 'taxonomy'. Default is 'post_type'.
* @param string $obj_name Optional. Accepts any registered taxonomy or post type name. Default is 'page'. * @param string $object Optional. Accepts any registered taxonomy or post type name. Default is 'page'.
* @param int $page Optional. The page number used to generate the query offset. Default is '0'. * @param int $page Optional. The page number used to generate the query offset. Default is '0'.
* @return WP_Error|array Returns either a WP_Error object or an array of menu items. * @return WP_Error|array Returns either a WP_Error object or an array of menu items.
*/ */
public function load_available_items_query( $obj_type = 'post_type', $obj_name = 'page', $page = 0 ) { public function load_available_items_query( $type = 'post_type', $object = 'page', $page = 0 ) {
$items = array(); $items = array();
if ( 'post_type' === $obj_type ) { if ( 'post_type' === $type ) {
if ( ! get_post_type_object( $obj_name ) ) { if ( ! get_post_type_object( $object ) ) {
return new WP_Error( 'nav_menus_invalid_post_type' ); return new WP_Error( 'nav_menus_invalid_post_type' );
} }
if ( 0 === $page && 'page' === $obj_name ) { if ( 0 === $page && 'page' === $object ) {
// Add "Home" link. Treat as a page, but switch to custom on add. // Add "Home" link. Treat as a page, but switch to custom on add.
$items[] = array( $items[] = array(
'id' => 'home', 'id' => 'home',
@ -128,7 +128,7 @@ final class WP_Customize_Nav_Menus {
'offset' => 10 * $page, 'offset' => 10 * $page,
'orderby' => 'date', 'orderby' => 'date',
'order' => 'DESC', 'order' => 'DESC',
'post_type' => $obj_name, 'post_type' => $object,
) ); ) );
foreach ( $posts as $post ) { foreach ( $posts as $post ) {
$post_title = $post->post_title; $post_title = $post->post_title;
@ -146,8 +146,8 @@ final class WP_Customize_Nav_Menus {
'url' => get_permalink( intval( $post->ID ) ), 'url' => get_permalink( intval( $post->ID ) ),
); );
} }
} elseif ( 'taxonomy' === $obj_type ) { } elseif ( 'taxonomy' === $type ) {
$terms = get_terms( $obj_name, array( $terms = get_terms( $object, array(
'child_of' => 0, 'child_of' => 0,
'exclude' => '', 'exclude' => '',
'hide_empty' => false, 'hide_empty' => false,
@ -176,6 +176,18 @@ final class WP_Customize_Nav_Menus {
} }
} }
/**
* Filter the available menu items.
*
* @since 4.3.0
*
* @param array $items The array of menu items.
* @param string $type The object type.
* @param string $object The object name.
* @param int $page The current page number.
*/
$items = apply_filters( 'customize_nav_menu_available_items', $items, $type, $object, $page );
return $items; return $items;
} }
@ -588,30 +600,47 @@ final class WP_Customize_Nav_Menus {
* *
* @since 4.3.0 * @since 4.3.0
* @access public * @access public
*
* @return array The available menu item types.
*/ */
public function available_item_types() { public function available_item_types() {
$items = array( $item_types = array();
'postTypes' => array(),
'taxonomies' => array(),
);
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
if ( $post_types ) {
foreach ( $post_types as $slug => $post_type ) { foreach ( $post_types as $slug => $post_type ) {
$items['postTypes'][ $slug ] = array( $item_types[] = array(
'label' => $post_type->labels->singular_name, 'title' => $post_type->labels->singular_name,
'type' => 'post_type',
'object' => $post_type->name,
); );
} }
}
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
if ( $taxonomies ) {
foreach ( $taxonomies as $slug => $taxonomy ) { foreach ( $taxonomies as $slug => $taxonomy ) {
if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) { if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) {
continue; continue;
} }
$items['taxonomies'][ $slug ] = array( $item_types[] = array(
'label' => $taxonomy->labels->singular_name, 'title' => $taxonomy->labels->singular_name,
'type' => 'taxonomy',
'object' => $taxonomy->name,
); );
} }
return $items; }
/**
* Filter the available menu item types.
*
* @since 4.3.0
*
* @param array $item_types Custom menu item types.
*/
$item_types = apply_filters( 'customize_nav_menu_available_item_types', $item_types );
return $item_types;
} }
/** /**
@ -716,32 +745,16 @@ final class WP_Customize_Nav_Menus {
</div> </div>
</div> </div>
<?php <?php
// @todo: consider using add_meta_box/do_accordion_section and making screen-optional?
// Containers for per-post-type item browsing; items added with JS. // Containers for per-post-type item browsing; items added with JS.
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' ); foreach ( $this->available_item_types() as $available_item_type ) {
if ( $post_types ) : $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] );
foreach ( $post_types as $type ) :
?> ?>
<div id="available-menu-items-<?php echo esc_attr( $type->name ); ?>" class="accordion-section"> <div id="<?php echo esc_attr( $id ); ?>" class="accordion-section">
<h4 class="accordion-section-title"><?php echo esc_html( $type->label ); ?> <span class="spinner"></span> <span class="no-items"><?php _e( 'No items' ); ?></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4> <h4 class="accordion-section-title"><?php echo esc_html( $available_item_type['title'] ); ?> <span class="no-items"><?php _e( 'No items' ); ?></span><span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
<ul class="accordion-section-content" data-type="<?php echo esc_attr( $type->name ); ?>" data-obj_type="post_type"></ul> <ul class="accordion-section-content" data-type="<?php echo esc_attr( $available_item_type['type'] ); ?>" data-object="<?php echo esc_attr( $available_item_type['object'] ); ?>"></ul>
</div> </div>
<?php <?php
endforeach; }
endif;
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
if ( $taxonomies ) :
foreach ( $taxonomies as $tax ) :
?>
<div id="available-menu-items-<?php echo esc_attr( $tax->name ); ?>" class="accordion-section">
<h4 class="accordion-section-title"><?php echo esc_html( $tax->label ); ?> <span class="spinner"></span> <span class="no-items"><?php _e( 'No items' ); ?></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
<ul class="accordion-section-content" data-type="<?php echo esc_attr( $tax->name ); ?>" data-obj_type="taxonomy"></ul>
</div>
<?php
endforeach;
endif;
?> ?>
</div><!-- #available-menu-items --> </div><!-- #available-menu-items -->
<?php <?php

View File

@ -968,7 +968,6 @@ class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
'post_title', 'post_title',
'post_type', 'post_type',
'to_ping', 'to_ping',
'type_label',
); );
foreach ( $irrelevant_properties as $property ) { foreach ( $irrelevant_properties as $property ) {
unset( $this->value[ $property ] ); unset( $this->value[ $property ] );
@ -1143,8 +1142,25 @@ class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting {
} }
if ( ! isset( $post->type_label ) ) { 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; return $post;
} }

View File

@ -4,7 +4,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '4.3-beta3-33365'; $wp_version = '4.3-beta3-33366';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.