Make WordPress Core


Ignore:
Timestamp:
08/29/2016 10:58:32 PM (10 years ago)
Author:
westonruter
Message:

Customize: Allow users to more seamlessly create page-based nav menus during customization.

Introduces the ability to create stubs for the various post types to add to a given menu. This eliminates the need to leave the customizer to first create the post in the admin and then return to managing menus. Only the title of the newly-created post can be supplied; the post content will be blank and will need to be provided in the normal edit post screen outside the customizer, unless a plugin enables a post editing in the customizer experience. When a post is created and added to a nav menu in the customizer, the newly created post that is added to a menu is given the auto-draft status, and if the changes are not published, the auto-draft post will be automatically deleted within 7 days via wp_delete_auto_drafts(). However, if the customizer changes are saved, then these nav menu item auto-draft post stubs will be transitioned to publish.

Includes portions of code from the Customize Posts <https://github.com/xwp/wp-customize-posts> and Front-end Editor <https://github.com/iseulde/wp-front-end-editor> plugins.

For more information, see https://make.wordpress.org/core/2016/06/16/feature-proposal-content-authorship-in-menus-with-live-preview/

Props celloexpressions, westonruter, valendesigns, afercia, melchoyce, mapk, iseulde, mrahmadawais.
Fixes #34923.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-customize-nav-menus.php

    r37900 r38436  
    5757                add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) );
    5858                add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) );
     59                add_action( 'wp_ajax_customize-nav-menus-insert-auto-draft', array( $this, 'ajax_insert_auto_draft_post' ) );
    5960                add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
    60 
    61                 // Needs to run after core Navigation section is set up.
    6261                add_action( 'customize_register', array( $this, 'customize_register' ), 11 );
    63 
    6462                add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 );
    6563                add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 );
     
    6765                add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
    6866                add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
     67                add_action( 'customize_preview_init', array( $this, 'make_auto_draft_status_previewable' ) );
     68                add_action( 'customize_save_nav_menus_created_posts', array( $this, 'save_nav_menus_created_posts' ) );
    6969
    7070                // Selective Refresh partials.
     
    627627                        'settings' => array(),
    628628                ) ) );
     629
     630                $this->manager->add_setting( new WP_Customize_Filter_Setting( $this->manager, 'nav_menus_created_posts', array(
     631                        'transport' => 'postMessage',
     632                        'default' => array(),
     633                        'sanitize_callback' => array( $this, 'sanitize_nav_menus_created_posts' ),
     634                ) ) );
    629635        }
    630636
     
    649655         *
    650656         * @since 4.3.0
     657         * @since 4.7.0  Each array item now includes a `$type_label` in in addition to `$title`, `$type`, and `$object`.
    651658         * @access public
    652659         *
     
    661668                                $item_types[] = array(
    662669                                        'title'  => $post_type->labels->name,
    663                                         'type'   => 'post_type',
     670                                        'type_label' => $post_type->labels->singular_name,
     671                                        'type' => 'post_type',
    664672                                        'object' => $post_type->name,
    665673                                );
     
    674682                                }
    675683                                $item_types[] = array(
    676                                         'title'  => $taxonomy->labels->name,
    677                                         'type'   => 'taxonomy',
     684                                        'title' => $taxonomy->labels->name,
     685                                        'type_label' => $taxonomy->labels->singular_name,
     686                                        'type' => 'taxonomy',
    678687                                        'object' => $taxonomy->name,
    679688                                );
     
    685694                 *
    686695                 * @since 4.3.0
     696                 * @since 4.7.0  Each array item now includes a `$type_label` in in addition to `$title`, `$type`, and `$object`.
    687697                 *
    688698                 * @param array $item_types Custom menu item types.
     
    691701
    692702                return $item_types;
     703        }
     704
     705        /**
     706         * Add a new `auto-draft` post.
     707         *
     708         * @access public
     709         * @since 4.7.0
     710         *
     711         * @param array $postarr {
     712         *     Abbreviated post array.
     713         *
     714         *     @var string $post_title Post title.
     715         *     @var string $post_type  Post type.
     716         * }
     717         * @return WP_Post|WP_Error Inserted auto-draft post object or error.
     718         */
     719        public function insert_auto_draft_post( $postarr ) {
     720                if ( ! isset( $postarr['post_type'] ) || ! post_type_exists( $postarr['post_type'] )  ) {
     721                        return new WP_Error( 'unknown_post_type', __( 'Unknown post type' ) );
     722                }
     723                if ( ! isset( $postarr['post_title'] ) ) {
     724                        $postarr['post_title'] = '';
     725                }
     726
     727                add_filter( 'wp_insert_post_empty_content', '__return_false', 1000 );
     728                $args = array(
     729                        'post_status' => 'auto-draft',
     730                        'post_type'   => $postarr['post_type'],
     731                        'post_title'  => $postarr['post_title'],
     732                        'post_name'   => sanitize_title( $postarr['post_title'] ), // Auto-drafts are allowed to have empty post_names, so we need to explicitly set it.
     733                );
     734                $r = wp_insert_post( wp_slash( $args ), true );
     735                remove_filter( 'wp_insert_post_empty_content', '__return_false', 1000 );
     736
     737                if ( is_wp_error( $r ) ) {
     738                        return $r;
     739                } else {
     740                        return get_post( $r );
     741                }
     742        }
     743
     744        /**
     745         * Ajax handler for adding a new auto-draft post.
     746         *
     747         * @access public
     748         * @since 4.7.0
     749         */
     750        public function ajax_insert_auto_draft_post() {
     751                if ( ! check_ajax_referer( 'customize-menus', 'customize-menus-nonce', false ) ) {
     752                        status_header( 400 );
     753                        wp_send_json_error( 'bad_nonce' );
     754                }
     755
     756                if ( ! current_user_can( 'customize' ) ) {
     757                        status_header( 403 );
     758                        wp_send_json_error( 'customize_not_allowed' );
     759                }
     760
     761                if ( empty( $_POST['params'] ) || ! is_array( $_POST['params'] ) ) {
     762                        status_header( 400 );
     763                        wp_send_json_error( 'missing_params' );
     764                }
     765
     766                $params = wp_array_slice_assoc(
     767                        array_merge(
     768                                array(
     769                                        'post_type' => '',
     770                                        'post_title' => '',
     771                                ),
     772                                wp_unslash( $_POST['params'] )
     773                        ),
     774                        array( 'post_type', 'post_title' )
     775                );
     776
     777                if ( empty( $params['post_type'] ) || ! post_type_exists( $params['post_type'] ) ) {
     778                        status_header( 400 );
     779                        wp_send_json_error( 'missing_post_type_param' );
     780                }
     781
     782                $post_type_object = get_post_type_object( $params['post_type'] );
     783                if ( ! current_user_can( $post_type_object->cap->create_posts ) || ! current_user_can( $post_type_object->cap->publish_posts ) ) {
     784                        status_header( 403 );
     785                        wp_send_json_error( 'insufficient_post_permissions' );
     786                }
     787
     788                $params['post_title'] = trim( $params['post_title'] );
     789                if ( '' === $params['post_title'] ) {
     790                        status_header( 400 );
     791                        wp_send_json_error( 'missing_post_title' );
     792                }
     793
     794                $r = $this->insert_auto_draft_post( $params );
     795                if ( is_wp_error( $r ) ) {
     796                        $error = $r;
     797                        if ( ! empty( $post_type_object->labels->singular_name ) ) {
     798                                $singular_name = $post_type_object->labels->singular_name;
     799                        } else {
     800                                $singular_name = __( 'Post' );
     801                        }
     802
     803                        $data = array(
     804                                /* translators: %1$s is the post type name and %2$s is the error message. */
     805                                'message' => sprintf( __( '%1$s could not be created: %2$s' ), $singular_name, $error->get_error_message() ),
     806                        );
     807                        wp_send_json_error( $data );
     808                } else {
     809                        $post = $r;
     810                        $data = array(
     811                                'post_id' => $post->ID,
     812                                'url'     => get_permalink( $post->ID ),
     813                        );
     814                        wp_send_json_success( $data );
     815                }
    693816        }
    694817
     
    769892                                </div>
    770893                                <button type="button" class="clear-results"><span class="screen-reader-text"><?php _e( 'Clear Results' ); ?></span></button>
    771                                 <ul class="accordion-section-content" data-type="search"></ul>
     894                                <ul class="accordion-section-content available-menu-items-list" data-type="search"></ul>
    772895                        </div>
    773896                        <div id="new-custom-menu-item" class="accordion-section">
     
    798921                        </div>
    799922                        <?php
    800                         // Containers for per-post-type item browsing; items added with JS.
     923
     924                        // Containers for per-post-type item browsing; items are added with JS.
    801925                        foreach ( $this->available_item_types() as $available_item_type ) {
    802926                                $id = sprintf( 'available-menu-items-%s-%s', $available_item_type['type'], $available_item_type['object'] );
     
    814938                                                </button>
    815939                                        </h4>
    816                                         <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>
     940                                        <div class="accordion-section-content">
     941                                                <?php if ( 'post_type' === $available_item_type['type'] ) : ?>
     942                                                        <?php $post_type_obj = get_post_type_object( $available_item_type['object'] ); ?>
     943                                                        <?php if ( current_user_can( $post_type_obj->cap->create_posts ) && current_user_can( $post_type_obj->cap->publish_posts ) ) : ?>
     944                                                                <div class="new-content-item">
     945                                                                        <input type="text" class="create-item-input" placeholder="<?php
     946                                                                        /* translators: %s: Singular title of post type or taxonomy */
     947                                                                        printf( __( 'Create New %s' ), $post_type_obj->labels->singular_name ); ?>">
     948                                                                        <button type="button" class="button add-content"><?php _e( 'Add' ); ?></button>
     949                                                                </div>
     950                                                        <?php endif; ?>
     951                                                <?php endif; ?>
     952                                                <ul class="available-menu-items-list" data-type="<?php echo esc_attr( $available_item_type['type'] ); ?>" data-object="<?php echo esc_attr( $available_item_type['object'] ); ?>" data-type_label="<?php echo esc_attr( isset( $available_item_type['type_label'] ) ? $available_item_type['type_label'] : $available_item_type['type'] ); ?>"></ul>
     953                                        </div>
    817954                                </div>
    818955                                <?php
     
    8791016                add_filter( 'wp_footer', array( $this, 'export_preview_data' ), 1 );
    8801017                add_filter( 'customize_render_partials_response', array( $this, 'export_partial_rendered_nav_menu_instances' ) );
     1018        }
     1019
     1020        /**
     1021         * Make the auto-draft status protected so that it can be queried.
     1022         *
     1023         * @since 4.7.0
     1024         * @access public
     1025         */
     1026        public function make_auto_draft_status_previewable() {
     1027                global $wp_post_statuses;
     1028                $wp_post_statuses['auto-draft']->protected = true;
     1029        }
     1030
     1031        /**
     1032         * Sanitize post IDs for auto-draft posts created for nav menu items to be published.
     1033         *
     1034         * @since 4.7.0
     1035         * @access public
     1036         *
     1037         * @param array $value Post IDs.
     1038         * @returns array Post IDs.
     1039         */
     1040        public function sanitize_nav_menus_created_posts( $value ) {
     1041                $post_ids = array();
     1042                foreach ( wp_parse_id_list( $value ) as $post_id ) {
     1043                        if ( empty( $post_id ) ) {
     1044                                continue;
     1045                        }
     1046                        $post = get_post( $post_id );
     1047                        if ( 'auto-draft' !== $post->post_status ) {
     1048                                continue;
     1049                        }
     1050                        $post_type_obj = get_post_type_object( $post->post_type );
     1051                        if ( ! $post_type_obj ) {
     1052                                continue;
     1053                        }
     1054                        if ( ! current_user_can( $post_type_obj->cap->publish_posts ) || ! current_user_can( $post_type_obj->cap->edit_post, $post_id ) ) {
     1055                                continue;
     1056                        }
     1057                        $post_ids[] = $post->ID;
     1058                }
     1059                return $post_ids;
     1060        }
     1061
     1062        /**
     1063         * Publish the auto-draft posts that were created for nav menu items.
     1064         *
     1065         * The post IDs will have been sanitized by already by
     1066         * `WP_Customize_Nav_Menu_Items::sanitize_nav_menus_created_posts()` to
     1067         * remove any post IDs for which the user cannot publish or for which the
     1068         * post is not an auto-draft.
     1069         *
     1070         * @since 4.7.0
     1071         * @access public
     1072         *
     1073         * @param WP_Customize_Setting $setting Customizer setting object.
     1074         */
     1075        public function save_nav_menus_created_posts( $setting ) {
     1076                $post_ids = $setting->post_value();
     1077                if ( ! empty( $post_ids ) ) {
     1078                        foreach ( $post_ids as $post_id ) {
     1079                                wp_publish_post( $post_id );
     1080                        }
     1081                }
    8811082        }
    8821083
Note: See TracChangeset for help on using the changeset viewer.