Make WordPress Core


Ignore:
Timestamp:
08/29/2016 10:58:32 PM (9 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.