Skip to content
This repository has been archived by the owner on Dec 16, 2022. It is now read-only.

Integrate Customize Posts with Nav Menus #288

Merged
merged 7 commits into from
Sep 21, 2016
18 changes: 17 additions & 1 deletion css/customize-posts.css
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,20 @@ body.customize-posts-content-editor-pane-resize #customize-preview:before {
.customize-posts-dropdown-pages-inputs > button.edit-page:before {
content: "\f464";
top: -2px;
}
}

.wp-core-ui .customize-posts-edit-nav-menu-item-original-object {
float: right;
margin-left: 5px;
margin-top: 5px;
}
.wp-core-ui .customize-posts-edit-nav-menu-item-original-object:before {
display: inline-block;
position: relative;
font: normal 20px/1 dashicons;
vertical-align: middle;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
content: "\f464";
top: -2px;
}
187 changes: 186 additions & 1 deletion js/customize-nav-menus-posts-extensions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
(function( api, $ ) {
/* global wp, jQuery */

wp.customize.Posts.NavMenusExtensions = (function( api, $ ) {
'use strict';

var component = {};

if ( api.Menus.insertAutoDraftPost ) {

/**
Expand Down Expand Up @@ -38,4 +42,185 @@
};
}

/**
* Add an edit post button to the nav menu control.
*
* @param {wp.customize.Control} control Control.
* @returns {void}
*/
component.addEditPostButton = function addEditPostButton( control ) {
var postTypeObj, editButton, navMenuItem = control.setting.get();
if ( 'post_type' !== navMenuItem.type || ! navMenuItem.object_id ) {
return;
}
postTypeObj = api.Posts.data.postTypes[ navMenuItem.object ];
if ( ! postTypeObj || ! postTypeObj.current_user_can.edit_published_posts ) {
return;
}

editButton = $( wp.template( 'customize-posts-edit-nav-menu-item-original-object' )( { editItemLabel: postTypeObj.labels.edit_item } ) );
editButton.on( 'click', function onClickEditButton() {
api.Posts.startEditPostFlow( {
postId: navMenuItem.object_id,
initiatingButton: editButton,
originatingConstruct: control,
restorePreviousUrl: true,
returnToOriginatingConstruct: true
} );
} );
control.container.find( '.menu-item-actions > .link-to-original' ).append( editButton );
};

/**
* Sync original item title when the post title changes.
*
* @param {wp.customize.Control} control Control.
* @returns {void}
*/
component.syncOriginalItemTitle = function syncOriginalItemTitle( control ) {
var postTypeObj, settingId, navMenuItem = control.setting.get();
if ( 'post_type' !== navMenuItem.type || ! navMenuItem.object_id ) {
return;
}
postTypeObj = api.Posts.data.postTypes[ navMenuItem.object ];
if ( ! postTypeObj ) {
return;
}

settingId = 'post[' + String( navMenuItem.object ) + '][' + String( navMenuItem.object_id ) + ']';
api( settingId, function( postSetting ) {
var setOriginalLinkTitle = function( newPostData, oldPostData ) {
var title, settingValue;
if ( ! oldPostData || newPostData.post_title !== oldPostData.post_title ) {
title = $.trim( newPostData.post_title ) || api.Posts.data.l10n.noTitle;
}
control.container.find( '.menu-item-actions > .link-to-original > .original-link' ).text( title );
control.container.find( '.edit-menu-item-title:first' ).attr( 'placeholder', title );

// Change original_title without triggering setting change since it's a readonly value.
settingValue = _.clone( control.setting.get() );
settingValue.original_title = newPostData.post_title;
control.setting.set( settingValue );
};
postSetting.bind( setOriginalLinkTitle );
setOriginalLinkTitle( postSetting.get(), null );
} );
};

/**
* Inject edit post button into the nav menu item controls.
*
* @param {wp.customize.Control} control Control.
* @returns {void}
*/
component.extendNavMenuItemOriginalObjectReference = function extendNavMenuItemOriginalObjectReference( control ) {
var onceExpanded;
if ( control.extended( api.Menus.MenuItemControl ) ) {

/**
* Trigger once expanded.
*
* @param {Boolean} expanded Whether expanded.
* @returns {void}
*/
onceExpanded = function onceExpandedFn( expanded ) {
if ( expanded ) {
control.expanded.unbind( onceExpanded );
component.addEditPostButton( control );
component.syncOriginalItemTitle( control );
}
};

control.deferred.embedded.done( function onDoneEmbedded() {
if ( control.expanded.get() ) {
onceExpanded();
} else {
control.expanded.bind( onceExpanded );
}
} );
}
};

/**
* Update available menu items to match a changing post title.
*
* @todo The ajax requests for search-available-menu-items-customizer and load-available-menu-items-customizer need to include the customized state.
*
* @param {wp.customize.Setting} setting Changed setting.
* @returns {void}
*/
component.watchPostSettingChanges = function watchPostSettingChanges( setting ) {
var idParts, postId, menuItemTitle, newTitle, availableItem, availableItemId, idPrefix, menuItemTpl;
idPrefix = 'post[';
if ( idPrefix !== setting.id.substr( 0, idPrefix.length ) ) {
return;
}
idParts = setting.id.substr( idPrefix.length ).replace( /]/g, '' ).split( '[' );
postId = parseInt( idParts[1], 10 );
if ( ! postId ) {
return;
}

newTitle = setting.get().post_title || api.Posts.data.l10n.noTitle;
availableItemId = 'post-' + String( postId );
menuItemTpl = $( '#menu-item-tpl-' + availableItemId );
if ( menuItemTpl.length ) {
menuItemTitle = menuItemTpl.find( '.menu-item-title:first' );
if ( $.trim( newTitle ) !== $.trim( menuItemTitle.text() ) ) {
menuItemTitle.text( newTitle );
}

// Ensure the available nav menu item is shown/hidden based on whether
if ( 'publish' !== setting.get().post_status && menuItemTpl.is( ':visible' ) ) {
menuItemTpl.hide();
} else if ( 'publish' === setting.get().post_status && ! menuItemTpl.is( ':visible' ) ) {
menuItemTpl.show();
}
}

availableItem = api.Menus.availableMenuItemsPanel.collection.get( availableItemId );
if ( availableItem ) {
availableItem.set( 'title', newTitle );
}
};

/**
* Rewrite Ajax requests to inject Customizer state.
*
* @param {object} options Options.
* @param {string} options.type Type.
* @param {string} options.url URL.
* @returns {void}
*/
component.ajaxPrefilterAvailableNavMenuItemRequests = function ajaxPrefilterAvailableNavMenuItemRequests( options ) {
var urlParser;

if ( 'POST' !== options.type.toUpperCase() ) {
return;
}

urlParser = document.createElement( 'a' );
urlParser.href = options.url;

// Ensure an admin ajax request.
if ( ! /wp-admin\/admin-ajax\.php$/.test( urlParser.pathname ) ) {
return;
}

// Ensure a request to search or load available nav menu items.
if ( ! /(^|&)action=(search-available-menu-items-customizer|load-available-menu-items-customizer)(&|$)/.test( options.data ) ) {
return;
}

// Add Customizer state.
options.data += '&';
options.data += $.param( { customized: api.previewer.query().customized } );
};

api.control.each( component.extendNavMenuItemOriginalObjectReference );
api.control.bind( 'add', component.extendNavMenuItemOriginalObjectReference );
api.bind( 'change', component.watchPostSettingChanges );
$.ajaxPrefilter( component.ajaxPrefilterAvailableNavMenuItemRequests );

return component;
})( wp.customize, jQuery );
17 changes: 16 additions & 1 deletion js/customize-posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
* @returns {void}
*/
done = function doneInsertAutoDraftPost( data ) {
var section;
var section, availableItem, availableMenuItemsList, itemTemplate;
component.addPostSettings( data.settings );

if ( ! data.postSettingId || ! api.has( data.postSettingId ) ) {
Expand All @@ -195,6 +195,21 @@
api.section( 'static_front_page' ).activate();
}

// Add the new item to the list of available items.
availableItem = new api.Menus.AvailableItemModel( {
'id': 'post-' + data.postId, // Used for available menu item Backbone models.
'title': api.Posts.data.l10n.noTitle,
'type': 'post_type',
'type_label': api.Posts.data.postTypes[ postType ].labels.singular_name,
'object': postType,
'object_id': data.postId,
'url': api.Posts.getPostUrl( { post_id: data.postId, post_type: postType } )
} );
api.Menus.availableMenuItemsPanel.collection.add( availableItem );
availableMenuItemsList = $( '#available-menu-items-post_type-' + postType ).find( '.available-menu-items-list' );
itemTemplate = wp.template( 'available-menu-item' );
availableMenuItemsList.prepend( itemTemplate( availableItem.attributes ) );

deferred.resolve( {
postId: data.postId,
section: section,
Expand Down
117 changes: 117 additions & 0 deletions php/class-wp-customize-posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,37 @@ public function __construct( WP_Customize_Manager $manager ) {
add_action( 'wp_ajax_customize-posts-insert-auto-draft', array( $this, 'ajax_insert_auto_draft_post' ) );
add_action( 'wp_ajax_customize-posts-fetch-settings', array( $this, 'ajax_fetch_settings' ) );
add_action( 'wp_ajax_customize-posts-select2-query', array( $this, 'ajax_posts_select2_query' ) );
add_action( 'customize_register', array( $this, 'replace_nav_menus_ajax_handlers' ) );

$this->preview = new WP_Customize_Posts_Preview( $this );
}

/**
* Replace core's load and search ajax handlers with forked versions that apply customized state.
*
* @see WP_Customize_Nav_Menus::ajax_load_available_items()
* @see WP_Customize_Nav_Menus::ajax_search_available_items()
* @param WP_Customize_Manager $wp_customize Manager.
*/
public function replace_nav_menus_ajax_handlers( $wp_customize ) {
if ( ! isset( $wp_customize->nav_menus ) ) {
return;
}

$handlers = array(
'wp_ajax_load-available-menu-items-customizer' => 'ajax_load_available_items',
'wp_ajax_search-available-menu-items-customizer' => 'ajax_search_available_items',
);

foreach ( $handlers as $action => $method_name ) {
$priority = has_action( $action, array( $wp_customize->nav_menus, $method_name ) );
if ( false !== $priority ) {
remove_action( $action, array( $wp_customize->nav_menus, $method_name ), $priority );
add_action( $action, array( $this, $method_name ), $priority );
}
}
}

/**
* Add nonce for customize posts.
*
Expand Down Expand Up @@ -686,6 +713,7 @@ public function enqueue_scripts() {
array(
'current_user_can' => array(
'create_posts' => isset( $post_type_obj->cap->create_posts ) && current_user_can( $post_type_obj->cap->create_posts ),
'edit_published_posts' => isset( $post_type_obj->cap->edit_published_posts ) && current_user_can( $post_type_obj->cap->edit_published_posts ),
'delete_posts' => isset( $post_type_obj->cap->delete_posts ) && current_user_can( $post_type_obj->cap->delete_posts ),
),
)
Expand Down Expand Up @@ -929,6 +957,12 @@ public function render_templates() {
<span class="customize-posts-trashed">(<?php esc_html_e( 'Trashed', 'customize-posts' ); ?>)</span>
</script>

<script id="tmpl-customize-posts-edit-nav-menu-item-original-object" type="text/html">
<button class="customize-posts-edit-nav-menu-item-original-object button button-secondary" type="button">
{{ data.editItemLabel }}
</button>
</script>

<script type="text/html" id="tmpl-customize-post-section-notifications">
<ul>
<# _.each( data.notifications, function( notification ) { #>
Expand Down Expand Up @@ -1427,4 +1461,87 @@ public function get_select2_item_result( $post ) {
}
return $result;
}

/**
* Ajax handler for loading available menu items.
*
* Forked from https://github.com/xwp/wordpress-develop/blob/2515aab6739ea0d2f065eea08ae429889a018fb3/src/wp-includes/class-wp-customize-nav-menus.php#L88-L115
*
* @codeCoverageIgnore
*/
public function ajax_load_available_items() {
check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );

if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_die( -1 );
}

if ( empty( $_POST['type'] ) || empty( $_POST['object'] ) ) {
wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' );
}

// Added logic in forked method.
foreach ( $this->manager->settings() as $setting ) {
/**
* Setting.
*
* @var WP_Customize_Setting $setting
*/
$setting->preview();
}

$type = sanitize_key( $_POST['type'] );
$object = sanitize_key( $_POST['object'] );
$page = empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] );
$items = $this->manager->nav_menus->load_available_items_query( $type, $object, $page );

if ( is_wp_error( $items ) ) {
wp_send_json_error( $items->get_error_code() );
} else {
wp_send_json_success( array( 'items' => $items ) );
}
}

/**
* Ajax handler for searching available menu items.
*
* Forked from https://github.com/xwp/wordpress-develop/blob/2515aab6739ea0d2f065eea08ae429889a018fb3/src/wp-includes/class-wp-customize-nav-menus.php#L228-L258
*
* @codeCoverageIgnore
*/
public function ajax_search_available_items() {
check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );

if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_die( -1 );
}

if ( empty( $_POST['search'] ) ) {
wp_send_json_error( 'nav_menus_missing_search_parameter' );
}

$p = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
if ( $p < 1 ) {
$p = 1;
}

// Added logic in forked method.
foreach ( $this->manager->settings() as $setting ) {
/**
* Setting.
*
* @var WP_Customize_Setting $setting
*/
$setting->preview();
}

$s = sanitize_text_field( wp_unslash( $_POST['search'] ) );
$items = $this->manager->nav_menus->search_available_items_query( array( 'pagenum' => $p, 's' => $s ) );

if ( empty( $items ) ) {
wp_send_json_error( array( 'message' => __( 'No results found.', 'default' ) ) );
} else {
wp_send_json_success( array( 'items' => $items ) );
}
}
}
Loading