WordPress.org

Make WordPress Core

Changeset 38436


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

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r38152 r38436  
    12201220
    12211221.add-new-widget:before,
    1222 .add-new-menu-item:before {
     1222.add-new-menu-item:before,
     1223#available-menu-items .new-content-item .add-content:before {
    12231224    content: "\f132";
    12241225    display: inline-block;
  • trunk/src/wp-admin/css/customize-nav-menus.css

    r38207 r38436  
    6060    float: right;
    6161    text-align: right;
     62}
     63
     64.customize-control-nav_menu_item.has-notifications .menu-item-handle {
     65    border-left: 4px solid #00a0d2;
    6266}
    6367
     
    498502}
    499503
    500 #available-menu-items .accordion-section-content {
     504#available-menu-items .available-menu-items-list {
    501505    overflow-y: auto;
    502506    max-height: 200px; /* This gets set in JS to fit the screen size, and based on # of sections. */
     
    535539
    536540#available-menu-items .accordion-section-content {
     541    max-height: 290px;
     542    margin: 0;
     543    padding: 0;
     544    position: relative;
     545    background: transparent;
     546}
     547
     548#available-menu-items .accordion-section-content .available-menu-items-list {
     549    margin: 0 0 45px 0;
    537550    padding: 1px 15px 15px 15px;
    538     margin: 0;
    539     max-height: 290px;
     551}
     552
     553#available-menu-items .accordion-section-content .available-menu-items-list:only-child { /* Types that do not support new items for the current user */
     554    margin-bottom: 0;
     555}
     556
     557#new-custom-menu-item .accordion-section-content {
     558    padding: 0 15px 15px 15px;
     559}
     560
     561#available-menu-items .accordion-section-content .new-content-item {
     562    width: calc(100% - 30px);
     563    padding: 8px 15px;
     564    position: absolute;
     565    bottom: 0;
     566    z-index: 10;
     567    background: #eee;
     568    display: -webkit-box;
     569    display: -moz-box;
     570    display: -ms-flexbox;
     571    display: -webkit-flex;
     572    display: flex;
     573}
     574
     575#available-menu-items .new-content-item .create-item-input {
     576    -webkit-box-flex: 10;
     577    -webkit-flex-grow: 10;
     578    -moz-box-flex: 10;
     579    -ms-flex-positive: 10;
     580    -ms-flex: 10;
     581    flex-grow: 10;
     582    margin-left: 5px;
     583    padding: 4.5px;
     584}
     585#available-menu-items .new-content-item .add-content {
     586    padding-left: 6px;
     587    -webkit-box-flex: 10;
     588    -webkit-flex-grow: 10;
     589    -moz-box-flex: 10;
     590    -ms-flex-positive: 10;
     591    -ms-flex: 10;
     592    flex-grow: 1;
    540593}
    541594
     
    547600#custom-menu-item-url.invalid,
    548601.menu-name-field.invalid,
    549 .menu-name-field.invalid:focus {
     602.menu-name-field.invalid:focus,
     603#available-menu-items .new-content-item .create-item-input.invalid,
     604#available-menu-items .new-content-item .create-item-input.invalid:focus {
    550605    border: 1px solid #f00;
    551606}
  • trunk/src/wp-admin/js/customize-nav-menus.js

    r38301 r38436  
    8181    api.Menus.availableMenuItems = new api.Menus.AvailableItemCollection( api.Menus.data.availableMenuItems );
    8282
     83    api.Menus.insertedAutoDrafts = [];
     84
     85    /**
     86     * Insert a new `auto-draft` post.
     87     *
     88     * @param {object} params - Parameters for the draft post to create.
     89     * @param {string} params.post_type - Post type to add.
     90     * @param {string} params.post_title - Post title to use.
     91     * @return {jQuery.promise} Promise resolved with the added post.
     92     */
     93    api.Menus.insertAutoDraftPost = function insertAutoDraftPost( params ) {
     94        var request, deferred = $.Deferred();
     95
     96        request = wp.ajax.post( 'customize-nav-menus-insert-auto-draft', {
     97            'customize-menus-nonce': api.settings.nonce['customize-menus'],
     98            'wp_customize': 'on',
     99            'params': params
     100        } );
     101
     102        request.done( function( response ) {
     103            if ( response.post_id ) {
     104                deferred.resolve( response );
     105                api.Menus.insertedAutoDrafts.push( response.post_id );
     106                api( 'nav_menus_created_posts' ).set( _.clone( api.Menus.insertedAutoDrafts ) );
     107            }
     108        } );
     109
     110        request.fail( function( response ) {
     111            var error = response || '';
     112
     113            if ( 'undefined' !== typeof response.message ) {
     114                error = response.message;
     115            }
     116
     117            console.error( error );
     118            deferred.rejectWith( error );
     119        } );
     120
     121        return deferred.promise();
     122    };
     123
    83124    /**
    84125     * wp.customize.Menus.AvailableMenuItemsPanelView
     
    101142            'click #custom-menu-item-submit': '_submitLink',
    102143            'keypress #custom-menu-item-name': '_submitLink',
     144            'click .new-content-item .add-content': '_submitNew',
     145            'keypress .create-item-input': '_submitNew',
    103146            'keydown': 'keyboardAccessible'
    104147        },
     
    116159        sectionContent: '',
    117160        loading: false,
     161        addingNew: false,
    118162
    119163        initialize: function() {
     
    125169
    126170            this.$search = $( '#menu-items-search' );
    127             this.sectionContent = this.$el.find( '.accordion-section-content' );
     171            this.sectionContent = this.$el.find( '.available-menu-items-list' );
    128172
    129173            this.debounceSearch = _.debounce( self.search, 500 );
     
    161205            // Load more items.
    162206            this.sectionContent.scroll( function() {
    163                 var totalHeight = self.$el.find( '.accordion-section.open .accordion-section-content' ).prop( 'scrollHeight' ),
     207                var totalHeight = self.$el.find( '.accordion-section.open .available-menu-items-list' ).prop( 'scrollHeight' ),
    164208                    visibleHeight = self.$el.find( '.accordion-section.open' ).height();
    165209
     
    338382                items = new api.Menus.AvailableItemCollection( items ); // @todo Why is this collection created and then thrown away?
    339383                self.collection.add( items.models );
    340                 typeInner = availableMenuItemContainer.find( '.accordion-section-content' );
     384                typeInner = availableMenuItemContainer.find( '.available-menu-items-list' );
    341385                items.each(function( menuItem ) {
    342386                    typeInner.append( itemTemplate( menuItem.attributes ) );
     
    357401        // Adjust the height of each section of items to fit the screen.
    358402        itemSectionHeight: function() {
    359             var sections, totalHeight, accordionHeight, diff;
     403            var sections, lists, totalHeight, accordionHeight, diff;
    360404            totalHeight = window.innerHeight;
    361405            sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' );
    362             accordionHeight =  46 * ( 2 + sections.length ) - 13; // Magic numbers.
     406            lists = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")' );
     407            accordionHeight =  46 * ( 1 + sections.length ) + 14; // Magic numbers.
    363408            diff = totalHeight - accordionHeight;
    364409            if ( 120 < diff && 290 > diff ) {
    365410                sections.css( 'max-height', diff );
     411                lists.css( 'max-height', ( diff - 60 ) );
    366412            }
    367413        },
     
    455501            itemUrl.val( 'http://' );
    456502            itemName.val( '' );
     503        },
     504
     505        // Submit handler for keypress (enter) on field and click on button.
     506        _submitNew: function( event ) {
     507            var container;
     508
     509            // Only proceed with keypress if it is Enter.
     510            if ( 'keypress' === event.type && 13 !== event.which ) {
     511                return;
     512            }
     513
     514            if ( this.addingNew ) {
     515                return;
     516            }
     517
     518            container = $( event.target ).closest( '.accordion-section' );
     519
     520            this.submitNew( container );
     521        },
     522
     523        // Creates a new object and adds an associated menu item to the menu.
     524        submitNew: function( container ) {
     525            var panel = this,
     526                itemName = container.find( '.create-item-input' ),
     527                title = itemName.val(),
     528                dataContainer = container.find( '.available-menu-items-list' ),
     529                itemType = dataContainer.data( 'type' ),
     530                itemObject = dataContainer.data( 'object' ),
     531                itemTypeLabel = dataContainer.data( 'type_label' ),
     532                promise;
     533
     534            if ( ! this.currentMenuControl ) {
     535                return;
     536            }
     537
     538            // Only posts are supported currently.
     539            if ( 'post_type' !== itemType ) {
     540                return;
     541            }
     542
     543            if ( '' === $.trim( itemName.val() ) ) {
     544                itemName.addClass( 'invalid' );
     545                itemName.focus();
     546                return;
     547            } else {
     548                itemName.removeClass( 'invalid' );
     549                container.find( '.accordion-section-title' ).addClass( 'loading' );
     550            }
     551
     552            panel.addingNew = true;
     553            itemName.attr( 'disabled', 'disabled' );
     554            promise = api.Menus.insertAutoDraftPost( {
     555                post_title: title,
     556                post_type: itemObject
     557            } );
     558            promise.done( function( data ) {
     559                var availableItem, $content, itemTemplate;
     560                availableItem = new api.Menus.AvailableItemModel( {
     561                    'id': 'post-' + data.post_id, // Used for available menu item Backbone models.
     562                    'title': itemName.val(),
     563                    'type': itemType,
     564                    'type_label': itemTypeLabel,
     565                    'object': itemObject,
     566                    'object_id': data.post_id,
     567                    'url': data.url
     568                } );
     569
     570                // Add new item to menu.
     571                panel.currentMenuControl.addItemToMenu( availableItem.attributes );
     572
     573                // Add the new item to the list of available items.
     574                api.Menus.availableMenuItemsPanel.collection.add( availableItem );
     575                $content = container.find( '.available-menu-items-list' );
     576                itemTemplate = wp.template( 'available-menu-item' );
     577                $content.prepend( itemTemplate( availableItem.attributes ) );
     578                $content.scrollTop();
     579
     580                // Reset the create content form.
     581                itemName.val( '' ).removeAttr( 'disabled' );
     582                panel.addingNew = false;
     583                container.find( '.accordion-section-title' ).removeClass( 'loading' );
     584            } );
    457585        },
    458586
     
    25462674                api.Menus.applySavedData( data );
    25472675            }
     2676
     2677            // Reset list of inserted auto draft post IDs.
     2678            api.Menus.insertedAutoDrafts = [];
    25482679        } );
    25492680
  • 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
  • trunk/src/wp-includes/class-wp-customize-setting.php

    r38411 r38436  
    499499     * Fetch and sanitize the $_POST value for the setting.
    500500     *
     501     * During a save request prior to save, post_value() provides the new value while value() does not.
     502     *
    501503     * @since 3.4.0
    502504     *
  • trunk/tests/phpunit/tests/ajax/CustomizeMenus.php

    r35244 r38436  
    527527        );
    528528    }
     529
     530    /**
     531     * Testing successful ajax_insert_auto_draft_post() call.
     532     *
     533     * @covers WP_Customize_Nav_Menus::ajax_insert_auto_draft_post()
     534     */
     535    function test_ajax_insert_auto_draft_post_success() {
     536        $_POST = wp_slash( array(
     537            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     538            'params' => array(
     539                'post_type' => 'post',
     540                'post_title' => 'Hello World',
     541            ),
     542        ) );
     543        $this->_last_response = '';
     544        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     545        $response = json_decode( $this->_last_response, true );
     546
     547        $this->assertTrue( $response['success'] );
     548        $this->assertArrayHasKey( 'post_id', $response['data'] );
     549        $this->assertArrayHasKey( 'url', $response['data'] );
     550    }
     551
     552    /**
     553     * Testing unsuccessful ajax_insert_auto_draft_post() call.
     554     *
     555     * @covers WP_Customize_Nav_Menus::ajax_insert_auto_draft_post()
     556     */
     557    function test_ajax_insert_auto_draft_failures() {
     558        // No nonce.
     559        $_POST = array();
     560        $this->_last_response = '';
     561        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     562        $response = json_decode( $this->_last_response, true );
     563        $this->assertFalse( $response['success'] );
     564        $this->assertEquals( 'bad_nonce', $response['data'] );
     565
     566        // Bad nonce.
     567        $_POST = wp_slash( array(
     568            'customize-menus-nonce' => 'bad',
     569        ) );
     570        $this->_last_response = '';
     571        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     572        $response = json_decode( $this->_last_response, true );
     573        $this->assertFalse( $response['success'] );
     574        $this->assertEquals( 'bad_nonce', $response['data'] );
     575
     576        // Bad nonce.
     577        wp_set_current_user( $this->factory()->user->create( array( 'role' => 'subscriber' ) ) );
     578        $_POST = wp_slash( array(
     579            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     580        ) );
     581        $this->_last_response = '';
     582        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     583        $response = json_decode( $this->_last_response, true );
     584        $this->assertFalse( $response['success'] );
     585        $this->assertEquals( 'customize_not_allowed', $response['data'] );
     586
     587        // Missing params.
     588        wp_set_current_user( $this->factory()->user->create( array( 'role' => 'administrator' ) ) );
     589        $_POST = wp_slash( array(
     590            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     591        ) );
     592        $this->_last_response = '';
     593        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     594        $response = json_decode( $this->_last_response, true );
     595        $this->assertFalse( $response['success'] );
     596        $this->assertEquals( 'missing_params', $response['data'] );
     597
     598        // insufficient_post_permissions.
     599        register_post_type( 'privilege', array( 'capability_type' => 'privilege' ) );
     600        $_POST = wp_slash( array(
     601            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     602            'params' => array(
     603                'post_type' => 'privilege',
     604            ),
     605        ) );
     606        $this->_last_response = '';
     607        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     608        $response = json_decode( $this->_last_response, true );
     609        $this->assertFalse( $response['success'] );
     610        $this->assertEquals( 'insufficient_post_permissions', $response['data'] );
     611
     612        // insufficient_post_permissions.
     613        $_POST = wp_slash( array(
     614            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     615            'params' => array(
     616                'post_type' => 'non-existent',
     617            ),
     618        ) );
     619        $this->_last_response = '';
     620        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     621        $response = json_decode( $this->_last_response, true );
     622        $this->assertFalse( $response['success'] );
     623        $this->assertEquals( 'missing_post_type_param', $response['data'] );
     624
     625        // missing_post_title.
     626        $_POST = wp_slash( array(
     627            'customize-menus-nonce' => wp_create_nonce( 'customize-menus' ),
     628            'params' => array(
     629                'post_type' => 'post',
     630                'post_title' => '    ',
     631            ),
     632        ) );
     633        $this->_last_response = '';
     634        $this->make_ajax_call( 'customize-nav-menus-insert-auto-draft' );
     635        $response = json_decode( $this->_last_response, true );
     636        $this->assertFalse( $response['success'] );
     637        $this->assertEquals( 'missing_post_title', $response['data'] );
     638    }
    529639}
  • trunk/tests/phpunit/tests/customize/nav-menus.php

    r36889 r38436  
    4646    function filter_item_types( $items ) {
    4747        $items[] = array(
    48             'title'  => 'Custom',
    49             'type'   => 'custom_type',
     48            'title' => 'Custom',
     49            'type' => 'custom_type',
    5050            'object' => 'custom_object',
     51            'type_label' => 'Custom Type',
    5152        );
    5253
     
    8586        $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
    8687        $this->assertInstanceOf( 'WP_Customize_Manager', $menus->manager );
     88
     89        $this->assertEquals( 10, add_filter( 'customize_refresh_nonces', array( $menus, 'filter_nonces' ) ) );
     90        $this->assertEquals( 10, add_action( 'wp_ajax_load-available-menu-items-customizer', array( $menus, 'ajax_load_available_items' ) ) );
     91        $this->assertEquals( 10, add_action( 'wp_ajax_search-available-menu-items-customizer', array( $menus, 'ajax_search_available_items' ) ) );
     92        $this->assertEquals( 10, add_action( 'wp_ajax_customize-nav-menus-insert-auto-draft', array( $menus, 'ajax_insert_auto_draft_post' ) ) );
     93        $this->assertEquals( 10, add_action( 'customize_controls_enqueue_scripts', array( $menus, 'enqueue_scripts' ) ) );
     94        $this->assertEquals( 11, add_action( 'customize_register', array( $menus, 'customize_register' ) ) );
     95        $this->assertEquals( 10, add_filter( 'customize_dynamic_setting_args', array( $menus, 'filter_dynamic_setting_args' ) ) );
     96        $this->assertEquals( 10, add_filter( 'customize_dynamic_setting_class', array( $menus, 'filter_dynamic_setting_class' ) ) );
     97        $this->assertEquals( 10, add_action( 'customize_controls_print_footer_scripts', array( $menus, 'print_templates' ) ) );
     98        $this->assertEquals( 10, add_action( 'customize_controls_print_footer_scripts', array( $menus, 'available_items_template' ) ) );
     99        $this->assertEquals( 10, add_action( 'customize_preview_init', array( $menus, 'customize_preview_init' ) ) );
     100        $this->assertEquals( 10, add_action( 'customize_preview_init', array( $menus, 'make_auto_draft_status_previewable' ) ) );
     101        $this->assertEquals( 10, add_action( 'customize_save_nav_menus_created_posts', array( $menus, 'save_nav_menus_created_posts' ) ) );
     102        $this->assertEquals( 10, add_filter( 'customize_dynamic_partial_args', array( $menus, 'customize_dynamic_partial_args' ) ) );
    87103    }
    88104
     
    445461            'menu-item-status'    => 'publish',
    446462        ) );
    447         $setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, "nav_menu_item[$item_id]" );
    448         do_action( 'customize_register', $this->wp_customize );
     463        do_action( 'customize_register', $this->wp_customize );
     464        $this->assertInstanceOf( 'WP_Customize_Nav_Menu_Item_Setting', $this->wp_customize->get_setting( "nav_menu_item[$item_id]" ) );
    449465        $this->assertEquals( 'Primary', $this->wp_customize->get_section( "nav_menu[$menu_id]" )->title );
    450466        $this->assertEquals( 'Hello World', $this->wp_customize->get_control( "nav_menu_item[$item_id]" )->label );
     467
     468        $nav_menus_created_posts_setting = $this->wp_customize->get_setting( 'nav_menus_created_posts' );
     469        $this->assertInstanceOf( 'WP_Customize_Filter_Setting', $nav_menus_created_posts_setting );
     470        $this->assertEquals( 'postMessage', $nav_menus_created_posts_setting->transport );
     471        $this->assertEquals( array(), $nav_menus_created_posts_setting->default );
     472        $this->assertEquals( array( $this->wp_customize->nav_menus, 'sanitize_nav_menus_created_posts' ), $nav_menus_created_posts_setting->sanitize_callback );
    451473    }
    452474
     
    480502
    481503        $expected = array(
    482             array( 'title' => 'Posts', 'type' => 'post_type', 'object' => 'post' ),
    483             array( 'title' => 'Pages', 'type' => 'post_type', 'object' => 'page' ),
    484             array( 'title' => 'Categories', 'type' => 'taxonomy', 'object' => 'category' ),
    485             array( 'title' => 'Tags', 'type' => 'taxonomy', 'object' => 'post_tag' ),
     504            array( 'title' => 'Posts', 'type' => 'post_type', 'object' => 'post', 'type_label' => __( 'Post' ) ),
     505            array( 'title' => 'Pages', 'type' => 'post_type', 'object' => 'page', 'type_label' => __( 'Page' ) ),
     506            array( 'title' => 'Categories', 'type' => 'taxonomy', 'object' => 'category', 'type_label' => __( 'Category' ) ),
     507            array( 'title' => 'Tags', 'type' => 'taxonomy', 'object' => 'post_tag', 'type_label' => __( 'Tag' ) ),
    486508        );
    487509
    488510        if ( current_theme_supports( 'post-formats' ) ) {
    489             $expected[] = array( 'title' => 'Format', 'type' => 'taxonomy', 'object' => 'post_format' );
     511            $expected[] = array( 'title' => 'Format', 'type' => 'taxonomy', 'object' => 'post_format', 'type_label' => __( 'Format' ) );
    490512        }
    491513
     
    493515
    494516        register_taxonomy( 'wptests_tax', array( 'post' ), array( 'labels' => array( 'name' => 'Foo' ) ) );
    495         $expected[] = array( 'title' => 'Foo', 'type' => 'taxonomy', 'object' => 'wptests_tax' );
     517        $expected[] = array( 'title' => 'Foo', 'type' => 'taxonomy', 'object' => 'wptests_tax', 'type_label' => 'Foo' );
    496518
    497519        $this->assertEquals( $expected, $menus->available_item_types() );
    498520
    499         $expected[] = array( 'title' => 'Custom', 'type' => 'custom_type', 'object' => 'custom_object' );
     521        $expected[] = array( 'title' => 'Custom', 'type' => 'custom_type', 'object' => 'custom_object', 'type_label' => 'Custom Type' );
    500522
    501523        add_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) );
     
    503525        remove_filter( 'customize_nav_menu_available_item_types', array( $this, 'filter_item_types' ) );
    504526
     527    }
     528
     529    /**
     530     * Test insert_auto_draft_post method.
     531     *
     532     * @covers WP_Customize_Nav_Menus::insert_auto_draft_post()
     533     */
     534    public function test_insert_auto_draft_post() {
     535        $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
     536
     537        $r = $menus->insert_auto_draft_post( array() );
     538        $this->assertInstanceOf( 'WP_Error', $r );
     539        $this->assertEquals( 'unknown_post_type', $r->get_error_code() );
     540
     541        $r = $menus->insert_auto_draft_post( array( 'post_type' => 'fake' ) );
     542        $this->assertInstanceOf( 'WP_Error', $r );
     543        $this->assertEquals( 'unknown_post_type', $r->get_error_code() );
     544
     545        $r = $menus->insert_auto_draft_post( array( 'post_title' => 'Hello World', 'post_type' => 'post' ) );
     546        $this->assertInstanceOf( 'WP_Post', $r );
     547        $this->assertEquals( 'Hello World', $r->post_title );
     548        $this->assertEquals( 'post', $r->post_type );
     549        $this->assertEquals( sanitize_title( $r->post_title ), $r->post_name );
    505550    }
    506551
     
    554599                $this->assertContains( 'data-type="post_type"', $template );
    555600                $this->assertContains( 'data-object="' . esc_attr( $type->name ) . '"', $template );
     601                $this->assertContains( 'data-type_label="' . esc_attr( $type->labels->singular_name ) . '"', $template );
    556602            }
    557603        }
     
    564610                $this->assertContains( 'data-type="taxonomy"', $template );
    565611                $this->assertContains( 'data-object="' . esc_attr( $tax->name ) . '"', $template );
     612                $this->assertContains( 'data-type_label="' . esc_attr( $tax->labels->singular_name ) . '"', $template );
    566613            }
    567614        }
     
    571618        $this->assertContains( 'data-type="custom_type"', $template );
    572619        $this->assertContains( 'data-object="custom_object"', $template );
     620        $this->assertContains( 'data-type_label="Custom Type"', $template );
    573621    }
    574622
     
    608656        $this->assertEquals( 1000, has_filter( 'wp_nav_menu_args', array( $menus, 'filter_wp_nav_menu_args' ) ) );
    609657        $this->assertEquals( 10, has_filter( 'wp_nav_menu', array( $menus, 'filter_wp_nav_menu' ) ) );
     658    }
     659
     660    /**
     661     * Test make_auto_draft_status_previewable.
     662     *
     663     * @covers WP_Customize_Nav_Menus::make_auto_draft_status_previewable()
     664     */
     665    function test_make_auto_draft_status_previewable() {
     666        global $wp_post_statuses;
     667        $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
     668        $menus->make_auto_draft_status_previewable();
     669        $this->assertTrue( $wp_post_statuses['auto-draft']->protected );
     670    }
     671
     672    /**
     673     * Test sanitize_nav_menus_created_posts.
     674     *
     675     * @covers WP_Customize_Nav_Menus::sanitize_nav_menus_created_posts()
     676     */
     677    function test_sanitize_nav_menus_created_posts() {
     678        $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
     679        $contributor_user_id = $this->factory()->user->create( array( 'role' => 'contributor' ) );
     680        $author_user_id = $this->factory()->user->create( array( 'role' => 'author' ) );
     681        $administrator_user_id = $this->factory()->user->create( array( 'role' => 'administrator' ) );
     682
     683        $contributor_post_id = $this->factory()->post->create( array(
     684            'post_status' => 'auto-draft',
     685            'post_title' => 'Contributor Post',
     686            'post_type' => 'post',
     687            'post_author' => $contributor_user_id,
     688        ) );
     689        $author_post_id = $this->factory()->post->create( array(
     690            'post_status' => 'auto-draft',
     691            'post_title' => 'Author Post',
     692            'post_type' => 'post',
     693            'post_author' => $author_user_id,
     694        ) );
     695        $administrator_post_id = $this->factory()->post->create( array(
     696            'post_status' => 'auto-draft',
     697            'post_title' => 'Admin Post',
     698            'post_type' => 'post',
     699            'post_author' => $administrator_user_id,
     700        ) );
     701
     702        $value = array(
     703            'bad',
     704            $contributor_post_id,
     705            $author_post_id,
     706            $administrator_post_id,
     707        );
     708
     709        wp_set_current_user( $contributor_user_id );
     710        $sanitized = $menus->sanitize_nav_menus_created_posts( $value );
     711        $this->assertEquals( array(), $sanitized );
     712
     713        wp_set_current_user( $author_user_id );
     714        $sanitized = $menus->sanitize_nav_menus_created_posts( $value );
     715        $this->assertEquals( array( $author_post_id ), $sanitized );
     716
     717        wp_set_current_user( $administrator_user_id );
     718        $sanitized = $menus->sanitize_nav_menus_created_posts( $value );
     719        $this->assertEquals( array( $contributor_post_id, $author_post_id, $administrator_post_id ), $sanitized );
     720    }
     721
     722    /**
     723     * Test save_nav_menus_created_posts.
     724     *
     725     * @covers WP_Customize_Nav_Menus::save_nav_menus_created_posts()
     726     */
     727    function test_save_nav_menus_created_posts() {
     728        $menus = new WP_Customize_Nav_Menus( $this->wp_customize );
     729        do_action( 'customize_register', $this->wp_customize );
     730
     731        $post_ids = $this->factory()->post->create_many( 3, array(
     732            'post_status' => 'auto-draft',
     733            'post_type' => 'post',
     734        ) );
     735        $pre_published_post_id = $this->factory()->post->create( array( 'post_status' => 'publish' ) );
     736
     737        $setting_id = 'nav_menus_created_posts';
     738        $this->wp_customize->set_post_value( $setting_id, array_merge( $post_ids, array( $pre_published_post_id ) ) );
     739        $setting = $this->wp_customize->get_setting( $setting_id );
     740        $this->assertInstanceOf( 'WP_Customize_Filter_Setting', $setting );
     741        $this->assertEquals( array( $menus, 'sanitize_nav_menus_created_posts' ), $setting->sanitize_callback );
     742        $this->assertEquals( $post_ids, $setting->post_value() );
     743        foreach ( $post_ids as $post_id ) {
     744            $this->assertEquals( 'auto-draft', get_post_status( $post_id ) );
     745        }
     746
     747        $save_action_count = did_action( 'customize_save_nav_menus_created_posts' );
     748        $setting->save();
     749        $this->assertEquals( $save_action_count + 1, did_action( 'customize_save_nav_menus_created_posts' ) );
     750        foreach ( $post_ids as $post_id ) {
     751            $this->assertEquals( 'publish', get_post_status( $post_id ) );
     752        }
    610753    }
    611754
Note: See TracChangeset for help on using the changeset viewer.