X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/8d3bb1a5dcfdea9857d3c88c3751f09593e34dc8..ef91a7f4f3c6468973e192335a27ec0e0faca0b5:/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 f9832a69..ca1b72f7 100644 --- a/wp-includes/class-wp-customize-nav-menus.php +++ b/wp-includes/class-wp-customize-nav-menus.php @@ -48,7 +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. + // See https://github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L469-L499 + add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); + add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 ); + add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 ); + + // Skip remaining hooks when the user can't manage nav menus anyway. if ( ! current_user_can( 'edit_theme_options' ) ) { return; } @@ -56,16 +61,13 @@ final class WP_Customize_Nav_Menus { 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( 'wp_ajax_customize-nav-menus-insert-auto-draft', array( $this, 'ajax_insert_auto_draft_post' ) ); add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - - // Needs to run after core Navigation section is set up. - add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); - - add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 ); - add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 ); 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' ) ); + add_action( 'customize_preview_init', array( $this, 'make_auto_draft_status_previewable' ) ); + add_action( 'customize_save_nav_menus_created_posts', array( $this, 'save_nav_menus_created_posts' ) ); // Selective Refresh partials. add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 ); @@ -98,20 +100,35 @@ final class WP_Customize_Nav_Menus { wp_die( -1 ); } - if ( empty( $_POST['type'] ) || empty( $_POST['object'] ) ) { + $all_items = array(); + $item_types = array(); + if ( isset( $_POST['item_types'] ) && is_array( $_POST['item_types'] ) ) { + $item_types = wp_unslash( $_POST['item_types'] ); + } elseif ( isset( $_POST['type'] ) && isset( $_POST['object'] ) ) { // Back compat. + $item_types[] = array( + 'type' => wp_unslash( $_POST['type'] ), + 'object' => wp_unslash( $_POST['object'] ), + 'page' => empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] ), + ); + } else { wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' ); } - $type = sanitize_key( $_POST['type'] ); - $object = sanitize_key( $_POST['object'] ); - $page = empty( $_POST['page'] ) ? 0 : absint( $_POST['page'] ); - $items = $this->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 ) ); + foreach ( $item_types as $item_type ) { + if ( empty( $item_type['type'] ) || empty( $item_type['object'] ) ) { + wp_send_json_error( 'nav_menus_missing_type_or_object_parameter' ); + } + $type = sanitize_key( $item_type['type'] ); + $object = sanitize_key( $item_type['object'] ); + $page = empty( $item_type['page'] ) ? 0 : absint( $item_type['page'] ); + $items = $this->load_available_items_query( $type, $object, $page ); + if ( is_wp_error( $items ) ) { + wp_send_json_error( $items->get_error_code() ); + } + $all_items[ $item_type['type'] . ':' . $item_type['object'] ] = $items; } + + wp_send_json_success( array( 'items' => $all_items ) ); } /** @@ -157,13 +174,25 @@ final class WP_Customize_Nav_Menus { ); } - $posts = get_posts( array( + // Prepend posts with nav_menus_created_posts on first page. + $posts = array(); + if ( 0 === $page && $this->manager->get_setting( 'nav_menus_created_posts' ) ) { + foreach ( $this->manager->get_setting( 'nav_menus_created_posts' )->value() as $post_id ) { + $auto_draft_post = get_post( $post_id ); + if ( $post_type->name === $auto_draft_post->post_type ) { + $posts[] = $auto_draft_post; + } + } + } + + $posts = array_merge( $posts, get_posts( array( 'numberposts' => 10, 'offset' => 10 * $page, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => $object, - ) ); + ) ) ); + foreach ( $posts as $post ) { $post_title = $post->post_title; if ( '' === $post_title ) { @@ -288,27 +317,42 @@ final class WP_Customize_Nav_Menus { $query['s'] = $args['s']; } + $posts = array(); + + // Prepend list of posts with nav_menus_created_posts search results on first page. + $nav_menus_created_posts_setting = $this->manager->get_setting( 'nav_menus_created_posts' ); + if ( 1 === $args['pagenum'] && $nav_menus_created_posts_setting && count( $nav_menus_created_posts_setting ) > 0 ) { + $stub_post_query = new WP_Query( array_merge( + $query, + array( + 'post_status' => 'auto-draft', + 'post__in' => $nav_menus_created_posts_setting->value(), + 'posts_per_page' => -1, + ) + ) ); + $posts = array_merge( $posts, $stub_post_query->posts ); + } + // Query posts. $get_posts = new WP_Query( $query ); - - // Check if any posts were found. - if ( $get_posts->post_count ) { - foreach ( $get_posts->posts as $post ) { - $post_title = $post->post_title; - if ( '' === $post_title ) { - /* translators: %d: ID of a post */ - $post_title = sprintf( __( '#%d (no title)' ), $post->ID ); - } - $items[] = array( - 'id' => 'post-' . $post->ID, - 'title' => html_entity_decode( $post_title, ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'type' => 'post_type', - 'type_label' => $post_type_objects[ $post->post_type ]->labels->singular_name, - 'object' => $post->post_type, - 'object_id' => intval( $post->ID ), - 'url' => get_permalink( intval( $post->ID ) ), - ); + $posts = array_merge( $posts, $get_posts->posts ); + + // Create items for posts. + foreach ( $posts as $post ) { + $post_title = $post->post_title; + if ( '' === $post_title ) { + /* translators: %d: ID of a post */ + $post_title = sprintf( __( '#%d (no title)' ), $post->ID ); } + $items[] = array( + 'id' => 'post-' . $post->ID, + 'title' => html_entity_decode( $post_title, ENT_QUOTES, get_bloginfo( 'charset' ) ), + 'type' => 'post_type', + 'type_label' => $post_type_objects[ $post->post_type ]->labels->singular_name, + 'object' => $post->post_type, + 'object_id' => intval( $post->ID ), + 'url' => get_permalink( intval( $post->ID ) ), + ); } // Query taxonomy terms. @@ -368,6 +412,7 @@ final class WP_Customize_Nav_Menus { 'untitled' => _x( '(no label)', 'missing menu item navigation label' ), 'unnamed' => _x( '(unnamed)', 'Missing menu name.' ), 'custom_label' => __( 'Custom Link' ), + 'page_label' => get_post_type_object( 'page' )->labels->singular_name, /* translators: %s: menu location */ 'menuLocation' => _x( '(Currently set to: %s)', 'menu' ), 'menuNameLabel' => __( 'Menu Name' ), @@ -486,6 +531,24 @@ final class WP_Customize_Nav_Menus { */ public function customize_register() { + /* + * Preview settings for nav menus early so that the sections and controls will be added properly. + * See https://github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L506-L543 + */ + $nav_menus_setting_ids = array(); + foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) { + if ( preg_match( '/^(nav_menu_locations|nav_menu|nav_menu_item)\[/', $setting_id ) ) { + $nav_menus_setting_ids[] = $setting_id; + } + } + $this->manager->add_dynamic_settings( $nav_menus_setting_ids ); + foreach ( $nav_menus_setting_ids as $setting_id ) { + $setting = $this->manager->get_setting( $setting_id ); + if ( $setting ) { + $setting->preview(); + } + } + // Require JS-rendered control types. $this->manager->register_panel_type( 'WP_Customize_Nav_Menus_Panel' ); $this->manager->register_control_type( 'WP_Customize_Nav_Menu_Control' ); @@ -587,6 +650,10 @@ final class WP_Customize_Nav_Menus { $menu_item_setting_id = 'nav_menu_item[' . $item->ID . ']'; $value = (array) $item; + if ( empty( $value['post_title'] ) ) { + $value['title'] = ''; + } + $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, @@ -626,6 +693,13 @@ final class WP_Customize_Nav_Menus { 'section' => 'add_menu', 'settings' => array(), ) ) ); + + $this->manager->add_setting( new WP_Customize_Filter_Setting( $this->manager, 'nav_menus_created_posts', array( + 'transport' => 'postMessage', + 'type' => 'option', // To prevent theme prefix in changeset. + 'default' => array(), + 'sanitize_callback' => array( $this, 'sanitize_nav_menus_created_posts' ), + ) ) ); } /** @@ -648,6 +722,7 @@ final class WP_Customize_Nav_Menus { * Return an array of all the available item types. * * @since 4.3.0 + * @since 4.7.0 Each array item now includes a `$type_label` in in addition to `$title`, `$type`, and `$object`. * @access public * * @return array The available menu item types. @@ -660,7 +735,8 @@ final class WP_Customize_Nav_Menus { foreach ( $post_types as $slug => $post_type ) { $item_types[] = array( 'title' => $post_type->labels->name, - 'type' => 'post_type', + 'type_label' => $post_type->labels->singular_name, + 'type' => 'post_type', 'object' => $post_type->name, ); } @@ -673,8 +749,9 @@ final class WP_Customize_Nav_Menus { continue; } $item_types[] = array( - 'title' => $taxonomy->labels->name, - 'type' => 'taxonomy', + 'title' => $taxonomy->labels->name, + 'type_label' => $taxonomy->labels->singular_name, + 'type' => 'taxonomy', 'object' => $taxonomy->name, ); } @@ -684,6 +761,7 @@ final class WP_Customize_Nav_Menus { * Filters the available menu item types. * * @since 4.3.0 + * @since 4.7.0 Each array item now includes a `$type_label` in in addition to `$title`, `$type`, and `$object`. * * @param array $item_types Custom menu item types. */ @@ -692,6 +770,130 @@ final class WP_Customize_Nav_Menus { return $item_types; } + /** + * Add a new `auto-draft` post. + * + * @access public + * @since 4.7.0 + * + * @param array $postarr { + * Post array. Note that post_status is overridden to be `auto-draft`. + * + * @var string $post_title Post title. Required. + * @var string $post_type Post type. Required. + * @var string $post_name Post name. + * @var string $post_content Post content. + * } + * @return WP_Post|WP_Error Inserted auto-draft post object or error. + */ + public function insert_auto_draft_post( $postarr ) { + if ( ! isset( $postarr['post_type'] ) || ! post_type_exists( $postarr['post_type'] ) ) { + return new WP_Error( 'unknown_post_type', __( 'Unknown post type' ) ); + } + if ( empty( $postarr['post_title'] ) ) { + return new WP_Error( 'empty_title', __( 'Empty title' ) ); + } + if ( ! empty( $postarr['post_status'] ) ) { + return new WP_Error( 'status_forbidden', __( 'Status is forbidden' ) ); + } + + $postarr['post_status'] = 'auto-draft'; + + // Auto-drafts are allowed to have empty post_names, so it has to be explicitly set. + if ( empty( $postarr['post_name'] ) ) { + $postarr['post_name'] = sanitize_title( $postarr['post_title'] ); + } + if ( ! isset( $postarr['meta_input'] ) ) { + $postarr['meta_input'] = array(); + } + $postarr['meta_input']['_customize_draft_post_name'] = $postarr['post_name']; + unset( $postarr['post_name'] ); + + add_filter( 'wp_insert_post_empty_content', '__return_false', 1000 ); + $r = wp_insert_post( wp_slash( $postarr ), true ); + remove_filter( 'wp_insert_post_empty_content', '__return_false', 1000 ); + + if ( is_wp_error( $r ) ) { + return $r; + } else { + return get_post( $r ); + } + } + + /** + * Ajax handler for adding a new auto-draft post. + * + * @access public + * @since 4.7.0 + */ + public function ajax_insert_auto_draft_post() { + if ( ! check_ajax_referer( 'customize-menus', 'customize-menus-nonce', false ) ) { + wp_send_json_error( 'bad_nonce', 400 ); + } + + if ( ! current_user_can( 'customize' ) ) { + wp_send_json_error( 'customize_not_allowed', 403 ); + } + + if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) { + wp_send_json_error( 'missing_params', 400 ); + } + + $params = wp_unslash( $_POST['params'] ); + $illegal_params = array_diff( array_keys( $params ), array( 'post_type', 'post_title' ) ); + if ( ! empty( $illegal_params ) ) { + wp_send_json_error( 'illegal_params', 400 ); + } + + $params = array_merge( + array( + 'post_type' => '', + 'post_title' => '', + ), + $params + ); + + if ( empty( $params['post_type'] ) || ! post_type_exists( $params['post_type'] ) ) { + status_header( 400 ); + wp_send_json_error( 'missing_post_type_param' ); + } + + $post_type_object = get_post_type_object( $params['post_type'] ); + if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) { + status_header( 403 ); + wp_send_json_error( 'insufficient_post_permissions' ); + } + + $params['post_title'] = trim( $params['post_title'] ); + if ( '' === $params['post_title'] ) { + status_header( 400 ); + wp_send_json_error( 'missing_post_title' ); + } + + $r = $this->insert_auto_draft_post( $params ); + if ( is_wp_error( $r ) ) { + $error = $r; + if ( ! empty( $post_type_object->labels->singular_name ) ) { + $singular_name = $post_type_object->labels->singular_name; + } else { + $singular_name = __( 'Post' ); + } + + $data = array( + /* translators: %1$s is the post type name and %2$s is the error message. */ + 'message' => sprintf( __( '%1$s could not be created: %2$s' ), $singular_name, $error->get_error_message() ), + ); + wp_send_json_error( $data ); + } else { + $post = $r; + $data = array( + 'post_id' => $post->ID, + 'url' => get_permalink( $post->ID ), + ); + wp_send_json_success( $data ); + } + } + /** * Print the JavaScript templates used to render Menu Customizer components. * @@ -767,61 +969,116 @@ final class WP_Customize_Nav_Menus { + - - -
- -
- - - -

- - - - -

-
+
available_item_types() as $available_item_type ) { - $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] ); - ?> -
- - -
- available_item_types(); + $page_item_type = null; + foreach ( $item_types as $i => $item_type ) { + if ( isset( $item_type['object'] ) && 'page' === $item_type['object'] ) { + $page_item_type = $item_type; + unset( $item_types[ $i ] ); + } + } + + $this->print_custom_links_available_menu_item(); + if ( $page_item_type ) { + $this->print_post_type_container( $page_item_type ); + } + // Containers for per-post-type item browsing; items are added with JS. + foreach ( $item_types as $item_type ) { + $this->print_post_type_container( $item_type ); } ?> +
+ +
+ + + cap->create_posts ) && current_user_can( $post_type_obj->cap->publish_posts ) ) : ?> +
+ + +
+ + + +
+
+ +
+ +
+ + + +

+ + + + +

+
+
+ protected = true; + } + + /** + * Sanitize post IDs for auto-draft posts created for nav menu items to be published. + * + * @since 4.7.0 + * @access public + * + * @param array $value Post IDs. + * @returns array Post IDs. + */ + public function sanitize_nav_menus_created_posts( $value ) { + $post_ids = array(); + foreach ( wp_parse_id_list( $value ) as $post_id ) { + if ( empty( $post_id ) ) { + continue; + } + $post = get_post( $post_id ); + if ( 'auto-draft' !== $post->post_status ) { + continue; + } + $post_type_obj = get_post_type_object( $post->post_type ); + if ( ! $post_type_obj ) { + continue; + } + if ( ! current_user_can( $post_type_obj->cap->publish_posts ) || ! current_user_can( $post_type_obj->cap->edit_post, $post_id ) ) { + continue; + } + $post_ids[] = $post->ID; + } + return $post_ids; + } + + /** + * Publish the auto-draft posts that were created for nav menu items. + * + * The post IDs will have been sanitized by already by + * `WP_Customize_Nav_Menu_Items::sanitize_nav_menus_created_posts()` to + * remove any post IDs for which the user cannot publish or for which the + * post is not an auto-draft. + * + * @since 4.7.0 + * @access public + * + * @param WP_Customize_Setting $setting Customizer setting object. + */ + public function save_nav_menus_created_posts( $setting ) { + $post_ids = $setting->post_value(); + if ( ! empty( $post_ids ) ) { + foreach ( $post_ids as $post_id ) { + $target_status = 'attachment' === get_post_type( $post_id ) ? 'inherit' : 'publish'; + $args = array( + 'ID' => $post_id, + 'post_status' => $target_status, + ); + $post_name = get_post_meta( $post_id, '_customize_draft_post_name', true ); + if ( $post_name ) { + $args['post_name'] = $post_name; + } + + // Note that wp_publish_post() cannot be used because unique slugs need to be assigned. + wp_update_post( wp_slash( $args ) ); + + delete_post_meta( $post_id, '_customize_draft_post_name' ); + } + } + } + /** * Keep track of the arguments that are being passed to wp_nav_menu(). *