diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 0ce64a659d6a6..c5953e1d4ce2c 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -724,6 +724,7 @@ function _wp_build_title_and_description_for_taxonomy_block_template( $taxonomy, * * @since 5.9.0 * @since 6.3.0 Added `modified` property to template objects. + * @since 6.4.0 Added support for a revision post to be passed to this function. * @access private * * @param WP_Post $post Template post. @@ -731,7 +732,14 @@ function _wp_build_title_and_description_for_taxonomy_block_template( $taxonomy, */ function _build_block_template_result_from_post( $post ) { $default_template_types = get_default_block_template_types(); - $terms = get_the_terms( $post, 'wp_theme' ); + + $post_id = wp_is_post_revision( $post ); + if ( ! $post_id ) { + $post_id = $post; + } + $parent_post = get_post( $post_id ); + + $terms = get_the_terms( $parent_post, 'wp_theme' ); if ( is_wp_error( $terms ) ) { return $terms; @@ -745,12 +753,12 @@ function _build_block_template_result_from_post( $post ) { $template_file = _get_block_template_file( $post->post_type, $post->post_name ); $has_theme_file = get_stylesheet() === $theme && null !== $template_file; - $origin = get_post_meta( $post->ID, 'origin', true ); - $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true ); + $origin = get_post_meta( $parent_post->ID, 'origin', true ); + $is_wp_suggestion = get_post_meta( $parent_post->ID, 'is_wp_suggestion', true ); $template = new WP_Block_Template(); $template->wp_id = $post->ID; - $template->id = $theme . '//' . $post->post_name; + $template->id = $theme . '//' . $parent_post->post_name; $template->theme = $theme; $template->content = $post->post_content; $template->slug = $post->post_name; @@ -765,23 +773,23 @@ function _build_block_template_result_from_post( $post ) { $template->author = $post->post_author; $template->modified = $post->post_modified; - if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) { + if ( 'wp_template' === $parent_post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) { $template->post_types = $template_file['postTypes']; } - if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { + if ( 'wp_template' === $parent_post->post_type && isset( $default_template_types[ $template->slug ] ) ) { $template->is_custom = false; } - if ( 'wp_template_part' === $post->post_type ) { - $type_terms = get_the_terms( $post, 'wp_template_part_area' ); + if ( 'wp_template_part' === $parent_post->post_type ) { + $type_terms = get_the_terms( $parent_post, 'wp_template_part_area' ); if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { $template->area = $type_terms[0]->name; } } // Check for a block template without a description and title or with a title equal to the slug. - if ( 'wp_template' === $post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) { + if ( 'wp_template' === $parent_post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) { $matches = array(); // Check for a block template for a single author, page, post, tag, category, custom post type, or custom taxonomy. diff --git a/src/wp-includes/class-wp-post-type.php b/src/wp-includes/class-wp-post-type.php index ce6d9d348b480..7a2769ed88327 100644 --- a/src/wp-includes/class-wp-post-type.php +++ b/src/wp-includes/class-wp-post-type.php @@ -396,6 +396,54 @@ final class WP_Post_Type { */ public $rest_controller; + /** + * The controller for this post type's revisions REST API endpoints. + * + * Custom controllers must extend WP_REST_Controller. + * + * @since 6.4.0 + * @var string|bool $revisions_rest_controller_class + */ + public $revisions_rest_controller_class; + + /** + * The controller instance for this post type's revisions REST API endpoints. + * + * Lazily computed. Should be accessed using {@see WP_Post_Type::get_revisions_rest_controller()}. + * + * @since 6.4.0 + * @var WP_REST_Controller $revisions_rest_controller + */ + public $revisions_rest_controller; + + /** + * The controller for this post type's autosave REST API endpoints. + * + * Custom controllers must extend WP_REST_Controller. + * + * @since 6.4.0 + * @var string|bool $autosave_rest_controller_class + */ + public $autosave_rest_controller_class; + + /** + * The controller instance for this post type's autosave REST API endpoints. + * + * Lazily computed. Should be accessed using {@see WP_Post_Type::get_autosave_rest_controller()}. + * + * @since 6.4.0 + * @var WP_REST_Controller $autosave_rest_controller + */ + public $autosave_rest_controller; + + /** + * A flag to register the post type REST API controller after its associated autosave / revisions controllers, instead of before. Registration order affects route matching priority. + * + * @since 6.4.0 + * @var bool $late_route_registration + */ + public $late_route_registration; + /** * Constructor. * @@ -455,6 +503,7 @@ public function set_props( $args ) { * - `register_page_post_type_args` * * @since 6.0.0 + * @since 6.4.0 Added `late_route_registration`, `autosave_rest_controller_class` and `revisions_rest_controller_class` arguments. * * @param array $args Array of arguments for registering a post type. * See the register_post_type() function for accepted arguments. @@ -466,37 +515,40 @@ public function set_props( $args ) { // Args prefixed with an underscore are reserved for internal use. $defaults = array( - 'labels' => array(), - 'description' => '', - 'public' => false, - 'hierarchical' => false, - 'exclude_from_search' => null, - 'publicly_queryable' => null, - 'show_ui' => null, - 'show_in_menu' => null, - 'show_in_nav_menus' => null, - 'show_in_admin_bar' => null, - 'menu_position' => null, - 'menu_icon' => null, - 'capability_type' => 'post', - 'capabilities' => array(), - 'map_meta_cap' => null, - 'supports' => array(), - 'register_meta_box_cb' => null, - 'taxonomies' => array(), - 'has_archive' => false, - 'rewrite' => true, - 'query_var' => true, - 'can_export' => true, - 'delete_with_user' => null, - 'show_in_rest' => false, - 'rest_base' => false, - 'rest_namespace' => false, - 'rest_controller_class' => false, - 'template' => array(), - 'template_lock' => false, - '_builtin' => false, - '_edit_link' => 'post.php?post=%d', + 'labels' => array(), + 'description' => '', + 'public' => false, + 'hierarchical' => false, + 'exclude_from_search' => null, + 'publicly_queryable' => null, + 'show_ui' => null, + 'show_in_menu' => null, + 'show_in_nav_menus' => null, + 'show_in_admin_bar' => null, + 'menu_position' => null, + 'menu_icon' => null, + 'capability_type' => 'post', + 'capabilities' => array(), + 'map_meta_cap' => null, + 'supports' => array(), + 'register_meta_box_cb' => null, + 'taxonomies' => array(), + 'has_archive' => false, + 'rewrite' => true, + 'query_var' => true, + 'can_export' => true, + 'delete_with_user' => null, + 'show_in_rest' => false, + 'rest_base' => false, + 'rest_namespace' => false, + 'rest_controller_class' => false, + 'autosave_rest_controller_class' => false, + 'revisions_rest_controller_class' => false, + 'late_route_registration' => false, + 'template' => array(), + 'template_lock' => false, + '_builtin' => false, + '_edit_link' => 'post.php?post=%d', ); $args = array_merge( $defaults, $args ); @@ -816,6 +868,85 @@ public function get_rest_controller() { return $this->rest_controller; } + /** + * Gets the REST API revisions controller for this post type. + * + * Will only instantiate the controller class once per request. + * + * @since 6.4.0 + * + * @return WP_REST_Controller|null The controller instance, or null if the post type + * is set not to show in rest. + */ + public function get_revisions_rest_controller() { + if ( ! $this->show_in_rest ) { + return null; + } + + if ( ! post_type_supports( $this->name, 'revisions' ) ) { + return null; + } + + $class = $this->revisions_rest_controller_class ? $this->revisions_rest_controller_class : WP_REST_Revisions_Controller::class; + if ( ! class_exists( $class ) ) { + return null; + } + + if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) { + return null; + } + + if ( ! $this->revisions_rest_controller ) { + $this->revisions_rest_controller = new $class( $this->name ); + } + + if ( ! ( $this->revisions_rest_controller instanceof $class ) ) { + return null; + } + + return $this->revisions_rest_controller; + } + + /** + * Gets the REST API autosave controller for this post type. + * + * Will only instantiate the controller class once per request. + * + * @since 6.4.0 + * + * @return WP_REST_Controller|null The controller instance, or null if the post type + * is set not to show in rest. + */ + public function get_autosave_rest_controller() { + if ( ! $this->show_in_rest ) { + return null; + } + + if ( 'attachment' === $this->name ) { + return null; + } + + $class = $this->autosave_rest_controller_class ? $this->autosave_rest_controller_class : WP_REST_Autosaves_Controller::class; + + if ( ! class_exists( $class ) ) { + return null; + } + + if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) { + return null; + } + + if ( ! $this->autosave_rest_controller ) { + $this->autosave_rest_controller = new $class( $this->name ); + } + + if ( ! ( $this->autosave_rest_controller instanceof $class ) ) { + return null; + } + + return $this->autosave_rest_controller; + } + /** * Returns the default labels for post types. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index a85d8fbdb5e87..c3910d2b3a2d3 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -346,7 +346,7 @@ function create_initial_post_types() { register_post_type( 'wp_template', array( - 'labels' => array( + 'labels' => array( 'name' => _x( 'Templates', 'post type general name' ), 'singular_name' => _x( 'Template', 'post type singular name' ), 'add_new' => __( 'Add New Template' ), @@ -366,19 +366,22 @@ function create_initial_post_types() { 'items_list_navigation' => __( 'Templates list navigation' ), 'items_list' => __( 'Templates list' ), ), - 'description' => __( 'Templates to include in your theme.' ), - 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */ - 'has_archive' => false, - 'show_ui' => false, - 'show_in_menu' => false, - 'show_in_rest' => true, - 'rewrite' => false, - 'rest_base' => 'templates', - 'rest_controller_class' => 'WP_REST_Templates_Controller', - 'capability_type' => array( 'template', 'templates' ), - 'capabilities' => array( + 'description' => __( 'Templates to include in your theme.' ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */ + 'has_archive' => false, + 'show_ui' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'rewrite' => false, + 'rest_base' => 'templates', + 'rest_controller_class' => 'WP_REST_Templates_Controller', + 'autosave_rest_controller_class' => 'WP_REST_Template_Autosaves_Controller', + 'revisions_rest_controller_class' => 'WP_REST_Template_Revisions_Controller', + 'late_route_registration' => true, + 'capability_type' => array( 'template', 'templates' ), + 'capabilities' => array( 'create_posts' => 'edit_theme_options', 'delete_posts' => 'edit_theme_options', 'delete_others_posts' => 'edit_theme_options', @@ -392,8 +395,8 @@ function create_initial_post_types() { 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', ), - 'map_meta_cap' => true, - 'supports' => array( + 'map_meta_cap' => true, + 'supports' => array( 'title', 'slug', 'excerpt', @@ -407,7 +410,7 @@ function create_initial_post_types() { register_post_type( 'wp_template_part', array( - 'labels' => array( + 'labels' => array( 'name' => _x( 'Template Parts', 'post type general name' ), 'singular_name' => _x( 'Template Part', 'post type singular name' ), 'add_new' => __( 'Add New Template Part' ), @@ -427,19 +430,22 @@ function create_initial_post_types() { 'items_list_navigation' => __( 'Template parts list navigation' ), 'items_list' => __( 'Template parts list' ), ), - 'description' => __( 'Template parts to include in your templates.' ), - 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */ - 'has_archive' => false, - 'show_ui' => false, - 'show_in_menu' => false, - 'show_in_rest' => true, - 'rewrite' => false, - 'rest_base' => 'template-parts', - 'rest_controller_class' => 'WP_REST_Templates_Controller', - 'map_meta_cap' => true, - 'capabilities' => array( + 'description' => __( 'Template parts to include in your templates.' ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */ + 'has_archive' => false, + 'show_ui' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'rewrite' => false, + 'rest_base' => 'template-parts', + 'rest_controller_class' => 'WP_REST_Templates_Controller', + 'autosave_rest_controller_class' => 'WP_REST_Template_Autosaves_Controller', + 'revisions_rest_controller_class' => 'WP_REST_Template_Revisions_Controller', + 'late_route_registration' => true, + 'map_meta_cap' => true, + 'capabilities' => array( 'create_posts' => 'edit_theme_options', 'delete_posts' => 'edit_theme_options', 'delete_others_posts' => 'edit_theme_options', @@ -453,7 +459,7 @@ function create_initial_post_types() { 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', ), - 'supports' => array( + 'supports' => array( 'title', 'slug', 'excerpt', @@ -1575,85 +1581,88 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) * @param array|string $args { * Array or string of arguments for registering a post type. * - * @type string $label Name of the post type shown in the menu. Usually plural. - * Default is value of $labels['name']. - * @type string[] $labels An array of labels for this post type. If not set, post - * labels are inherited for non-hierarchical types and page - * labels for hierarchical ones. See get_post_type_labels() for a full - * list of supported labels. - * @type string $description A short descriptive summary of what the post type is. - * Default empty. - * @type bool $public Whether a post type is intended for use publicly either via - * the admin interface or by front-end users. While the default - * settings of $exclude_from_search, $publicly_queryable, $show_ui, - * and $show_in_nav_menus are inherited from $public, each does not - * rely on this relationship and controls a very specific intention. - * Default false. - * @type bool $hierarchical Whether the post type is hierarchical (e.g. page). Default false. - * @type bool $exclude_from_search Whether to exclude posts with this post type from front end search - * results. Default is the opposite value of $public. - * @type bool $publicly_queryable Whether queries can be performed on the front end for the post type - * as part of parse_request(). Endpoints would include: - * * ?post_type={post_type_key} - * * ?{post_type_key}={single_post_slug} - * * ?{post_type_query_var}={single_post_slug} - * If not set, the default is inherited from $public. - * @type bool $show_ui Whether to generate and allow a UI for managing this post type in the - * admin. Default is value of $public. - * @type bool|string $show_in_menu Where to show the post type in the admin menu. To work, $show_ui - * must be true. If true, the post type is shown in its own top level - * menu. If false, no menu is shown. If a string of an existing top - * level menu ('tools.php' or 'edit.php?post_type=page', for example), the - * post type will be placed as a sub-menu of that. - * Default is value of $show_ui. - * @type bool $show_in_nav_menus Makes this post type available for selection in navigation menus. - * Default is value of $public. - * @type bool $show_in_admin_bar Makes this post type available via the admin bar. Default is value - * of $show_in_menu. - * @type bool $show_in_rest Whether to include the post type in the REST API. Set this to true - * for the post type to be available in the block editor. - * @type string $rest_base To change the base URL of REST API route. Default is $post_type. - * @type string $rest_namespace To change the namespace URL of REST API route. Default is wp/v2. - * @type string $rest_controller_class REST API controller class name. Default is 'WP_REST_Posts_Controller'. - * @type int $menu_position The position in the menu order the post type should appear. To work, - * $show_in_menu must be true. Default null (at the bottom). - * @type string $menu_icon The URL to the icon to be used for this menu. Pass a base64-encoded - * SVG using a data URI, which will be colored to match the color scheme - * -- this should begin with 'data:image/svg+xml;base64,'. Pass the name - * of a Dashicons helper class to use a font icon, e.g. - * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty - * so an icon can be added via CSS. Defaults to use the posts icon. - * @type string|array $capability_type The string to use to build the read, edit, and delete capabilities. - * May be passed as an array to allow for alternative plurals when using - * this argument as a base to construct the capabilities, e.g. - * array('story', 'stories'). Default 'post'. - * @type string[] $capabilities Array of capabilities for this post type. $capability_type is used - * as a base to construct capabilities by default. - * See get_post_type_capabilities(). - * @type bool $map_meta_cap Whether to use the internal default meta capability handling. - * Default false. - * @type array $supports Core feature(s) the post type supports. Serves as an alias for calling - * add_post_type_support() directly. Core features include 'title', - * 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', - * 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'. - * Additionally, the 'revisions' feature dictates whether the post type - * will store revisions, and the 'comments' feature dictates whether the - * comments count will show on the edit screen. A feature can also be - * specified as an array of arguments to provide additional information - * about supporting that feature. - * Example: `array( 'my_feature', array( 'field' => 'value' ) )`. - * Default is an array containing 'title' and 'editor'. - * @type callable $register_meta_box_cb Provide a callback function that sets up the meta boxes for the - * edit form. Do remove_meta_box() and add_meta_box() calls in the - * callback. Default null. - * @type string[] $taxonomies An array of taxonomy identifiers that will be registered for the - * post type. Taxonomies can be registered later with register_taxonomy() - * or register_taxonomy_for_object_type(). - * Default empty array. - * @type bool|string $has_archive Whether there should be post type archives, or if a string, the - * archive slug to use. Will generate the proper rewrite rules if - * $rewrite is enabled. Default false. - * @type bool|array $rewrite { + * @type string $label Name of the post type shown in the menu. Usually plural. + * Default is value of $labels['name']. + * @type string[] $labels An array of labels for this post type. If not set, post + * labels are inherited for non-hierarchical types and page + * labels for hierarchical ones. See get_post_type_labels() for a full + * list of supported labels. + * @type string $description A short descriptive summary of what the post type is. + * Default empty. + * @type bool $public Whether a post type is intended for use publicly either via + * the admin interface or by front-end users. While the default + * settings of $exclude_from_search, $publicly_queryable, $show_ui, + * and $show_in_nav_menus are inherited from $public, each does not + * rely on this relationship and controls a very specific intention. + * Default false. + * @type bool $hierarchical Whether the post type is hierarchical (e.g. page). Default false. + * @type bool $exclude_from_search Whether to exclude posts with this post type from front end search + * results. Default is the opposite value of $public. + * @type bool $publicly_queryable Whether queries can be performed on the front end for the post type + * as part of parse_request(). Endpoints would include: + * * ?post_type={post_type_key} + * * ?{post_type_key}={single_post_slug} + * * ?{post_type_query_var}={single_post_slug} + * If not set, the default is inherited from $public. + * @type bool $show_ui Whether to generate and allow a UI for managing this post type in the + * admin. Default is value of $public. + * @type bool|string $show_in_menu Where to show the post type in the admin menu. To work, $show_ui + * must be true. If true, the post type is shown in its own top level + * menu. If false, no menu is shown. If a string of an existing top + * level menu ('tools.php' or 'edit.php?post_type=page', for example), the + * post type will be placed as a sub-menu of that. + * Default is value of $show_ui. + * @type bool $show_in_nav_menus Makes this post type available for selection in navigation menus. + * Default is value of $public. + * @type bool $show_in_admin_bar Makes this post type available via the admin bar. Default is value + * of $show_in_menu. + * @type bool $show_in_rest Whether to include the post type in the REST API. Set this to true + * for the post type to be available in the block editor. + * @type string $rest_base To change the base URL of REST API route. Default is $post_type. + * @type string $rest_namespace To change the namespace URL of REST API route. Default is wp/v2. + * @type string $rest_controller_class REST API controller class name. Default is 'WP_REST_Posts_Controller'. + * @type string|bool $autosave_rest_controller_class REST API controller class name. Default is 'WP_REST_Autosaves_Controller'. + * @type string|bool $revisions_rest_controller_class REST API controller class name. Default is 'WP_REST_Revisions_Controller'. + * @type bool $late_route_registration A flag to direct the REST API controllers for autosave / revisions should be registered before/after the post type controller. + * @type int $menu_position The position in the menu order the post type should appear. To work, + * $show_in_menu must be true. Default null (at the bottom). + * @type string $menu_icon The URL to the icon to be used for this menu. Pass a base64-encoded + * SVG using a data URI, which will be colored to match the color scheme + * -- this should begin with 'data:image/svg+xml;base64,'. Pass the name + * of a Dashicons helper class to use a font icon, e.g. + * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty + * so an icon can be added via CSS. Defaults to use the posts icon. + * @type string|array $capability_type The string to use to build the read, edit, and delete capabilities. + * May be passed as an array to allow for alternative plurals when using + * this argument as a base to construct the capabilities, e.g. + * array('story', 'stories'). Default 'post'. + * @type string[] $capabilities Array of capabilities for this post type. $capability_type is used + * as a base to construct capabilities by default. + * See get_post_type_capabilities(). + * @type bool $map_meta_cap Whether to use the internal default meta capability handling. + * Default false. + * @type array $supports Core feature(s) the post type supports. Serves as an alias for calling + * add_post_type_support() directly. Core features include 'title', + * 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', + * 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'. + * Additionally, the 'revisions' feature dictates whether the post type + * will store revisions, and the 'comments' feature dictates whether the + * comments count will show on the edit screen. A feature can also be + * specified as an array of arguments to provide additional information + * about supporting that feature. + * Example: `array( 'my_feature', array( 'field' => 'value' ) )`. + * Default is an array containing 'title' and 'editor'. + * @type callable $register_meta_box_cb Provide a callback function that sets up the meta boxes for the + * edit form. Do remove_meta_box() and add_meta_box() calls in the + * callback. Default null. + * @type string[] $taxonomies An array of taxonomy identifiers that will be registered for the + * post type. Taxonomies can be registered later with register_taxonomy() + * or register_taxonomy_for_object_type(). + * Default empty array. + * @type bool|string $has_archive Whether there should be post type archives, or if a string, the + * archive slug to use. Will generate the proper rewrite rules if + * $rewrite is enabled. Default false. + * @type bool|array $rewrite { * Triggers the handling of rewrites for this post type. To prevent rewrite, set to false. * Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be * passed with any of these keys: @@ -1668,32 +1677,32 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) * inherits from $permalink_epmask. If not specified and permalink_epmask * is not set, defaults to EP_PERMALINK. * } - * @type string|bool $query_var Sets the query_var key for this post type. Defaults to $post_type - * key. If false, a post type cannot be loaded at - * ?{query_var}={post_slug}. If specified as a string, the query - * ?{query_var_string}={post_slug} will be valid. - * @type bool $can_export Whether to allow this post type to be exported. Default true. - * @type bool $delete_with_user Whether to delete posts of this type when deleting a user. - * * If true, posts of this type belonging to the user will be moved - * to Trash when the user is deleted. - * * If false, posts of this type belonging to the user will *not* - * be trashed or deleted. - * * If not set (the default), posts are trashed if post type supports - * the 'author' feature. Otherwise posts are not trashed or deleted. - * Default null. - * @type array $template Array of blocks to use as the default initial state for an editor - * session. Each item should be an array containing block name and - * optional attributes. Default empty array. - * @type string|false $template_lock Whether the block template should be locked if $template is set. - * * If set to 'all', the user is unable to insert new blocks, - * move existing blocks and delete blocks. - * * If set to 'insert', the user is able to move existing blocks - * but is unable to insert new blocks and delete blocks. - * Default false. - * @type bool $_builtin FOR INTERNAL USE ONLY! True if this post type is a native or - * "built-in" post_type. Default false. - * @type string $_edit_link FOR INTERNAL USE ONLY! URL segment to use for edit link of - * this post type. Default 'post.php?post=%d'. + * @type string|bool $query_var Sets the query_var key for this post type. Defaults to $post_type + * key. If false, a post type cannot be loaded at + * ?{query_var}={post_slug}. If specified as a string, the query + * ?{query_var_string}={post_slug} will be valid. + * @type bool $can_export Whether to allow this post type to be exported. Default true. + * @type bool $delete_with_user Whether to delete posts of this type when deleting a user. + * * If true, posts of this type belonging to the user will be moved + * to Trash when the user is deleted. + * * If false, posts of this type belonging to the user will *not* + * be trashed or deleted. + * * If not set (the default), posts are trashed if post type supports + * the 'author' feature. Otherwise posts are not trashed or deleted. + * Default null. + * @type array $template Array of blocks to use as the default initial state for an editor + * session. Each item should be an array containing block name and + * optional attributes. Default empty array. + * @type string|false $template_lock Whether the block template should be locked if $template is set. + * * If set to 'all', the user is unable to insert new blocks, + * move existing blocks and delete blocks. + * * If set to 'insert', the user is able to move existing blocks + * but is unable to insert new blocks and delete blocks. + * Default false. + * @type bool $_builtin FOR INTERNAL USE ONLY! True if this post type is a native or + * "built-in" post_type. Default false. + * @type string $_edit_link FOR INTERNAL USE ONLY! URL segment to use for edit link of + * this post type. Default 'post.php?post=%d'. * } * @return WP_Post_Type|WP_Error The registered post type object on success, * WP_Error object on failure. diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 71539b084c26d..19d29d051f5fe 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -241,17 +241,23 @@ function create_initial_rest_routes() { continue; } - $controller->register_routes(); + if ( ! $post_type->late_route_registration ) { + $controller->register_routes(); + } - if ( post_type_supports( $post_type->name, 'revisions' ) ) { - $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); + $revisions_controller = $post_type->get_revisions_rest_controller(); + if ( $revisions_controller ) { $revisions_controller->register_routes(); } - if ( 'attachment' !== $post_type->name ) { - $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name ); + $autosaves_controller = $post_type->get_autosave_rest_controller(); + if ( $autosaves_controller ) { $autosaves_controller->register_routes(); } + + if ( $post_type->late_route_registration ) { + $controller->register_routes(); + } } // Post types. diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php index d14119133aff3..5545625df4609 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php @@ -65,8 +65,13 @@ public function __construct( $parent_post_type ) { $parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); } - $this->parent_controller = $parent_controller; - $this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); + $this->parent_controller = $parent_controller; + + $revisions_controller = $post_type_object->get_revisions_rest_controller(); + if ( ! $revisions_controller ) { + $revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); + } + $this->revisions_controller = $revisions_controller; $this->rest_base = 'autosaves'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; @@ -205,11 +210,11 @@ public function create_item_permissions_check( $request ) { */ public function create_item( $request ) { - if ( ! defined( 'DOING_AUTOSAVE' ) ) { + if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) { define( 'DOING_AUTOSAVE', true ); } - $post = get_post( $request['id'] ); + $post = $this->get_parent( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php new file mode 100644 index 0000000000000..c996894a5933c --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php @@ -0,0 +1,276 @@ +parent_post_type = $parent_post_type; + $post_type_object = get_post_type_object( $parent_post_type ); + $parent_controller = $post_type_object->get_rest_controller(); + + if ( ! $parent_controller ) { + $parent_controller = new WP_REST_Templates_Controller( $parent_post_type ); + } + + $this->parent_controller = $parent_controller; + + $revisions_controller = $post_type_object->get_revisions_rest_controller(); + if ( ! $revisions_controller ) { + $revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); + } + $this->revisions_controller = $revisions_controller; + $this->rest_base = 'autosaves'; + $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; + } + + /** + * Registers the routes for autosaves. + * + * @since 6.4.0 + * + * @see register_rest_route() + */ + public function register_routes() { + register_rest_route( + $this->namespace, + sprintf( + '/%s/(?P%s%s)/%s', + $this->parent_base, + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ + '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', + // Matches the template name. + '[\/\w%-]+', + $this->rest_base + ), + array( + 'args' => array( + 'id' => array( + 'description' => __( 'The id of a template' ), + 'type' => 'string', + 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + sprintf( + '/%s/(?P%s%s)/%s/%s', + $this->parent_base, + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ + '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', + // Matches the template name. + '[\/\w%-]+', + $this->rest_base, + '(?P[\d]+)' + ), + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The id of a template' ), + 'type' => 'string', + 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), + ), + 'id' => array( + 'description' => __( 'The ID for the autosave.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Prepares the item for the REST response. + * + * @since 6.4.0 + * + * @param WP_Post $item Post revision object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $template = _build_block_template_result_from_post( $item ); + $response = $this->parent_controller->prepare_item_for_response( $template, $request ); + + $fields = $this->get_fields_for_response( $request ); + $data = $response->get_data(); + + if ( in_array( 'parent', $fields, true ) ) { + $data['parent'] = (int) $item->post_parent; + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = new WP_REST_Response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $template ); + $response->add_links( $links ); + } + + return $response; + } + + /** + * Gets the autosave, if the ID is valid. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Post|WP_Error Autosave post object if ID is valid, WP_Error otherwise. + */ + public function get_item( $request ) { + $parent = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $autosave = wp_get_post_autosave( $parent->ID ); + + if ( ! $autosave ) { + return new WP_Error( + 'rest_post_no_autosave', + __( 'There is no autosave revision for this template.' ), + array( 'status' => 404 ) + ); + } + + $response = $this->prepare_item_for_response( $autosave, $request ); + return $response; + } + + /** + * Get the parent post. + * + * @since 6.4.0 + * + * @param int $parent_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent( $parent_id ) { + return $this->revisions_controller->get_parent( $parent_id ); + } + + /** + * Prepares links for the request. + * + * @since 6.4.0 + * + * @param WP_Block_Template $template Template. + * @return array Links for the given post. + */ + protected function prepare_links( $template ) { + $links = array( + 'self' => array( + 'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ), + ), + 'parent' => array( + 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ), + ), + ); + + return $links; + } + + /** + * Retrieves the autosave's schema, conforming to JSON Schema. + * + * @since 6.4.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $this->schema = $this->revisions_controller->get_item_schema(); + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php new file mode 100644 index 0000000000000..8d32ecb7c0904 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php @@ -0,0 +1,297 @@ +parent_post_type = $parent_post_type; + $post_type_object = get_post_type_object( $parent_post_type ); + $parent_controller = $post_type_object->get_rest_controller(); + + if ( ! $parent_controller ) { + $parent_controller = new WP_REST_Templates_Controller( $parent_post_type ); + } + + $this->parent_controller = $parent_controller; + $this->rest_base = 'revisions'; + $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; + } + + /** + * Registers the routes for revisions based on post types supporting revisions. + * + * @since 6.4.0 + * + * @see register_rest_route() + */ + public function register_routes() { + + register_rest_route( + $this->namespace, + sprintf( + '/%s/(?P%s%s)/%s', + $this->parent_base, + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ + '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', + // Matches the template name. + '[\/\w%-]+', + $this->rest_base + ), + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The id of a template' ), + 'type' => 'string', + 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + sprintf( + '/%s/(?P%s%s)/%s/%s', + $this->parent_base, + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ + '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', + // Matches the template name. + '[\/\w%-]+', + $this->rest_base, + '(?P[\d]+)' + ), + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The id of a template' ), + 'type' => 'string', + 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), + ), + 'id' => array( + 'description' => __( 'Unique identifier for the revision.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Required to be true, as revisions do not support trashing.' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Gets the parent post, if the ID is valid. + * + * @since 6.4.0 + * + * @param int $parent_post_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent( $parent_post_id ) { + $template = get_block_template( $parent_post_id, $this->parent_post_type ); + + if ( ! $template ) { + return new WP_Error( + 'rest_post_invalid_parent', + __( 'Invalid template parent ID.' ), + array( 'status' => 404 ) + ); + } + + return get_post( $template->wp_id ); + } + + /** + * Prepares the item for the REST response. + * + * @since 6.4.0 + * + * @param WP_Post $item Post revision object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $template = _build_block_template_result_from_post( $item ); + $response = $this->parent_controller->prepare_item_for_response( $template, $request ); + + $fields = $this->get_fields_for_response( $request ); + $data = $response->get_data(); + + if ( in_array( 'parent', $fields, true ) ) { + $data['parent'] = (int) $item->post_parent; + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->filter_response_by_context( $data, $context ); + + // Wrap the data in a response object. + $response = new WP_REST_Response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $template ); + $response->add_links( $links ); + } + + return $response; + } + + /** + * Checks if a given request has access to delete a revision. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + */ + public function delete_item_permissions_check( $request ) { + $parent = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + if ( ! current_user_can( 'delete_post', $parent->ID ) ) { + return new WP_Error( + 'rest_cannot_delete', + __( 'Sorry, you are not allowed to delete revisions of this post.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + $revision = $this->get_revision( $request['id'] ); + if ( is_wp_error( $revision ) ) { + return $revision; + } + + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_delete', + __( 'Sorry, you are not allowed to delete this revision.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Prepares links for the request. + * + * @since 6.4.0 + * + * @param WP_Block_Template $template Template. + * @return array Links for the given post. + */ + protected function prepare_links( $template ) { + $links = array( + 'self' => array( + 'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ), + ), + 'parent' => array( + 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ), + ), + ); + + return $links; + } + + /** + * Retrieves the item's schema, conforming to JSON Schema. + * + * @since 6.4.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = $this->parent_controller->get_item_schema(); + + $schema['properties']['parent'] = array( + 'description' => __( 'The ID for the parent of the revision.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index e9904ff234912..53f8faa75595b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -760,7 +760,7 @@ public function prepare_item_for_response( $item, $request ) { protected function prepare_links( $id ) { $links = array( 'self' => array( - 'href' => rest_url( rest_get_route_for_post( $id ) ), + 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ), ), 'collection' => array( 'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ), @@ -770,6 +770,27 @@ protected function prepare_links( $id ) { ), ); + if ( post_type_supports( $this->post_type, 'revisions' ) ) { + $template = get_block_template( $id, $this->post_type ); + if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) { + $revisions = wp_get_latest_revision_id_and_total_count( $template->wp_id ); + $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; + $revisions_base = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id ); + + $links['version-history'] = array( + 'href' => rest_url( $revisions_base ), + 'count' => $revisions_count, + ); + + if ( $revisions_count > 0 ) { + $links['predecessor-version'] = array( + 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), + 'id' => $revisions['latest_id'], + ); + } + } + } + return $links; } diff --git a/src/wp-settings.php b/src/wp-settings.php index 5b1e1205d2719..38b03ecf7268f 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -273,7 +273,9 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-template-revisions-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-items-controller.php'; diff --git a/tests/phpunit/tests/post/wpPostType.php b/tests/phpunit/tests/post/wpPostType.php index 8443c804ad868..2a8ad42f0a2ca 100644 --- a/tests/phpunit/tests/post/wpPostType.php +++ b/tests/phpunit/tests/post/wpPostType.php @@ -229,4 +229,214 @@ public function test_applies_registration_args_filters() { $this->assertSame( 3, $action->get_call_count() ); } + + /** + * @ticket 56922 + * + * @dataProvider data_should_have_correct_custom_revisions_and_autosaves_controllers_properties + * + * @covers WP_Post_Type::set_props + * + * @param string $property_name Property name. + * @param string $property_value Property value. + * @param string|bool $expected_property_value Expected property value. + */ + public function test_should_have_correct_custom_revisions_and_autosaves_controllers_properties( $property_name, $property_value, $expected_property_value ) { + $properties = null === $property_value ? array() : array( $property_name => $property_value ); + + $post_type = new WP_Post_Type( 'test_post_type', $properties ); + + $this->assertObjectHasProperty( $property_name, $post_type, "The WP_Post_Type object does not have the expected {$property_name} property." ); + $this->assertSame( + $expected_property_value, + $post_type->$property_name, + sprintf( 'Expected the property "%s" to have the %s value.', $property_name, var_export( $expected_property_value, true ) ) + ); + } + + /** + * Data provider for test_should_allow_to_set_custom_revisions_and_autosaves_controllers_properties. + * + * @return array[] Arguments { + * @type string $property_name Property name. + * @type string $property_value Property value. + * @type string|bool $expected_property_value Expected property value. + * } + */ + public function data_should_have_correct_custom_revisions_and_autosaves_controllers_properties() { + return array( + 'autosave_rest_controller_class property' => array( + 'autosave_rest_controller_class', + 'My_Custom_Template_Autosaves_Controller', + 'My_Custom_Template_Autosaves_Controller', + ), + 'autosave_rest_controller_class property (null value)' => array( + 'autosave_rest_controller_class', + null, + false, + ), + 'revisions_rest_controller_class property' => array( + 'revisions_rest_controller_class', + 'My_Custom_Template_Revisions_Controller', + 'My_Custom_Template_Revisions_Controller', + ), + 'revisions_rest_controller_class property (null value)' => array( + 'revisions_rest_controller_class', + null, + false, + ), + ); + } + + /** + * @ticket 56922 + * + * @covers WP_Post_Type::get_revisions_rest_controller + * + * @dataProvider data_get_revisions_rest_controller_should_return_correct_values + * + * @param bool $show_in_rest Enables "show_in_rest" support. + * @param bool $supports_revisions Enables revisions support. + * @param string|bool $revisions_rest_controller_class Custom revisions REST controller class. + * @param string|null $expected_value Expected value. + */ + public function test_get_revisions_rest_controller_should_return_correct_values( $show_in_rest, $supports_revisions, $revisions_rest_controller_class, $expected_value ) { + $post_type = 'test_post_type'; + $properties = array( + 'show_in_rest' => $show_in_rest, + 'supports' => $supports_revisions ? array( 'revisions' ) : array(), + 'revisions_rest_controller_class' => $revisions_rest_controller_class, + ); + register_post_type( $post_type, $properties ); + $post_type = get_post_type_object( $post_type ); + + $controller = $post_type->get_revisions_rest_controller(); + if ( $expected_value ) { + $this->assertInstanceOf( $expected_value, $controller ); + + return; + } + + $this->assertSame( $expected_value, $controller ); + } + + /** + * Data provider for test_get_revisions_rest_controller_should_return_correct_values. + * + * @return array[] Arguments { + * @type bool $show_in_rest Enables "show_in_rest" support. + * @type bool $supports_revisions Enables revisions support. + * @type string|bool $revisions_rest_controller_class Custom revisions REST controller class. + * @type string|null $expected_value Expected value. + * } + */ + public function data_get_revisions_rest_controller_should_return_correct_values() { + return array( + 'disable show_in_rest' => array( + false, + false, + false, + null, + ), + 'disable revisions support' => array( + true, + false, + false, + null, + ), + 'default rest revisions controller' => array( + true, + true, + false, + WP_REST_Revisions_Controller::class, + ), + 'incorrect rest revisions controller' => array( + true, + true, + stdClass::class, + null, + ), + 'correct rest revisions controller' => array( + true, + true, + WP_REST_Template_Revisions_Controller::class, + WP_REST_Template_Revisions_Controller::class, + ), + ); + } + + /** + * @ticket 56922 + * + * @covers WP_Post_Type::get_autosave_rest_controller + * + * @dataProvider data_get_autosave_rest_controller_should_return_correct_values + * + * @param bool $show_in_rest Enables "show_in_rest" support. + * @param string $post_type Post type. + * @param string|bool $autosave_rest_controller_class Custom autosave REST controller class. + * @param string|null $expected_value Expected value. + */ + public function test_get_autosave_rest_controller_should_return_correct_values( $show_in_rest, $post_type, $autosave_rest_controller_class, $expected_value ) { + $properties = array( + 'show_in_rest' => $show_in_rest, + 'autosave_rest_controller_class' => $autosave_rest_controller_class, + ); + register_post_type( $post_type, $properties ); + $post_type = get_post_type_object( $post_type ); + + $controller = $post_type->get_autosave_rest_controller(); + if ( $expected_value ) { + $this->assertInstanceOf( $expected_value, $controller ); + + return; + } + + $this->assertSame( $expected_value, $controller ); + } + + /** + * Data provider for test_get_autosave_rest_controller_should_return_correct_values. + * + * @return array[] Arguments { + * @type bool $show_in_rest Enables "show_in_rest" support. + * @type string $post_type Post type. + * @type string|bool $autosave_rest_controller_class Custom autosave REST controller class. + * @type string|null $expected_value Expected value. + * } + */ + public function data_get_autosave_rest_controller_should_return_correct_values() { + return array( + 'disable show_in_rest' => array( + false, + 'attachment', + false, + null, + ), + 'invalid post type' => array( + true, + 'attachment', + false, + null, + ), + 'default rest autosave controller' => array( + true, + 'test_post_type', + false, + WP_REST_Autosaves_Controller::class, + ), + 'incorrect rest autosave controller' => array( + true, + 'test_post_type', + stdClass::class, + null, + ), + 'correct rest autosave controller' => array( + true, + 'test_post_type', + WP_REST_Template_Autosaves_Controller::class, + WP_REST_Template_Autosaves_Controller::class, + ), + ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index dfd98877d8ed3..c53f887bc822c 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -144,18 +144,18 @@ public function test_expected_routes_in_schema() { '/wp/v2/block-types/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', '/wp/v2/settings', '/wp/v2/template-parts', - '/wp/v2/template-parts/(?P[\d]+)/autosaves', '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)', - '/wp/v2/template-parts/(?P[\d]+)/autosaves/(?P[\d]+)', - '/wp/v2/template-parts/(?P[\d]+)/revisions', - '/wp/v2/template-parts/(?P[\d]+)/revisions/(?P[\d]+)', + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves', + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves/(?P[\d]+)', + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions', + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions/(?P[\d]+)', '/wp/v2/template-parts/lookup', '/wp/v2/templates', - '/wp/v2/templates/(?P[\d]+)/autosaves', '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)', - '/wp/v2/templates/(?P[\d]+)/autosaves/(?P[\d]+)', - '/wp/v2/templates/(?P[\d]+)/revisions', - '/wp/v2/templates/(?P[\d]+)/revisions/(?P[\d]+)', + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves', + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves/(?P[\d]+)', + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions', + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions/(?P[\d]+)', '/wp/v2/templates/lookup', '/wp/v2/themes', '/wp/v2/themes/(?P[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php new file mode 100644 index 0000000000000..9452a188433be --- /dev/null +++ b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php @@ -0,0 +1,413 @@ +user->create( + array( + 'role' => 'contributor', + ) + ); + + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( self::$admin_id ); + + // Set up template post. + self::$template_post = $factory->post->create_and_get( + array( + 'post_type' => self::PARENT_POST_TYPE, + 'post_name' => self::TEMPLATE_NAME, + 'post_title' => 'My Template', + 'post_content' => 'Content', + 'post_excerpt' => 'Description of my template', + 'tax_input' => array( + 'wp_theme' => array( + self::TEST_THEME, + ), + ), + ) + ); + wp_set_post_terms( self::$template_post->ID, self::TEST_THEME, 'wp_theme' ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::register_routes + * @ticket 56922 + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves', + $routes, + 'Template autosaves route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves/(?P[\d]+)', + $routes, + 'Single template autosave based on the given ID route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves', + $routes, + 'Template part autosaves route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves/(?P[\d]+)', + $routes, + 'Single template part autosave based on the given ID route does not exist.' + ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::get_context_param + * @ticket 56922 + */ + public function test_context_param() { + // Collection. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + // Collection. + $this->assertCount( + 2, + $data['endpoints'], + 'Failed to assert that the collection autosave endpoints count is 2.' + ); + $this->assertSame( + 'view', + $data['endpoints'][0]['args']['context']['default'], + 'Failed to assert that the default context for the GET collection endpoint is "view".' + ); + $this->assertSame( + array( 'view', 'embed', 'edit' ), + $data['endpoints'][0]['args']['context']['enum'], + "Failed to assert that the enum values for the GET collection endpoint are 'view', 'embed', and 'edit'." + ); + + // Single. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves/1' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( + 1, + $data['endpoints'], + 'Failed to assert that the single autosave endpoints count is 1.' + ); + $this->assertSame( + 'view', + $data['endpoints'][0]['args']['context']['default'], + 'Failed to assert that the default context for the single autosave endpoint is "view".' + ); + $this->assertSame( + array( 'view', 'embed', 'edit' ), + $data['endpoints'][0]['args']['context']['enum'], + "Failed to assert that the enum values for the single autosave endpoint are 'view', 'embed', and 'edit'." + ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::get_items + * @ticket 56922 + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $autosave_post_id = wp_create_post_autosave( + array( + 'post_content' => 'Autosave content.', + 'post_ID' => self::$template_post->ID, + 'post_type' => self::PARENT_POST_TYPE, + ) + ); + + $request = new WP_REST_Request( + 'GET', + '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves' + ); + $response = rest_get_server()->dispatch( $request ); + $autosaves = $response->get_data(); + + $this->assertCount( + 1, + $autosaves, + 'Failed asserting that the response data contains exactly 1 item.' + ); + + $this->assertSame( + $autosave_post_id, + $autosaves[0]['wp_id'], + 'Failed asserting that the ID of the autosave matches the expected autosave post ID.' + ); + $this->assertSame( + self::$template_post->ID, + $autosaves[0]['parent'], + 'Failed asserting that the parent ID of the autosave matches the template post ID.' + ); + $this->assertSame( + 'Autosave content.', + $autosaves[0]['content']['raw'], + 'Failed asserting that the content of the autosave is "Autosave content.".' + ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::get_item + * @ticket 56922 + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + + $autosave_post_id = wp_create_post_autosave( + array( + 'post_content' => 'Autosave content.', + 'post_ID' => self::$template_post->ID, + 'post_type' => self::PARENT_POST_TYPE, + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves/' . $autosave_post_id ); + $response = rest_get_server()->dispatch( $request ); + $autosave = $response->get_data(); + + $this->assertIsArray( $autosave, 'Failed asserting that the autosave is an array.' ); + $this->assertSame( + $autosave_post_id, + $autosave['wp_id'], + "Failed asserting that the autosave id is the same as $autosave_post_id." + ); + $this->assertSame( + self::$template_post->ID, + $autosave['parent'], + sprintf( + 'Failed asserting that the parent id of the autosave is the same as %s.', + self::$template_post->ID + ) + ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::prepare_item_for_response + * @ticket 56922 + */ + public function test_prepare_item() { + wp_set_current_user( self::$admin_id ); + $autosave_post_id = wp_create_post_autosave( + array( + 'post_content' => 'Autosave content.', + 'post_ID' => self::$template_post->ID, + 'post_type' => self::PARENT_POST_TYPE, + ) + ); + $autosave_db_post = get_post( $autosave_post_id ); + $template_id = self::TEST_THEME . '//' . self::TEMPLATE_NAME; + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . $template_id . '/autosaves/' . $autosave_db_post->ID ); + $controller = new WP_REST_Template_Autosaves_Controller( self::PARENT_POST_TYPE ); + $response = $controller->prepare_item_for_response( $autosave_db_post, $request ); + $this->assertInstanceOf( + WP_REST_Response::class, + $response, + 'Failed asserting that the response object is an instance of WP_REST_Response.' + ); + + $autosave = $response->get_data(); + $this->assertIsArray( $autosave, 'Failed asserting that the autosave is an array.' ); + $this->assertSame( + $autosave_db_post->ID, + $autosave['wp_id'], + "Failed asserting that the autosave id is the same as $autosave_db_post->ID." + ); + $this->assertSame( + self::$template_post->ID, + $autosave['parent'], + sprintf( + 'Failed asserting that the parent id of the autosave is the same as %s.', + self::$template_post->ID + ) + ); + + $links = $response->get_links(); + $this->assertIsArray( $links, 'Failed asserting that the links are an array.' ); + + $this->assertStringEndsWith( + $template_id . '/autosaves/' . $autosave_db_post->ID, + $links['self'][0]['href'], + "Failed asserting that the self link ends with $template_id . '/autosaves/' . $autosave_db_post->ID." + ); + + $this->assertStringEndsWith( + $template_id, + $links['parent'][0]['href'], + "Failed asserting that the parent link ends with %$template_id." + ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::get_item_schema + * @ticket 56922 + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $properties = $data['schema']['properties']; + + $this->assertCount( 16, $properties ); + $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' ); + $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' ); + $this->assertArrayHasKey( 'theme', $properties, 'Theme key should exist in properties.' ); + $this->assertArrayHasKey( 'source', $properties, 'Source key should exist in properties.' ); + $this->assertArrayHasKey( 'origin', $properties, 'Origin key should exist in properties.' ); + $this->assertArrayHasKey( 'content', $properties, 'Content key should exist in properties.' ); + $this->assertArrayHasKey( 'title', $properties, 'Title key should exist in properties.' ); + $this->assertArrayHasKey( 'description', $properties, 'description key should exist in properties.' ); + $this->assertArrayHasKey( 'status', $properties, 'status key should exist in properties.' ); + $this->assertArrayHasKey( 'wp_id', $properties, 'wp_id key should exist in properties.' ); + $this->assertArrayHasKey( 'has_theme_file', $properties, 'has_theme_file key should exist in properties.' ); + $this->assertArrayHasKey( 'author', $properties, 'author key should exist in properties.' ); + $this->assertArrayHasKey( 'modified', $properties, 'modified key should exist in properties.' ); + $this->assertArrayHasKey( 'is_custom', $properties, 'is_custom key should exist in properties.' ); + $this->assertArrayHasKey( 'parent', $properties, 'Parent key should exist in properties.' ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::create_item + * @ticket 56922 + */ + public function test_create_item() { + wp_set_current_user( self::$admin_id ); + + $template_id = self::TEST_THEME . '/' . self::TEMPLATE_NAME; + $request = new WP_REST_Request( 'POST', '/wp/v2/templates/' . $template_id . '/autosaves' ); + $request->add_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + + $request_parameters = array( + 'title' => 'Post Title', + 'content' => 'Post content', + 'excerpt' => 'Post excerpt', + 'name' => 'test', + 'id' => $template_id, + ); + + $request->set_body_params( $request_parameters ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertNotWPError( $response, 'The response from this request should not return a WP_Error object' ); + $response = rest_ensure_response( $response ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'content', $data, 'Response should contain a key called content' ); + $this->assertSame( $request_parameters['content'], $data['content']['raw'], 'Response data should match for field content' ); + + $this->assertArrayHasKey( 'title', $data, 'Response should contain a key called title' ); + $this->assertSame( $request_parameters['title'], $data['title']['raw'], 'Response data should match for field title' ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::delete_item + * @ticket 56922 + */ + public function test_create_item_incorrect_permission() { + wp_set_current_user( self::$contributor_id ); + $template_id = self::TEST_THEME . '/' . self::TEMPLATE_NAME; + $request = new WP_REST_Request( 'POST', '/wp/v2/templates/' . $template_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_manage_templates', $response, WP_Http::FORBIDDEN ); + } + + /** + * @covers WP_REST_Template_Autosaves_Controller::delete_item + * @ticket 56922 + */ + public function test_create_item_no_permission() { + wp_set_current_user( 0 ); + $template_id = self::TEST_THEME . '/' . self::TEMPLATE_NAME; + $request = new WP_REST_Request( 'POST', '/wp/v2/templates/' . $template_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_manage_templates', $response, WP_Http::UNAUTHORIZED ); + } + + /** + * @coversNothing + * @ticket 56922 + */ + public function test_update_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to update template autosaves.", + WP_REST_Template_Autosaves_Controller::class + ) + ); + } + + /** + * @coversNothing + * @ticket 56922 + */ + public function test_delete_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to delete template autosaves.", + WP_REST_Template_Autosaves_Controller::class + ) + ); + } +} diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php new file mode 100644 index 0000000000000..91370b21c726b --- /dev/null +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -0,0 +1,511 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( self::$admin_id ); + + self::$contributor_id = $factory->user->create( + array( + 'role' => 'contributor', + ) + ); + + // Set up template post. + self::$template_post = $factory->post->create_and_get( + array( + 'post_type' => self::PARENT_POST_TYPE, + 'post_name' => self::TEMPLATE_NAME, + 'post_title' => 'My Template', + 'post_content' => 'Content', + 'post_excerpt' => 'Description of my template', + 'tax_input' => array( + 'wp_theme' => array( + self::TEST_THEME, + ), + ), + ) + ); + wp_set_post_terms( self::$template_post->ID, self::TEST_THEME, 'wp_theme' ); + + // Update post to create a new revisions. + self::$revisions[] = _wp_put_post_revision( + array( + 'ID' => self::$template_post->ID, + 'post_content' => 'Content revision #2', + ) + ); + + // Update post to create a new revisions. + self::$revisions[] = _wp_put_post_revision( + array( + 'ID' => self::$template_post->ID, + 'post_content' => 'Content revision #3', + ) + ); + + // Update post to create a new revisions. + self::$revisions[] = _wp_put_post_revision( + array( + 'ID' => self::$template_post->ID, + 'post_content' => 'Content revision #4', + ) + ); + + // Update post to create a new revisions. + self::$revisions[] = _wp_put_post_revision( + array( + 'ID' => self::$template_post->ID, + 'post_content' => 'Content revision #5', + ) + ); + } + + /** + * Remove revisions when tests are complete. + */ + public static function wpTearDownAfterClass() { + // Also deletes revisions. + foreach ( self::$revisions as $revision ) { + wp_delete_post( $revision, true ); + } + } + + /** + * @covers WP_REST_Template_Revisions_Controller::register_routes + * @ticket 56922 + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions', + $routes, + 'Template revisions route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions/(?P[\d]+)', + $routes, + 'Single template revision based on the given ID route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions', + $routes, + 'Template part revisions route does not exist.' + ); + $this->assertArrayHasKey( + '/wp/v2/template-parts/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions/(?P[\d]+)', + $routes, + 'Single template part revision based on the given ID route does not exist.' + ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_context_param + * @ticket 56922 + */ + public function test_context_param() { + // Collection. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( + 'view', + $data['endpoints'][0]['args']['context']['default'], + 'Failed to assert that the default context for the collection endpoint is "view".' + ); + $this->assertSame( + array( 'view', 'embed', 'edit' ), + $data['endpoints'][0]['args']['context']['enum'], + 'Failed to assert correct enum values for the collection endpoint.' + ); + + // Single. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/1' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( + 2, + $data['endpoints'], + 'Failed to assert that the single revision endpoint count is 2.' + ); + $this->assertSame( + 'view', + $data['endpoints'][0]['args']['context']['default'], + 'Failed to assert that the default context for the single revision endpoint is "view".' + ); + $this->assertSame( + array( 'view', 'embed', 'edit' ), + $data['endpoints'][0]['args']['context']['enum'], + 'Failed to assert correct enum values for the single revision endpoint.' + ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_items + * @ticket 56922 + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( + 'GET', + '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions' + ); + $response = rest_get_server()->dispatch( $request ); + $revisions = $response->get_data(); + + $this->assertCount( + 4, + $revisions, + 'Failed asserting that the response data contains exactly 4 items.' + ); + + $this->assertSame( + self::$template_post->ID, + $revisions[0]['parent'], + 'Failed asserting that the parent ID of the revision matches the template post ID.' + ); + $this->assertSame( + 'Content revision #5', + $revisions[0]['content']['raw'], + 'Failed asserting that the content of the revision is "Content revision #5".' + ); + + $this->assertSame( + self::$template_post->ID, + $revisions[1]['parent'], + 'Failed asserting that the parent ID of the revision matches the template post ID.' + ); + $this->assertSame( + 'Content revision #4', + $revisions[1]['content']['raw'], + 'Failed asserting that the content of the revision is "Content revision #4".' + ); + + $this->assertSame( + self::$template_post->ID, + $revisions[2]['parent'], + 'Failed asserting that the parent ID of the revision matches the template post ID.' + ); + $this->assertSame( + 'Content revision #3', + $revisions[2]['content']['raw'], + 'Failed asserting that the content of the revision is "Content revision #3".' + ); + + $this->assertSame( + self::$template_post->ID, + $revisions[3]['parent'], + 'Failed asserting that the parent ID of the revision matches the template post ID.' + ); + $this->assertSame( + 'Content revision #2', + $revisions[3]['content']['raw'], + 'Failed asserting that the content of the revision is "Content revision #2".' + ); + } + + + /** + * @covers WP_REST_Template_Revisions_Controller::get_items_permissions_check + * @ticket 56922 + */ + public function test_get_items_endpoint_should_return_unauthorized_https_status_code_for_unauthorized_request() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, WP_Http::UNAUTHORIZED ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_items_permissions_check + * @ticket 56922 + */ + public function test_get_items_endpoint_should_return_forbidden_https_status_code_for_users_with_insufficient_permissions() { + wp_set_current_user( self::$contributor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, WP_Http::FORBIDDEN ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_item + * @ticket 56922 + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + + $revisions = wp_get_post_revisions( self::$template_post, array( 'fields' => 'ids' ) ); + $revision_id = array_shift( $revisions ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id ); + $response = rest_get_server()->dispatch( $request ); + $revision = $response->get_data(); + + $this->assertIsArray( $revision, 'Failed asserting that the revision is an array.' ); + $this->assertSame( + $revision_id, + $revision['wp_id'], + "Failed asserting that the revision id is the same as $revision_id" + ); + $this->assertSame( + self::$template_post->ID, + $revision['parent'], + sprintf( + 'Failed asserting that the parent id of the revision is the same as %s.', + self::$template_post->ID + ) + ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_item + * @ticket 56922 + */ + public function test_get_item_not_found() { + wp_set_current_user( self::$admin_id ); + + $revisions = wp_get_post_revisions( self::$template_post, array( 'fields' => 'ids' ) ); + $revision_id = array_shift( $revisions ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/invalid//parent/revisions/' . $revision_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, WP_Http::NOT_FOUND ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::prepare_item_for_response + * @ticket 56922 + */ + public function test_prepare_item() { + $revisions = wp_get_post_revisions( self::$template_post, array( 'fields' => 'ids' ) ); + $revision_id = array_shift( $revisions ); + $post = get_post( $revision_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id ); + $controller = new WP_REST_Template_Revisions_Controller( self::PARENT_POST_TYPE ); + $response = $controller->prepare_item_for_response( $post, $request ); + $this->assertInstanceOf( + WP_REST_Response::class, + $response, + 'Failed asserting that the response object is an instance of WP_REST_Response.' + ); + + $revision = $response->get_data(); + $this->assertIsArray( $revision, 'Failed asserting that the revision is an array.' ); + $this->assertSame( + $revision_id, + $revision['wp_id'], + "Failed asserting that the revision id is the same as $revision_id." + ); + $this->assertSame( + self::$template_post->ID, + $revision['parent'], + sprintf( + 'Failed asserting that the parent id of the revision is the same as %s.', + self::$template_post->ID + ) + ); + + $links = $response->get_links(); + $this->assertIsArray( $links, 'Failed asserting that the links are an array.' ); + + $this->assertStringEndsWith( + self::TEST_THEME . '//' . self::TEMPLATE_NAME . '/revisions/' . $revision_id, + $links['self'][0]['href'], + sprintf( + 'Failed asserting that the self link ends with %s.', + self::TEST_THEME . '//' . self::TEMPLATE_NAME . '/revisions/' . $revision_id + ) + ); + + $this->assertStringEndsWith( + self::TEST_THEME . '//' . self::TEMPLATE_NAME, + $links['parent'][0]['href'], + sprintf( + 'Failed asserting that the parent link ends with %s.', + self::TEST_THEME . '//' . self::TEMPLATE_NAME + ) + ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_item_schema + * @ticket 56922 + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $properties = $data['schema']['properties']; + + $this->assertCount( 16, $properties ); + $this->assertArrayHasKey( 'id', $properties, 'ID key should exist in properties.' ); + $this->assertArrayHasKey( 'slug', $properties, 'Slug key should exist in properties.' ); + $this->assertArrayHasKey( 'theme', $properties, 'Theme key should exist in properties.' ); + $this->assertArrayHasKey( 'source', $properties, 'Source key should exist in properties.' ); + $this->assertArrayHasKey( 'origin', $properties, 'Origin key should exist in properties.' ); + $this->assertArrayHasKey( 'content', $properties, 'Content key should exist in properties.' ); + $this->assertArrayHasKey( 'title', $properties, 'Title key should exist in properties.' ); + $this->assertArrayHasKey( 'description', $properties, 'description key should exist in properties.' ); + $this->assertArrayHasKey( 'status', $properties, 'status key should exist in properties.' ); + $this->assertArrayHasKey( 'wp_id', $properties, 'wp_id key should exist in properties.' ); + $this->assertArrayHasKey( 'has_theme_file', $properties, 'has_theme_file key should exist in properties.' ); + $this->assertArrayHasKey( 'author', $properties, 'author key should exist in properties.' ); + $this->assertArrayHasKey( 'modified', $properties, 'modified key should exist in properties.' ); + $this->assertArrayHasKey( 'is_custom', $properties, 'is_custom key should exist in properties.' ); + $this->assertArrayHasKey( 'parent', $properties, 'Parent key should exist in properties.' ); + } + + /** + * @coversNothing + * @ticket 56922 + */ + public function test_create_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to create template revisions.", + WP_REST_Template_Revisions_Controller::class + ) + ); + } + + /** + * @coversNothing + * @ticket 56922 + */ + public function test_update_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to update template revisions.", + WP_REST_Template_Revisions_Controller::class + ) + ); + } + + /** + * @covers WP_REST_Templates_Controller::delete_item + * @ticket 56922 + */ + public function test_delete_item() { + wp_set_current_user( self::$admin_id ); + + $revision_id = _wp_put_post_revision( self::$template_post ); + self::$revisions[] = $revision_id; + + $request = new WP_REST_Request( 'DELETE', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status(), 'Failed asserting that the response status is 200.' ); + $this->assertNull( get_post( $revision_id ), 'Failed asserting that the post with the given revision ID is deleted.' ); + } + + /** + * @covers WP_REST_Templates_Controller::delete_item + * @ticket 56922 + */ + public function test_delete_item_incorrect_permission() { + wp_set_current_user( self::$contributor_id ); + $revision_id = _wp_put_post_revision( self::$template_post ); + self::$revisions[] = $revision_id; + + $request = new WP_REST_Request( 'DELETE', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, WP_Http::FORBIDDEN ); + } + + /** + * @covers WP_REST_Templates_Controller::delete_item + * @ticket 56922 + */ + public function test_delete_item_no_permission() { + wp_set_current_user( 0 ); + $revision_id = _wp_put_post_revision( self::$template_post ); + self::$revisions[] = $revision_id; + + $request = new WP_REST_Request( 'DELETE', '/wp/v2/templates/' . self::TEST_THEME . '/' . self::TEMPLATE_NAME . '/revisions/' . $revision_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, WP_Http::UNAUTHORIZED ); + } + + /** + * @covers WP_REST_Template_Revisions_Controller::get_item + * @ticket 56922 + */ + public function test_delete_item_not_found() { + wp_set_current_user( self::$admin_id ); + + $revision_id = _wp_put_post_revision( self::$template_post ); + self::$revisions[] = $revision_id; + + $request = new WP_REST_Request( 'DELETE', '/wp/v2/templates/invalid//parent/revisions/' . $revision_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, WP_Http::NOT_FOUND ); + } +} diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 8341830452a1f..d212facf26006 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -2707,7 +2707,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/media": { + "/wp/v2/menu-items": { "namespace": "wp/v2", "methods": [ "GET", @@ -2718,6 +2718,9 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -2740,7 +2743,7 @@ mockedApiResponse.Schema = { "per_page": { "description": "Maximum number of items to be returned in result set.", "type": "integer", - "default": 10, + "default": 100, "minimum": 1, "maximum": 100, "required": false @@ -2762,24 +2765,6 @@ mockedApiResponse.Schema = { "format": "date-time", "required": false }, - "author": { - "description": "Limit result set to posts assigned to specific authors.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "author_exclude": { - "description": "Ensure result set excludes posts assigned to specific authors.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, "before": { "description": "Limit response to posts published before a given ISO8601 compliant date.", "type": "string", @@ -2818,7 +2803,7 @@ mockedApiResponse.Schema = { "order": { "description": "Order sort attribute ascending or descending.", "type": "string", - "default": "desc", + "default": "asc", "enum": [ "asc", "desc" @@ -2826,9 +2811,9 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by post attribute.", + "description": "Sort collection by object attribute.", "type": "string", - "default": "date", + "default": "menu_order", "enum": [ "author", "date", @@ -2839,28 +2824,11 @@ mockedApiResponse.Schema = { "relevance", "slug", "include_slugs", - "title" + "title", + "menu_order" ], "required": false }, - "parent": { - "description": "Limit result set to items with particular parent IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "parent_exclude": { - "description": "Limit result set to all items except those of a particular parent ID.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, "search_columns": { "default": [], "description": "Array of column names to be searched.", @@ -2884,36 +2852,118 @@ mockedApiResponse.Schema = { "required": false }, "status": { - "default": "inherit", + "default": "publish", "description": "Limit result set to posts assigned one or more statuses.", "type": "array", "items": { "enum": [ - "inherit", + "publish", + "future", + "draft", + "pending", "private", - "trash" + "trash", + "auto-draft", + "inherit", + "request-pending", + "request-confirmed", + "request-failed", + "request-completed", + "any" ], "type": "string" }, "required": false }, - "media_type": { - "default": null, - "description": "Limit result set to attachments of a particular media type.", + "tax_relation": { + "description": "Limit result set based on relationship between multiple taxonomies.", "type": "string", "enum": [ - "image", - "video", - "text", - "application", - "audio" + "AND", + "OR" ], "required": false }, - "mime_type": { - "default": null, - "description": "Limit result set to attachments of a particular MIME type.", - "type": "string", + "menus": { + "description": "Limit result set to items with specific terms assigned in the menus taxonomy.", + "type": [ + "object", + "array" + ], + "oneOf": [ + { + "title": "Term ID List", + "description": "Match terms with the listed IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + { + "title": "Term ID Taxonomy Query", + "description": "Perform an advanced term query.", + "type": "object", + "properties": { + "terms": { + "description": "Term IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [] + }, + "operator": { + "description": "Whether items must be assigned all or any of the specified terms.", + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "OR" + } + }, + "additionalProperties": false + } + ], + "required": false + }, + "menus_exclude": { + "description": "Limit result set to items except those with specific terms assigned in the menus taxonomy.", + "type": [ + "object", + "array" + ], + "oneOf": [ + { + "title": "Term ID List", + "description": "Match terms with the listed IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + { + "title": "Term ID Taxonomy Query", + "description": "Perform an advanced term query.", + "type": "object", + "properties": { + "terms": { + "description": "Term IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [] + } + }, + "additionalProperties": false + } + ], + "required": false + }, + "menu_order": { + "description": "Limit result set to posts with a specific menu_order value.", + "type": "integer", "required": false } } @@ -2922,55 +2972,26 @@ mockedApiResponse.Schema = { "methods": [ "POST" ], + "allow_batch": { + "v1": true + }, "args": { - "date": { - "description": "The date the post was published, in the site's timezone.", - "type": [ - "string", - "null" - ], - "format": "date-time", - "required": false - }, - "date_gmt": { - "description": "The date the post was published, as GMT.", + "title": { + "description": "The title for the object.", "type": [ "string", - "null" - ], - "format": "date-time", - "required": false - }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", - "type": "string", - "required": false - }, - "status": { - "description": "A named status for the post.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" + "object" ], - "required": false - }, - "title": { - "description": "The title for the post.", - "type": "object", "properties": { "raw": { - "description": "Title for the post, as it exists in the database.", + "description": "Title for the object, as it exists in the database.", "type": "string", "context": [ "edit" ] }, "rendered": { - "description": "HTML title for the post, transformed for display.", + "description": "HTML title for the object, transformed for display.", "type": "string", "context": [ "view", @@ -2982,817 +3003,40 @@ mockedApiResponse.Schema = { }, "required": false }, - "author": { - "description": "The ID for the author of the post.", - "type": "integer", - "required": false - }, - "comment_status": { - "description": "Whether or not comments are open on the post.", + "type": { + "default": "custom", + "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", "type": "string", "enum": [ - "open", - "closed" + "taxonomy", + "post_type", + "post_type_archive", + "custom" ], "required": false }, - "ping_status": { - "description": "Whether or not the post can be pinged.", + "status": { + "default": "publish", + "description": "A named status for the object.", "type": "string", "enum": [ - "open", - "closed" + "publish", + "future", + "draft", + "pending", + "private" ], "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false - }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", + "parent": { + "default": 0, + "description": "The ID for the parent of the object.", + "type": "integer", + "minimum": 0, "required": false }, - "alt_text": { - "description": "Alternative text to display when attachment is not displayed.", - "type": "string", - "required": false - }, - "caption": { - "description": "The attachment caption.", - "type": "object", - "properties": { - "raw": { - "description": "Caption for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML caption for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "description": { - "description": "The attachment description.", - "type": "object", - "properties": { - "raw": { - "description": "Description for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML description for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit" - ], - "readonly": true - } - }, - "required": false - }, - "post": { - "description": "The ID for the associated post of the attachment.", - "type": "integer", - "required": false - } - } - } - ], - "_links": { - "self": "http://example.org/index.php?rest_route=/wp/v2/media" - } - }, - "/wp/v2/media/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "id": { - "description": "Unique identifier for the post.", - "type": "integer", - "required": false - }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - } - } - }, - { - "methods": [ - "POST", - "PUT", - "PATCH" - ], - "args": { - "id": { - "description": "Unique identifier for the post.", - "type": "integer", - "required": false - }, - "date": { - "description": "The date the post was published, in the site's timezone.", - "type": [ - "string", - "null" - ], - "format": "date-time", - "required": false - }, - "date_gmt": { - "description": "The date the post was published, as GMT.", - "type": [ - "string", - "null" - ], - "format": "date-time", - "required": false - }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", - "type": "string", - "required": false - }, - "status": { - "description": "A named status for the post.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "title": { - "description": "The title for the post.", - "type": "object", - "properties": { - "raw": { - "description": "Title for the post, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML title for the post, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "author": { - "description": "The ID for the author of the post.", - "type": "integer", - "required": false - }, - "comment_status": { - "description": "Whether or not comments are open on the post.", - "type": "string", - "enum": [ - "open", - "closed" - ], - "required": false - }, - "ping_status": { - "description": "Whether or not the post can be pinged.", - "type": "string", - "enum": [ - "open", - "closed" - ], - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false - }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", - "required": false - }, - "alt_text": { - "description": "Alternative text to display when attachment is not displayed.", - "type": "string", - "required": false - }, - "caption": { - "description": "The attachment caption.", - "type": "object", - "properties": { - "raw": { - "description": "Caption for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML caption for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "description": { - "description": "The attachment description.", - "type": "object", - "properties": { - "raw": { - "description": "Description for the attachment, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML description for the attachment, transformed for display.", - "type": "string", - "context": [ - "view", - "edit" - ], - "readonly": true - } - }, - "required": false - }, - "post": { - "description": "The ID for the associated post of the attachment.", - "type": "integer", - "required": false - } - } - }, - { - "methods": [ - "DELETE" - ], - "args": { - "id": { - "description": "Unique identifier for the post.", - "type": "integer", - "required": false - }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", - "required": false - } - } - } - ] - }, - "/wp/v2/media/(?P[\\d]+)/post-process": { - "namespace": "wp/v2", - "methods": [ - "POST" - ], - "endpoints": [ - { - "methods": [ - "POST" - ], - "args": { - "id": { - "description": "Unique identifier for the attachment.", - "type": "integer", - "required": false - }, - "action": { - "type": "string", - "enum": [ - "create-image-subsizes" - ], - "required": true - } - } - } - ] - }, - "/wp/v2/media/(?P[\\d]+)/edit": { - "namespace": "wp/v2", - "methods": [ - "POST" - ], - "endpoints": [ - { - "methods": [ - "POST" - ], - "args": { - "src": { - "description": "URL to the edited image file.", - "type": "string", - "format": "uri", - "required": true - }, - "modifiers": { - "description": "Array of image edits.", - "type": "array", - "minItems": 1, - "items": { - "description": "Image edit.", - "type": "object", - "required": [ - "type", - "args" - ], - "oneOf": [ - { - "title": "Rotation", - "properties": { - "type": { - "description": "Rotation type.", - "type": "string", - "enum": [ - "rotate" - ] - }, - "args": { - "description": "Rotation arguments.", - "type": "object", - "required": [ - "angle" - ], - "properties": { - "angle": { - "description": "Angle to rotate clockwise in degrees.", - "type": "number" - } - } - } - } - }, - { - "title": "Crop", - "properties": { - "type": { - "description": "Crop type.", - "type": "string", - "enum": [ - "crop" - ] - }, - "args": { - "description": "Crop arguments.", - "type": "object", - "required": [ - "left", - "top", - "width", - "height" - ], - "properties": { - "left": { - "description": "Horizontal position from the left to begin the crop as a percentage of the image width.", - "type": "number" - }, - "top": { - "description": "Vertical position from the top to begin the crop as a percentage of the image height.", - "type": "number" - }, - "width": { - "description": "Width of the crop as a percentage of the image width.", - "type": "number" - }, - "height": { - "description": "Height of the crop as a percentage of the image height.", - "type": "number" - } - } - } - } - } - ] - }, - "required": false - }, - "rotation": { - "description": "The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.", - "type": "integer", - "minimum": 0, - "exclusiveMinimum": true, - "maximum": 360, - "exclusiveMaximum": true, - "required": false - }, - "x": { - "description": "As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead.", - "type": "number", - "minimum": 0, - "maximum": 100, - "required": false - }, - "y": { - "description": "As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.", - "type": "number", - "minimum": 0, - "maximum": 100, - "required": false - }, - "width": { - "description": "As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead.", - "type": "number", - "minimum": 0, - "maximum": 100, - "required": false - }, - "height": { - "description": "As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead.", - "type": "number", - "minimum": 0, - "maximum": 100, - "required": false - } - } - } - ] - }, - "/wp/v2/menu-items": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "allow_batch": { - "v1": true - }, - "args": { - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - }, - "page": { - "description": "Current page of the collection.", - "type": "integer", - "default": 1, - "minimum": 1, - "required": false - }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "default": 100, - "minimum": 1, - "maximum": 100, - "required": false - }, - "search": { - "description": "Limit results to those matching a string.", - "type": "string", - "required": false - }, - "after": { - "description": "Limit response to posts published after a given ISO8601 compliant date.", - "type": "string", - "format": "date-time", - "required": false - }, - "modified_after": { - "description": "Limit response to posts modified after a given ISO8601 compliant date.", - "type": "string", - "format": "date-time", - "required": false - }, - "before": { - "description": "Limit response to posts published before a given ISO8601 compliant date.", - "type": "string", - "format": "date-time", - "required": false - }, - "modified_before": { - "description": "Limit response to posts modified before a given ISO8601 compliant date.", - "type": "string", - "format": "date-time", - "required": false - }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", - "required": false - }, - "order": { - "description": "Order sort attribute ascending or descending.", - "type": "string", - "default": "asc", - "enum": [ - "asc", - "desc" - ], - "required": false - }, - "orderby": { - "description": "Sort collection by object attribute.", - "type": "string", - "default": "menu_order", - "enum": [ - "author", - "date", - "id", - "include", - "modified", - "parent", - "relevance", - "slug", - "include_slugs", - "title", - "menu_order" - ], - "required": false - }, - "search_columns": { - "default": [], - "description": "Array of column names to be searched.", - "type": "array", - "items": { - "enum": [ - "post_title", - "post_content", - "post_excerpt" - ], - "type": "string" - }, - "required": false - }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "status": { - "default": "publish", - "description": "Limit result set to posts assigned one or more statuses.", - "type": "array", - "items": { - "enum": [ - "publish", - "future", - "draft", - "pending", - "private", - "trash", - "auto-draft", - "inherit", - "request-pending", - "request-confirmed", - "request-failed", - "request-completed", - "any" - ], - "type": "string" - }, - "required": false - }, - "tax_relation": { - "description": "Limit result set based on relationship between multiple taxonomies.", - "type": "string", - "enum": [ - "AND", - "OR" - ], - "required": false - }, - "menus": { - "description": "Limit result set to items with specific terms assigned in the menus taxonomy.", - "type": [ - "object", - "array" - ], - "oneOf": [ - { - "title": "Term ID List", - "description": "Match terms with the listed IDs.", - "type": "array", - "items": { - "type": "integer" - } - }, - { - "title": "Term ID Taxonomy Query", - "description": "Perform an advanced term query.", - "type": "object", - "properties": { - "terms": { - "description": "Term IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [] - }, - "operator": { - "description": "Whether items must be assigned all or any of the specified terms.", - "type": "string", - "enum": [ - "AND", - "OR" - ], - "default": "OR" - } - }, - "additionalProperties": false - } - ], - "required": false - }, - "menus_exclude": { - "description": "Limit result set to items except those with specific terms assigned in the menus taxonomy.", - "type": [ - "object", - "array" - ], - "oneOf": [ - { - "title": "Term ID List", - "description": "Match terms with the listed IDs.", - "type": "array", - "items": { - "type": "integer" - } - }, - { - "title": "Term ID Taxonomy Query", - "description": "Perform an advanced term query.", - "type": "object", - "properties": { - "terms": { - "description": "Term IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [] - } - }, - "additionalProperties": false - } - ], - "required": false - }, - "menu_order": { - "description": "Limit result set to posts with a specific menu_order value.", - "type": "integer", - "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "allow_batch": { - "v1": true - }, - "args": { - "title": { - "description": "The title for the object.", - "type": [ - "string", - "object" - ], - "properties": { - "raw": { - "description": "Title for the object, as it exists in the database.", - "type": "string", - "context": [ - "edit" - ] - }, - "rendered": { - "description": "HTML title for the object, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "type": { - "default": "custom", - "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", - "type": "string", - "enum": [ - "taxonomy", - "post_type", - "post_type_archive", - "custom" - ], - "required": false - }, - "status": { - "default": "publish", - "description": "A named status for the object.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "parent": { - "default": 0, - "description": "The ID for the parent of the object.", - "type": "integer", - "minimum": 0, - "required": false - }, - "attr_title": { - "description": "Text for the title attribute of the link element for this menu item.", + "attr_title": { + "description": "Text for the title attribute of the link element for this menu item.", "type": "string", "required": false }, @@ -4631,33 +3875,316 @@ mockedApiResponse.Schema = { "properties": [], "required": false }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + }, + "wp_pattern_category": { + "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", + "type": "array", + "items": { + "type": "integer" + }, + "required": false + } + } + } + ], + "_links": { + "self": "http://example.org/index.php?rest_route=/wp/v2/blocks" + } + }, + "/wp/v2/blocks/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "POST", + "PUT", + "PATCH" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit" + ] + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit" + ] + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + }, + "wp_pattern_category": { + "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", + "type": "array", + "items": { + "type": "integer" + }, + "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", + "required": false + } + } + } + ] + }, + "/wp/v2/blocks/(?P[\\d]+)/revisions": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", + "include": { + "description": "Limit result set to specific IDs.", "type": "array", "items": { "type": "integer" }, + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by object attribute.", + "type": "string", + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], "required": false } } } - ], - "_links": { - "self": "http://example.org/index.php?rest_route=/wp/v2/blocks" - } + ] }, - "/wp/v2/blocks/(?P[\\d]+)": { + "/wp/v2/blocks/(?P[\\d]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", "DELETE" ], "endpoints": [ @@ -4665,12 +4192,14 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, @@ -4684,26 +4213,71 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false }, - "password": { - "description": "The password for the post if it is password protected.", + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", + "required": false + } + } + } + ] + }, + "/wp/v2/blocks/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } }, { "methods": [ - "POST", - "PUT", - "PATCH" + "POST" ], - "allow_batch": { - "v1": true - }, "args": { - "id": { - "description": "Unique identifier for the post.", + "parent": { + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, @@ -4815,31 +4389,46 @@ mockedApiResponse.Schema = { "required": false } } - }, + } + ] + }, + "/wp/v2/blocks/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ { "methods": [ - "DELETE" + "GET" ], - "allow_batch": { - "v1": true - }, "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "The ID for the autosave.", "type": "integer", "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } } ] }, - "/wp/v2/blocks/(?P[\\d]+)/revisions": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { "namespace": "wp/v2", "methods": [ "GET" @@ -4851,8 +4440,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "context": { @@ -4937,7 +4526,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/blocks/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -4950,8 +4539,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -4978,8 +4567,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -4997,7 +4586,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/blocks/(?P[\\d]+)/autosaves": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { "namespace": "wp/v2", "methods": [ "GET", @@ -5009,9 +4598,9 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "context": { @@ -5032,89 +4621,73 @@ mockedApiResponse.Schema = { "POST" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, - "date": { - "description": "The date the post was published, in the site's timezone.", - "type": [ - "string", - "null" - ], - "format": "date-time", - "required": false - }, - "date_gmt": { - "description": "The date the post was published, as GMT.", - "type": [ - "string", - "null" - ], - "format": "date-time", + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", + "description": "Unique slug identifying the template.", "type": "string", + "minLength": 1, + "pattern": "[a-zA-Z0-9_\\%-]+", "required": false }, - "status": { - "description": "A named status for the post.", + "theme": { + "description": "Theme identifier for the template.", "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "type": { + "description": "Type of template.", "type": "string", "required": false }, - "title": { - "description": "The title for the post.", - "type": "object", + "content": { + "description": "Content of template.", + "type": [ + "object", + "string" + ], "properties": { "raw": { - "description": "Title for the post, as it exists in the database.", + "description": "Content for the template, as it exists in the database.", "type": "string", "context": [ "view", "edit" ] + }, + "block_version": { + "description": "Version of the content block format used by the template.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true } }, "required": false }, - "content": { - "description": "The content for the post.", - "type": "object", + "title": { + "description": "Title of template.", + "type": [ + "object", + "string" + ], "properties": { "raw": { - "description": "Content for the post, as it exists in the database.", + "description": "Title for the template, as it exists in the database.", "type": "string", "context": [ "view", - "edit" + "edit", + "embed" ] }, - "block_version": { - "description": "Version of the content block format used by the post.", - "type": "integer", - "context": [ - "edit" - ], - "readonly": true - }, - "protected": { - "description": "Whether the content is protected with a password.", - "type": "boolean", + "rendered": { + "description": "HTML title for the template, transformed for display.", + "type": "string", "context": [ "view", "edit", @@ -5125,30 +4698,33 @@ mockedApiResponse.Schema = { }, "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "description": { + "description": "Description of template.", + "type": "string", "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "status": { + "description": "Status of template.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", - "type": "array", - "items": { - "type": "integer" - }, + "author": { + "description": "The ID for the author of the template.", + "type": "integer", "required": false } } } ] }, - "/wp/v2/blocks/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET" @@ -5160,8 +4736,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -5536,7 +5112,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P[\\d]+)/revisions": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { "namespace": "wp/v2", "methods": [ "GET" @@ -5548,8 +5124,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "context": { @@ -5634,7 +5210,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -5647,8 +5223,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -5675,8 +5251,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -5694,7 +5270,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P[\\d]+)/autosaves": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { "namespace": "wp/v2", "methods": [ "GET", @@ -5706,204 +5282,20 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, - "slug": { - "description": "Unique slug identifying the template.", - "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", - "required": false - }, - "theme": { - "description": "Theme identifier for the template.", - "type": "string", - "required": false - }, - "type": { - "description": "Type of template.", - "type": "string", - "required": false - }, - "content": { - "description": "Content of template.", - "type": [ - "object", - "string" - ], - "properties": { - "raw": { - "description": "Content for the template, as it exists in the database.", - "type": "string", - "context": [ - "view", - "edit" - ] - }, - "block_version": { - "description": "Version of the content block format used by the template.", - "type": "integer", - "context": [ - "edit" - ], - "readonly": true - } - }, - "required": false - }, - "title": { - "description": "Title of template.", - "type": [ - "object", - "string" - ], - "properties": { - "raw": { - "description": "Title for the template, as it exists in the database.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ] - }, - "rendered": { - "description": "HTML title for the template, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - } - }, - "required": false - }, - "description": { - "description": "Description of template.", - "type": "string", - "required": false - }, - "status": { - "description": "Status of template.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "author": { - "description": "The ID for the author of the template.", - "type": "integer", - "required": false - } - } - } - ] - }, - "/wp/v2/templates/(?P[\\d]+)/autosaves/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, "id": { - "description": "The ID for the autosave.", - "type": "integer", - "required": false - }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "description": "The id of a template", "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", "required": false - } - } - } - ] - }, - "/wp/v2/template-parts": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { + }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", "enum": [ "view", "embed", - "edit" - ], - "default": "view", - "required": false - }, - "wp_id": { - "description": "Limit to the specified post id.", - "type": "integer", - "required": false - }, - "area": { - "description": "Limit to the specified template part area.", - "type": "string", - "required": false - }, - "post_type": { - "description": "Post type to get the templates for.", - "type": "string", + "edit" + ], + "default": "view", "required": false } } @@ -5913,12 +5305,17 @@ mockedApiResponse.Schema = { "POST" ], "args": { + "id": { + "description": "The id of a template", + "type": "string", + "required": false + }, "slug": { "description": "Unique slug identifying the template.", "type": "string", "minLength": 1, "pattern": "[a-zA-Z0-9_\\%-]+", - "required": true + "required": false }, "theme": { "description": "Theme identifier for the template.", @@ -5931,7 +5328,6 @@ mockedApiResponse.Schema = { "required": false }, "content": { - "default": "", "description": "Content of template.", "type": [ "object", @@ -5958,7 +5354,6 @@ mockedApiResponse.Schema = { "required": false }, "title": { - "default": "", "description": "Title of template.", "type": [ "object", @@ -5988,13 +5383,11 @@ mockedApiResponse.Schema = { "required": false }, "description": { - "default": "", "description": "Description of template.", "type": "string", "required": false }, "status": { - "default": "publish", "description": "Status of template.", "type": "string", "enum": [ @@ -6018,16 +5411,9 @@ mockedApiResponse.Schema = { } } } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts" - } - ] - } + ] }, - "/wp/v2/template-parts/lookup": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET" @@ -6038,40 +5424,36 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "slug": { - "description": "The slug of the template to get the fallback for", + "parent": { + "description": "The id of a template", "type": "string", - "required": true + "required": false }, - "is_custom": { - "description": "Indicates if a template is custom or part of the template hierarchy", - "type": "boolean", + "id": { + "description": "The ID for the autosave.", + "type": "integer", "required": false }, - "template_prefix": { - "description": "The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts/lookup" - } - ] - } + ] }, - "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { + "/wp/v2/template-parts": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", - "DELETE" + "POST" ], "endpoints": [ { @@ -6079,11 +5461,6 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "id": { - "description": "The id of a template", - "type": "string", - "required": false - }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", @@ -6094,27 +5471,35 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + }, + "wp_id": { + "description": "Limit to the specified post id.", + "type": "integer", + "required": false + }, + "area": { + "description": "Limit to the specified template part area.", + "type": "string", + "required": false + }, + "post_type": { + "description": "Post type to get the templates for.", + "type": "string", + "required": false } } }, { "methods": [ - "POST", - "PUT", - "PATCH" + "POST" ], "args": { - "id": { - "description": "The id of a template", - "type": "string", - "required": false - }, "slug": { "description": "Unique slug identifying the template.", "type": "string", "minLength": 1, "pattern": "[a-zA-Z0-9_\\%-]+", - "required": false + "required": true }, "theme": { "description": "Theme identifier for the template.", @@ -6127,6 +5512,7 @@ mockedApiResponse.Schema = { "required": false }, "content": { + "default": "", "description": "Content of template.", "type": [ "object", @@ -6153,6 +5539,7 @@ mockedApiResponse.Schema = { "required": false }, "title": { + "default": "", "description": "Title of template.", "type": [ "object", @@ -6182,11 +5569,13 @@ mockedApiResponse.Schema = { "required": false }, "description": { + "default": "", "description": "Description of template.", "type": "string", "required": false }, "status": { + "default": "publish", "description": "Status of template.", "type": "string", "enum": [ @@ -6209,190 +5598,61 @@ mockedApiResponse.Schema = { "required": false } } - }, - { - "methods": [ - "DELETE" - ], - "args": { - "id": { - "description": "The id of a template", - "type": "string", - "required": false - }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", - "required": false - } - } } - ] - }, - "/wp/v2/template-parts/(?P[\\d]+)/revisions": { - "namespace": "wp/v2", - "methods": [ - "GET" ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", - "required": false - }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - }, - "page": { - "description": "Current page of the collection.", - "type": "integer", - "default": 1, - "minimum": 1, - "required": false - }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "minimum": 1, - "maximum": 100, - "required": false - }, - "search": { - "description": "Limit results to those matching a string.", - "type": "string", - "required": false - }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], - "required": false - }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", - "required": false - }, - "order": { - "description": "Order sort attribute ascending or descending.", - "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ], - "required": false - }, - "orderby": { - "description": "Sort collection by object attribute.", - "type": "string", - "default": "date", - "enum": [ - "date", - "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" - ], - "required": false - } + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts" } - } - ] + ] + } }, - "/wp/v2/template-parts/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/template-parts/lookup": { "namespace": "wp/v2", "methods": [ - "GET", - "DELETE" + "GET" ], "endpoints": [ { "methods": [ - "GET" - ], - "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", - "required": false - }, - "id": { - "description": "Unique identifier for the revision.", - "type": "integer", - "required": false - }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - } - } - }, - { - "methods": [ - "DELETE" - ], - "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", - "required": false + "GET" + ], + "args": { + "slug": { + "description": "The slug of the template to get the fallback for", + "type": "string", + "required": true }, - "id": { - "description": "Unique identifier for the revision.", - "type": "integer", + "is_custom": { + "description": "Indicates if a template is custom or part of the template hierarchy", + "type": "boolean", "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Required to be true, as revisions do not support trashing.", + "template_prefix": { + "description": "The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`", + "type": "string", "required": false } } } - ] + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts/lookup" + } + ] + } }, - "/wp/v2/template-parts/(?P[\\d]+)/autosaves": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { @@ -6400,9 +5660,9 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "context": { @@ -6420,12 +5680,14 @@ mockedApiResponse.Schema = { }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "slug": { @@ -6528,39 +5790,21 @@ mockedApiResponse.Schema = { "required": false } } - } - ] - }, - "/wp/v2/template-parts/(?P[\\d]+)/autosaves/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ + }, { "methods": [ - "GET" + "DELETE" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, "id": { - "description": "The ID for the autosave.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", "required": false } } @@ -6702,48 +5946,229 @@ mockedApiResponse.Schema = { }, "required": false }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" - }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "request-pending", + "request-confirmed", + "request-failed", + "request-completed", + "any" + ], + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "allow_batch": { + "v1": true + }, + "args": { + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit", + "embed" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + } + ] + } + }, + "/wp/v2/navigation/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false }, - "status": { - "default": "publish", - "description": "Limit result set to posts assigned one or more statuses.", - "type": "array", - "items": { - "enum": [ - "publish", - "future", - "draft", - "pending", - "private", - "trash", - "auto-draft", - "inherit", - "request-pending", - "request-confirmed", - "request-failed", - "request-completed", - "any" - ], - "type": "string" - }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", "required": false } } }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "allow_batch": { "v1": true }, "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, "date": { "description": "The date the post was published, in the site's timezone.", "type": [ @@ -6859,23 +6284,132 @@ mockedApiResponse.Schema = { "required": false } } + }, + { + "methods": [ + "DELETE" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", + "required": false + } + } } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "namespace": "wp/v2", + "methods": [ + "GET" ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by object attribute.", + "type": "string", + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + } } - ] - } + } + ] }, - "/wp/v2/navigation/(?P[\\d]+)": { + "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", "DELETE" ], "endpoints": [ @@ -6883,12 +6417,14 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, @@ -6902,26 +6438,71 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false }, - "password": { - "description": "The password for the post if it is password protected.", + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } }, { "methods": [ - "POST", - "PUT", - "PATCH" + "POST" ], - "allow_batch": { - "v1": true - }, "args": { - "id": { - "description": "Unique identifier for the post.", + "parent": { + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, @@ -7040,34 +6621,50 @@ mockedApiResponse.Schema = { "required": false } } - }, + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ { "methods": [ - "DELETE" + "GET" ], - "allow_batch": { - "v1": true - }, "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "The ID for the autosave.", "type": "integer", "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "/wp/v2/media": { "namespace": "wp/v2", "methods": [ - "GET" + "GET", + "POST" ], "endpoints": [ { @@ -7075,11 +6672,6 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", - "required": false - }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", @@ -7101,6 +6693,7 @@ mockedApiResponse.Schema = { "per_page": { "description": "Maximum number of items to be returned in result set.", "type": "integer", + "default": 10, "minimum": 1, "maximum": 100, "required": false @@ -7110,6 +6703,48 @@ mockedApiResponse.Schema = { "type": "string", "required": false }, + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "modified_after": { + "description": "Limit response to posts modified after a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "author": { + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "author_exclude": { + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, + "modified_before": { + "description": "Limit response to posts modified before a given ISO8601 compliant date.", + "type": "string", + "format": "date-time", + "required": false + }, "exclude": { "description": "Ensure result set excludes specific IDs.", "type": "array", @@ -7125,108 +6760,287 @@ mockedApiResponse.Schema = { "items": { "type": "integer" }, - "default": [], + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + }, + "parent": { + "description": "Limit result set to items with particular parent IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "parent_exclude": { + "description": "Limit result set to all items except those of a particular parent ID.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "search_columns": { + "default": [], + "description": "Array of column names to be searched.", + "type": "array", + "items": { + "enum": [ + "post_title", + "post_content", + "post_excerpt" + ], + "type": "string" + }, + "required": false + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "status": { + "default": "inherit", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "inherit", + "private", + "trash" + ], + "type": "string" + }, + "required": false + }, + "media_type": { + "default": null, + "description": "Limit result set to attachments of a particular media type.", + "type": "string", + "enum": [ + "image", + "video", + "text", + "application", + "audio" + ], + "required": false + }, + "mime_type": { + "default": null, + "description": "Limit result set to attachments of a particular MIME type.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, "required": false }, - "offset": { - "description": "Offset the result set by a specific number of items.", + "author": { + "description": "The ID for the author of the post.", "type": "integer", "required": false }, - "order": { - "description": "Order sort attribute ascending or descending.", + "comment_status": { + "description": "Whether or not comments are open on the post.", "type": "string", - "default": "desc", "enum": [ - "asc", - "desc" + "open", + "closed" ], "required": false }, - "orderby": { - "description": "Sort collection by object attribute.", + "ping_status": { + "description": "Whether or not the post can be pinged.", "type": "string", - "default": "date", "enum": [ - "date", - "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" + "open", + "closed" ], "required": false - } - } - } - ] - }, - "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "DELETE" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "id": { - "description": "Unique identifier for the revision.", - "type": "integer", + "template": { + "description": "The theme file to use to display the post.", + "type": "string", "required": false }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", "required": false - } - } - }, - { - "methods": [ - "DELETE" - ], - "args": { - "parent": { - "description": "The ID for the parent of the revision.", - "type": "integer", + }, + "caption": { + "description": "The attachment caption.", + "type": "object", + "properties": { + "raw": { + "description": "Caption for the attachment, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML caption for the attachment, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, "required": false }, - "id": { - "description": "Unique identifier for the revision.", - "type": "integer", + "description": { + "description": "The attachment description.", + "type": "object", + "properties": { + "raw": { + "description": "Description for the attachment, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML description for the attachment, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + } + }, "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Required to be true, as revisions do not support trashing.", + "post": { + "description": "The ID for the associated post of the attachment.", + "type": "integer", "required": false } } } - ] + ], + "_links": { + "self": "http://example.org/index.php?rest_route=/wp/v2/media" + } }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "/wp/v2/media/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { @@ -7234,8 +7048,8 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + "id": { + "description": "Unique identifier for the post.", "type": "integer", "required": false }, @@ -7254,11 +7068,13 @@ mockedApiResponse.Schema = { }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + "id": { + "description": "Unique identifier for the post.", "type": "integer", "required": false }, @@ -7297,11 +7113,6 @@ mockedApiResponse.Schema = { ], "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", - "type": "string", - "required": false - }, "title": { "description": "The title for the post.", "type": "object", @@ -7310,8 +7121,7 @@ mockedApiResponse.Schema = { "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ - "edit", - "embed" + "edit" ] }, "rendered": { @@ -7327,20 +7137,58 @@ mockedApiResponse.Schema = { }, "required": false }, - "content": { - "description": "The content for the post.", + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, + "comment_status": { + "description": "Whether or not comments are open on the post.", + "type": "string", + "enum": [ + "open", + "closed" + ], + "required": false + }, + "ping_status": { + "description": "Whether or not the post can be pinged.", + "type": "string", + "enum": [ + "open", + "closed" + ], + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + }, + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", + "type": "string", + "required": false + }, + "caption": { + "description": "The attachment caption.", "type": "object", "properties": { "raw": { - "description": "Content for the post, as it exists in the database.", + "description": "Caption for the attachment, as it exists in the database.", "type": "string", "context": [ - "edit", - "embed" + "edit" ] }, "rendered": { - "description": "HTML content for the post, transformed for display.", + "description": "HTML caption for the attachment, transformed for display.", "type": "string", "context": [ "view", @@ -7348,68 +7196,220 @@ mockedApiResponse.Schema = { "embed" ], "readonly": true + } + }, + "required": false + }, + "description": { + "description": "The attachment description.", + "type": "object", + "properties": { + "raw": { + "description": "Description for the attachment, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] }, - "block_version": { - "description": "Version of the content block format used by the post.", - "type": "integer", - "context": [ - "edit", - "embed" - ], - "readonly": true - }, - "protected": { - "description": "Whether the content is protected with a password.", - "type": "boolean", + "rendered": { + "description": "HTML description for the attachment, transformed for display.", + "type": "string", "context": [ "view", - "edit", - "embed" + "edit" ], "readonly": true } }, "required": false }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", + "post": { + "description": "The ID for the associated post of the attachment.", + "type": "integer", + "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", "required": false } } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "/wp/v2/media/(?P[\\d]+)/post-process": { "namespace": "wp/v2", "methods": [ - "GET" + "POST" ], "endpoints": [ { "methods": [ - "GET" + "POST" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", - "required": false - }, "id": { - "description": "The ID for the autosave.", + "description": "Unique identifier for the attachment.", "type": "integer", "required": false }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "action": { "type": "string", "enum": [ - "view", - "embed", - "edit" + "create-image-subsizes" ], - "default": "view", + "required": true + } + } + } + ] + }, + "/wp/v2/media/(?P[\\d]+)/edit": { + "namespace": "wp/v2", + "methods": [ + "POST" + ], + "endpoints": [ + { + "methods": [ + "POST" + ], + "args": { + "src": { + "description": "URL to the edited image file.", + "type": "string", + "format": "uri", + "required": true + }, + "modifiers": { + "description": "Array of image edits.", + "type": "array", + "minItems": 1, + "items": { + "description": "Image edit.", + "type": "object", + "required": [ + "type", + "args" + ], + "oneOf": [ + { + "title": "Rotation", + "properties": { + "type": { + "description": "Rotation type.", + "type": "string", + "enum": [ + "rotate" + ] + }, + "args": { + "description": "Rotation arguments.", + "type": "object", + "required": [ + "angle" + ], + "properties": { + "angle": { + "description": "Angle to rotate clockwise in degrees.", + "type": "number" + } + } + } + } + }, + { + "title": "Crop", + "properties": { + "type": { + "description": "Crop type.", + "type": "string", + "enum": [ + "crop" + ] + }, + "args": { + "description": "Crop arguments.", + "type": "object", + "required": [ + "left", + "top", + "width", + "height" + ], + "properties": { + "left": { + "description": "Horizontal position from the left to begin the crop as a percentage of the image width.", + "type": "number" + }, + "top": { + "description": "Vertical position from the top to begin the crop as a percentage of the image height.", + "type": "number" + }, + "width": { + "description": "Width of the crop as a percentage of the image width.", + "type": "number" + }, + "height": { + "description": "Height of the crop as a percentage of the image height.", + "type": "number" + } + } + } + } + } + ] + }, + "required": false + }, + "rotation": { + "description": "The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.", + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 360, + "exclusiveMaximum": true, + "required": false + }, + "x": { + "description": "As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead.", + "type": "number", + "minimum": 0, + "maximum": 100, + "required": false + }, + "y": { + "description": "As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.", + "type": "number", + "minimum": 0, + "maximum": 100, + "required": false + }, + "width": { + "description": "As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead.", + "type": "number", + "minimum": 0, + "maximum": 100, + "required": false + }, + "height": { + "description": "As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead.", + "type": "number", + "minimum": 0, + "maximum": 100, "required": false } } @@ -8780,12 +8780,12 @@ mockedApiResponse.Schema = { "enum": { "post": "post", "page": "page", - "attachment": "attachment", "nav_menu_item": "nav_menu_item", "wp_block": "wp_block", "wp_template": "wp_template", "wp_template_part": "wp_template_part", - "wp_navigation": "wp_navigation" + "wp_navigation": "wp_navigation", + "attachment": "attachment" } }, "required": false @@ -12352,36 +12352,6 @@ mockedApiResponse.TypesCollection = { ] } }, - "attachment": { - "description": "", - "hierarchical": false, - "has_archive": false, - "name": "Media", - "slug": "attachment", - "icon": "dashicons-admin-media", - "taxonomies": [], - "rest_base": "media", - "rest_namespace": "wp/v2", - "_links": { - "collection": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/types" - } - ], - "wp:items": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/media" - } - ], - "curies": [ - { - "name": "wp", - "href": "https://api.w.org/{rel}", - "templated": true - } - ] - } - }, "nav_menu_item": { "description": "", "hierarchical": false, @@ -12535,6 +12505,36 @@ mockedApiResponse.TypesCollection = { } ] } + }, + "attachment": { + "description": "", + "hierarchical": false, + "has_archive": false, + "name": "Media", + "slug": "attachment", + "icon": "dashicons-admin-media", + "taxonomies": [], + "rest_base": "media", + "rest_namespace": "wp/v2", + "_links": { + "collection": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/types" + } + ], + "wp:items": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/media" + } + ], + "curies": [ + { + "name": "wp", + "href": "https://api.w.org/{rel}", + "templated": true + } + ] + } } };