X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/0459461f9ea42e0b090759ff6fe5f48360bef750..refs/tags/wordpress-4.5:/wp-includes/class-wp-customize-nav-menus.php diff --git a/wp-includes/class-wp-customize-nav-menus.php b/wp-includes/class-wp-customize-nav-menus.php index f6be2094..77845672 100644 --- a/wp-includes/class-wp-customize-nav-menus.php +++ b/wp-includes/class-wp-customize-nav-menus.php @@ -48,6 +48,12 @@ final class WP_Customize_Nav_Menus { $this->previewed_menus = array(); $this->manager = $manager; + // Skip useless hooks when the user can't manage nav menus anyway. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return; + } + + add_filter( 'customize_refresh_nonces', array( $this, 'filter_nonces' ) ); add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) ); add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) ); add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); @@ -60,6 +66,23 @@ final class WP_Customize_Nav_Menus { add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) ); add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) ); + + // Selective Refresh partials. + add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 ); + } + + /** + * Adds a nonce for customizing menus. + * + * @since 4.5.0 + * @access public + * + * @param array $nonces Array of nonces. + * @return array $nonces Modified array of nonces. + */ + public function filter_nonces( $nonces ) { + $nonces['customize-menus'] = wp_create_nonce( 'customize-menus' ); + return $nonces; } /** @@ -311,6 +334,16 @@ final class WP_Customize_Nav_Menus { } } + /** + * Filter the available menu items during a search request. + * + * @since 4.5.0 + * + * @param array $items The array of menu items. + * @param array $args Includes 'pagenum' and 's' (search) arguments. + */ + $items = apply_filters( 'customize_nav_menu_searched_items', $items, $args ); + return $items; } @@ -329,14 +362,13 @@ final class WP_Customize_Nav_Menus { // Pass data to JS. $settings = array( - 'nonce' => wp_create_nonce( 'customize-menus' ), 'allMenus' => wp_get_nav_menus(), 'itemTypes' => $this->available_item_types(), 'l10n' => array( 'untitled' => _x( '(no label)', 'missing menu item navigation label' ), 'unnamed' => _x( '(unnamed)', 'Missing menu name.' ), 'custom_label' => __( 'Custom Link' ), - /* translators: %s: menu location slug */ + /* translators: %s: menu location */ 'menuLocation' => _x( '(Currently set to: %s)', 'menu' ), 'menuNameLabel' => __( 'Menu Name' ), 'itemAdded' => __( 'Menu item added' ), @@ -361,12 +393,13 @@ final class WP_Customize_Nav_Menus { 'reorderLabelOn' => esc_attr__( 'Reorder menu items' ), 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), ), - 'menuItemTransport' => 'postMessage', + 'settingTransport' => 'postMessage', 'phpIntMax' => PHP_INT_MAX, 'defaultSettingValues' => array( 'nav_menu' => $temp_nav_menu_setting->default, 'nav_menu_item' => $temp_nav_menu_item_setting->default, ), + 'locationSlugMappedToName' => get_registered_nav_menus(), ); $data = sprintf( 'var _wpCustomizeNavMenusSettings = %s;', wp_json_encode( $settings ) ); @@ -411,11 +444,13 @@ final class WP_Customize_Nav_Menus { public function filter_dynamic_setting_args( $setting_args, $setting_id ) { if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) { $setting_args = array( - 'type' => WP_Customize_Nav_Menu_Setting::TYPE, + 'type' => WP_Customize_Nav_Menu_Setting::TYPE, + 'transport' => 'postMessage', ); } elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) { $setting_args = array( - 'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE, + 'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE, + 'transport' => 'postMessage', ); } return $setting_args; @@ -534,7 +569,9 @@ final class WP_Customize_Nav_Menus { ) ) ); $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']'; - $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id ) ); + $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id, array( + 'transport' => 'postMessage', + ) ) ); // Add the menu contents. $menu_items = (array) wp_get_nav_menu_items( $menu_id ); @@ -547,7 +584,8 @@ final class WP_Customize_Nav_Menus { $value = (array) $item; $value['nav_menu_term_id'] = $menu_id; $this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id, array( - 'value' => $value, + 'value' => $value, + 'transport' => 'postMessage', ) ) ); // Create a control for each menu item. @@ -568,28 +606,20 @@ final class WP_Customize_Nav_Menus { 'priority' => 999, ) ) ); - $this->manager->add_setting( 'new_menu_name', array( - 'type' => 'new_menu', - 'default' => '', - 'transport' => 'postMessage', - ) ); - $this->manager->add_control( 'new_menu_name', array( 'label' => '', 'section' => 'add_menu', 'type' => 'text', + 'settings' => array(), 'input_attrs' => array( 'class' => 'menu-name-field', 'placeholder' => __( 'New menu name' ), ), ) ); - $this->manager->add_setting( 'create_new_menu', array( - 'type' => 'new_menu', - ) ); - $this->manager->add_control( new WP_Customize_New_Menu_Control( $this->manager, 'create_new_menu', array( - 'section' => 'add_menu', + 'section' => 'add_menu', + 'settings' => array(), ) ) ); } @@ -743,19 +773,15 @@ final class WP_Customize_Nav_Menus { -
+
- -

@@ -791,28 +817,49 @@ final class WP_Customize_Nav_Menus { 'nav_menu_instance', + 'render_callback' => array( $this, 'render_nav_menu_partial' ), + 'container_inclusive' => true, + 'settings' => array(), // Empty because the nav menu instance may relate to a menu or a location. + 'capability' => 'edit_theme_options', + ) + ); + } + + return $partial_args; + } /** * Add hooks for the Customizer preview. @@ -821,13 +868,11 @@ final class WP_Customize_Nav_Menus { * @access public */ public function customize_preview_init() { - add_action( 'template_redirect', array( $this, 'render_menu' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) ); - - if ( ! isset( $_REQUEST[ self::RENDER_QUERY_VAR ] ) ) { - add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 ); - add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 ); - } + add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 ); + add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 ); + add_filter( 'wp_footer', array( $this, 'export_preview_data' ), 1 ); + add_filter( 'customize_render_partials_response', array( $this, 'export_partial_rendered_nav_menu_instances' ) ); } /** @@ -835,52 +880,71 @@ final class WP_Customize_Nav_Menus { * * @since 4.3.0 * @access public - * * @see wp_nav_menu() + * @see WP_Customize_Widgets_Partial_Refresh::filter_dynamic_sidebar_params() * * @param array $args An array containing wp_nav_menu() arguments. * @return array Arguments. */ public function filter_wp_nav_menu_args( $args ) { - $this->preview_nav_menu_instance_number += 1; - $args['instance_number'] = $this->preview_nav_menu_instance_number; - + /* + * The following conditions determine whether or not this instance of + * wp_nav_menu() can use selective refreshed. A wp_nav_menu() can be + * selective refreshed if... + */ $can_partial_refresh = ( + // ...if wp_nav_menu() is directly echoing out the menu (and thus isn't manipulating the string after generated), ! empty( $args['echo'] ) && + // ...and if the fallback_cb can be serialized to JSON, since it will be included in the placement context data, ( empty( $args['fallback_cb'] ) || is_string( $args['fallback_cb'] ) ) && + // ...and if the walker can also be serialized to JSON, since it will be included in the placement context data as well, ( empty( $args['walker'] ) || is_string( $args['walker'] ) ) - && - ( + // ...and if it has a theme location assigned or an assigned menu to display, + && ( ! empty( $args['theme_location'] ) || ( ! empty( $args['menu'] ) && ( is_numeric( $args['menu'] ) || is_object( $args['menu'] ) ) ) ) + && + // ...and if the nav menu would be rendered with a wrapper container element (upon which to attach data-* attributes). + ( + ! empty( $args['container'] ) + || + ( isset( $args['items_wrap'] ) && '<' === substr( $args['items_wrap'], 0, 1 ) ) + ) ); $args['can_partial_refresh'] = $can_partial_refresh; - $hashed_args = $args; + $exported_args = $args; + // Empty out args which may not be JSON-serializable. if ( ! $can_partial_refresh ) { - $hashed_args['fallback_cb'] = ''; - $hashed_args['walker'] = ''; + $exported_args['fallback_cb'] = ''; + $exported_args['walker'] = ''; } - // Replace object menu arg with a term_id menu arg, as this exports better to JS and is easier to compare hashes. - if ( ! empty( $hashed_args['menu'] ) && is_object( $hashed_args['menu'] ) ) { - $hashed_args['menu'] = $hashed_args['menu']->term_id; + /* + * Replace object menu arg with a term_id menu arg, as this exports better + * to JS and is easier to compare hashes. + */ + if ( ! empty( $exported_args['menu'] ) && is_object( $exported_args['menu'] ) ) { + $exported_args['menu'] = $exported_args['menu']->term_id; } - ksort( $hashed_args ); - $hashed_args['args_hash'] = $this->hash_nav_menu_args( $hashed_args ); + ksort( $exported_args ); + $exported_args['args_hmac'] = $this->hash_nav_menu_args( $exported_args ); - $this->preview_nav_menu_instance_args[ $this->preview_nav_menu_instance_number ] = $hashed_args; + $args['customize_preview_nav_menus_args'] = $exported_args; + $this->preview_nav_menu_instance_args[ $exported_args['args_hmac'] ] = $exported_args; return $args; } /** - * Prepare wp_nav_menu() calls for partial refresh. Wraps output in container for refreshing. + * Prepares wp_nav_menu() calls for partial refresh. + * + * Injects attributes into container element. * * @since 4.3.0 * @access public @@ -892,29 +956,29 @@ final class WP_Customize_Nav_Menus { * @return null */ public function filter_wp_nav_menu( $nav_menu_content, $args ) { - if ( ! empty( $args->can_partial_refresh ) && ! empty( $args->instance_number ) ) { - $nav_menu_content = preg_replace( - '/(?<=class=")/', - sprintf( 'partial-refreshable-nav-menu partial-refreshable-nav-menu-%1$d ', $args->instance_number ), - $nav_menu_content, - 1 // Only update the class on the first element found, the menu container. - ); + if ( isset( $args->customize_preview_nav_menus_args['can_partial_refresh'] ) && $args->customize_preview_nav_menus_args['can_partial_refresh'] ) { + $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'nav_menu_instance[' . $args->customize_preview_nav_menus_args['args_hmac'] . ']' ) ); + $attributes .= ' data-customize-partial-type="nav_menu_instance"'; + $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $args->customize_preview_nav_menus_args ) ) ); + $nav_menu_content = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $nav_menu_content, 1 ); } return $nav_menu_content; } /** - * Hash (hmac) the arguments with the nonce and secret auth key to ensure they - * are not tampered with when submitted in the Ajax request. + * Hashes (hmac) the nav menu arguments to ensure they are not tampered with when + * submitted in the Ajax request. + * + * Note that the array is expected to be pre-sorted. * * @since 4.3.0 * @access public * * @param array $args The arguments to hash. - * @return string + * @return string Hashed nav menu arguments. */ public function hash_nav_menu_args( $args ) { - return wp_hash( wp_create_nonce( self::RENDER_AJAX_ACTION ) . serialize( $args ) ); + return wp_hash( serialize( $args ) ); } /** @@ -924,14 +988,12 @@ final class WP_Customize_Nav_Menus { * @access public */ public function customize_preview_enqueue_deps() { - wp_enqueue_script( 'customize-preview-nav-menus' ); + wp_enqueue_script( 'customize-preview-nav-menus' ); // Note that we have overridden this. wp_enqueue_style( 'customize-preview' ); - - add_action( 'wp_print_footer_scripts', array( $this, 'export_preview_data' ) ); } /** - * Export data from PHP to JS. + * Exports data from PHP to JS. * * @since 4.3.0 * @access public @@ -940,21 +1002,25 @@ final class WP_Customize_Nav_Menus { // Why not wp_localize_script? Because we're not localizing, and it forces values into strings. $exports = array( - 'renderQueryVar' => self::RENDER_QUERY_VAR, - 'renderNonceValue' => wp_create_nonce( self::RENDER_AJAX_ACTION ), - 'renderNoncePostKey' => self::RENDER_NONCE_POST_KEY, - 'requestUri' => empty( $_SERVER['REQUEST_URI'] ) ? home_url( '/' ) : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ), - 'theme' => array( - 'stylesheet' => $this->manager->get_stylesheet(), - 'active' => $this->manager->is_theme_active(), - ), - 'previewCustomizeNonce' => wp_create_nonce( 'preview-customize_' . $this->manager->get_stylesheet() ), - 'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args, + 'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args, ); - printf( '', wp_json_encode( $exports ) ); } + /** + * Export any wp_nav_menu() calls during the rendering of any partials. + * + * @since 4.5.0 + * @access public + * + * @param array $response Response. + * @return array Response. + */ + public function export_partial_rendered_nav_menu_instances( $response ) { + $response['nav_menu_instance_args'] = $this->preview_nav_menu_instance_args; + return $response; + } + /** * Render a specific menu via wp_nav_menu() using the supplied arguments. * @@ -962,49 +1028,32 @@ final class WP_Customize_Nav_Menus { * @access public * * @see wp_nav_menu() + * + * @param WP_Customize_Partial $partial Partial. + * @param array $nav_menu_args Nav menu args supplied as container context. + * @return string|false */ - public function render_menu() { - if ( empty( $_POST[ self::RENDER_QUERY_VAR ] ) ) { - return; - } - - $this->manager->remove_preview_signature(); - - if ( empty( $_POST[ self::RENDER_NONCE_POST_KEY ] ) ) { - wp_send_json_error( 'missing_nonce_param' ); - } + public function render_nav_menu_partial( $partial, $nav_menu_args ) { + unset( $partial ); - if ( ! is_customize_preview() ) { - wp_send_json_error( 'expected_customize_preview' ); + if ( ! isset( $nav_menu_args['args_hmac'] ) ) { + // Error: missing_args_hmac. + return false; } - if ( ! check_ajax_referer( self::RENDER_AJAX_ACTION, self::RENDER_NONCE_POST_KEY, false ) ) { - wp_send_json_error( 'nonce_check_fail' ); - } + $nav_menu_args_hmac = $nav_menu_args['args_hmac']; + unset( $nav_menu_args['args_hmac'] ); - if ( ! current_user_can( 'edit_theme_options' ) ) { - wp_send_json_error( 'unauthorized' ); + ksort( $nav_menu_args ); + if ( ! hash_equals( $this->hash_nav_menu_args( $nav_menu_args ), $nav_menu_args_hmac ) ) { + // Error: args_hmac_mismatch. + return false; } - if ( ! isset( $_POST['wp_nav_menu_args'] ) ) { - wp_send_json_error( 'missing_param' ); - } - - if ( ! isset( $_POST['wp_nav_menu_args_hash'] ) ) { - wp_send_json_error( 'missing_param' ); - } - - $wp_nav_menu_args = json_decode( wp_unslash( $_POST['wp_nav_menu_args'] ), true ); - if ( ! is_array( $wp_nav_menu_args ) ) { - wp_send_json_error( 'wp_nav_menu_args_not_array' ); - } - - $wp_nav_menu_args_hash = sanitize_text_field( wp_unslash( $_POST['wp_nav_menu_args_hash'] ) ); - if ( ! hash_equals( $this->hash_nav_menu_args( $wp_nav_menu_args ), $wp_nav_menu_args_hash ) ) { - wp_send_json_error( 'wp_nav_menu_args_hash_mismatch' ); - } + ob_start(); + wp_nav_menu( $nav_menu_args ); + $content = ob_get_clean(); - $wp_nav_menu_args['echo'] = false; - wp_send_json_success( wp_nav_menu( $wp_nav_menu_args ) ); + return $content; } }