From e0d3c4bf133b674da26b8d3f779470fff63135a1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 5 Feb 2024 14:21:51 +0000 Subject: [PATCH 1/8] Font Library: Add all REST API endpoints. --- src/wp-includes/post.php | 44 +- src/wp-includes/rest-api.php | 4 + ...ss-wp-rest-font-collections-controller.php | 326 +++++ .../class-wp-rest-font-faces-controller.php | 941 +++++++++++++++ ...class-wp-rest-font-families-controller.php | 554 +++++++++ src/wp-settings.php | 3 + .../wpRestFontCollectionsController.php | 203 ++++ .../wpRestFontFacesController.php | 1067 +++++++++++++++++ .../wpRestFontFamiliesController.php | 1050 ++++++++++++++++ 9 files changed, 4174 insertions(+), 18 deletions(-) create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php create mode 100644 tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php create mode 100644 tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php create mode 100644 tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 08cb5fb2cacf9..5fa058363af9b 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -567,14 +567,14 @@ function create_initial_post_types() { register_post_type( 'wp_font_family', array( - 'labels' => array( + 'labels' => array( 'name' => __( 'Font Families' ), 'singular_name' => __( 'Font Family' ), ), - 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - 'hierarchical' => false, - 'capabilities' => array( + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'hierarchical' => false, + 'capabilities' => array( 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', 'create_posts' => 'edit_theme_options', @@ -586,24 +586,28 @@ function create_initial_post_types() { 'delete_others_posts' => 'edit_theme_options', 'delete_published_posts' => 'edit_theme_options', ), - 'map_meta_cap' => true, - 'query_var' => false, - 'show_in_rest' => false, - 'rewrite' => false, + 'map_meta_cap' => true, + 'query_var' => false, + 'rewrite' => false, + 'show_in_rest' => true, + 'rest_base' => 'font-families', + 'rest_controller_class' => 'WP_REST_Font_Families_Controller', + // Disable autosave endpoints for font families. + 'autosave_rest_controller_class' => 'stdClass', ) ); register_post_type( 'wp_font_face', array( - 'labels' => array( + 'labels' => array( 'name' => __( 'Font Faces' ), 'singular_name' => __( 'Font Face' ), ), - 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - 'hierarchical' => false, - 'capabilities' => array( + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'hierarchical' => false, + 'capabilities' => array( 'read' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', 'create_posts' => 'edit_theme_options', @@ -615,10 +619,14 @@ function create_initial_post_types() { 'delete_others_posts' => 'edit_theme_options', 'delete_published_posts' => 'edit_theme_options', ), - 'map_meta_cap' => true, - 'query_var' => false, - 'show_in_rest' => false, - 'rewrite' => false, + 'map_meta_cap' => true, + 'query_var' => false, + 'rewrite' => false, + 'show_in_rest' => true, + 'rest_base' => 'font-families/(?P[\d]+)/font-faces', + 'rest_controller_class' => 'WP_REST_Font_Faces_Controller', + // Disable autosave endpoints for font faces. + 'autosave_rest_controller_class' => 'stdClass', ) ); diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 2631a6663fe92..b44b205afb6ef 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -391,6 +391,10 @@ function create_initial_rest_routes() { // Navigation Fallback. $controller = new WP_REST_Navigation_Fallback_Controller(); $controller->register_routes(); + + // Font Collections. + $font_collections_controller = new WP_REST_Font_Collections_Controller(); + $font_collections_controller->register_routes(); } /** diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php new file mode 100644 index 0000000000000..c6576cc77c435 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php @@ -0,0 +1,326 @@ +rest_base = 'font-collections'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.5.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + 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, + '/' . $this->rest_base . '/(?P[\/\w-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Gets the font collections available. + * + * @since 6.5.0 + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $collections_all = WP_Font_Library::get_instance()->get_font_collections(); + + $page = $request['page']; + $per_page = $request['per_page']; + $total_items = count( $collections_all ); + $max_pages = ceil( $total_items / $per_page ); + + if ( $page > $max_pages && $total_items > 0 ) { + return new WP_Error( + 'rest_post_invalid_page_number', + __( 'The page number requested is larger than the number of pages available.' ), + array( 'status' => 400 ) + ); + } + + $collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page ); + + $items = array(); + foreach ( $collections_page as $collection ) { + $item = $this->prepare_item_for_response( $collection, $request ); + + // If there's an error loading a collection, skip it and continue loading valid collections. + if ( is_wp_error( $item ) ) { + continue; + } + $item = $this->prepare_response_for_collection( $item ); + $items[] = $item; + } + + $response = rest_ensure_response( $items ); + + $response->header( 'X-WP-Total', (int) $total_items ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + $request_params = $request->get_query_params(); + $collection_url = rest_url( $this->namespace . '/' . $this->rest_base ); + $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); + + if ( $page > 1 ) { + $prev_page = $page - 1; + + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Gets a font collection. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $slug = $request->get_param( 'slug' ); + $collection = WP_Font_Library::get_instance()->get_font_collection( $slug ); + + // If the collection doesn't exist returns a 404. + if ( is_wp_error( $collection ) ) { + $collection->add_data( array( 'status' => 404 ) ); + return $collection; + } + + return $this->prepare_item_for_response( $collection, $request ); + } + + /** + * Prepare a single collection output for response. + * + * @since 6.5.0 + * + * @param WP_Font_Collection $collection Collection object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $collection, $request ) { + $fields = $this->get_fields_for_response( $request ); + $item = array(); + + if ( rest_is_field_included( 'slug', $fields ) ) { + $item['slug'] = $collection->slug; + } + + // If any data fields are requested, get the collection data. + $data_fields = array( 'name', 'description', 'font_families', 'categories' ); + if ( ! empty( array_intersect( $fields, $data_fields ) ) ) { + $collection_data = $collection->get_data(); + if ( is_wp_error( $collection_data ) ) { + $collection_data->add_data( array( 'status' => 500 ) ); + return $collection_data; + } + + foreach ( $data_fields as $field ) { + if ( rest_is_field_included( $field, $fields ) ) { + $item[ $field ] = $collection_data[ $field ]; + } + } + } + + $response = rest_ensure_response( $item ); + + if ( rest_is_field_included( '_links', $fields ) ) { + $links = $this->prepare_links( $collection ); + $response->add_links( $links ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $response->data = $this->add_additional_fields_to_object( $response->data, $request ); + $response->data = $this->filter_response_by_context( $response->data, $context ); + + /** + * Filters a font collection returned from the REST API. + * + * Allows modification of the font collection right before it is returned. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Font_Collection $collection The Font Collection object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'rest_prepare_font_collection', $response, $collection, $request ); + } + + /** + * Retrieves the font collection's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'font-collection', + 'type' => 'object', + 'properties' => array( + 'slug' => array( + 'description' => __( 'Unique identifier for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'The name for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'The description for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'font_families' => array( + 'description' => __( 'The font families for the font collection.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'categories' => array( + 'description' => __( 'The categories for the font collection.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Prepares links for the request. + * + * @since 6.5.0 + * + * @param WP_Font_Collection $collection Font collection data + * @return array Links for the given font collection. + */ + protected function prepare_links( $collection ) { + return array( + 'self' => array( + 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ), + ), + 'collection' => array( + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), + ), + ); + } + + /** + * Retrieves the search params for the font collections. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + $query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); + + unset( $query_params['search'] ); + + /** + * Filters REST API collection parameters for the font collections controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_font_collections_collection_params', $query_params ); + } + + /** + * Checks whether the user has permissions to use the Fonts Collections. + * + * @since 6.5.0 + * + * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + if ( current_user_can( 'edit_theme_options' ) ) { + return true; + } + + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font collections.' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php new file mode 100644 index 0000000000000..6a3ae958f4f6d --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -0,0 +1,941 @@ +namespace, + '/' . $this->rest_base, + array( + 'args' => array( + 'font_family_id' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + ), + 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->get_create_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'font_family_id' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + 'id' => array( + 'description' => __( 'Unique identifier for the font face.' ), + 'type' => 'integer', + 'required' => true, + ), + ), + 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' => __( 'Whether to bypass Trash and force deletion.', 'default' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Checks if a given request has access to font faces. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class + $post_type = get_post_type_object( $this->post_type ); + + if ( ! current_user_can( $post_type->cap->read ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font faces.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Checks if a given request has access to a font face. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( ! current_user_can( 'read_post', $post->ID ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access this font face.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Validates settings when creating a font face. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font face settings. + * @param WP_REST_Request $request Request object. + * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. + */ + public function validate_create_font_face_settings( $value, $request ) { + $settings = json_decode( $value, true ); + + // Check settings string is valid JSON. + if ( null === $settings ) { + return new WP_Error( + 'rest_invalid_param', + __( 'font_face_settings parameter must be a valid JSON string.' ), + array( 'status' => 400 ) + ); + } + + // Check that the font face settings match the theme.json schema. + $schema = $this->get_item_schema()['properties']['font_face_settings']; + $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' ); + + if ( is_wp_error( $has_valid_settings ) ) { + $has_valid_settings->add_data( array( 'status' => 400 ) ); + return $has_valid_settings; + } + + // Check that none of the required settings are empty values. + $required = $schema['required']; + foreach ( $required as $key ) { + if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */ + sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ), + array( 'status' => 400 ) + ); + } + } + + $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] ); + $files = $request->get_file_params(); + + foreach ( $srcs as $src ) { + // Check that each src is a non-empty string. + $src = ltrim( $src ); + if ( empty( $src ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Font face source parameter name: "font_face_settings[src]". */ + sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ), + array( 'status' => 400 ) + ); + } + + // Check that srcs are valid URLs or file references. + if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */ + sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ), + array( 'status' => 400 ) + ); + } + } + + // Check that each file in the request references a src in the settings. + foreach ( array_keys( $files ) as $file ) { + if ( ! in_array( $file, $srcs, true ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */ + sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ), + array( 'status' => 400 ) + ); + } + } + + return true; + } + + /** + * Sanitizes the font face settings when creating a font face. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font face settings. + * @param WP_REST_Request $request Request object. + * @return array Decoded array of font face settings. + */ + public function sanitize_font_face_settings( $value ) { + // Settings arrive as stringified JSON, since this is a multipart/form-data request. + $settings = json_decode( $value, true ); + $schema = $this->get_item_schema()['properties']['font_face_settings']['properties']; + + // Sanitize settings based on callbacks in the schema. + foreach ( $settings as $key => $value ) { + $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; + $settings[ $key ] = call_user_func( $sanitize_callback, $value ); + } + + return $settings; + } + + /** + * Retrieves a collection of font faces within the parent font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + return parent::get_items( $request ); + } + + /** + * Retrieves a single font face within the parent font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + // Check that the font face has a valid parent font family. + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + if ( (int) $font_family->ID !== (int) $post->post_parent ) { + return new WP_Error( + 'rest_font_face_parent_id_mismatch', + /* translators: %d: A post id. */ + sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), + array( 'status' => 404 ) + ); + } + + return parent::get_item( $request ); + } + + /** + * Creates a font face for the parent font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function create_item( $request ) { + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + // Settings have already been decoded by ::sanitize_font_face_settings(). + $settings = $request->get_param( 'font_face_settings' ); + $file_params = $request->get_file_params(); + + // Check that the necessary font face properties are unique. + $query = new WP_Query( + array( + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'title' => WP_Font_Utils::get_font_face_slug( $settings ), + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + if ( ! empty( $query->get_posts() ) ) { + return new WP_Error( + 'rest_duplicate_font_face', + __( 'A font face matching those settings already exists.' ), + array( 'status' => 400 ) + ); + } + + // Move the uploaded font asset from the temp folder to the fonts directory. + if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + $srcs = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src']; + $processed_srcs = array(); + $font_file_meta = array(); + + foreach ( $srcs as $src ) { + // If src not a file reference, use it as is. + if ( ! isset( $file_params[ $src ] ) ) { + $processed_srcs[] = $src; + continue; + } + + $file = $file_params[ $src ]; + $font_file = $this->handle_font_file_upload( $file ); + if ( is_wp_error( $font_file ) ) { + return $font_file; + } + + $processed_srcs[] = $font_file['url']; + $font_file_meta[] = $this->relative_fonts_path( $font_file['file'] ); + } + + // Store the updated settings for prepare_item_for_database to use. + $settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs; + $request->set_param( 'font_face_settings', $settings ); + + // Ensure that $settings data is slashed, so values with quotes are escaped. + // WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content. + $font_face_post = parent::create_item( $request ); + + if ( is_wp_error( $font_face_post ) ) { + return $font_face_post; + } + + $font_face_id = $font_face_post->data['id']; + + foreach ( $font_file_meta as $font_file_path ) { + add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path ); + } + + return $font_face_post; + } + + /** + * Deletes a single font face. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function delete_item( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); + if ( is_wp_error( $font_family ) ) { + return $font_family; + } + + if ( (int) $font_family->ID !== (int) $post->post_parent ) { + return new WP_Error( + 'rest_font_face_parent_id_mismatch', + /* translators: %d: A post id. */ + sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), + array( 'status' => 404 ) + ); + } + + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; + + // We don't support trashing for font faces. + if ( ! $force ) { + return new WP_Error( + 'rest_trash_not_supported', + /* translators: %s: force=true */ + sprintf( __( "Font faces do not support trashing. Set '%s' to delete." ), 'force=true' ), + array( 'status' => 501 ) + ); + } + + return parent::delete_item( $request ); + } + + /** + * Prepares a single font face output for response. + * + * @since 6.5.0 + * + * @param WP_Post $item Post object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = $item->ID; + } + if ( rest_is_field_included( 'theme_json_version', $fields ) ) { + $data['theme_json_version'] = 2; + } + + if ( rest_is_field_included( 'parent', $fields ) ) { + $data['parent'] = $item->post_parent; + } + + if ( rest_is_field_included( 'font_face_settings', $fields ) ) { + $data['font_face_settings'] = $this->get_settings_from_post( $item ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + /** + * Filters the font face data for a REST API response. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Font face post object. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request ); + } + + /** + * Retrieves the post's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + // Base properties for every Post. + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the post.', 'default' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'theme_json_version' => array( + 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), + 'type' => 'integer', + 'default' => 2, + 'minimum' => 2, + 'maximum' => 2, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'parent' => array( + 'description' => __( 'The ID for the parent font family of the font face.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + // Font face settings come directly from theme.json schema + // See https://schemas.wp.org/trunk/theme.json + 'font_face_settings' => array( + 'description' => __( 'font-face declaration in theme.json format.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'fontFamily' => array( + 'description' => __( 'CSS font-family value.' ), + 'type' => 'string', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + ), + ), + 'fontStyle' => array( + 'description' => __( 'CSS font-style value.' ), + 'type' => 'string', + 'default' => 'normal', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontWeight' => array( + 'description' => __( 'List of available font weights, separated by a space.' ), + 'default' => '400', + // Changed from `oneOf` to avoid errors from loose type checking. + // e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check. + 'type' => array( 'string', 'integer' ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontDisplay' => array( + 'description' => __( 'CSS font-display value.' ), + 'type' => 'string', + 'default' => 'fallback', + 'enum' => array( + 'auto', + 'block', + 'fallback', + 'swap', + 'optional', + ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'src' => array( + 'description' => __( 'Paths or URLs to the font files.' ), + // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array, + // and causing a "matches more than one of the expected formats" error. + 'anyOf' => array( + array( + 'type' => 'string', + ), + array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + 'default' => array(), + 'arg_options' => array( + 'sanitize_callback' => function ( $value ) { + return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value ); + }, + ), + ), + 'fontStretch' => array( + 'description' => __( 'CSS font-stretch value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'ascentOverride' => array( + 'description' => __( 'CSS ascent-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'descentOverride' => array( + 'description' => __( 'CSS descent-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontVariant' => array( + 'description' => __( 'CSS font-variant value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontFeatureSettings' => array( + 'description' => __( 'CSS font-feature-settings value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'fontVariationSettings' => array( + 'description' => __( 'CSS font-variation-settings value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'lineGapOverride' => array( + 'description' => __( 'CSS line-gap-override value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'sizeAdjust' => array( + 'description' => __( 'CSS size-adjust value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'unicodeRange' => array( + 'description' => __( 'CSS unicode-range value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'preview' => array( + 'description' => __( 'URL to a preview image of the font face.' ), + 'type' => 'string', + 'format' => 'uri', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_url', + ), + ), + ), + 'required' => array( 'fontFamily', 'src' ), + 'additionalProperties' => false, + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the item's schema for display / public consumption purposes. + * + * @since 6.5.0 + * + * @return array Public item schema data. + */ + public function get_public_item_schema() { + + $schema = parent::get_public_item_schema(); + + // Also remove `arg_options' from child font_family_settings properties, since the parent + // controller only handles the top level properties. + foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) { + unset( $property['arg_options'] ); + } + + return $schema; + } + + /** + * Retrieves the query params for the font face collection. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + // Remove unneeded params. + unset( $query_params['after'] ); + unset( $query_params['modified_after'] ); + unset( $query_params['before'] ); + unset( $query_params['modified_before'] ); + unset( $query_params['search'] ); + unset( $query_params['search_columns'] ); + unset( $query_params['slug'] ); + unset( $query_params['status'] ); + + $query_params['orderby']['default'] = 'id'; + $query_params['orderby']['enum'] = array( 'id', 'include' ); + + /** + * Filters collection parameters for the font face controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_wp_font_face_collection_params', $query_params ); + } + + /** + * Get the params used when creating a new font face. + * + * @since 6.5.0 + * + * @return array Font face create arguments. + */ + public function get_create_params() { + $properties = $this->get_item_schema()['properties']; + return array( + 'theme_json_version' => $properties['theme_json_version'], + // When creating, font_face_settings is stringified JSON, to work with multipart/form-data used + // when uploading font files. + 'font_face_settings' => array( + 'description' => __( 'font-face declaration in theme.json format, encoded as a string.' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'validate_create_font_face_settings' ), + 'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ), + ), + ); + } + + /** + * Get the parent font family, if the ID is valid. + * + * @since 6.5.0 + * + * @param int $font_family_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent_font_family_post( $font_family_id ) { + $error = new WP_Error( + 'rest_post_invalid_parent', + __( 'Invalid post parent ID.', 'default' ), + array( 'status' => 404 ) + ); + + if ( (int) $font_family_id <= 0 ) { + return $error; + } + + $font_family_post = get_post( (int) $font_family_id ); + + if ( empty( $font_family_post ) || empty( $font_family_post->ID ) + || 'wp_font_family' !== $font_family_post->post_type + ) { + return $error; + } + + return $font_family_post; + } + + /** + * Prepares links for the request. + * + * @since 6.5.0 + * + * @param WP_Post $post Post object. + * @return array Links for the given post. + */ + protected function prepare_links( $post ) { + // Entity meta. + return array( + 'self' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), + ), + 'collection' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ), + ), + 'parent' => array( + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ), + ), + ); + } + + /** + * Prepares a single font face post for creation. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Request object. + * @return stdClass|WP_Error Post object or WP_Error. + */ + protected function prepare_item_for_database( $request ) { + $prepared_post = new stdClass(); + + // Settings have already been decoded by ::sanitize_font_face_settings(). + $settings = $request->get_param( 'font_face_settings' ); + + // Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting, + // which may contain multibyte characters. + $title = WP_Font_Utils::get_font_face_slug( $settings ); + + $prepared_post->post_type = $this->post_type; + $prepared_post->post_parent = $request['font_family_id']; + $prepared_post->post_status = 'publish'; + $prepared_post->post_title = $title; + $prepared_post->post_name = sanitize_title( $title ); + $prepared_post->post_content = wp_json_encode( $settings ); + + return $prepared_post; + } + + /** + * Sanitizes a single src value for a font face. + * + * @since 6.5.0 + * + * @param string $value Font face src that is a URL or the key for a $_FILES array item. + * + * @return string Sanitized value. + */ + protected function sanitize_src( $value ) { + $value = ltrim( $value ); + return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value ); + } + + /** + * Handles the upload of a font file using wp_handle_upload(). + * + * @since 6.5.0 + * + * @param array $file Single file item from $_FILES. + * @return array Array containing uploaded file attributes on success, or error on failure. + */ + protected function handle_font_file_upload( $file ) { + add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); + add_filter( 'upload_dir', 'wp_get_font_dir' ); + + $overrides = array( + 'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ), + // Arbitrary string to avoid the is_uploaded_file() check applied + // when using 'wp_handle_upload'. + 'action' => 'wp_handle_font_upload', + // Not testing a form submission. + 'test_form' => false, + // Seems mime type for files that are not images cannot be tested. + // See wp_check_filetype_and_ext(). + 'test_type' => true, + // Only allow uploading font files for this request. + 'mimes' => WP_Font_Utils::get_allowed_font_mime_types(), + ); + + $uploaded_file = wp_handle_upload( $file, $overrides ); + + remove_filter( 'upload_dir', 'wp_get_font_dir' ); + remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); + + return $uploaded_file; + } + + /** + * Handles file upload error. + * + * @since 6.5.0 + * + * @param array $file File upload data. + * @param string $message Error message from wp_handle_upload(). + * @return WP_Error WP_Error object. + */ + public function handle_font_file_upload_error( $file, $message ) { + $status = 500; + $code = 'rest_font_upload_unknown_error'; + + if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) { + $status = 400; + $code = 'rest_font_upload_invalid_file_type'; + } + + return new WP_Error( $code, $message, array( 'status' => $status ) ); + } + + /** + * Returns relative path to an uploaded font file. + * + * The path is relative to the current fonts directory. + * + * @since 6.5.0 + * @access private + * + * @param string $path Full path to the file. + * @return string Relative path on success, unchanged path on failure. + */ + protected function relative_fonts_path( $path ) { + $new_path = $path; + + $fonts_dir = wp_get_font_dir(); + if ( str_starts_with( $new_path, $fonts_dir['path'] ) ) { + $new_path = str_replace( $fonts_dir, '', $new_path ); + $new_path = ltrim( $new_path, '/' ); + } + + return $new_path; + } + + /** + * Gets the font face's settings from the post. + * + * @since 6.5.0 + * + * @param WP_Post $post Font face post object. + * @return array Font face settings array. + */ + protected function get_settings_from_post( $post ) { + $settings = json_decode( $post->post_content, true ); + $properties = $this->get_item_schema()['properties']['font_face_settings']['properties']; + + // Provide required, empty settings if needed. + if ( null === $settings ) { + $settings = array( + 'fontFamily' => '', + 'src' => array(), + ); + } + + // Only return the properties defined in the schema. + return array_intersect_key( $settings, $properties ); + } +} diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php new file mode 100644 index 0000000000000..a04723d31669a --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -0,0 +1,554 @@ +post_type ); + + if ( ! current_user_can( $post_type->cap->read ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access font families.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Checks if a given request has access to a font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + $post = $this->get_post( $request['id'] ); + if ( is_wp_error( $post ) ) { + return $post; + } + + if ( ! current_user_can( 'read_post', $post->ID ) ) { + return new WP_Error( + 'rest_cannot_read', + __( 'Sorry, you are not allowed to access this font family.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Validates settings when creating or updating a font family. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font family settings. + * @param WP_REST_Request $request Request object. + * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. + */ + public function validate_font_family_settings( $value, $request ) { + $settings = json_decode( $value, true ); + + // Check settings string is valid JSON. + if ( null === $settings ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Parameter name: "font_family_settings". */ + sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ), + array( 'status' => 400 ) + ); + } + + $schema = $this->get_item_schema()['properties']['font_family_settings']; + $required = $schema['required']; + + if ( isset( $request['id'] ) ) { + // Allow sending individual properties if we are updating an existing font family. + unset( $schema['required'] ); + + // But don't allow updating the slug, since it is used as a unique identifier. + if ( isset( $settings['slug'] ) ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of parameter being updated: font_family_settings[slug]". */ + sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ), + array( 'status' => 400 ) + ); + } + } + + // Check that the font face settings match the theme.json schema. + $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' ); + + if ( is_wp_error( $has_valid_settings ) ) { + $has_valid_settings->add_data( array( 'status' => 400 ) ); + return $has_valid_settings; + } + + // Check that none of the required settings are empty values. + foreach ( $required as $key ) { + if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { + return new WP_Error( + 'rest_invalid_param', + /* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */ + sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ), + array( 'status' => 400 ) + ); + } + } + + return true; + } + + /** + * Sanitizes the font family settings when creating or updating a font family. + * + * @since 6.5.0 + * + * @param string $value Encoded JSON string of font family settings. + * @param WP_REST_Request $request Request object. + * @return array Decoded array font family settings. + */ + public function sanitize_font_family_settings( $value ) { + // Settings arrive as stringified JSON, since this is a multipart/form-data request. + $settings = json_decode( $value, true ); + $schema = $this->get_item_schema()['properties']['font_family_settings']['properties']; + + // Sanitize settings based on callbacks in the schema. + foreach ( $settings as $key => $value ) { + $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; + $settings[ $key ] = call_user_func( $sanitize_callback, $value ); + } + + return $settings; + } + + /** + * Creates a single font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function create_item( $request ) { + $settings = $request->get_param( 'font_family_settings' ); + + // Check that the font family slug is unique. + $query = new WP_Query( + array( + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'name' => $settings['slug'], + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + if ( ! empty( $query->get_posts() ) ) { + return new WP_Error( + 'rest_duplicate_font_family', + /* translators: %s: Font family slug. */ + sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ), + array( 'status' => 400 ) + ); + } + + return parent::create_item( $request ); + } + + /** + * Deletes a single font family. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function delete_item( $request ) { + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; + + // We don't support trashing for font families. + if ( ! $force ) { + return new WP_Error( + 'rest_trash_not_supported', + /* translators: %s: force=true */ + sprintf( __( "Font faces do not support trashing. Set '%s' to delete." ), 'force=true' ), + array( 'status' => 501 ) + ); + } + + return parent::delete_item( $request ); + } + + /** + * Prepares a single font family output for response. + * + * @since 6.5.0 + * + * @param WP_Post $item Post object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = $item->ID; + } + + if ( rest_is_field_included( 'theme_json_version', $fields ) ) { + $data['theme_json_version'] = 2; + } + + if ( rest_is_field_included( 'font_faces', $fields ) ) { + $data['font_faces'] = $this->get_font_face_ids( $item->ID ); + } + + if ( rest_is_field_included( 'font_family_settings', $fields ) ) { + $data['font_family_settings'] = $this->get_settings_from_post( $item ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + if ( rest_is_field_included( '_links', $fields ) ) { + $links = $this->prepare_links( $item ); + $response->add_links( $links ); + } + + /** + * Filters the font family data for a REST API response. + * + * @since 6.5.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post Font family post object. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request ); + } + + /** + * Retrieves the post's schema, conforming to JSON Schema. + * + * @since 6.5.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => $this->post_type, + 'type' => 'object', + // Base properties for every Post. + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the post.', 'default' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'theme_json_version' => array( + 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), + 'type' => 'integer', + 'default' => 2, + 'minimum' => 2, + 'maximum' => 2, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'font_faces' => array( + 'description' => __( 'The IDs of the child font faces in the font family.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit', 'embed' ), + 'items' => array( + 'type' => 'integer', + ), + ), + // Font family settings come directly from theme.json schema + // See https://schemas.wp.org/trunk/theme.json + 'font_family_settings' => array( + 'description' => __( 'font-face definition in theme.json format.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'name' => array( + 'description' => __( 'Name of the font family preset, translatable.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'slug' => array( + 'description' => __( 'Kebab-case unique identifier for the font family preset.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_title', + ), + ), + 'fontFamily' => array( + 'description' => __( 'CSS font-family value.' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + ), + ), + 'preview' => array( + 'description' => __( 'URL to a preview image of the font family.' ), + 'type' => 'string', + 'format' => 'uri', + 'default' => '', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_url', + ), + ), + ), + 'required' => array( 'name', 'slug', 'fontFamily' ), + 'additionalProperties' => false, + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the item's schema for display / public consumption purposes. + * + * @since 6.5.0 + * + * @return array Public item schema data. + */ + public function get_public_item_schema() { + + $schema = parent::get_public_item_schema(); + + // Also remove `arg_options' from child font_family_settings properties, since the parent + // controller only handles the top level properties. + foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) { + unset( $property['arg_options'] ); + } + + return $schema; + } + + /** + * Retrieves the query params for the font family collection. + * + * @since 6.5.0 + * + * @return array Collection parameters. + */ + public function get_collection_params() { + $query_params = parent::get_collection_params(); + + // Remove unneeded params. + unset( $query_params['after'] ); + unset( $query_params['modified_after'] ); + unset( $query_params['before'] ); + unset( $query_params['modified_before'] ); + unset( $query_params['search'] ); + unset( $query_params['search_columns'] ); + unset( $query_params['status'] ); + + $query_params['orderby']['default'] = 'id'; + $query_params['orderby']['enum'] = array( 'id', 'include' ); + + /** + * Filters collection parameters for the font family controller. + * + * @since 6.5.0 + * + * @param array $query_params JSON Schema-formatted collection parameters. + */ + return apply_filters( 'rest_wp_font_family_collection_params', $query_params ); + } + + /** + * Get the arguments used when creating or updating a font family. + * + * @since 6.5.0 + * + * @return array Font family create/edit arguments. + */ + public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { + if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) { + $properties = $this->get_item_schema()['properties']; + return array( + 'theme_json_version' => $properties['theme_json_version'], + // When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data. + // Font families don't currently support file uploads, but may accept preview files in the future. + 'font_family_settings' => array( + 'description' => __( 'font-family declaration in theme.json format, encoded as a string.' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'validate_font_family_settings' ), + 'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ), + ), + ); + } + + return parent::get_endpoint_args_for_item_schema( $method ); + } + + /** + * Get the child font face post IDs. + * + * @since 6.5.0 + * + * @param int $font_family_id Font family post ID. + * @return int[] Array of child font face post IDs. + */ + protected function get_font_face_ids( $font_family_id ) { + $query = new WP_Query( + array( + 'fields' => 'ids', + 'post_parent' => $font_family_id, + 'post_type' => 'wp_font_face', + 'posts_per_page' => 99, + 'order' => 'ASC', + 'orderby' => 'id', + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + + return $query->get_posts(); + } + + /** + * Prepares font family links for the request. + * + * @since 6.5.0 + * + * @param WP_Post $post Post object. + * @return array Links for the given post. + */ + protected function prepare_links( $post ) { + // Entity meta. + $links = parent::prepare_links( $post ); + + return array( + 'self' => $links['self'], + 'collection' => $links['collection'], + 'font_faces' => $this->prepare_font_face_links( $post->ID ), + ); + } + + /** + * Prepares child font face links for the request. + * + * @param int $font_family_id Font family post ID. + * @return array Links for the child font face posts. + */ + protected function prepare_font_face_links( $font_family_id ) { + $font_face_ids = $this->get_font_face_ids( $font_family_id ); + $links = array(); + foreach ( $font_face_ids as $font_face_id ) { + $links[] = array( + 'embeddable' => true, + 'href' => rest_url( $this->namespace . '/' . $this->rest_base . '/' . $font_family_id . '/font-faces/' . $font_face_id ), + ); + } + return $links; + } + + /** + * Prepares a single font family post for create or update. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Request object. + * @return stdClass|WP_Error Post object or WP_Error. + */ + protected function prepare_item_for_database( $request ) { + $prepared_post = new stdClass(); + // Settings have already been decoded by ::sanitize_font_family_settings(). + $settings = $request->get_param( 'font_family_settings' ); + + // This is an update and we merge with the existing font family. + if ( isset( $request['id'] ) ) { + $existing_post = $this->get_post( $request['id'] ); + if ( is_wp_error( $existing_post ) ) { + return $existing_post; + } + + $prepared_post->ID = $existing_post->ID; + $existing_settings = $this->get_settings_from_post( $existing_post ); + $settings = array_merge( $existing_settings, $settings ); + } + + $prepared_post->post_type = $this->post_type; + $prepared_post->post_status = 'publish'; + $prepared_post->post_title = $settings['name']; + $prepared_post->post_name = sanitize_title( $settings['slug'] ); + + // Remove duplicate information from settings. + unset( $settings['name'] ); + unset( $settings['slug'] ); + + $prepared_post->post_content = wp_json_encode( $settings ); + + return $prepared_post; + } + + /** + * Gets the font family's settings from the post. + * + * @since 6.5.0 + * + * @param WP_Post $post Font family post object. + * @return array Font family settings array. + */ + protected function get_settings_from_post( $post ) { + $settings_json = json_decode( $post->post_content, true ); + + // Default to empty strings if the settings are missing. + return array( + 'name' => isset( $post->post_title ) && $post->post_title ? $post->post_title : '', + 'slug' => isset( $post->post_name ) && $post->post_name ? $post->post_name : '', + 'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '', + 'preview' => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '', + ); + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index 22683b37d1f5d..bdb27aae007d3 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -310,6 +310,9 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-url-details-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-families-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-faces-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-collections-controller.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php'; diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php new file mode 100644 index 0000000000000..60f50e503fdbe --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php @@ -0,0 +1,203 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + $mock_file = wp_tempnam( 'my-collection-data-' ); + file_put_contents( $mock_file, '{"name": "Mock Collection", "font_families": [ "mock" ], "categories": [ "mock" ] }' ); + + wp_register_font_collection( 'mock-col-slug', $mock_file ); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + wp_unregister_font_collection( 'mock-col-slug' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'Rest server has not the collections path initialized.' ); + $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' ); + + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'Rest server has not the GET method for collections initialized.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection initialized.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $content = $response->get_data(); + $this->assertIsArray( $content ); + $this->assertSame( 200, $response->get_status() ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items_should_only_return_valid_collections() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + wp_set_current_user( self::$admin_id ); + wp_register_font_collection( 'invalid-collection', 'invalid-collection-file' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $content = $response->get_data(); + + wp_unregister_font_collection( 'invalid-collection' ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 1, $content, 'The response should only contain valid collections.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + + $response_data = $response->get_data(); + $this->assertArrayHasKey( 'name', $response_data, 'Response data does not have the name key.' ); + $this->assertArrayHasKey( 'slug', $response_data, 'Response data does not have the slug key.' ); + $this->assertArrayHasKey( 'description', $response_data, 'Response data does not have the description key.' ); + $this->assertArrayHasKey( 'font_families', $response_data, 'Response data does not have the font_families key.' ); + $this->assertArrayHasKey( 'categories', $response_data, 'Response data does not have the categories key.' ); + + $this->assertIsString( $response_data['name'], 'name is not a string.' ); + $this->assertIsString( $response_data['slug'], 'slug is not a string.' ); + $this->assertIsString( $response_data['description'], 'description is not a string.' ); + + $this->assertIsArray( $response_data['font_families'], 'font_families is not an array.' ); + $this->assertIsArray( $response_data['categories'], 'categories is not an array.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_slug() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'font_collection_not_found', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_collection() { + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); + + wp_set_current_user( self::$admin_id ); + $slug = 'invalid-collection'; + wp_register_font_collection( $slug, 'invalid-collection-file' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/' . $slug ); + $response = rest_get_server()->dispatch( $request ); + + wp_unregister_font_collection( $slug ); + + $this->assertErrorResponse( 'font_collection_json_missing', $response, 500, 'When the collection json file is invalid, the response should return an error for "font_collection_json_missing" with 500 status.' ); + } + + /** + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_invalid_id_permission() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/mock-col-slug' ); + + wp_set_current_user( 0 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response status should be 401 for non-authenticated users.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response status should be 403 for users without the right permissions.' ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // Controller does not use get_context_param(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_create_item() { + // Controller does not use test_create_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_update_item() { + // Controller does not use test_update_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_delete_item() { + // Controller does not use test_delete_item(). + } + + /** + * @doesNotPerformAssertions + */ + public function test_prepare_item() { + // Controller does not use test_prepare_item(). + } + + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 5, $properties, 'There should be 5 properties in the response data schema.' ); + $this->assertArrayHasKey( 'slug', $properties, 'The slug property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'name', $properties, 'The name property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'description', $properties, 'The description property should exist in the response data schema.' ); + $this->assertArrayHasKey( 'font_families', $properties, 'The slug font_families should exist in the response data schema.' ); + $this->assertArrayHasKey( 'categories', $properties, 'The categories property should exist in the response data schema.' ); + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php new file mode 100644 index 0000000000000..bbfeaca30707a --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -0,0 +1,1067 @@ + '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ); + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$font_family_id = Tests_REST_WpRestFontFamiliesController::create_font_family_post(); + self::$other_font_family_id = Tests_REST_WpRestFontFamiliesController::create_font_family_post(); + + self::$font_face_id1 = self::create_font_face_post( + self::$font_family_id, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ), + ) + ); + self::$font_face_id2 = self::create_font_face_post( + self::$font_family_id, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '900', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ), + ) + ); + + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + + self::$post_ids_for_cleanup = array(); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + + wp_delete_post( self::$font_family_id, true ); + wp_delete_post( self::$other_font_family_id, true ); + wp_delete_post( self::$font_face_id1, true ); + wp_delete_post( self::$font_face_id2, true ); + } + + public function tear_down() { + foreach ( self::$post_ids_for_cleanup as $post_id ) { + wp_delete_post( $post_id, true ); + } + self::$post_ids_for_cleanup = array(); + parent::tear_down(); + } + + public static function create_font_face_post( $parent_id, $settings = array() ) { + $settings = array_merge( self::$default_settings, $settings ); + $title = WP_Font_Utils::get_font_face_slug( $settings ); + $post_id = self::factory()->post->create( + wp_slash( + array( + 'post_type' => 'wp_font_face', + 'post_status' => 'publish', + 'post_title' => $title, + 'post_name' => sanitize_title( $title ), + 'post_content' => wp_json_encode( $settings ), + 'post_parent' => $parent_id, + ) + ) + ); + + self::$post_ids_for_cleanup[] = $post_id; + + return $post_id; + } + + /** + * @covers WP_REST_Font_Faces_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces', + $routes, + 'Font faces collection for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families/(?P[\d]+)/font-faces'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)', + $routes, + 'Single font face route for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + } + + public function test_font_faces_no_autosave_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves', + $routes, + 'Font faces autosaves route exists.' + ); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)/autosaves/(?P[\d]+)', + $routes, + 'Font faces autosaves by id route exists.' + ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // See test_get_context_param(). + } + + /** + * @dataProvider data_get_context_param + * + * @covers WP_REST_Font_Faces_Controller::get_context_param + * + * @param bool $single_route Whether to test a single route. + */ + public function test_get_context_param( $single_route ) { + $route = '/wp/v2/font-families/' . self::$font_family_id . '/font-faces'; + if ( $single_route ) { + $route .= '/' . self::$font_face_id1; + } + + $request = new WP_REST_Request( 'OPTIONS', $route ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $endpoint_data = $data['endpoints'][0]; + $this->assertArrayNotHasKey( 'allow_batch', $endpoint_data, 'The allow_batch property should not exist in the endpoint data.' ); + $this->assertSame( 'view', $endpoint_data['args']['context']['default'], 'The endpoint\'s args::context::default should be set to view.' ); + $this->assertSame( array( 'view', 'embed', 'edit' ), $endpoint_data['args']['context']['enum'], 'The endpoint\'s args::context::enum should be set to [ view, embed, edit ].' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_context_param() { + return array( + 'Collection' => array( false ), + 'Single' => array( true ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200' ); + $this->assertCount( 2, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( '_links', $data[0], 'The _links property should exist in the response data 0.' ); + $this->check_font_face_data( $data[0], self::$font_face_id2, $data[0]['_links'] ); + $this->assertArrayHasKey( '_links', $data[1], 'The _links property should exist in the response data 1.' ); + $this->check_font_face_data( $data[1], self::$font_face_id1, $data[1]['_links'] ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_items + */ + public function test_get_items_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_face_data( $data, self::$font_face_id1, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_get_item_removes_extra_settings() { + $font_face_id = self::create_font_face_post( self::$font_family_id, array( 'extra' => array() ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertArrayNotHasKey( 'extra', $data['font_face_settings'], 'The extra property should exist in the font_face_settings data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_get_item_malformed_post_content_returns_empty_settings() { + $font_face_id = wp_insert_post( + array( + 'post_type' => 'wp_font_face', + 'post_parent' => self::$font_family_id, + 'post_status' => 'publish', + 'post_content' => 'invalid', + ) + ); + + self::$post_ids_for_cleanup[] = $font_face_id; + + $empty_settings = array( + 'fontFamily' => '', + 'src' => array(), + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertSame( $empty_settings, $data['font_face_settings'], 'The empty settings should exist in the font_face_settings data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_invalid_font_face_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_valid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_get_item_invalid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404 ); + + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item() { + wp_set_current_user( self::$admin_id ); + $files = $this->setup_font_file_upload( array( 'woff2' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => array_keys( $files )[0], + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + $this->check_file_meta( $data['id'], array( $data['font_face_settings']['src'] ) ); + + $settings = $data['font_face_settings']; + unset( $settings['src'] ); + $this->assertSame( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + ), + $settings, + 'The font_face_settings data should match the expected data.' + ); + + $this->assertSame( self::$font_family_id, $data['parent'], 'The returned parent id should match the font family id.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_multiple_font_files() { + wp_set_current_user( self::$admin_id ); + $files = $this->setup_font_file_upload( array( 'ttf', 'otf', 'woff', 'woff2' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => array_keys( $files ), + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + $this->check_file_meta( $data['id'], $data['font_face_settings']['src'] ); + + $settings = $data['font_face_settings']; + $this->assertCount( 4, $settings['src'], 'There should be 4 items in the font_face_settings::src data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_invalid_file_type() { + $image_file = DIR_TESTDATA . '/images/canola.jpg'; + $image_path = wp_tempnam( 'canola.jpg' ); + copy( $image_file, $image_path ); + + $files = array( + 'file-0' => array( + 'name' => 'canola.jpg', + 'full_path' => 'canola.jpg', + 'type' => 'font/woff2', + 'tmp_name' => $image_path, + 'error' => 0, + 'size' => filesize( $image_path ), + ), + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( + self::$default_settings, + array( + 'fontWeight' => '200', + 'src' => array_keys( $files )[0], + ) + ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_font_upload_invalid_file_type', $response, 400 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_url_src() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'normal', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_face_data( $data, $data['id'], $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_all_properties() { + wp_set_current_user( self::$admin_id ); + + $properties = array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '300 500', + 'fontStyle' => 'oblique 30deg 50deg', + 'fontDisplay' => 'swap', + 'fontStretch' => 'expanded', + 'ascentOverride' => '70%', + 'descentOverride' => '30%', + 'fontVariant' => 'normal', + 'fontFeatureSettings' => '"swsh" 2', + 'fontVariationSettings' => '"xhgt" 0.7', + 'lineGapOverride' => '10%', + 'sizeAdjust' => '90%', + 'unicodeRange' => 'U+0025-00FF, U+4??', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_face_settings', wp_json_encode( $properties ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in the response data.' ); + $this->assertSame( $properties, $data['font_face_settings'], 'The font_face_settings should match the expected properties.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces' ); + $request->set_param( + 'font_face_settings', + wp_json_encode( array_merge( self::$default_settings, array( 'fontWeight' => '100' ) ) ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + public function test_create_item_with_duplicate_properties() { + $settings = array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'fontStyle' => 'italic', + 'src' => home_url( '/wp-content/fonts/open-sans-italic-light.ttf' ), + ); + self::create_font_face_post( self::$font_family_id, $settings ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_duplicate_font_face', $response, 400, 'The response should return an error for "rest_duplicate_font_face" with 400 status.' ); + $expected_message = 'A font face matching those settings already exists.'; + $message = $response->as_error()->get_error_messages()[0]; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_request + */ + public function test_create_item_default_theme_json_version() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '200', + 'src' => 'https://fonts.gstatic.com/s/open-sans/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf', + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); + $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' ); + } + + /** + * @dataProvider data_create_item_invalid_theme_json_version + * + * @covers WP_REST_Font_Faces_Controller::create_item + * + * @param int $theme_json_version Version input to test. + */ + public function test_create_item_invalid_theme_json_version( $theme_json_version ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', $theme_json_version ); + $request->set_param( 'font_face_settings', '' ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_theme_json_version() { + return array( + array( 1 ), + array( 3 ), + ); + } + + /** + * @dataProvider data_create_item_invalid_settings + * + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + * + * @param mixed $settings Settings to test. + */ + public function test_create_item_invalid_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_settings() { + return array( + 'Missing fontFamily' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Empty fontFamily' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Wrong fontFamily type' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ), + ), + 'Invalid fontDisplay' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontDisplay' => 'invalid' ) ), + ), + 'Missing src' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'src' => '' ) ), + ), + 'Empty src string' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => '' ) ), + ), + 'Empty src array' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => array() ) ), + ), + 'Empty src array values' => array( + 'settings' => array_merge( self::$default_settings, array( '', '' ) ), + ), + 'Wrong src type' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => 1234 ) ), + ), + 'Wrong src array types' => array( + 'settings' => array_merge( self::$default_settings, array( 'src' => array( 1234, 5678 ) ) ), + ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_invalid_settings_json() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_face_settings', 'invalid' ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_face_settings parameter must be a valid JSON string.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_invalid_file_src() { + $files = $this->setup_font_file_upload( array( 'woff2' ) ); + + wp_set_current_user( self::$admin_id ); + $src = 'invalid'; + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( self::$default_settings, array( 'src' => $src ) ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_face_settings[src] value "' . $src . '" must be a valid URL or file reference.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings + */ + public function test_create_item_missing_file_src() { + $files = $this->setup_font_file_upload( array( 'woff2', 'woff' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( + 'font_face_settings', + wp_json_encode( + array_merge( self::$default_settings, array( 'src' => array( array_keys( $files )[0] ) ) ) + ) + ); + $request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'File ' . array_keys( $files )[1] . ' must be used in font_face_settings[src].'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @dataProvider data_sanitize_font_face_settings + * + * @covers WP_REST_Font_Face_Controller::sanitize_font_face_settings + * + * @param string $settings Settings to test. + * @param string $expected Expected settings result. + */ + public function test_create_item_sanitize_font_face_settings( $settings, $expected ) { + $settings = array_merge( self::$default_settings, $settings ); + $expected = array_merge( self::$default_settings, $expected ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + wp_delete_post( $data['id'], true ); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertSame( $expected, $data['font_face_settings'], 'The response font_face_settings should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_font_face_settings() { + return array( + 'settings with tags, extra whitespace, new lines' => array( + 'settings' => array( + 'fontFamily' => " Open Sans\n ", + 'fontStyle' => " oblique 20deg 50deg\n ", + 'fontWeight' => " 200\n ", + 'src' => " https://example.com/ ", + 'fontStretch' => " expanded\n ", + 'ascentOverride' => " 70%\n ", + 'descentOverride' => " 30%\n ", + 'fontVariant' => " normal\n ", + 'fontFeatureSettings' => " \"swsh\" 2\n ", + 'fontVariationSettings' => " \"xhgt\" 0.7\n ", + 'lineGapOverride' => " 10%\n ", + 'sizeAdjust' => " 90%\n ", + 'unicodeRange' => " U+0025-00FF, U+4??\n ", + 'preview' => " https://example.com/ ", + ), + 'expected' => array( + 'fontFamily' => '"Open Sans"', + 'fontStyle' => 'oblique 20deg 50deg', + 'fontWeight' => '200', + 'src' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20', + 'fontStretch' => 'expanded', + 'ascentOverride' => '70%', + 'descentOverride' => '30%', + 'fontVariant' => 'normal', + 'fontFeatureSettings' => '"swsh" 2', + 'fontVariationSettings' => '"xhgt" 0.7', + 'lineGapOverride' => '10%', + 'sizeAdjust' => '90%', + 'unicodeRange' => 'U+0025-00FF, U+4??', + 'preview' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20', + ), + ), + 'multiword font family name with integer' => array( + 'settings' => array( + 'fontFamily' => 'Libre Barcode 128 Text', + ), + 'expected' => array( + 'fontFamily' => '"Libre Barcode 128 Text"', + ), + ), + 'multiword font family name' => array( + 'settings' => array( + 'fontFamily' => 'B612 Mono', + ), + 'expected' => array( + 'fontFamily' => '"B612 Mono"', + ), + ), + 'comma-separated font family names' => array( + 'settings' => array( + 'fontFamily' => 'Open Sans, Noto Sans, sans-serif', + ), + 'expected' => array( + 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif', + ), + ), + ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::create_item + */ + // public function test_create_item_no_permission() {} + + /** + * @covers WP_REST_Font_Faces_Controller::update_item + */ + public function test_update_item() { + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id1 ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_no_route', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item() { + wp_set_current_user( self::$admin_id ); + $font_face_id = self::create_font_face_post( self::$font_family_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 201.' ); + $this->assertNull( get_post( $font_face_id ), 'The deleted post should not exist.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_no_trash() { + wp_set_current_user( self::$admin_id ); + $font_face_id = self::create_font_face_post( self::$font_family_id ); + + // Attempt trashing. + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'The response should return an error for "rest_trash_not_supported" with 501 status.' ); + + $request->set_param( 'force', 'false' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'When "force" is false, the response should return an error for "rest_trash_not_supported" with 501 status.' ); + + // Ensure the post still exists. + $post = get_post( $font_face_id ); + $this->assertNotEmpty( $post, 'The post should still exists.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_invalid_font_face_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete + */ + public function test_delete_item_missing_parent() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/font-faces/' . self::$font_face_id1 ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item + */ + public function test_delete_item_invalid_parent_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$other_font_family_id . '/font-faces/' . self::$font_face_id1 ); + $request->set_param( 'force', true ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404, 'The response should return an error for "rest_font_face_parent_id_mismatch" with 404 status.' ); + + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"'; + $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::delete_item + */ + public function test_delete_item_no_permissions() { + $font_face_id = $this->create_font_face_post( self::$font_family_id ); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 401, 'The response should return an error for "rest_cannot_delete" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . $font_face_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 403, 'The response should return an error for "rest_cannot_delete" with 403 status for a user without permission.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response + */ + public function test_prepare_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces/' . self::$font_face_id2 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_face_data( $data, self::$font_face_id2, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item_schema + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 4, $properties, 'There should be 4 properties in the schema::properties data.' ); + $this->assertArrayHasKey( 'id', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'theme_json_version', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'parent', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_face_settings', $properties, 'The id property should exist in the schema::properties data.' ); + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_item_schema + */ + public function test_get_item_schema_font_face_settings_should_all_have_sanitize_callbacks() { + $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_item_schema(); + $font_face_settings_schema = $schema['properties']['font_face_settings']; + + $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' ); + $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_face_settings_schema['properties'] as $property ) { + $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' ); + $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' ); + $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'The sanitize_callback value should be callable.' ); + } + } + + /** + * @covers WP_REST_Font_Faces_Controller::get_public_item_schema + */ + public function test_get_public_item_schema_should_not_have_arg_options() { + $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_public_item_schema(); + $font_face_settings_schema = $schema['properties']['font_face_settings']; + + $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' ); + $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_face_settings_schema['properties'] as $property ) { + $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' ); + } + } + + protected function check_font_face_data( $data, $post_id, $links ) { + self::$post_ids_for_cleanup[] = $post_id; + $post = get_post( $post_id ); + + $this->assertArrayHasKey( 'id', $data, 'The id property should exist in response data.' ); + $this->assertSame( $post->ID, $data['id'], 'The "id" from the response data should match the post ID.' ); + + $this->assertArrayHasKey( 'parent', $data, 'The parent property should exist in response data.' ); + $this->assertSame( $post->post_parent, $data['parent'], 'The "parent" from the response data should match the post parent.' ); + + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'], 'The "theme_json_version" from the response data should match WP_Theme_JSON::LATEST_SCHEMA.' ); + + $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in response data.' ); + $this->assertSame( $post->post_content, wp_json_encode( $data['font_face_settings'] ), 'The encoded "font_face_settings" from the response data should match the post content.' ); + + $this->assertNotEmpty( $links, 'The links should not be empty in the response data.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ); + $this->assertSame( $expected, $links['self'][0]['href'], 'The links URL from the response data should match the post\'s REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent . '/font-faces' ); + $this->assertSame( $expected, $links['collection'][0]['href'], 'The links collection URL from the response data should match the REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->post_parent ); + $this->assertSame( $expected, $links['parent'][0]['href'], 'The links for a parent URL from the response data should match the parent\'s REST endpoint.' ); + } + + protected function check_file_meta( $font_face_id, $src_attributes ) { + $file_meta = get_post_meta( $font_face_id, '_wp_font_face_file' ); + + foreach ( $src_attributes as $src_attribute ) { + $file_name = basename( $src_attribute ); + $this->assertContains( $file_name, $file_meta, 'The uploaded font file path should be saved in the post meta.' ); + } + } + + protected function setup_font_file_upload( $formats ) { + $files = array(); + foreach ( $formats as $format ) { + $font_file = DIR_TESTDATA . 'fonts/OpenSans-Regular.' . $format; + $font_path = wp_tempnam( 'OpenSans-Regular.' . $format ); + copy( $font_file, $font_path ); + + $files[ 'file-' . count( $files ) ] = array( + 'name' => 'OpenSans-Regular.' . $format, + 'full_path' => 'OpenSans-Regular.' . $format, + 'type' => 'font/' . $format, + 'tmp_name' => $font_path, + 'error' => 0, + 'size' => filesize( $font_path ), + ); + } + + return $files; + } +} diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php new file mode 100644 index 0000000000000..94ad5eccd7e57 --- /dev/null +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -0,0 +1,1050 @@ + 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ); + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + + self::$font_family_id1 = self::create_font_family_post( + array( + 'name' => 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ) + ); + self::$font_family_id2 = self::create_font_family_post( + array( + 'name' => 'Helvetica', + 'slug' => 'helvetica', + 'fontFamily' => 'Helvetica, Arial, sans-serif', + ) + ); + self::$font_face_id1 = Tests_REST_WpRestFontFacesController::create_font_face_post( + self::$font_family_id1, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '400', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-medium.ttf' ), + ) + ); + self::$font_face_id2 = Tests_REST_WpRestFontFacesController::create_font_face_post( + self::$font_family_id1, + array( + 'fontFamily' => '"Open Sans"', + 'fontWeight' => '900', + 'fontStyle' => 'normal', + 'src' => home_url( '/wp-content/fonts/open-sans-bold.ttf' ), + ) + ); + + static::$post_ids_to_cleanup = array(); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$admin_id ); + self::delete_user( self::$editor_id ); + + wp_delete_post( self::$font_family_id1 ); + wp_delete_post( self::$font_family_id2 ); + wp_delete_post( self::$font_face_id1 ); + wp_delete_post( self::$font_face_id2 ); + } + + public function tear_down() { + foreach ( static::$post_ids_to_cleanup as $post_id ) { + wp_delete_post( $post_id, true ); + } + static::$post_ids_to_cleanup = array(); + + parent::tear_down(); + } + + public static function create_font_family_post( $settings = array() ) { + $settings = array_merge( self::$default_settings, $settings ); + $post_id = self::factory()->post->create( + wp_slash( + array( + 'post_type' => 'wp_font_family', + 'post_status' => 'publish', + 'post_title' => $settings['name'], + 'post_name' => $settings['slug'], + 'post_content' => wp_json_encode( + array( + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ) + ), + ) + ) + ); + + static::$post_ids_to_cleanup[] = $post_id; + + return $post_id; + } + + /** + * @covers WP_REST_Font_Families_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/font-families', + $routes, + 'Font faces collection for the given font family does not exist' + ); + $this->assertCount( + 2, + $routes['/wp/v2/font-families'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + $this->assertArrayHasKey( + '/wp/v2/font-families/(?P[\d]+)', + $routes, + 'Single font face route for the given font family does not exist' + ); + $this->assertCount( + 3, + $routes['/wp/v2/font-families/(?P[\d]+)'], + 'Font faces collection for the given font family does not have exactly two elements' + ); + } + + public function test_font_families_no_autosave_routes() { + // @core-merge: Enable this test. + $this->markTestSkipped( 'This test only works with WP 6.4 and above. Enable it once 6.5 is released.' ); + $routes = rest_get_server()->get_routes(); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/autosaves', + $routes, + 'Font families autosaves route exists.' + ); + $this->assertArrayNotHasKey( + '/wp/v2/font-families/(?P[\d]+)/autosaves/(?P[\d]+)', + $routes, + 'Font families autosaves by id route exists.' + ); + } + + /** + * @doesNotPerformAssertions + */ + public function test_context_param() { + // See test_get_context_param(). + } + + /** + * @dataProvider data_get_context_param + * + * @covers WP_REST_Font_Families_Controller::get_context_param + * + * @param bool $single_route Whether to test a single route. + */ + public function test_get_context_param( $single_route ) { + $route = '/wp/v2/font-families'; + if ( $single_route ) { + $route .= '/' . self::$font_family_id1; + } + + $request = new WP_REST_Request( 'OPTIONS', $route ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $endpoint_data = $data['endpoints'][0]; + $this->assertArrayNotHasKey( 'allow_batch', $endpoint_data, 'The allow_batch property should not exist in the endpoint data.' ); + $this->assertSame( 'view', $endpoint_data['args']['context']['default'], 'The endpoint\'s args::context::default should be set to view.' ); + $this->assertSame( array( 'view', 'embed', 'edit' ), $endpoint_data['args']['context']['enum'], 'The endpoint\'s args::context::enum should be set to [ view, embed, edit ].' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_get_context_param() { + return array( + 'Collection' => array( false ), + 'Single' => array( true ), + ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 2, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( '_links', $data[0], 'The _links property should exist in the response data 0.' ); + $this->check_font_family_data( $data[0], self::$font_family_id2, $data[0]['_links'] ); + $this->assertArrayHasKey( '_links', $data[1], 'The _links property should exist in the response data 1.' ); + $this->check_font_family_data( $data[1], self::$font_family_id1, $data[1]['_links'] ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items_by_slug() { + $font_family = get_post( self::$font_family_id2 ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $request->set_param( 'slug', $font_family->post_name ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertCount( 1, $data, 'There should be 2 properties in the response data.' ); + $this->assertArrayHasKey( 'id', $data[0], 'The id property should exist in the response data.' ); + $this->assertSame( $font_family->ID, $data[0]['id'], 'The id should match the expected ID in the response data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_items + */ + public function test_get_items_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, self::$font_family_id1, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_get_item_embedded_font_faces() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( '_embed', true ); + $response = rest_get_server()->dispatch( $request ); + $data = rest_get_server()->response_to_data( $response, true ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayHasKey( '_embedded', $data, 'The _embedded property should exist in the response data.' ); + $this->assertArrayHasKey( 'font_faces', $data['_embedded'], 'The font_faces property should exist in _embedded data.' ); + $this->assertCount( 2, $data['_embedded']['font_faces'], 'There should be 2 font_faces in the _embedded data.' ); + + foreach ( $data['_embedded']['font_faces'] as $font_face ) { + $this->assertArrayHasKey( 'id', $font_face, 'The id property should exist in the _embedded font_face data.' ); + + $font_face_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 . '/font-faces/' . $font_face['id'] ); + $font_face_response = rest_get_server()->dispatch( $font_face_request ); + $font_face_data = rest_get_server()->response_to_data( $font_face_response, true ); + + $this->assertSame( $font_face_data, $font_face, 'The embedded font_face data should match when the data from a single request.' ); + } + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_removes_extra_settings() { + $font_family_id = self::create_font_family_post( array( 'fontFace' => array() ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertArrayNotHasKey( 'fontFace', $data['font_family_settings'], 'The fontFace property should not exist in the font_family_settings data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_get_item_malformed_post_content_returns_empty_settings() { + $font_family_id = wp_insert_post( + array( + 'post_type' => 'wp_font_family', + 'post_status' => 'publish', + 'post_content' => 'invalid', + ) + ); + + static::$post_ids_to_cleanup[] = $font_family_id; + + $empty_settings = array( + 'name' => '', + // Slug will default to the post id. + 'slug' => (string) $font_family_id, + 'fontFamily' => '', + 'preview' => '', + ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( $empty_settings, $data['font_family_settings'], 'The empty settings should exist in the font_family_settings data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_invalid_font_family_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item + */ + public function test_get_item_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id1 ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response should return an error with a "rest_cannot_read" code and 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response should return an error with a "rest_cannot_read" code and 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::create_item + */ + public function test_create_item() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->check_font_family_data( $data, $data['id'], $response->get_links() ); + + $reponse_settings = $data['font_family_settings']; + $this->assertSame( $settings, $reponse_settings, 'The expected settings should exist in the font_family_settings data.' ); + $this->assertEmpty( $data['font_faces'], 'The font_faces should be empty or not exist in the response data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::validate_create_font_face_request + */ + public function test_create_item_default_theme_json_version() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); + $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' ); + } + + /** + * @dataProvider data_create_item_invalid_theme_json_version + * + * @covers WP_REST_Font_Families_Controller::create_item + * + * @param int $theme_json_version Version to test. + */ + public function test_create_item_invalid_theme_json_version( $theme_json_version ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', $theme_json_version ); + $request->set_param( 'font_family_settings', wp_json_encode( self::$default_settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_theme_json_version() { + return array( + array( 1 ), + array( 3 ), + ); + } + + /** + * @dataProvider data_create_item_with_default_preview + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param array $settings Settings to test. + */ + public function test_create_item_with_default_preview( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $response_settings = $data['font_family_settings']; + $this->assertArrayHasKey( 'preview', $response_settings, 'The preview property should exist in the font_family_settings data.' ); + $this->assertSame( '', $response_settings['preview'], 'The preview data should be an empty string.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_with_default_preview() { + $default_settings = array( + 'name' => 'Open Sans', + 'slug' => 'open-sans-2', + 'fontFamily' => '"Open Sans", sans-serif', + ); + return array( + 'No preview param' => array( + 'settings' => $default_settings, + ), + 'Empty preview' => array( + 'settings' => array_merge( $default_settings, array( 'preview' => '' ) ), + ), + ); + } + + /** + * @dataProvider data_sanitize_font_family_settings + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param string $settings Font family settings to test. + * @param string $expected Expected settings result. + */ + public function test_create_item_santize_font_family_settings( $settings, $expected ) { + $settings = array_merge( self::$default_settings, $settings ); + $expected = array_merge( self::$default_settings, $expected ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + static::$post_ids_to_cleanup[] = $data['id']; + + $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); + $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_sanitize_font_family_settings() { + return array( + 'settings with tags, extra whitespace, new lines' => array( + 'settings' => array( + 'name' => " Opening Sans\n ", + 'slug' => " OPENing SanS \n ", + 'fontFamily' => " Opening Sans\n ", + 'preview' => " https://example.com/ ", + ), + 'expected' => array( + 'name' => 'Opening Sans', + 'slug' => 'opening-sans-alertxss', + 'fontFamily' => '"Opening Sans"', + 'preview' => "https://example.com//stylescriptalert('XSS');/script%20%20%20%20%20%20", + ), + ), + 'multiword font family name with integer' => array( + 'settings' => array( + 'slug' => 'libre-barcode-128-text', + 'fontFamily' => 'Libre Barcode 128 Text', + ), + 'expected' => array( + 'slug' => 'libre-barcode-128-text', + 'fontFamily' => '"Libre Barcode 128 Text"', + ), + ), + 'multiword font family name' => array( + 'settings' => array( + 'slug' => 'b612-mono', + 'fontFamily' => 'B612 Mono', + ), + 'expected' => array( + 'slug' => 'b612-mono', + 'fontFamily' => '"B612 Mono"', + ), + ), + 'comma-separated font family names' => array( + 'settings' => array( + 'slug' => 'open-sans-noto-sans', + 'fontFamily' => 'Open Sans, Noto Sans, sans-serif', + ), + 'expected' => array( + 'slug' => 'open-sans-noto-sans', + 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif', + ), + ), + ); + } + + /** + * @dataProvider data_create_item_invalid_settings + * + * @covers WP_REST_Font_Families_Controller::validate_create_font_face_settings + * + * @param array $settings Settings to test. + */ + public function test_create_item_invalid_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_create_item_invalid_settings() { + return array( + 'Missing name' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'name' => '' ) ), + ), + 'Empty name' => array( + 'settings' => array_merge( self::$default_settings, array( 'name' => '' ) ), + ), + 'Wrong name type' => array( + 'settings' => array_merge( self::$default_settings, array( 'name' => 1234 ) ), + ), + 'Missing slug' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'slug' => '' ) ), + ), + 'Empty slug' => array( + 'settings' => array_merge( self::$default_settings, array( 'slug' => '' ) ), + ), + 'Wrong slug type' => array( + 'settings' => array_merge( self::$default_settings, array( 'slug' => 1234 ) ), + ), + 'Missing fontFamily' => array( + 'settings' => array_diff_key( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Empty fontFamily' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => '' ) ), + ), + 'Wrong fontFamily type' => array( + 'settings' => array_merge( self::$default_settings, array( 'fontFamily' => 1234 ) ), + ), + ); + } + + /** + * @covers WP_REST_Font_Family_Controller::validate_font_family_settings + */ + public function test_create_item_invalid_settings_json() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_family_settings', 'invalid' ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_family_settings parameter must be a valid JSON string.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Family_Controller::create_item + */ + public function test_create_item_with_duplicate_slug() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'font_family_settings', wp_json_encode( array_merge( self::$default_settings, array( 'slug' => 'helvetica' ) ) ) ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_duplicate_font_family', $response, 400, 'The response should return an error for "rest_duplicate_font_family" with 400 status.' ); + $expected_message = 'A font family with slug "helvetica" already exists.'; + $message = $response->as_error()->get_error_messages()[0]; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::create_item + */ + public function test_create_item_no_permission() { + $settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) ); + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_create', $response, 401, 'The response should return an error for "rest_cannot_create" with 401 status.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $request->set_param( + 'font_family_settings', + wp_json_encode( + array( + 'name' => 'Open Sans', + 'slug' => 'open-sans', + 'fontFamily' => '"Open Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/open-sans/open-sans-400-normal.svg', + ) + ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_create', $response, 403, 'The response should return an error for "rest_cannot_create" with 403 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item() { + wp_set_current_user( self::$admin_id ); + + $settings = array( + 'name' => 'Open Sans', + 'fontFamily' => '"Open Sans, "Noto Sans", sans-serif', + 'preview' => 'https://s.w.org/images/fonts/16.9/previews/open-sans/open-sans-400-normal.svg', + ); + + $font_family_id = self::create_font_family_post( array( 'slug' => 'open-sans-2' ) ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( + 'font_family_settings', + wp_json_encode( $settings ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, $font_family_id, $response->get_links() ); + + $expected_settings = array( + 'name' => $settings['name'], + 'slug' => 'open-sans-2', + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ); + $this->assertSame( $expected_settings, $data['font_family_settings'], 'The response font_family_settings should match expected settings.' ); + } + + /** + * @dataProvider data_update_item_individual_settings + * + * @covers WP_REST_Font_Families_Controller::update_item + * + * @param array $settings Settings to test. + */ + public function test_update_item_individual_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + + $font_family_id = self::create_font_family_post(); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $key = key( $settings ); + $value = current( $settings ); + $this->assertArrayHasKey( $key, $data['font_family_settings'], 'The expected key should exist in the font_family_settings data.' ); + $this->assertSame( $value, $data['font_family_settings'][ $key ], 'The font_family_settings data should match.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_update_item_individual_settings() { + return array( + array( array( 'name' => 'Opened Sans' ) ), + array( array( 'fontFamily' => '"Opened Sans", sans-serif' ) ), + array( array( 'preview' => 'https://s.w.org/images/fonts/16.7/previews/opened-sans/opened-sans-400-normal.svg' ) ), + // Empty preview is allowed. + array( array( 'preview' => '' ) ), + ); + } + + /** + * @dataProvider data_sanitize_font_family_settings + * + * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings + * + * @param string $settings Font family settings to test. + * @param string $expected Expected settings result. + */ + public function test_update_item_santize_font_family_settings( $settings, $expected ) { + // Unset/modify slug from the data provider, since we're updating rather than creating. + unset( $settings['slug'] ); + $initial_settings = array( 'slug' => 'open-sans-update' ); + $expected = array_merge( self::$default_settings, $expected, $initial_settings ); + + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post( $initial_settings ); + static::$post_ids_to_cleanup[] = $font_family_id; + + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' ); + } + + /** + * @dataProvider data_update_item_invalid_settings + * + * @covers WP_REST_Font_Families_Controller::update_item + * + * @param array $settings Settings to test. + */ + public function test_update_item_empty_settings( $settings ) { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( + 'font_family_settings', + wp_json_encode( $settings ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_update_item_invalid_settings() { + return array( + 'Empty name' => array( + array( 'name' => '' ), + ), + 'Wrong name type' => array( + array( 'name' => 1234 ), + ), + 'Empty fontFamily' => array( + array( 'fontFamily' => '' ), + ), + 'Wrong fontFamily type' => array( + array( 'fontFamily' => 1234 ), + ), + ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_update_slug_not_allowed() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( + 'font_family_settings', + wp_json_encode( array( 'slug' => 'new-slug' ) ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' ); + $expected_message = 'font_family_settings[slug] cannot be updated.'; + $message = $response->as_error()->get_all_error_data()[0]['params']['font_family_settings']; + $this->assertSame( $expected_message, $message, 'The response error message should match.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_invalid_font_family_id() { + $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) ); + + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404, 'The response should return an error for "rest_post_invalid_id" with 404 status.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::update_item + */ + public function test_update_item_no_permission() { + $settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) ); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_edit', $response, 401, 'The response should return an error for "rest_cannot_edit" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id1 ); + $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_edit', $response, 403, 'The response should return an error for "rest_cannot_edit" with 403 status for a user without permission.' ); + } + + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item() { + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post(); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $request['force'] = true; + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->assertNull( get_post( $font_family_id ), 'The post should not exist after deleting.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_no_trash() { + wp_set_current_user( self::$admin_id ); + $font_family_id = self::create_font_family_post(); + + // Attempt trashing. + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'The response should return an error for "rest_trash_not_supported" with 501 status.' ); + + $request->set_param( 'force', 'false' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501, 'When "force" is false, the response should return an error for "rest_trash_not_supported" with 501 status.' ); + + // Ensure the post still exists. + $post = get_post( $font_family_id ); + $this->assertNotEmpty( $post, 'The post should still exist.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_invalid_font_family_id() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 ); + } + + /** + * @covers WP_REST_Font_Families_Controller::delete_item + */ + public function test_delete_item_no_permissions() { + $font_family_id = self::create_font_family_post(); + + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 401, 'The response should return an error for "rest_cannot_delete" with 401 status for an invalid user.' ); + + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $font_family_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_delete', $response, 403, 'The response should return an error for "rest_cannot_delete" with 403 status for a user without permission.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::prepare_item_for_response + */ + public function test_prepare_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . self::$font_family_id2 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $this->check_font_family_data( $data, self::$font_family_id2, $response->get_links() ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item_schema + */ + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' ); + $properties = $data['schema']['properties']; + $this->assertCount( 4, $properties, 'There should be 4 properties in the schema::properties data.' ); + $this->assertArrayHasKey( 'id', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'theme_json_version', $properties, 'The theme_json_version property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_faces', $properties, 'The font_faces property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_family_settings', $properties, 'The font_family_settings property should exist in the schema::properties data.' ); + } + + /** + * @covers WP_REST_Font_Families_Controller::get_item_schema + */ + public function test_get_item_schema_font_family_settings_should_all_have_sanitize_callbacks() { + $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_item_schema(); + $font_family_settings_schema = $schema['properties']['font_family_settings']; + + $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' ); + $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_family_settings_schema['properties'] as $property ) { + $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' ); + $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' ); + $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'That sanitize_callback value should be callable.' ); + } + } + + /** + * @covers WP_REST_Font_Families_Controller::get_public_item_schema + */ + public function test_get_public_item_schema_should_not_have_arg_options() { + $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_public_item_schema(); + $font_family_settings_schema = $schema['properties']['font_family_settings']; + + $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' ); + $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' ); + + // arg_options should be removed for each setting property. + foreach ( $font_family_settings_schema['properties'] as $property ) { + $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' ); + } + } + + protected function check_font_family_data( $data, $post_id, $links ) { + static::$post_ids_to_cleanup[] = $post_id; + $post = get_post( $post_id ); + + $this->assertArrayHasKey( 'id', $data, 'The id property should exist in response data.' ); + $this->assertSame( $post->ID, $data['id'], 'The "id" from the response data should match the post ID.' ); + + $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'], 'The "theme_json_version" from the response data should match WP_Theme_JSON::LATEST_SCHEMA.' ); + + $font_face_ids = get_children( + array( + 'fields' => 'ids', + 'post_parent' => $post_id, + 'post_type' => 'wp_font_face', + 'order' => 'ASC', + 'orderby' => 'ID', + ) + ); + $this->assertArrayHasKey( 'font_faces', $data, 'The font_faces property should exist in the response data.' ); + + foreach ( $font_face_ids as $font_face_id ) { + $this->assertContains( $font_face_id, $data['font_faces'], 'The ID is in the font_faces data.' ); + } + + $this->assertArrayHasKey( 'font_family_settings', $data, 'The font_family_settings property should exist in the response data.' ); + $settings = $data['font_family_settings']; + $expected_settings = array( + 'name' => $post->post_title, + 'slug' => $post->post_name, + 'fontFamily' => $settings['fontFamily'], + 'preview' => $settings['preview'], + ); + $this->assertSame( $expected_settings, $settings, 'The font_family_settings should match.' ); + + $this->assertNotEmpty( $links, 'The links should not be empty in the response data.' ); + $expected = rest_url( 'wp/v2/font-families/' . $post->ID ); + $this->assertSame( $expected, $links['self'][0]['href'], 'The links URL from the response data should match the post\'s REST endpoint.' ); + $expected = rest_url( 'wp/v2/font-families' ); + $this->assertSame( $expected, $links['collection'][0]['href'], 'The links collection URL from the response data should match the REST endpoint.' ); + + if ( ! $font_face_ids ) { + return; + } + + // Check font_face links, if present. + $this->assertArrayHasKey( 'font_faces', $links ); + foreach ( $links['font_faces'] as $index => $link ) { + $expected = rest_url( 'wp/v2/font-families/' . $post->ID . '/font-faces/' . $font_face_ids[ $index ] ); + $this->assertSame( $expected, $link['href'], 'The links for a font faces URL from the response data should match the REST endpoint.' ); + + $embeddable = isset( $link['attributes']['embeddable'] ) + ? $link['attributes']['embeddable'] + : $link['embeddable']; + $this->assertTrue( $embeddable, 'The embeddable should be true.' ); + } + } +} From f305fadccb726a32571f8b624c171167a5d1a11d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 6 Feb 2024 10:08:42 +0100 Subject: [PATCH 2/8] Fix unit tests --- .../wpRestFontFacesController.php | 6 +- .../tests/rest-api/rest-schema-setup.php | 7 + tests/qunit/fixtures/wp-api-generated.js | 3865 ++++++++++------- 3 files changed, 2195 insertions(+), 1683 deletions(-) diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php index bbfeaca30707a..de9ee79e093d0 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -348,7 +348,7 @@ public function test_get_item_invalid_parent_id() { $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404 ); - $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"'; + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '".'; $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); } @@ -926,7 +926,7 @@ public function test_delete_item_invalid_parent_id() { $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_font_face_parent_id_mismatch', $response, 404, 'The response should return an error for "rest_font_face_parent_id_mismatch" with 404 status.' ); - $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '"'; + $expected_message = 'The font face does not belong to the specified font family with id of "' . self::$other_font_family_id . '".'; $this->assertSame( $expected_message, $response->as_error()->get_error_messages()[0], 'The message must contain the correct parent ID.' ); } @@ -1048,7 +1048,7 @@ protected function check_file_meta( $font_face_id, $src_attributes ) { protected function setup_font_file_upload( $formats ) { $files = array(); foreach ( $formats as $format ) { - $font_file = DIR_TESTDATA . 'fonts/OpenSans-Regular.' . $format; + $font_file = DIR_TESTDATA . '/fonts/OpenSans-Regular.' . $format; $font_path = wp_tempnam( 'OpenSans-Regular.' . $format ); copy( $font_file, $font_path ); diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 3ff35260f0fb8..b5721d75b82c7 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -189,6 +189,13 @@ public function test_expected_routes_in_schema() { '/wp-site-health/v1/directory-sizes', '/wp/v2/wp_pattern_category', '/wp/v2/wp_pattern_category/(?P[\d]+)', + '/wp/v2/font-collections', + '/wp/v2/font-collections/(?P[\/\w-]+)', + '/wp/v2/font-families', + '/wp/v2/font-families/(?P[\d]+)/font-faces', + '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)', + '/wp/v2/font-families/(?P[\d]+)', + ); $this->assertSameSets( $expected_routes, $routes ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 6c961548eb8e2..b305d74c577b0 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -18,13 +18,7 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": { - "application-passwords": { - "endpoints": { - "authorization": "http://example.org/wp-admin/authorize-application.php" - } - } - }, + "authentication": [], "routes": { "/": { "namespace": "", @@ -2707,7 +2701,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/menu-items": { + "/wp/v2/media": { "namespace": "wp/v2", "methods": [ "GET", @@ -2718,9 +2712,6 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "args": { "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -2743,7 +2734,7 @@ mockedApiResponse.Schema = { "per_page": { "description": "Maximum number of items to be returned in result set.", "type": "integer", - "default": 100, + "default": 10, "minimum": 1, "maximum": 100, "required": false @@ -2765,6 +2756,24 @@ 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", @@ -2803,7 +2812,7 @@ mockedApiResponse.Schema = { "order": { "description": "Order sort attribute ascending or descending.", "type": "string", - "default": "asc", + "default": "desc", "enum": [ "asc", "desc" @@ -2811,9 +2820,9 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by object attribute.", + "description": "Sort collection by post attribute.", "type": "string", - "default": "menu_order", + "default": "date", "enum": [ "author", "date", @@ -2824,11 +2833,28 @@ mockedApiResponse.Schema = { "relevance", "slug", "include_slugs", - "title", - "menu_order" + "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.", @@ -2852,118 +2878,36 @@ mockedApiResponse.Schema = { "required": false }, "status": { - "default": "publish", + "default": "inherit", "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" + "private", + "trash" ], "type": "string" }, "required": false }, - "tax_relation": { - "description": "Limit result set based on relationship between multiple taxonomies.", + "media_type": { + "default": null, + "description": "Limit result set to attachments of a particular media type.", "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 - } + "image", + "video", + "text", + "application", + "audio" ], "required": false }, - "menu_order": { - "description": "Limit result set to posts with a specific menu_order value.", - "type": "integer", + "mime_type": { + "default": null, + "description": "Limit result set to attachments of a particular MIME type.", + "type": "string", "required": false } } @@ -2972,26 +2916,55 @@ mockedApiResponse.Schema = { "methods": [ "POST" ], - "allow_batch": { - "v1": true - }, "args": { - "title": { - "description": "The title for the object.", + "date": { + "description": "The date the post was published, in the site's timezone.", "type": [ "string", - "object" + "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 object, as it exists in the database.", + "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ "edit" ] }, "rendered": { - "description": "HTML title for the object, transformed for display.", + "description": "HTML title for the post, transformed for display.", "type": "string", "context": [ "view", @@ -3003,121 +2976,105 @@ mockedApiResponse.Schema = { }, "required": false }, - "type": { - "default": "custom", - "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", + "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": [ - "taxonomy", - "post_type", - "post_type_archive", - "custom" + "open", + "closed" ], "required": false }, - "status": { - "default": "publish", - "description": "A named status for the object.", + "ping_status": { + "description": "Whether or not the post can be pinged.", "type": "string", "enum": [ - "publish", - "future", - "draft", - "pending", - "private" + "open", + "closed" ], "required": false }, - "parent": { - "default": 0, - "description": "The ID for the parent of the object.", - "type": "integer", - "minimum": 0, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "attr_title": { - "description": "Text for the title attribute of the link element for this menu item.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", "required": false }, - "classes": { - "description": "Class names for the link element of this menu item.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "description": { - "description": "The description of this menu item.", - "type": "string", - "required": false - }, - "menu_order": { - "default": 1, - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", - "type": "integer", - "minimum": 1, - "required": false - }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", - "required": false - }, - "object_id": { - "default": 0, - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", - "minimum": 0, - "required": false - }, - "target": { - "description": "The target attribute of the link element for this menu item.", + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", "type": "string", - "enum": [ - "_blank", - "" - ], "required": false }, - "url": { - "description": "The URL to which this menu item points.", - "type": "string", - "format": "uri", + "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 }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", - "type": "array", - "items": { - "type": "string" + "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 }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", + "post": { + "description": "The ID for the associated post of the attachment.", "type": "integer", "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false } } } ], "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/menu-items" - } - ] + "self": "http://example.org/index.php?rest_route=/wp/v2/media" } }, - "/wp/v2/menu-items/(?P[\\d]+)": { + "/wp/v2/media/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -3131,9 +3088,6 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "args": { "id": { "description": "Unique identifier for the post.", @@ -3159,31 +3113,60 @@ mockedApiResponse.Schema = { "PUT", "PATCH" ], - "allow_batch": { - "v1": true - }, "args": { "id": { "description": "Unique identifier for the post.", "type": "integer", "required": false }, - "title": { - "description": "The title for the object.", + "date": { + "description": "The date the post was published, in the site's timezone.", "type": [ "string", - "object" + "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 object, as it exists in the database.", + "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ "edit" ] }, "rendered": { - "description": "HTML title for the object, transformed for display.", + "description": "HTML title for the post, transformed for display.", "type": "string", "context": [ "view", @@ -3195,102 +3178,95 @@ mockedApiResponse.Schema = { }, "required": false }, - "type": { - "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", + "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": [ - "taxonomy", - "post_type", - "post_type_archive", - "custom" + "open", + "closed" ], "required": false }, - "status": { - "description": "A named status for the object.", + "ping_status": { + "description": "Whether or not the post can be pinged.", "type": "string", "enum": [ - "publish", - "future", - "draft", - "pending", - "private" + "open", + "closed" ], "required": false }, - "parent": { - "description": "The ID for the parent of the object.", - "type": "integer", - "minimum": 0, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "attr_title": { - "description": "Text for the title attribute of the link element for this menu item.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", "required": false }, - "classes": { - "description": "Class names for the link element of this menu item.", - "type": "array", - "items": { - "type": "string" + "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 description of this menu item.", - "type": "string", + "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 }, - "menu_order": { - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", + "post": { + "description": "The ID for the associated post of the attachment.", "type": "integer", - "minimum": 1, - "required": false - }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", - "required": false - }, - "object_id": { - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", - "minimum": 0, - "required": false - }, - "target": { - "description": "The target attribute of the link element for this menu item.", - "type": "string", - "enum": [ - "_blank", - "" - ], - "required": false - }, - "url": { - "description": "The URL to which this menu item points.", - "type": "string", - "format": "uri", - "required": false - }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", - "type": "integer", - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], "required": false } } @@ -3299,9 +3275,6 @@ mockedApiResponse.Schema = { "methods": [ "DELETE" ], - "allow_batch": { - "v1": true - }, "args": { "id": { "description": "Unique identifier for the post.", @@ -3318,207 +3291,173 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/menu-items/(?P[\\d]+)/autosaves": { + "/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.", + "id": { + "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": false + "required": true } } - }, + } + ] + }, + "/wp/v2/media/(?P[\\d]+)/edit": { + "namespace": "wp/v2", + "methods": [ + "POST" + ], + "endpoints": [ { "methods": [ "POST" ], "args": { - "parent": { - "description": "The ID for the parent of the object.", - "type": "integer", - "minimum": 0, - "required": false - }, - "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": { - "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": { - "description": "A named status for the object.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "attr_title": { - "description": "Text for the title attribute of the link element for this menu item.", + "src": { + "description": "URL to the edited image file.", "type": "string", - "required": false + "format": "uri", + "required": true }, - "classes": { - "description": "Class names for the link element of this menu item.", + "modifiers": { + "description": "Array of image edits.", "type": "array", + "minItems": 1, "items": { - "type": "string" + "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 }, - "description": { - "description": "The description of this menu item.", - "type": "string", - "required": false - }, - "menu_order": { - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", + "rotation": { + "description": "The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.", "type": "integer", - "minimum": 1, + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 360, + "exclusiveMaximum": true, "required": false }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", + "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 }, - "object_id": { - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", + "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 }, - "target": { - "description": "The target attribute of the link element for this menu item.", - "type": "string", - "enum": [ - "_blank", - "" - ], + "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 }, - "url": { - "description": "The URL to which this menu item points.", - "type": "string", - "format": "uri", - "required": false - }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", - "type": "integer", - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false - } - } - } - ] - }, - "/wp/v2/menu-items/(?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.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", + "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/blocks": { + "/wp/v2/menu-items": { "namespace": "wp/v2", "methods": [ "GET", @@ -3554,7 +3493,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 @@ -3614,7 +3553,7 @@ mockedApiResponse.Schema = { "order": { "description": "Order sort attribute ascending or descending.", "type": "string", - "default": "desc", + "default": "asc", "enum": [ "asc", "desc" @@ -3622,9 +3561,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", @@ -3635,7 +3574,8 @@ mockedApiResponse.Schema = { "relevance", "slug", "include_slugs", - "title" + "title", + "menu_order" ], "required": false }, @@ -3694,8 +3634,8 @@ mockedApiResponse.Schema = { ], "required": false }, - "wp_pattern_category": { - "description": "Limit result set to items with specific terms assigned in the wp_pattern_category taxonomy.", + "menus": { + "description": "Limit result set to items with specific terms assigned in the menus taxonomy.", "type": [ "object", "array" @@ -3737,8 +3677,8 @@ mockedApiResponse.Schema = { ], "required": false }, - "wp_pattern_category_exclude": { - "description": "Limit result set to items except those with specific terms assigned in the wp_pattern_category taxonomy.", + "menus_exclude": { + "description": "Limit result set to items except those with specific terms assigned in the menus taxonomy.", "type": [ "object", "array" @@ -3770,6 +3710,11 @@ mockedApiResponse.Schema = { } ], "required": false + }, + "menu_order": { + "description": "Limit result set to posts with a specific menu_order value.", + "type": "integer", + "required": false } } }, @@ -3781,31 +3726,48 @@ mockedApiResponse.Schema = { "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" + "object" ], - "format": "date-time", + "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 }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", + "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": { - "description": "A named status for the post.", + "default": "publish", + "description": "A named status for the object.", "type": "string", "enum": [ "publish", @@ -3816,87 +3778,97 @@ mockedApiResponse.Schema = { ], "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "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.", "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" - ] - } + "classes": { + "description": "Class names for the link element of this menu item.", + "type": "array", + "items": { + "type": "string" }, "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 - } - }, + "description": { + "description": "The description of this menu item.", + "type": "string", "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "menu_order": { + "default": 1, + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", + "type": "integer", + "minimum": 1, "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", "type": "string", "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", + "object_id": { + "default": 0, + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", + "minimum": 0, + "required": false + }, + "target": { + "description": "The target attribute of the link element for this menu item.", + "type": "string", + "enum": [ + "_blank", + "" + ], + "required": false + }, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", "type": "array", "items": { - "type": "integer" + "type": "string" }, "required": false + }, + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", + "type": "integer", + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false } } } ], "_links": { - "self": "http://example.org/index.php?rest_route=/wp/v2/blocks" - } - }, - "/wp/v2/blocks/(?P[\\d]+)": { - "namespace": "wp/v2", + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/menu-items" + } + ] + } + }, + "/wp/v2/menu-items/(?P[\\d]+)": { + "namespace": "wp/v2", "methods": [ "GET", "POST", @@ -3928,11 +3900,6 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false - }, - "password": { - "description": "The password for the post if it is password protected.", - "type": "string", - "required": false } } }, @@ -3951,31 +3918,46 @@ mockedApiResponse.Schema = { "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.", + "title": { + "description": "The title for the object.", "type": [ "string", - "null" + "object" ], - "format": "date-time", + "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 }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", + "type": { + "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": { - "description": "A named status for the post.", + "description": "A named status for the object.", "type": "string", "enum": [ "publish", @@ -3986,77 +3968,80 @@ mockedApiResponse.Schema = { ], "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "parent": { + "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.", "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" - ] - } + "classes": { + "description": "Class names for the link element of this menu item.", + "type": "array", + "items": { + "type": "string" }, "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 - } - }, + "description": { + "description": "The description of this menu item.", + "type": "string", "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "menu_order": { + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", + "type": "integer", + "minimum": 1, "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", "type": "string", "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", + "object_id": { + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", + "minimum": 0, + "required": false + }, + "target": { + "description": "The target attribute of the link element for this menu item.", + "type": "string", + "enum": [ + "_blank", + "" + ], + "required": false + }, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", "type": "array", "items": { - "type": "integer" + "type": "string" }, "required": false + }, + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", + "type": "integer", + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false } } }, @@ -4083,10 +4068,11 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/blocks/(?P[\\d]+)/revisions": { + "/wp/v2/menu-items/(?P[\\d]+)/autosaves": { "namespace": "wp/v2", "methods": [ - "GET" + "GET", + "POST" ], "endpoints": [ { @@ -4095,7 +4081,7 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, @@ -4109,83 +4095,147 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false - }, - "page": { - "description": "Current page of the collection.", + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "parent": { + "description": "The ID for the parent of the object.", "type": "integer", - "default": 1, - "minimum": 1, + "minimum": 0, "required": false }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "minimum": 1, - "maximum": 100, + "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 }, - "search": { - "description": "Limit results to those matching a string.", + "type": { + "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 }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "status": { + "description": "A named status for the object.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" + "attr_title": { + "description": "Text for the title attribute of the link element for this menu item.", + "type": "string", + "required": false + }, + "classes": { + "description": "Class names for the link element of this menu item.", + "type": "array", + "items": { + "type": "string" }, - "default": [], "required": false }, - "offset": { - "description": "Offset the result set by a specific number of items.", + "description": { + "description": "The description of this menu item.", + "type": "string", + "required": false + }, + "menu_order": { + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", "type": "integer", + "minimum": 1, "required": false }, - "order": { - "description": "Order sort attribute ascending or descending.", + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ], "required": false }, - "orderby": { - "description": "Sort collection by object attribute.", + "object_id": { + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", + "minimum": 0, + "required": false + }, + "target": { + "description": "The target attribute of the link element for this menu item.", "type": "string", - "default": "date", "enum": [ - "date", - "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" + "_blank", + "" ], "required": false + }, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", + "type": "integer", + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false } } } ] }, - "/wp/v2/blocks/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/menu-items/(?P[\\d]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ - "GET", - "DELETE" + "GET" ], "endpoints": [ { @@ -4194,12 +4244,12 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the revision.", + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, "id": { - "description": "Unique identifier for the revision.", + "description": "The ID for the autosave.", "type": "integer", "required": false }, @@ -4215,33 +4265,10 @@ mockedApiResponse.Schema = { "required": false } } - }, - { - "methods": [ - "DELETE" - ], - "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 - }, - "force": { - "type": "boolean", - "default": false, - "description": "Required to be true, as revisions do not support trashing.", - "required": false - } - } } ] }, - "/wp/v2/blocks/(?P[\\d]+)/autosaves": { + "/wp/v2/blocks": { "namespace": "wp/v2", "methods": [ "GET", @@ -4252,12 +4279,10 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "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", @@ -4268,122 +4293,847 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + }, + "page": { + "description": "Current page of the collection.", "type": "integer", + "default": 1, + "minimum": 1, "required": false }, - "date": { - "description": "The date the post was published, in the site's timezone.", - "type": [ - "string", - "null" - ], - "format": "date-time", + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, "required": false }, - "date_gmt": { - "description": "The date the post was published, as GMT.", - "type": [ - "string", - "null" - ], + "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 }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", + "modified_after": { + "description": "Limit response to posts modified after a given ISO8601 compliant date.", "type": "string", + "format": "date-time", "required": false }, - "status": { - "description": "A named status for the post.", + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], + "format": "date-time", "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "modified_before": { + "description": "Limit response to posts modified before a given ISO8601 compliant date.", "type": "string", + "format": "date-time", "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" - ] - } + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" }, + "default": [], "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 - } + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" }, + "default": [], "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "order": { + "description": "Order sort attribute ascending or descending.", "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", - "type": "array", - "items": { + "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 + }, + "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 + }, + "wp_pattern_category": { + "description": "Limit result set to items with specific terms assigned in the wp_pattern_category 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 + }, + "wp_pattern_category_exclude": { + "description": "Limit result set to items except those with specific terms assigned in the wp_pattern_category 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 + } + } + }, + { + "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": [ + "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 + } + } + } + ], + "_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 + }, + "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/blocks/(?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", + "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 + }, + "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" + ], + "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", + "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 @@ -5914,261 +6664,80 @@ mockedApiResponse.Schema = { ], "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 - }, - "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 - } - } - }, - { - "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.", + "orderby": { + "description": "Sort collection by post attribute.", "type": "string", + "default": "date", "enum": [ - "view", - "embed", - "edit" + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" ], - "default": "view", "required": false }, - "password": { - "description": "The password for the post if it is password protected.", - "type": "string", + "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 } } }, { "methods": [ - "POST", - "PUT", - "PATCH" + "POST" ], "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": [ @@ -6284,202 +6853,36 @@ 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" - ], - "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]+)/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", - "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 - }, - "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 - } - } - } - ] + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + } + ] + } }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "/wp/v2/navigation/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + "id": { + "description": "Unique identifier for the post.", "type": "integer", "required": false }, @@ -6493,16 +6896,26 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + }, + "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": { - "parent": { - "description": "The ID for the parent of the autosave.", + "id": { + "description": "Unique identifier for the post.", "type": "integer", "required": false }, @@ -6621,50 +7034,34 @@ mockedApiResponse.Schema = { "required": false } } - } - ] - }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ + }, { "methods": [ - "GET" + "DELETE" ], + "allow_batch": { + "v1": true + }, "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 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", + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", "required": false } } } ] }, - "/wp/v2/media": { + "/wp/v2/navigation/(?P[\\d]+)/revisions": { "namespace": "wp/v2", "methods": [ - "GET", - "POST" + "GET" ], "endpoints": [ { @@ -6672,6 +7069,11 @@ 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", @@ -6693,7 +7095,6 @@ 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 @@ -6703,48 +7104,6 @@ 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", @@ -6779,94 +7138,110 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by post attribute.", + "description": "Sort collection by object attribute.", "type": "string", "default": "date", "enum": [ - "author", "date", "id", "include", - "modified", - "parent", "relevance", "slug", "include_slugs", "title" ], "required": false - }, + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { "parent": { - "description": "Limit result set to items with particular parent IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, - "parent_exclude": { - "description": "Limit result set to all items except those of a particular parent ID.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", "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" - }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false - }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" - }, + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", "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" - }, + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", "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" - ], + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", "required": false - }, - "mime_type": { - "default": null, - "description": "Limit result set to attachments of a particular MIME type.", + } + } + } + ] + }, + "/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 } } @@ -6876,6 +7251,11 @@ 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": [ @@ -6911,6 +7291,11 @@ 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", @@ -6919,7 +7304,8 @@ mockedApiResponse.Schema = { "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ - "edit" + "edit", + "embed" ] }, "rendered": { @@ -6935,58 +7321,20 @@ 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": "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.", + "content": { + "description": "The content for the post.", "type": "object", "properties": { "raw": { - "description": "Caption for the attachment, as it exists in the database.", + "description": "Content for the post, as it exists in the database.", "type": "string", "context": [ - "edit" + "edit", + "embed" ] }, "rendered": { - "description": "HTML caption for the attachment, transformed for display.", + "description": "HTML content for the post, transformed for display.", "type": "string", "context": [ "view", @@ -6994,53 +7342,42 @@ 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", + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", "context": [ - "edit" - ] + "edit", + "embed" + ], + "readonly": true }, - "rendered": { - "description": "HTML description for the attachment, transformed for display.", - "type": "string", + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", "context": [ "view", - "edit" + "edit", + "embed" ], "readonly": true } }, "required": false }, - "post": { - "description": "The ID for the associated post of the attachment.", - "type": "integer", + "template": { + "description": "The theme file to use to display the post.", + "type": "string", "required": false } } } - ], - "_links": { - "self": "http://example.org/index.php?rest_route=/wp/v2/media" - } + ] }, - "/wp/v2/media/(?P[\\d]+)": { + "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" + "GET" ], "endpoints": [ { @@ -7048,8 +7385,13 @@ mockedApiResponse.Schema = { "GET" ], "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 }, @@ -7065,351 +7407,378 @@ mockedApiResponse.Schema = { "required": false } } - }, + } + ] + }, + "/wp/v2/font-families": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ { "methods": [ - "POST", - "PUT", - "PATCH" + "GET" ], "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.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", "enum": [ - "publish", - "future", - "draft", - "pending", - "private" + "view", + "embed", + "edit" ], + "default": "view", "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.", + "page": { + "description": "Current page of the collection.", "type": "integer", + "default": 1, + "minimum": 1, "required": false }, - "comment_status": { - "description": "Whether or not comments are open on the post.", - "type": "string", - "enum": [ - "open", - "closed" - ], + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, "required": false }, - "ping_status": { - "description": "Whether or not the post can be pinged.", - "type": "string", - "enum": [ - "open", - "closed" - ], + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], "required": false }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", "required": false }, - "alt_text": { - "description": "Alternative text to display when attachment is not displayed.", + "order": { + "description": "Order sort attribute ascending or descending.", "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], "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 - } - }, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "id", + "enum": [ + "id", + "include" + ], "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 - } + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" }, "required": false - }, - "post": { - "description": "The ID for the associated post of the attachment.", - "type": "integer", - "required": false } } }, { "methods": [ - "DELETE" + "POST" ], "args": { - "id": { - "description": "Unique identifier for the post.", + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", - "required": false + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true } } } - ] + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" + } + ] + } }, - "/wp/v2/media/(?P[\\d]+)/post-process": { + "/wp/v2/font-families/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ - "POST" + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { "methods": [ - "POST" + "GET" ], "args": { "id": { - "description": "Unique identifier for the attachment.", + "description": "Unique identifier for the post.", "type": "integer", "required": false }, - "action": { + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", "enum": [ - "create-image-subsizes" + "view", + "embed", + "edit" ], + "default": "view", + "required": false + } + } + }, + { + "methods": [ + "POST", + "PUT", + "PATCH" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, + "required": false + }, + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", "required": true } } + }, + { + "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]+)/edit": { + "/wp/v2/font-families/(?P[\\d]+)/font-faces": { "namespace": "wp/v2", "methods": [ + "GET", "POST" ], "endpoints": [ { "methods": [ - "POST" + "GET" ], "args": { - "src": { - "description": "URL to the edited image file.", - "type": "string", - "format": "uri", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", "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" - } - } - } - } - } - ] + "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": 10, + "minimum": 1, + "maximum": 100, + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" }, + "default": [], "required": false }, - "rotation": { - "description": "The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.", + "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", - "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, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], "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, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "id", + "enum": [ + "id", + "include" + ], "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", + "required": true }, - "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, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, "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, + "font_face_settings": { + "description": "font-face declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } + } + ] + }, + "/wp/v2/font-families/(?P[\\d]+)/font-faces/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", + "required": true + }, + "id": { + "description": "Unique identifier for the font face.", + "type": "integer", + "required": true + }, + "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": { + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", + "required": true + }, + "id": { + "description": "Unique identifier for the font face.", + "type": "integer", + "required": true + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", "required": false } } @@ -8780,12 +9149,14 @@ 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", - "attachment": "attachment" + "wp_font_family": "wp_font_family", + "wp_font_face": "wp_font_face" } }, "required": false @@ -11504,6 +11875,80 @@ mockedApiResponse.Schema = { } ] } + }, + "/wp/v2/font-collections": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "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 + }, + "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": 10, + "minimum": 1, + "maximum": 100, + "required": false + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/font-collections" + } + ] + } + }, + "/wp/v2/font-collections/(?P[\\/\\w-]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "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 + } + } + } + ] } }, "site_logo": 0, @@ -12387,6 +12832,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 + } + ] + } + }, "nav_menu_item": { "description": "", "hierarchical": false, @@ -12541,15 +13016,15 @@ mockedApiResponse.TypesCollection = { ] } }, - "attachment": { + "wp_font_family": { "description": "", "hierarchical": false, "has_archive": false, - "name": "Media", - "slug": "attachment", - "icon": "dashicons-admin-media", + "name": "Font Families", + "slug": "wp_font_family", + "icon": null, "taxonomies": [], - "rest_base": "media", + "rest_base": "font-families", "rest_namespace": "wp/v2", "_links": { "collection": [ @@ -12559,7 +13034,37 @@ mockedApiResponse.TypesCollection = { ], "wp:items": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/media" + "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" + } + ], + "curies": [ + { + "name": "wp", + "href": "https://api.w.org/{rel}", + "templated": true + } + ] + } + }, + "wp_font_face": { + "description": "", + "hierarchical": false, + "has_archive": false, + "name": "Font Faces", + "slug": "wp_font_face", + "icon": null, + "taxonomies": [], + "rest_base": "font-families/(?P[\\d]+)/font-faces", + "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/font-families/(?P[\\d]+)/font-faces" } ], "curies": [ From 9635c4e4f8b6e1f03ce810ff43763be1109ce25f Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 6 Feb 2024 12:49:31 +0100 Subject: [PATCH 3/8] Regenerate api fixtures --- tests/qunit/fixtures/wp-api-generated.js | 186 +++++++++++++++++++---- 1 file changed, 160 insertions(+), 26 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index b305d74c577b0..c48e0011aff9f 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -810,7 +810,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -1089,7 +1095,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -1528,7 +1540,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "sticky": { @@ -1996,7 +2014,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -2247,7 +2271,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -2653,7 +2683,13 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "footnotes": { + "type": "string", + "description": "", + "default": "" + } + }, "required": false }, "template": { @@ -4622,7 +4658,17 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + } + }, "required": false }, "template": { @@ -4792,7 +4838,17 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + } + }, "required": false }, "template": { @@ -5122,7 +5178,17 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "wp_pattern_sync_status": { + "type": "string", + "description": "", + "default": "", + "enum": [ + "partial", + "unsynced" + ] + } + }, "required": false }, "template": { @@ -9244,7 +9310,25 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -9375,7 +9459,25 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -9516,7 +9618,25 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": [], + "properties": { + "persisted_preferences": { + "type": "object", + "description": "", + "default": [], + "context": [ + "edit" + ], + "properties": { + "_modified": { + "description": "The date and time the preferences were updated.", + "type": "string", + "format": "date-time", + "readonly": false + } + }, + "additionalProperties": true + } + }, "required": false } } @@ -12132,7 +12252,8 @@ mockedApiResponse.PostsCollection = [ "template": "", "format": "standard", "meta": { - "meta_key": "meta_value" + "meta_key": "meta_value", + "footnotes": "" }, "categories": [ 1 @@ -12232,7 +12353,8 @@ mockedApiResponse.PostModel = { "template": "", "format": "standard", "meta": { - "meta_key": "meta_value" + "meta_key": "meta_value", + "footnotes": "" }, "categories": [ 1 @@ -12263,7 +12385,8 @@ mockedApiResponse.postRevisions = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12295,7 +12418,8 @@ mockedApiResponse.postRevisions = [ "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12329,7 +12453,8 @@ mockedApiResponse.revision = { "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12356,7 +12481,8 @@ mockedApiResponse.postAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12390,7 +12516,8 @@ mockedApiResponse.autosave = { "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12427,7 +12554,8 @@ mockedApiResponse.PagesCollection = [ "ping_status": "closed", "template": "", "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "self": [ @@ -12511,7 +12639,8 @@ mockedApiResponse.PageModel = { "ping_status": "closed", "template": "", "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12538,7 +12667,8 @@ mockedApiResponse.pageRevisions = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12570,7 +12700,8 @@ mockedApiResponse.pageRevisions = [ "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12604,7 +12735,8 @@ mockedApiResponse.pageRevision = { "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; @@ -12631,7 +12763,8 @@ mockedApiResponse.pageAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" }, "_links": { "parent": [ @@ -12665,7 +12798,8 @@ mockedApiResponse.pageAutosave = { "rendered": "" }, "meta": { - "meta_key": "" + "meta_key": "", + "footnotes": "" } }; From 608ca3117c0394b13ec2b3af097219a9fa563a3d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 6 Feb 2024 12:54:08 +0100 Subject: [PATCH 4/8] Generate the right fixtures --- tests/qunit/fixtures/wp-api-generated.js | 186 ++++------------------- 1 file changed, 26 insertions(+), 160 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index c48e0011aff9f..b305d74c577b0 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -810,13 +810,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -1095,13 +1089,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -1540,13 +1528,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "sticky": { @@ -2014,13 +1996,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -2271,13 +2247,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -2683,13 +2653,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "footnotes": { - "type": "string", - "description": "", - "default": "" - } - }, + "properties": [], "required": false }, "template": { @@ -4658,17 +4622,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - } - }, + "properties": [], "required": false }, "template": { @@ -4838,17 +4792,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - } - }, + "properties": [], "required": false }, "template": { @@ -5178,17 +5122,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "wp_pattern_sync_status": { - "type": "string", - "description": "", - "default": "", - "enum": [ - "partial", - "unsynced" - ] - } - }, + "properties": [], "required": false }, "template": { @@ -9310,25 +9244,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -9459,25 +9375,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -9618,25 +9516,7 @@ mockedApiResponse.Schema = { "meta": { "description": "Meta fields.", "type": "object", - "properties": { - "persisted_preferences": { - "type": "object", - "description": "", - "default": [], - "context": [ - "edit" - ], - "properties": { - "_modified": { - "description": "The date and time the preferences were updated.", - "type": "string", - "format": "date-time", - "readonly": false - } - }, - "additionalProperties": true - } - }, + "properties": [], "required": false } } @@ -12252,8 +12132,7 @@ mockedApiResponse.PostsCollection = [ "template": "", "format": "standard", "meta": { - "meta_key": "meta_value", - "footnotes": "" + "meta_key": "meta_value" }, "categories": [ 1 @@ -12353,8 +12232,7 @@ mockedApiResponse.PostModel = { "template": "", "format": "standard", "meta": { - "meta_key": "meta_value", - "footnotes": "" + "meta_key": "meta_value" }, "categories": [ 1 @@ -12385,8 +12263,7 @@ mockedApiResponse.postRevisions = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12418,8 +12295,7 @@ mockedApiResponse.postRevisions = [ "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12453,8 +12329,7 @@ mockedApiResponse.revision = { "rendered": "

REST API Client Fixture: Post

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -12481,8 +12356,7 @@ mockedApiResponse.postAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12516,8 +12390,7 @@ mockedApiResponse.autosave = { "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -12554,8 +12427,7 @@ mockedApiResponse.PagesCollection = [ "ping_status": "closed", "template": "", "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "self": [ @@ -12639,8 +12511,7 @@ mockedApiResponse.PageModel = { "ping_status": "closed", "template": "", "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -12667,8 +12538,7 @@ mockedApiResponse.pageRevisions = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12700,8 +12570,7 @@ mockedApiResponse.pageRevisions = [ "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12735,8 +12604,7 @@ mockedApiResponse.pageRevision = { "rendered": "

REST API Client Fixture: Page

\n" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; @@ -12763,8 +12631,7 @@ mockedApiResponse.pageAutosaves = [ "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" }, "_links": { "parent": [ @@ -12798,8 +12665,7 @@ mockedApiResponse.pageAutosave = { "rendered": "" }, "meta": { - "meta_key": "", - "footnotes": "" + "meta_key": "" } }; From 3ddf0235098ecdb1fec572649ff360b295b7c604 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 6 Feb 2024 12:30:50 -0300 Subject: [PATCH 5/8] remove PHPCS comments not needed in core requested in https://github.com/WordPress/wordpress-develop/pull/6034#discussion_r1479458090 --- .../rest-api/endpoints/class-wp-rest-font-faces-controller.php | 2 +- .../endpoints/class-wp-rest-font-families-controller.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php index 6a3ae958f4f6d..b84a1c4a91db3 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -103,7 +103,7 @@ public function register_routes() { * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ - public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class + public function get_items_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); if ( ! current_user_can( $post_type->cap->read ) ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php index a04723d31669a..4744fd95b54cc 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -29,7 +29,7 @@ class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ - public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class + public function get_items_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); if ( ! current_user_can( $post_type->cap->read ) ) { From a0836f0f6b4fffc3259a2711f2ea43b02823a6e2 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 6 Feb 2024 19:47:39 -0600 Subject: [PATCH 6/8] Updates from gutenberg #58752, #58735, and #58736 to address feedback comments --- .../fonts/class-wp-font-library.php | 5 +- ...ss-wp-rest-font-collections-controller.php | 38 +++++++-------- .../class-wp-rest-font-faces-controller.php | 47 +++++++++++-------- ...class-wp-rest-font-families-controller.php | 46 +++++++++++------- .../wpFontLibrary/getFontCollection.php | 2 +- .../wpRestFontCollectionsController.php | 8 +++- .../wpRestFontFacesController.php | 37 +++++++++------ .../wpRestFontFamiliesController.php | 25 ++++++---- 8 files changed, 122 insertions(+), 86 deletions(-) diff --git a/src/wp-includes/fonts/class-wp-font-library.php b/src/wp-includes/fonts/class-wp-font-library.php index a0c07eeffcd8f..f9ca90327121d 100644 --- a/src/wp-includes/fonts/class-wp-font-library.php +++ b/src/wp-includes/fonts/class-wp-font-library.php @@ -115,14 +115,13 @@ public function get_font_collections() { * @since 6.5.0 * * @param string $slug Font collection slug. - * @return WP_Font_Collection|WP_Error Font collection object, - * or WP_Error object if the font collection doesn't exist. + * @return WP_Font_Collection|null Font collection object, or null if the font collection doesn't exist. */ public function get_font_collection( $slug ) { if ( $this->is_collection_registered( $slug ) ) { return $this->collections[ $slug ]; } - return new WP_Error( 'font_collection_not_found', __( 'Font collection not found.' ) ); + return null; } /** diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php index c6576cc77c435..05ef0c7b11b64 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php @@ -142,10 +142,8 @@ public function get_item( $request ) { $slug = $request->get_param( 'slug' ); $collection = WP_Font_Library::get_instance()->get_font_collection( $slug ); - // If the collection doesn't exist returns a 404. - if ( is_wp_error( $collection ) ) { - $collection->add_data( array( 'status' => 404 ) ); - return $collection; + if ( ! $collection ) { + return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) ); } return $this->prepare_item_for_response( $collection, $request ); @@ -156,22 +154,22 @@ public function get_item( $request ) { * * @since 6.5.0 * - * @param WP_Font_Collection $collection Collection object. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. + * @param WP_Font_Collection $item Font collection object. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ - public function prepare_item_for_response( $collection, $request ) { + public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); - $item = array(); + $data = array(); if ( rest_is_field_included( 'slug', $fields ) ) { - $item['slug'] = $collection->slug; + $data['slug'] = $item->slug; } // If any data fields are requested, get the collection data. $data_fields = array( 'name', 'description', 'font_families', 'categories' ); if ( ! empty( array_intersect( $fields, $data_fields ) ) ) { - $collection_data = $collection->get_data(); + $collection_data = $item->get_data(); if ( is_wp_error( $collection_data ) ) { $collection_data->add_data( array( 'status' => 500 ) ); return $collection_data; @@ -179,15 +177,15 @@ public function prepare_item_for_response( $collection, $request ) { foreach ( $data_fields as $field ) { if ( rest_is_field_included( $field, $fields ) ) { - $item[ $field ] = $collection_data[ $field ]; + $data[ $field ] = $collection_data[ $field ]; } } } - $response = rest_ensure_response( $item ); + $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) ) { - $links = $this->prepare_links( $collection ); + $links = $this->prepare_links( $item ); $response->add_links( $links ); } @@ -196,17 +194,15 @@ public function prepare_item_for_response( $collection, $request ) { $response->data = $this->filter_response_by_context( $response->data, $context ); /** - * Filters a font collection returned from the REST API. - * - * Allows modification of the font collection right before it is returned. + * Filters the font collection data for a REST API response. * * @since 6.5.0 * - * @param WP_REST_Response $response The response object. - * @param WP_Font_Collection $collection The Font Collection object. - * @param WP_REST_Request $request Request used to generate the response. + * @param WP_REST_Response $response The response object. + * @param WP_Font_Collection $item The font collection object. + * @param WP_REST_Request $request Request used to generate the response. */ - return apply_filters( 'rest_prepare_font_collection', $response, $collection, $request ); + return apply_filters( 'rest_prepare_font_collection', $response, $item, $request ); } /** diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php index b84a1c4a91db3..0a870b8c705ce 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -11,6 +11,15 @@ * Class to access font faces through the REST API. */ class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller { + + /** + * The latest version of theme.json schema supported by the controller. + * + * @since 6.5.0 + * @var int + */ + const LATEST_THEME_JSON_VERSION_SUPPORTED = 2; + /** * Whether the controller supports batching. * @@ -231,9 +240,8 @@ public function validate_create_font_face_settings( $value, $request ) { * * @since 6.5.0 * - * @param string $value Encoded JSON string of font face settings. - * @param WP_REST_Request $request Request object. - * @return array Decoded array of font face settings. + * @param string $value Encoded JSON string of font face settings. + * @return array Decoded and sanitized array of font face settings. */ public function sanitize_font_face_settings( $value ) { // Settings arrive as stringified JSON, since this is a multipart/form-data request. @@ -326,7 +334,7 @@ public function create_item( $request ) { 'update_post_term_cache' => false, ) ); - if ( ! empty( $query->get_posts() ) ) { + if ( ! empty( $query->posts ) ) { return new WP_Error( 'rest_duplicate_font_face', __( 'A font face matching those settings already exists.' ), @@ -416,7 +424,7 @@ public function delete_item( $request ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ - sprintf( __( "Font faces do not support trashing. Set '%s' to delete." ), 'force=true' ), + sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), array( 'status' => 501 ) ); } @@ -441,7 +449,7 @@ public function prepare_item_for_response( $item, $request ) { $data['id'] = $item->ID; } if ( rest_is_field_included( 'theme_json_version', $fields ) ) { - $data['theme_json_version'] = 2; + $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; } if ( rest_is_field_included( 'parent', $fields ) ) { @@ -502,9 +510,9 @@ public function get_item_schema() { 'theme_json_version' => array( 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 'type' => 'integer', - 'default' => 2, + 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'minimum' => 2, - 'maximum' => 2, + 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'context' => array( 'view', 'edit', 'embed' ), ), 'parent' => array( @@ -697,14 +705,16 @@ public function get_collection_params() { $query_params = parent::get_collection_params(); // Remove unneeded params. - unset( $query_params['after'] ); - unset( $query_params['modified_after'] ); - unset( $query_params['before'] ); - unset( $query_params['modified_before'] ); - unset( $query_params['search'] ); - unset( $query_params['search_columns'] ); - unset( $query_params['slug'] ); - unset( $query_params['status'] ); + unset( + $query_params['after'], + $query_params['modified_after'], + $query_params['before'], + $query_params['modified_before'], + $query_params['search'], + $query_params['search_columns'], + $query_params['slug'], + $query_params['status'] + ); $query_params['orderby']['default'] = 'id'; $query_params['orderby']['enum'] = array( 'id', 'include' ); @@ -801,7 +811,7 @@ protected function prepare_links( $post ) { * @since 6.5.0 * * @param WP_REST_Request $request Request object. - * @return stdClass|WP_Error Post object or WP_Error. + * @return stdClass Post object. */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass(); @@ -829,7 +839,6 @@ protected function prepare_item_for_database( $request ) { * @since 6.5.0 * * @param string $value Font face src that is a URL or the key for a $_FILES array item. - * * @return string Sanitized value. */ protected function sanitize_src( $value ) { @@ -843,7 +852,7 @@ protected function sanitize_src( $value ) { * @since 6.5.0 * * @param array $file Single file item from $_FILES. - * @return array Array containing uploaded file attributes on success, or error on failure. + * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure. */ protected function handle_font_file_upload( $file ) { add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php index 4744fd95b54cc..184b42d141dd3 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -13,6 +13,15 @@ * @since 6.5.0 */ class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { + + /** + * The latest version of theme.json schema supported by the controller. + * + * @since 6.5.0 + * @var int + */ + const LATEST_THEME_JSON_VERSION_SUPPORTED = 2; + /** * Whether the controller supports batching. * @@ -29,7 +38,7 @@ class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ - public function get_items_permissions_check( $request ) { + public function get_items_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); if ( ! current_user_can( $post_type->cap->read ) ) { @@ -136,9 +145,8 @@ public function validate_font_family_settings( $value, $request ) { * * @since 6.5.0 * - * @param string $value Encoded JSON string of font family settings. - * @param WP_REST_Request $request Request object. - * @return array Decoded array font family settings. + * @param string $value Encoded JSON string of font family settings. + * @return array Decoded array of font family settings. */ public function sanitize_font_family_settings( $value ) { // Settings arrive as stringified JSON, since this is a multipart/form-data request. @@ -175,7 +183,7 @@ public function create_item( $request ) { 'update_post_term_cache' => false, ) ); - if ( ! empty( $query->get_posts() ) ) { + if ( ! empty( $query->posts ) ) { return new WP_Error( 'rest_duplicate_font_family', /* translators: %s: Font family slug. */ @@ -203,7 +211,7 @@ public function delete_item( $request ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ - sprintf( __( "Font faces do not support trashing. Set '%s' to delete." ), 'force=true' ), + sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), array( 'status' => 501 ) ); } @@ -229,7 +237,7 @@ public function prepare_item_for_response( $item, $request ) { } if ( rest_is_field_included( 'theme_json_version', $fields ) ) { - $data['theme_json_version'] = 2; + $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; } if ( rest_is_field_included( 'font_faces', $fields ) ) { @@ -290,9 +298,9 @@ public function get_item_schema() { 'theme_json_version' => array( 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 'type' => 'integer', - 'default' => 2, + 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'minimum' => 2, - 'maximum' => 2, + 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'context' => array( 'view', 'edit', 'embed' ), ), 'font_faces' => array( @@ -383,13 +391,15 @@ public function get_collection_params() { $query_params = parent::get_collection_params(); // Remove unneeded params. - unset( $query_params['after'] ); - unset( $query_params['modified_after'] ); - unset( $query_params['before'] ); - unset( $query_params['modified_before'] ); - unset( $query_params['search'] ); - unset( $query_params['search_columns'] ); - unset( $query_params['status'] ); + unset( + $query_params['after'], + $query_params['modified_after'], + $query_params['before'], + $query_params['modified_before'], + $query_params['search'], + $query_params['search_columns'], + $query_params['status'] + ); $query_params['orderby']['default'] = 'id'; $query_params['orderby']['enum'] = array( 'id', 'include' ); @@ -453,7 +463,7 @@ protected function get_font_face_ids( $font_family_id ) { ) ); - return $query->get_posts(); + return $query->posts; } /** @@ -487,7 +497,7 @@ protected function prepare_font_face_links( $font_family_id ) { foreach ( $font_face_ids as $font_face_id ) { $links[] = array( 'embeddable' => true, - 'href' => rest_url( $this->namespace . '/' . $this->rest_base . '/' . $font_family_id . '/font-faces/' . $font_face_id ), + 'href' => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ), ); } return $links; diff --git a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php index 675efe81aec59..e01cc2c1a105a 100644 --- a/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/tests/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -25,6 +25,6 @@ public function test_should_get_font_collection() { public function test_should_get_no_font_collection_if_the_slug_is_not_registered() { $font_collection = WP_Font_Library::get_instance()->get_font_collection( 'not-registered-font-collection' ); - $this->assertWPError( $font_collection ); + $this->assertNull( $font_collection ); } } diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php index 60f50e503fdbe..0f7fe8f9c662b 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php @@ -19,6 +19,12 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + // Clear the font collections. + $collections = WP_Font_Library::get_instance()->get_font_collections(); + foreach ( $collections as $slug => $collection ) { + WP_Font_Library::get_instance()->unregister_font_collection( $slug ); + } + self::$admin_id = $factory->user->create( array( 'role' => 'administrator', @@ -115,7 +121,7 @@ public function test_get_item_invalid_slug() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection' ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'font_collection_not_found', $response, 404 ); + $this->assertErrorResponse( 'rest_font_collection_not_found', $response, 404 ); } /** diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php index de9ee79e093d0..3e74b23b7cb20 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -360,7 +360,7 @@ public function test_create_item() { $files = $this->setup_font_file_upload( array( 'woff2' ) ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -404,7 +404,7 @@ public function test_create_item_with_multiple_font_files() { $files = $this->setup_font_file_upload( array( 'ttf', 'otf', 'woff', 'woff2' ) ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -450,7 +450,7 @@ public function test_create_item_invalid_file_type() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -476,7 +476,7 @@ public function test_create_item_invalid_file_type() { public function test_create_item_with_url_src() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -521,7 +521,7 @@ public function test_create_item_with_all_properties() { ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( $properties ) ); $response = rest_get_server()->dispatch( $request ); @@ -595,7 +595,7 @@ public function test_create_item_default_theme_json_version() { $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); - $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' ); + $this->assertSame( WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The default theme.json version should match the latest version supported by the controller.' ); } /** @@ -637,7 +637,7 @@ public function data_create_item_invalid_theme_json_version() { public function test_create_item_invalid_settings( $settings ) { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( $settings ) ); $response = rest_get_server()->dispatch( $request ); @@ -691,7 +691,7 @@ public function data_create_item_invalid_settings() { public function test_create_item_invalid_settings_json() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', 'invalid' ); $response = rest_get_server()->dispatch( $request ); @@ -711,7 +711,7 @@ public function test_create_item_invalid_file_src() { wp_set_current_user( self::$admin_id ); $src = 'invalid'; $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -736,7 +736,7 @@ public function test_create_item_missing_file_src() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_face_settings', wp_json_encode( @@ -972,9 +972,9 @@ public function test_get_item_schema() { $properties = $data['schema']['properties']; $this->assertCount( 4, $properties, 'There should be 4 properties in the schema::properties data.' ); $this->assertArrayHasKey( 'id', $properties, 'The id property should exist in the schema::properties data.' ); - $this->assertArrayHasKey( 'theme_json_version', $properties, 'The id property should exist in the schema::properties data.' ); - $this->assertArrayHasKey( 'parent', $properties, 'The id property should exist in the schema::properties data.' ); - $this->assertArrayHasKey( 'font_face_settings', $properties, 'The id property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'theme_json_version', $properties, 'The theme_json_version property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'parent', $properties, 'The parent property should exist in the schema::properties data.' ); + $this->assertArrayHasKey( 'font_face_settings', $properties, 'The font_face_settings property should exist in the schema::properties data.' ); } /** @@ -1011,6 +1011,15 @@ public function test_get_public_item_schema_should_not_have_arg_options() { } } + /** + * If WP_Theme_JSON::LATEST_SCHEMA is changed, the controller should be updated to handle any differences + * in `fontFace` structure to ensure support for the latest theme.json schema, and backwards compatibility + * for existing wp_font_face posts. + */ + public function test_controller_supports_latest_theme_json_version() { + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + } + protected function check_font_face_data( $data, $post_id, $links ) { self::$post_ids_for_cleanup[] = $post_id; $post = get_post( $post_id ); @@ -1022,7 +1031,7 @@ protected function check_font_face_data( $data, $post_id, $links ) { $this->assertSame( $post->post_parent, $data['parent'], 'The "parent" from the response data should match the post parent.' ); $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); - $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'], 'The "theme_json_version" from the response data should match WP_Theme_JSON::LATEST_SCHEMA.' ); + $this->assertSame( WP_REST_Font_Faces_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The "theme_json_version" from the response data should match the latest version supported by the controller.' ); $this->assertArrayHasKey( 'font_face_settings', $data, 'The font_face_settings property should exist in response data.' ); $this->assertSame( $post->post_content, wp_json_encode( $data['font_face_settings'] ), 'The encoded "font_face_settings" from the response data should match the post content.' ); diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php index 94ad5eccd7e57..0676c3ce4b88d 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -151,8 +151,6 @@ public function test_register_routes() { } public function test_font_families_no_autosave_routes() { - // @core-merge: Enable this test. - $this->markTestSkipped( 'This test only works with WP 6.4 and above. Enable it once 6.5 is released.' ); $routes = rest_get_server()->get_routes(); $this->assertArrayNotHasKey( '/wp/v2/font-families/(?P[\d]+)/autosaves', @@ -376,7 +374,7 @@ public function test_create_item() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); @@ -405,7 +403,7 @@ public function test_create_item_default_theme_json_version() { $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' ); $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in the response data.' ); - $this->assertSame( 2, $data['theme_json_version'], 'The default theme.json version should be 2.' ); + $this->assertSame( WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The default theme.json version should match the latest version supported by the controller.' ); } /** @@ -447,7 +445,7 @@ public function data_create_item_invalid_theme_json_version() { public function test_create_item_with_default_preview( $settings ) { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); @@ -569,7 +567,7 @@ public function data_sanitize_font_family_settings() { public function test_create_item_invalid_settings( $settings ) { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_family_settings', wp_json_encode( $settings ) ); $response = rest_get_server()->dispatch( $request ); @@ -619,7 +617,7 @@ public function data_create_item_invalid_settings() { public function test_create_item_invalid_settings_json() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_family_settings', 'invalid' ); $response = rest_get_server()->dispatch( $request ); @@ -636,7 +634,7 @@ public function test_create_item_invalid_settings_json() { public function test_create_item_with_duplicate_slug() { wp_set_current_user( self::$admin_id ); $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $request->set_param( 'theme_json_version', 2 ); + $request->set_param( 'theme_json_version', WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); $request->set_param( 'font_family_settings', wp_json_encode( array_merge( self::$default_settings, array( 'slug' => 'helvetica' ) ) ) ); $response = rest_get_server()->dispatch( $request ); @@ -990,6 +988,15 @@ public function test_get_public_item_schema_should_not_have_arg_options() { } } + /** + * If WP_Theme_JSON::LATEST_SCHEMA is changed, the controller should be updated to handle any differences + * in `fontFamilies` structure to ensure support for the latest theme.json schema, and backwards compatibility + * for existing wp_font_family posts. + */ + public function test_controller_supports_latest_theme_json_version() { + $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED ); + } + protected function check_font_family_data( $data, $post_id, $links ) { static::$post_ids_to_cleanup[] = $post_id; $post = get_post( $post_id ); @@ -998,7 +1005,7 @@ protected function check_font_family_data( $data, $post_id, $links ) { $this->assertSame( $post->ID, $data['id'], 'The "id" from the response data should match the post ID.' ); $this->assertArrayHasKey( 'theme_json_version', $data, 'The theme_json_version property should exist in response data.' ); - $this->assertSame( WP_Theme_JSON::LATEST_SCHEMA, $data['theme_json_version'], 'The "theme_json_version" from the response data should match WP_Theme_JSON::LATEST_SCHEMA.' ); + $this->assertSame( WP_REST_Font_Families_Controller::LATEST_THEME_JSON_VERSION_SUPPORTED, $data['theme_json_version'], 'The "theme_json_version" from the response data should match the latest version supported by the controller.' ); $font_face_ids = get_children( array( From 2cf254702e0747b2701b1e52d789ec553e4ff96d Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 6 Feb 2024 19:52:56 -0600 Subject: [PATCH 7/8] Remove extra empty line in rest-schema-setup.php --- tests/phpunit/tests/rest-api/rest-schema-setup.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index b5721d75b82c7..a81ef0d4338df 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -195,7 +195,6 @@ public function test_expected_routes_in_schema() { '/wp/v2/font-families/(?P[\d]+)/font-faces', '/wp/v2/font-families/(?P[\d]+)/font-faces/(?P[\d]+)', '/wp/v2/font-families/(?P[\d]+)', - ); $this->assertSameSets( $expected_routes, $routes ); From 8d61ec0fb1c913e82fe67f0f624cd3ef14ba74cc Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 6 Feb 2024 20:17:42 -0600 Subject: [PATCH 8/8] Attempt to fix unit tests by commiting updates to wp-api-generated.js from test run --- tests/qunit/fixtures/wp-api-generated.js | 4744 +++++++++++----------- 1 file changed, 2375 insertions(+), 2369 deletions(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index b305d74c577b0..8cf6b63cfba27 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -18,7 +18,13 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": [], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "http://example.org/wp-admin/authorize-application.php" + } + } + }, "routes": { "/": { "namespace": "", @@ -2701,7 +2707,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/media": { + "/wp/v2/menu-items": { "namespace": "wp/v2", "methods": [ "GET", @@ -2712,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.", @@ -2734,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 @@ -2756,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", @@ -2812,7 +2803,7 @@ mockedApiResponse.Schema = { "order": { "description": "Order sort attribute ascending or descending.", "type": "string", - "default": "desc", + "default": "asc", "enum": [ "asc", "desc" @@ -2820,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", @@ -2833,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.", @@ -2878,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 } } @@ -2916,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", @@ -2976,105 +3003,121 @@ 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": [], + "parent": { + "default": 0, + "description": "The ID for the parent of the object.", + "type": "integer", + "minimum": 0, "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "attr_title": { + "description": "Text for the title attribute of the link element for this menu item.", "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 - } + "classes": { + "description": "Class names for the link element of this menu item.", + "type": "array", + "items": { + "type": "string" }, "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 - } + "description": "The description of this menu item.", + "type": "string", + "required": false + }, + "menu_order": { + "default": 1, + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", + "type": "integer", + "minimum": 1, + "required": false + }, + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", + "type": "string", + "required": false + }, + "object_id": { + "default": 0, + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", + "minimum": 0, + "required": false + }, + "target": { + "description": "The target attribute of the link element for this menu item.", + "type": "string", + "enum": [ + "_blank", + "" + ], + "required": false + }, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", + "type": "array", + "items": { + "type": "string" }, "required": false }, - "post": { - "description": "The ID for the associated post of the attachment.", + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", "type": "integer", "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false } } } ], "_links": { - "self": "http://example.org/index.php?rest_route=/wp/v2/media" + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/menu-items" + } + ] } }, - "/wp/v2/media/(?P[\\d]+)": { + "/wp/v2/menu-items/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -3088,6 +3131,9 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "id": { "description": "Unique identifier for the post.", @@ -3113,60 +3159,31 @@ mockedApiResponse.Schema = { "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.", + "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", @@ -3178,103 +3195,113 @@ 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": { + "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": { + "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": { + "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.", + "attr_title": { + "description": "Text for the title attribute of the link element for this menu item.", "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 - } + "classes": { + "description": "Class names for the link element of this menu item.", + "type": "array", + "items": { + "type": "string" }, "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 - } - }, + "description": "The description of this menu item.", + "type": "string", "required": false }, - "post": { - "description": "The ID for the associated post of the attachment.", + "menu_order": { + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", "type": "integer", + "minimum": 1, "required": false - } - } - }, - { + }, + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", + "type": "string", + "required": false + }, + "object_id": { + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", + "minimum": 0, + "required": false + }, + "target": { + "description": "The target attribute of the link element for this menu item.", + "type": "string", + "enum": [ + "_blank", + "" + ], + "required": false + }, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", + "type": "integer", + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false + } + } + }, + { "methods": [ "DELETE" ], + "allow_batch": { + "v1": true + }, "args": { "id": { "description": "Unique identifier for the post.", @@ -3291,173 +3318,207 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/media/(?P[\\d]+)/post-process": { + "/wp/v2/menu-items/(?P[\\d]+)/autosaves": { "namespace": "wp/v2", "methods": [ + "GET", "POST" ], "endpoints": [ { "methods": [ - "POST" + "GET" ], "args": { - "id": { - "description": "Unique identifier for the attachment.", + "parent": { + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, - "action": { + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", "enum": [ - "create-image-subsizes" + "view", + "embed", + "edit" ], - "required": true + "default": "view", + "required": false } } - } - ] - }, - "/wp/v2/media/(?P[\\d]+)/edit": { - "namespace": "wp/v2", - "methods": [ - "POST" - ], - "endpoints": [ + }, { "methods": [ "POST" ], "args": { - "src": { - "description": "URL to the edited image file.", + "parent": { + "description": "The ID for the parent of the object.", + "type": "integer", + "minimum": 0, + "required": false + }, + "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": { + "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", "type": "string", - "format": "uri", - "required": true + "enum": [ + "taxonomy", + "post_type", + "post_type_archive", + "custom" + ], + "required": false }, - "modifiers": { - "description": "Array of image edits.", + "status": { + "description": "A named status for the object.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "attr_title": { + "description": "Text for the title attribute of the link element for this menu item.", + "type": "string", + "required": false + }, + "classes": { + "description": "Class names for the link element of this menu item.", "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" - } - } - } - } - } - ] + "type": "string" }, "required": false }, - "rotation": { - "description": "The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.", + "description": { + "description": "The description of this menu item.", + "type": "string", + "required": false + }, + "menu_order": { + "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", "type": "integer", - "minimum": 0, - "exclusiveMinimum": true, - "maximum": 360, - "exclusiveMaximum": true, + "minimum": 1, "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, + "object": { + "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", + "type": "string", "required": false }, - "y": { - "description": "As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.", - "type": "number", + "object_id": { + "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", + "type": "integer", "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, + "target": { + "description": "The target attribute of the link element for this menu item.", + "type": "string", + "enum": [ + "_blank", + "" + ], "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, + "url": { + "description": "The URL to which this menu item points.", + "type": "string", + "format": "uri", + "required": false + }, + "xfn": { + "description": "The XFN relationship expressed in the link of this menu item.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "menus": { + "description": "The terms assigned to the object in the nav_menu taxonomy.", + "type": "integer", + "required": false + }, + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false } } } ] }, - "/wp/v2/menu-items": { + "/wp/v2/menu-items/(?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.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + } + ] + }, + "/wp/v2/blocks": { "namespace": "wp/v2", "methods": [ "GET", @@ -3493,7 +3554,7 @@ mockedApiResponse.Schema = { "per_page": { "description": "Maximum number of items to be returned in result set.", "type": "integer", - "default": 100, + "default": 10, "minimum": 1, "maximum": 100, "required": false @@ -3553,7 +3614,7 @@ mockedApiResponse.Schema = { "order": { "description": "Order sort attribute ascending or descending.", "type": "string", - "default": "asc", + "default": "desc", "enum": [ "asc", "desc" @@ -3561,9 +3622,9 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by object attribute.", + "description": "Sort collection by post attribute.", "type": "string", - "default": "menu_order", + "default": "date", "enum": [ "author", "date", @@ -3574,8 +3635,7 @@ mockedApiResponse.Schema = { "relevance", "slug", "include_slugs", - "title", - "menu_order" + "title" ], "required": false }, @@ -3634,8 +3694,8 @@ mockedApiResponse.Schema = { ], "required": false }, - "menus": { - "description": "Limit result set to items with specific terms assigned in the menus taxonomy.", + "wp_pattern_category": { + "description": "Limit result set to items with specific terms assigned in the wp_pattern_category taxonomy.", "type": [ "object", "array" @@ -3677,8 +3737,8 @@ mockedApiResponse.Schema = { ], "required": false }, - "menus_exclude": { - "description": "Limit result set to items except those with specific terms assigned in the menus taxonomy.", + "wp_pattern_category_exclude": { + "description": "Limit result set to items except those with specific terms assigned in the wp_pattern_category taxonomy.", "type": [ "object", "array" @@ -3710,11 +3770,6 @@ mockedApiResponse.Schema = { } ], "required": false - }, - "menu_order": { - "description": "Limit result set to posts with a specific menu_order value.", - "type": "integer", - "required": false } } }, @@ -3726,48 +3781,31 @@ mockedApiResponse.Schema = { "v1": true }, "args": { - "title": { - "description": "The title for the object.", + "date": { + "description": "The date the post was published, in the site's timezone.", "type": [ "string", - "object" + "null" ], - "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 - } - }, + "format": "date-time", "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" + "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": { - "default": "publish", - "description": "A named status for the object.", + "description": "A named status for the post.", "type": "string", "enum": [ "publish", @@ -3778,96 +3816,86 @@ mockedApiResponse.Schema = { ], "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.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, - "classes": { - "description": "Class names for the link element of this menu item.", - "type": "array", - "items": { - "type": "string" + "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 }, - "description": { - "description": "The description of this menu item.", - "type": "string", - "required": false - }, - "menu_order": { - "default": 1, - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", - "type": "integer", - "minimum": 1, - "required": false - }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", - "required": false - }, - "object_id": { - "default": 0, - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", - "minimum": 0, + "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 }, - "target": { - "description": "The target attribute of the link element for this menu item.", - "type": "string", - "enum": [ - "_blank", - "" - ], + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "url": { - "description": "The URL to which this menu item points.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", - "format": "uri", "required": false }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", + "wp_pattern_category": { + "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", "type": "array", "items": { - "type": "string" + "type": "integer" }, "required": false - }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", - "type": "integer", - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false } } } ], "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/menu-items" - } - ] + "self": "http://example.org/index.php?rest_route=/wp/v2/blocks" } }, - "/wp/v2/menu-items/(?P[\\d]+)": { + "/wp/v2/blocks/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -3900,6 +3928,11 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", + "required": false } } }, @@ -3918,46 +3951,31 @@ mockedApiResponse.Schema = { "type": "integer", "required": false }, - "title": { - "description": "The title for the object.", + "date": { + "description": "The date the post was published, in the site's timezone.", "type": [ "string", - "object" + "null" ], - "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 - } - }, + "format": "date-time", "required": false }, - "type": { - "description": "The family of objects originally represented, such as \"post_type\" or \"taxonomy\".", - "type": "string", - "enum": [ - "taxonomy", - "post_type", - "post_type_archive", - "custom" + "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 object.", + "description": "A named status for the post.", "type": "string", "enum": [ "publish", @@ -3968,80 +3986,77 @@ mockedApiResponse.Schema = { ], "required": false }, - "parent": { - "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.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, - "classes": { - "description": "Class names for the link element of this menu item.", - "type": "array", - "items": { - "type": "string" + "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 }, - "description": { - "description": "The description of this menu item.", - "type": "string", - "required": false - }, - "menu_order": { - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", - "type": "integer", - "minimum": 1, - "required": false - }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", - "required": false - }, - "object_id": { - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", - "minimum": 0, + "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 }, - "target": { - "description": "The target attribute of the link element for this menu item.", - "type": "string", - "enum": [ - "_blank", - "" - ], + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "url": { - "description": "The URL to which this menu item points.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", - "format": "uri", "required": false }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", + "wp_pattern_category": { + "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", "type": "array", "items": { - "type": "string" + "type": "integer" }, "required": false - }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", - "type": "integer", - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false } } }, @@ -4068,11 +4083,10 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/menu-items/(?P[\\d]+)/autosaves": { + "/wp/v2/blocks/(?P[\\d]+)/revisions": { "namespace": "wp/v2", "methods": [ - "GET", - "POST" + "GET" ], "endpoints": [ { @@ -4081,7 +4095,7 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the autosave.", + "description": "The ID for the parent of the revision.", "type": "integer", "required": false }, @@ -4095,563 +4109,82 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "parent": { - "description": "The ID for the parent of the object.", - "type": "integer", - "minimum": 0, - "required": false - }, - "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": { - "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": { - "description": "A named status for the object.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "attr_title": { - "description": "Text for the title attribute of the link element for this menu item.", - "type": "string", - "required": false }, - "classes": { - "description": "Class names for the link element of this menu item.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "description": { - "description": "The description of this menu item.", - "type": "string", - "required": false - }, - "menu_order": { - "description": "The DB ID of the nav_menu_item that is this item's menu parent, if any, otherwise 0.", - "type": "integer", - "minimum": 1, - "required": false - }, - "object": { - "description": "The type of object originally represented, such as \"category\", \"post\", or \"attachment\".", - "type": "string", - "required": false - }, - "object_id": { - "description": "The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.", - "type": "integer", - "minimum": 0, - "required": false - }, - "target": { - "description": "The target attribute of the link element for this menu item.", - "type": "string", - "enum": [ - "_blank", - "" - ], - "required": false - }, - "url": { - "description": "The URL to which this menu item points.", - "type": "string", - "format": "uri", - "required": false - }, - "xfn": { - "description": "The XFN relationship expressed in the link of this menu item.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - }, - "menus": { - "description": "The terms assigned to the object in the nav_menu taxonomy.", - "type": "integer", - "required": false - }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], - "required": false - } - } - } - ] - }, - "/wp/v2/menu-items/(?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.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", - "required": false - } - } - } - ] - }, - "/wp/v2/blocks": { - "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": 10, - "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": "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 - }, - "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 - }, - "wp_pattern_category": { - "description": "Limit result set to items with specific terms assigned in the wp_pattern_category 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 - }, - "wp_pattern_category_exclude": { - "description": "Limit result set to items except those with specific terms assigned in the wp_pattern_category 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 - } - } - }, - { - "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", + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, "required": false }, - "status": { - "description": "A named status for the post.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "minimum": 1, + "maximum": 100, "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "search": { + "description": "Limit results to those matching a string.", "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" - ] - } + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" }, + "default": [], "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 - } + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" }, + "default": [], "required": false }, - "meta": { - "description": "Meta fields.", - "type": "object", - "properties": [], + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "order": { + "description": "Order sort attribute ascending or descending.", "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], "required": false }, - "wp_pattern_category": { - "description": "The terms assigned to the post in the wp_pattern_category taxonomy.", - "type": "array", - "items": { - "type": "integer" - }, + "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": [ @@ -4659,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 }, @@ -4678,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 }, @@ -4809,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" @@ -4845,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": { @@ -4931,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", @@ -4944,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": { @@ -4972,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": { @@ -4991,7 +4586,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/blocks/(?P[\\d]+)/autosaves": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { "namespace": "wp/v2", "methods": [ "GET", @@ -5001,11 +4596,11 @@ mockedApiResponse.Schema = { { "methods": [ "GET" - ], - "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + ], + "args": { + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "context": { @@ -5026,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", @@ -5119,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" @@ -5154,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": { @@ -5178,10 +4760,11 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { + "/wp/v2/templates": { "namespace": "wp/v2", "methods": [ - "GET" + "GET", + "POST" ], "endpoints": [ { @@ -5189,11 +4772,6 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "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", @@ -5205,82 +4783,141 @@ mockedApiResponse.Schema = { "default": "view", "required": false }, - "page": { - "description": "Current page of the collection.", + "wp_id": { + "description": "Limit to the specified post id.", "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, + "area": { + "description": "Limit to the specified template part area.", + "type": "string", "required": false }, - "search": { - "description": "Limit results to those matching a string.", + "post_type": { + "description": "Post type to get the templates for.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "slug": { + "description": "Unique slug identifying the template.", + "type": "string", + "minLength": 1, + "pattern": "[a-zA-Z0-9_\\%-]+", + "required": true + }, + "theme": { + "description": "Theme identifier for the template.", "type": "string", "required": false }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "type": { + "description": "Type of template.", + "type": "string", "required": false }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" + "content": { + "default": "", + "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 + } }, - "default": [], "required": false }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", + "title": { + "default": "", + "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 }, - "order": { - "description": "Order sort attribute ascending or descending.", + "description": { + "default": "", + "description": "Description of template.", "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ], "required": false }, - "orderby": { - "description": "Sort collection by object attribute.", + "status": { + "default": "publish", + "description": "Status of template.", "type": "string", - "default": "date", "enum": [ - "date", - "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" + "publish", + "future", + "draft", + "pending", + "private" ], "required": false + }, + "author": { + "description": "The ID for the author of the template.", + "type": "integer", + "required": false } } } - ] + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/templates" + } + ] + } }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { + "/wp/v2/templates/lookup": { "namespace": "wp/v2", "methods": [ - "GET", - "DELETE" + "GET" ], "endpoints": [ { @@ -5288,59 +4925,40 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The id of a template", + "slug": { + "description": "The slug of the template to get the fallback for", "type": "string", - "required": false + "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 }, - "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 of a template", + "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 - }, - "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 } } } - ] + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/templates/lookup" + } + ] + } }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { @@ -5368,7 +4986,9 @@ mockedApiResponse.Schema = { }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "args": { "id": { @@ -5471,10 +5091,28 @@ 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/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { "namespace": "wp/v2", "methods": [ "GET" @@ -5490,11 +5128,6 @@ mockedApiResponse.Schema = { "type": "string", "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.", "type": "string", @@ -5505,16 +5138,83 @@ mockedApiResponse.Schema = { ], "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/templates": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "DELETE" ], "endpoints": [ { @@ -5522,6 +5222,16 @@ mockedApiResponse.Schema = { "GET" ], "args": { + "parent": { + "description": "The id of a template", + "type": "string", + "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", @@ -5532,20 +5242,60 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The id of a template", + "type": "string", + "required": false }, - "wp_id": { - "description": "Limit to the specified post id.", + "id": { + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, - "area": { - "description": "Limit to the specified template part area.", + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", + "required": false + } + } + } + ] + }, + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "id": { + "description": "The id of a template", "type": "string", "required": false }, - "post_type": { - "description": "Post type to get the templates for.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } @@ -5555,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.", @@ -5573,7 +5328,6 @@ mockedApiResponse.Schema = { "required": false }, "content": { - "default": "", "description": "Content of template.", "type": [ "object", @@ -5600,7 +5354,6 @@ mockedApiResponse.Schema = { "required": false }, "title": { - "default": "", "description": "Title of template.", "type": [ "object", @@ -5630,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": [ @@ -5652,19 +5403,17 @@ mockedApiResponse.Schema = { "description": "The ID for the author of the template.", "type": "integer", "required": false + }, + "area": { + "description": "Where the template part is intended for use (header, footer, etc.)", + "type": "string", + "required": false } } } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/templates" - } - ] - } + ] }, - "/wp/v2/templates/lookup": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET" @@ -5675,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/templates/lookup" - } - ] - } + ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { + "/wp/v2/template-parts": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", - "DELETE" + "POST" ], "endpoints": [ { @@ -5716,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", @@ -5731,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.", @@ -5764,6 +5512,7 @@ mockedApiResponse.Schema = { "required": false }, "content": { + "default": "", "description": "Content of template.", "type": [ "object", @@ -5790,6 +5539,7 @@ mockedApiResponse.Schema = { "required": false }, "title": { + "default": "", "description": "Title of template.", "type": [ "object", @@ -5819,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": [ @@ -5839,192 +5591,68 @@ mockedApiResponse.Schema = { "description": "The ID for the author of the template.", "type": "integer", "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([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "parent": { - "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", - "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.", + "area": { + "description": "Where the template part is intended for use (header, footer, etc.)", "type": "string", - "default": "date", - "enum": [ - "date", - "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" - ], "required": false } } } - ] - }, - "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "DELETE" ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "parent": { - "description": "The id of a template", - "type": "string", - "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 - } + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts" } - }, + ] + } + }, + "/wp/v2/template-parts/lookup": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ { "methods": [ - "DELETE" + "GET" ], "args": { - "parent": { - "description": "The id of a template", + "slug": { + "description": "The slug of the template to get the fallback for", "type": "string", - "required": false + "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([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { + "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST" + "POST", + "PUT", + "PATCH", + "DELETE" ], "endpoints": [ { @@ -6052,7 +5680,9 @@ mockedApiResponse.Schema = { }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "args": { "id": { @@ -6160,46 +5790,28 @@ mockedApiResponse.Schema = { "required": false } } - } - ] - }, - "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ + }, { "methods": [ - "GET" + "DELETE" ], "args": { - "parent": { + "id": { "description": "The id of a template", "type": "string", "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.", - "type": "string", - "enum": [ - "view", - "embed", - "edit" - ], - "default": "view", + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", "required": false } } } ] }, - "/wp/v2/template-parts": { + "/wp/v2/navigation": { "namespace": "wp/v2", "methods": [ "GET", @@ -6210,6 +5822,9 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -6222,19 +5837,145 @@ mockedApiResponse.Schema = { "default": "view", "required": false }, - "wp_id": { - "description": "Limit to the specified post id.", + "page": { + "description": "Current page of the collection.", "type": "integer", + "default": 1, + "minimum": 1, "required": false }, - "area": { - "description": "Limit to the specified template part area.", + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", "type": "string", "required": false }, - "post_type": { - "description": "Post type to get the templates for.", + "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": "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 + }, + "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 } } @@ -6243,70 +5984,89 @@ 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.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, "slug": { - "description": "Unique slug identifying the template.", + "description": "An alphanumeric identifier for the post unique to its type.", "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", - "required": true + "required": false }, - "theme": { - "description": "Theme identifier for the template.", + "status": { + "description": "A named status for the post.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "type": { - "description": "Type of template.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, - "content": { - "default": "", - "description": "Content of template.", - "type": [ - "object", - "string" - ], + "title": { + "description": "The title for the post.", + "type": "object", "properties": { "raw": { - "description": "Content for the template, as it exists in the database.", + "description": "Title for the post, 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 template.", - "type": "integer", + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", "context": [ - "edit" + "view", + "edit", + "embed" ], "readonly": true } }, "required": false }, - "title": { - "default": "", - "description": "Title of template.", - "type": [ - "object", - "string" - ], + "content": { + "description": "The content for the post.", + "type": "object", "properties": { "raw": { - "description": "Title for the template, as it exists in the database.", + "description": "Content for the post, as it exists in the database.", "type": "string", "context": [ - "view", "edit", "embed" ] }, "rendered": { - "description": "HTML title for the template, transformed for display.", + "description": "HTML content for the post, transformed for display.", "type": "string", "context": [ "view", @@ -6314,73 +6074,31 @@ mockedApiResponse.Schema = { "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 }, - "description": { - "default": "", - "description": "Description of template.", - "type": "string", - "required": false - }, - "status": { - "default": "publish", - "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 - }, - "area": { - "description": "Where the template part is intended for use (header, footer, etc.)", - "type": "string", - "required": false - } - } - } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts" - } - ] - } - }, - "/wp/v2/template-parts/lookup": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "slug": { - "description": "The slug of the template to get the fallback for", - "type": "string", - "required": true - }, - "is_custom": { - "description": "Indicates if a template is custom or part of the template hierarchy", - "type": "boolean", - "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`", + "template": { + "description": "The theme file to use to display the post.", "type": "string", "required": false } @@ -6390,12 +6108,12 @@ mockedApiResponse.Schema = { "_links": { "self": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts/lookup" + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" } ] } }, - "/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { + "/wp/v2/navigation/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -6409,10 +6127,13 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "description": "Unique identifier for the post.", + "type": "integer", "required": false }, "context": { @@ -6425,6 +6146,11 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", + "required": false } } }, @@ -6434,73 +6160,94 @@ mockedApiResponse.Schema = { "PUT", "PATCH" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "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": "Unique slug identifying the template.", + "description": "An alphanumeric identifier for the post unique to its type.", "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", "required": false }, - "theme": { - "description": "Theme identifier for the template.", + "status": { + "description": "A named status for the post.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "type": { - "description": "Type of template.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, - "content": { - "description": "Content of template.", - "type": [ - "object", - "string" - ], + "title": { + "description": "The title for the post.", + "type": "object", "properties": { "raw": { - "description": "Content for the template, as it exists in the database.", + "description": "Title for the post, 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 template.", - "type": "integer", + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", "context": [ - "edit" + "view", + "edit", + "embed" ], "readonly": true } }, "required": false }, - "title": { - "description": "Title of template.", - "type": [ - "object", - "string" - ], + "content": { + "description": "The content for the post.", + "type": "object", "properties": { "raw": { - "description": "Title for the template, as it exists in the database.", + "description": "Content for the post, as it exists in the database.", "type": "string", "context": [ - "view", "edit", "embed" ] }, "rendered": { - "description": "HTML title for the template, transformed for display.", + "description": "HTML content for the post, transformed for display.", "type": "string", "context": [ "view", @@ -6508,34 +6255,31 @@ mockedApiResponse.Schema = { "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 }, - "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 - }, - "area": { - "description": "Where the template part is intended for use (header, footer, etc.)", + "template": { + "description": "The theme file to use to display the post.", "type": "string", "required": false } @@ -6545,10 +6289,13 @@ mockedApiResponse.Schema = { "methods": [ "DELETE" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "description": "Unique identifier for the post.", + "type": "integer", "required": false }, "force": { @@ -6561,21 +6308,22 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/navigation": { + "/wp/v2/navigation/(?P[\\d]+)/revisions": { "namespace": "wp/v2", "methods": [ - "GET", - "POST" + "GET" ], "endpoints": [ { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "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", @@ -6597,7 +6345,6 @@ 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 @@ -6607,30 +6354,6 @@ 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 - }, - "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", @@ -6665,224 +6388,98 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by post attribute.", + "description": "Sort collection by object attribute.", "type": "string", "default": "date", "enum": [ - "author", "date", "id", "include", - "modified", - "parent", "relevance", "slug", "include_slugs", "title" ], "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 } } - }, + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "DELETE" + ], + "endpoints": [ { "methods": [ - "POST" + "GET" ], - "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", + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, - "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", - "type": "string", + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", "required": false }, - "status": { - "description": "A named status for the post.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", "enum": [ - "publish", - "future", - "draft", - "pending", - "private" + "view", + "embed", + "edit" ], + "default": "view", "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 - } - }, + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, - "template": { - "description": "The theme file to use to display the post.", - "type": "string", + "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 } } } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" - } - ] - } + ] }, - "/wp/v2/navigation/(?P[\\d]+)": { + "/wp/v2/navigation/(?P[\\d]+)/autosaves": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", - "DELETE" + "POST" ], "endpoints": [ { "methods": [ "GET" ], - "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 }, @@ -6896,26 +6493,16 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false - }, - "password": { - "description": "The password for the post if it is password protected.", - "type": "string", - "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 }, @@ -7028,10 +6615,228 @@ mockedApiResponse.Schema = { }, "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/v2/navigation/(?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.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + } + ] + }, + "/wp/v2/font-families": { + "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 + }, + "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": 10, + "minimum": 1, + "maximum": 100, + "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 post attribute.", + "type": "string", + "default": "id", + "enum": [ + "id", + "include" + ], + "required": false + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, + "required": false + }, + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" + } + ] + } + }, + "/wp/v2/font-families/(?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 + }, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, "required": false + }, + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true } } }, @@ -7039,9 +6844,6 @@ mockedApiResponse.Schema = { "methods": [ "DELETE" ], - "allow_batch": { - "v1": true - }, "args": { "id": { "description": "Unique identifier for the post.", @@ -7058,10 +6860,11 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "/wp/v2/font-families/(?P[\\d]+)/font-faces": { "namespace": "wp/v2", "methods": [ - "GET" + "GET", + "POST" ], "endpoints": [ { @@ -7069,10 +6872,10 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -7095,15 +6898,11 @@ 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 }, - "search": { - "description": "Limit results to those matching a string.", - "type": "string", - "required": false - }, "exclude": { "description": "Ensure result set excludes specific IDs.", "type": "array", @@ -7138,25 +6937,45 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by object attribute.", + "description": "Sort collection by post attribute.", "type": "string", - "default": "date", + "default": "id", "enum": [ - "date", "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" + "include" ], "required": false } } + }, + { + "methods": [ + "POST" + ], + "args": { + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", + "required": true + }, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 2, + "minimum": 2, + "maximum": 2, + "required": false + }, + "font_face_settings": { + "description": "font-face declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/font-families/(?P[\\d]+)/font-faces/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -7168,15 +6987,15 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "id": { - "description": "Unique identifier for the revision.", + "description": "Unique identifier for the font face.", "type": "integer", - "required": false + "required": true }, "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -7196,27 +7015,27 @@ mockedApiResponse.Schema = { "DELETE" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "id": { - "description": "Unique identifier for the revision.", + "description": "Unique identifier for the font face.", "type": "integer", - "required": false + "required": true }, "force": { "type": "boolean", "default": false, - "description": "Required to be true, as revisions do not support trashing.", + "description": "Whether to bypass Trash and force deletion.", "required": false } } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "/wp/v2/media": { "namespace": "wp/v2", "methods": [ "GET", @@ -7228,20 +7047,201 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + "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": 10, + "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 + }, + "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", + "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 }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "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": [ - "view", - "embed", - "edit" + "image", + "video", + "text", + "application", + "audio" ], - "default": "view", + "required": false + }, + "mime_type": { + "default": null, + "description": "Limit result set to attachments of a particular MIME type.", + "type": "string", "required": false } } @@ -7251,11 +7251,6 @@ 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": [ @@ -7291,11 +7286,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", @@ -7304,8 +7294,7 @@ mockedApiResponse.Schema = { "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ - "edit", - "embed" + "edit" ] }, "rendered": { @@ -7321,216 +7310,105 @@ mockedApiResponse.Schema = { }, "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 - } - } - } - ] - }, - "/wp/v2/navigation/(?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.", + "author": { + "description": "The ID for the author of the post.", "type": "integer", "required": false }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "comment_status": { + "description": "Whether or not comments are open on the post.", "type": "string", "enum": [ - "view", - "embed", - "edit" + "open", + "closed" ], - "default": "view", "required": false - } - } - } - ] - }, - "/wp/v2/font-families": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + }, + "ping_status": { + "description": "Whether or not the post can be pinged.", "type": "string", "enum": [ - "view", - "embed", - "edit" + "open", + "closed" ], - "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": 10, - "minimum": 1, - "maximum": 100, - "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": [], + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", + "template": { + "description": "The theme file to use to display the post.", + "type": "string", "required": false }, - "order": { - "description": "Order sort attribute ascending or descending.", + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ], "required": false }, - "orderby": { - "description": "Sort collection by post attribute.", - "type": "string", - "default": "id", - "enum": [ - "id", - "include" - ], + "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 }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" + "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 - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", + }, + "post": { + "description": "The ID for the associated post of the attachment.", "type": "integer", - "default": 2, - "minimum": 2, - "maximum": 2, "required": false - }, - "font_family_settings": { - "description": "font-family declaration in theme.json format, encoded as a string.", - "type": "string", - "required": true } } } ], "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" - } - ] + "self": "http://example.org/index.php?rest_route=/wp/v2/media" } }, - "/wp/v2/font-families/(?P[\\d]+)": { + "/wp/v2/media/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -7575,210 +7453,338 @@ mockedApiResponse.Schema = { "type": "integer", "required": false }, - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", - "type": "integer", - "default": 2, - "minimum": 2, - "maximum": 2, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", "required": false }, - "font_family_settings": { - "description": "font-family declaration in theme.json format, encoded as a string.", - "type": "string", - "required": true - } - } - }, - { - "methods": [ - "DELETE" - ], - "args": { - "id": { - "description": "Unique identifier for the post.", - "type": "integer", + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", "required": false - } - } - } - ] - }, - "/wp/v2/font-families/(?P[\\d]+)/font-faces": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true }, - "context": { - "description": "Scope under which the request is made; determines fields present in response.", + "status": { + "description": "A named status for the post.", "type": "string", "enum": [ - "view", - "embed", - "edit" + "publish", + "future", + "draft", + "pending", + "private" ], - "default": "view", "required": false }, - "page": { - "description": "Current page of the collection.", - "type": "integer", - "default": 1, - "minimum": 1, + "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 }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", + "author": { + "description": "The ID for the author of the post.", "type": "integer", - "default": 10, - "minimum": 1, - "maximum": 100, "required": false }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "comment_status": { + "description": "Whether or not comments are open on the post.", + "type": "string", + "enum": [ + "open", + "closed" + ], "required": false }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" - }, - "default": [], + "ping_status": { + "description": "Whether or not the post can be pinged.", + "type": "string", + "enum": [ + "open", + "closed" + ], "required": false }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "order": { - "description": "Order sort attribute ascending or descending.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", - "default": "desc", - "enum": [ - "asc", - "desc" - ], "required": false }, - "orderby": { - "description": "Sort collection by post attribute.", + "alt_text": { + "description": "Alternative text to display when attachment is not displayed.", "type": "string", - "default": "id", - "enum": [ - "id", - "include" - ], + "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": [ - "POST" + "DELETE" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true - }, - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", + "id": { + "description": "Unique identifier for the post.", "type": "integer", - "default": 2, - "minimum": 2, - "maximum": 2, "required": false }, - "font_face_settings": { - "description": "font-face declaration in theme.json format, encoded as a string.", - "type": "string", - "required": true + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", + "required": false } } } ] }, - "/wp/v2/font-families/(?P[\\d]+)/font-faces/(?P[\\d]+)": { + "/wp/v2/media/(?P[\\d]+)/post-process": { "namespace": "wp/v2", "methods": [ - "GET", - "DELETE" + "POST" ], "endpoints": [ { "methods": [ - "GET" + "POST" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true - }, "id": { - "description": "Unique identifier for the font face.", + "description": "Unique identifier for the attachment.", "type": "integer", - "required": true + "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": false + "required": true } } - }, + } + ] + }, + "/wp/v2/media/(?P[\\d]+)/edit": { + "namespace": "wp/v2", + "methods": [ + "POST" + ], + "endpoints": [ { "methods": [ - "DELETE" + "POST" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", + "src": { + "description": "URL to the edited image file.", + "type": "string", + "format": "uri", "required": true }, - "id": { - "description": "Unique identifier for the font face.", + "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", - "required": true + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 360, + "exclusiveMaximum": true, + "required": false }, - "force": { - "type": "boolean", - "default": false, - "description": "Whether to bypass Trash and force deletion.", + "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 } } @@ -9149,14 +9155,14 @@ 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_font_family": "wp_font_family", - "wp_font_face": "wp_font_face" + "wp_font_face": "wp_font_face", + "attachment": "attachment" } }, "required": false @@ -12832,36 +12838,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, @@ -13075,6 +13051,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 + } + ] + } } };