diff --git a/lib/experimental/fonts/font-library/class-wp-rest-autosave-font-families-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-autosave-font-families-controller.php deleted file mode 100644 index 0e31bd4004b40..0000000000000 --- a/lib/experimental/fonts/font-library/class-wp-rest-autosave-font-families-controller.php +++ /dev/null @@ -1,25 +0,0 @@ -post_type = $post_type; - - $post_type_obj = get_post_type_object( $post_type ); - $this->rest_base = $post_type_obj->rest_base; - - $parent_post_type = 'wp_font_family'; - $this->parent_post_type = $parent_post_type; - $parent_post_type_obj = get_post_type_object( $parent_post_type ); - $this->parent_base = $parent_post_type_obj->rest_base; - $this->namespace = $parent_post_type_obj->rest_namespace; - } + protected $allow_batch = false; /** * Registers the routes for posts. @@ -55,7 +33,7 @@ public function __construct() { public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, + '/' . $this->rest_base, array( 'args' => array( 'font_family_id' => array( @@ -67,7 +45,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_font_faces_permissions_check' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( @@ -82,7 +60,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', + '/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'font_family_id' => array( @@ -99,8 +77,10 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_font_faces_permissions_check' ), - 'args' => array(), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'edit' ) ), + ), ), array( 'methods' => WP_REST_Server::DELETABLE, @@ -124,12 +104,13 @@ public function register_routes() { * * @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_font_faces_permissions_check() { + 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->edit_posts ) ) { + if ( ! current_user_can( $post_type->cap->read ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access font faces.', 'gutenberg' ), @@ -140,6 +121,18 @@ public function get_font_faces_permissions_check() { 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 ) { + return $this->get_items_permissions_check( $request ); + } + /** * Validates settings when creating a font face. * @@ -240,7 +233,7 @@ public function sanitize_font_face_settings( $value ) { * @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_font_family_post( $request['font_family_id'] ); + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } @@ -263,14 +256,12 @@ public function get_item( $request ) { } // Check that the font face has a valid parent font family. - $font_family = $this->get_font_family_post( $request['font_family_id'] ); + $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } - $response = parent::get_item( $request ); - - if ( (int) $font_family->ID !== (int) $response->data['parent'] ) { + if ( (int) $font_family->ID !== (int) $post->post_parent ) { return new WP_Error( 'rest_font_face_parent_id_mismatch', /* translators: %d: A post id. */ @@ -279,7 +270,7 @@ public function get_item( $request ) { ); } - return $response; + return parent::get_item( $request ); } /** @@ -291,19 +282,26 @@ public function get_item( $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. - $existing_font_face = get_posts( + $query = new WP_Query( array( - 'post_type' => $this->post_type, - 'posts_per_page' => 1, - 'title' => WP_Font_Family_Utils::get_font_face_slug( $settings ), + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'title' => WP_Font_Family_Utils::get_font_face_slug( $settings ), + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, ) ); - if ( ! empty( $existing_font_face ) ) { + if ( ! empty( $query->get_posts() ) ) { return new WP_Error( 'rest_duplicate_font_face', __( 'A font face matching those settings already exists.', 'gutenberg' ), @@ -367,9 +365,28 @@ public function create_item( $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"', 'gutenberg' ), $font_family->ID ), + array( 'status' => 404 ) + ); + } + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; - // We don't support trashing for revisions. + // We don't support trashing for font faces. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', @@ -391,19 +408,46 @@ public function delete_item( $request ) { * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ - public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class - $data = array(); + 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; + } - $data['id'] = $item->ID; - $data['theme_json_version'] = 2; - $data['parent'] = $item->post_parent; - $data['font_face_settings'] = $this->get_settings_from_post( $item ); + 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'] : 'edit'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); - $links = $this->prepare_links( $item ); - $response->add_links( $links ); - return $response; + 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 ); } /** @@ -427,6 +471,7 @@ public function get_item_schema() { 'id' => array( 'description' => __( 'Unique identifier for the post.', 'default' ), 'type' => 'integer', + 'context' => array( 'edit', 'embed' ), 'readonly' => true, ), 'theme_json_version' => array( @@ -435,36 +480,39 @@ public function get_item_schema() { 'default' => 2, 'minimum' => 2, 'maximum' => 2, + 'context' => array( 'edit', 'embed' ), ), 'parent' => array( 'description' => __( 'The ID for the parent font family of the font face.', 'gutenberg' ), 'type' => 'integer', + 'context' => array( '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.', 'gutenberg' ), 'type' => 'object', + 'context' => array( 'edit', 'embed' ), 'properties' => array( 'fontFamily' => array( - 'description' => 'CSS font-family value.', + 'description' => __( 'CSS font-family value.', 'gutenberg' ), 'type' => 'string', 'default' => '', ), 'fontStyle' => array( - 'description' => 'CSS font-style value.', + 'description' => __( 'CSS font-style value.', 'gutenberg' ), 'type' => 'string', 'default' => 'normal', ), 'fontWeight' => array( - 'description' => 'List of available font weights, separated by a space.', + 'description' => __( 'List of available font weights, separated by a space.', 'gutenberg' ), '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' ), ), 'fontDisplay' => array( - 'description' => 'CSS font-display value.', + 'description' => __( 'CSS font-display value.', 'gutenberg' ), 'type' => 'string', 'default' => 'fallback', 'enum' => array( @@ -476,7 +524,7 @@ public function get_item_schema() { ), ), 'src' => array( - 'description' => 'Paths or URLs to the font files.', + 'description' => __( 'Paths or URLs to the font files.', 'gutenberg' ), // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array. 'anyOf' => array( array( @@ -492,43 +540,43 @@ public function get_item_schema() { 'default' => array(), ), 'fontStretch' => array( - 'description' => 'CSS font-stretch value.', + 'description' => __( 'CSS font-stretch value.', 'gutenberg' ), 'type' => 'string', ), 'ascentOverride' => array( - 'description' => 'CSS ascent-override value.', + 'description' => __( 'CSS ascent-override value.', 'gutenberg' ), 'type' => 'string', ), 'descentOverride' => array( - 'description' => 'CSS descent-override value.', + 'description' => __( 'CSS descent-override value.', 'gutenberg' ), 'type' => 'string', ), 'fontVariant' => array( - 'description' => 'CSS font-variant value.', + 'description' => __( 'CSS font-variant value.', 'gutenberg' ), 'type' => 'string', ), 'fontFeatureSettings' => array( - 'description' => 'CSS font-feature-settings value.', + 'description' => __( 'CSS font-feature-settings value.', 'gutenberg' ), 'type' => 'string', ), 'fontVariationSettings' => array( - 'description' => 'CSS font-variation-settings value.', + 'description' => __( 'CSS font-variation-settings value.', 'gutenberg' ), 'type' => 'string', ), 'lineGapOverride' => array( - 'description' => 'CSS line-gap-override value.', + 'description' => __( 'CSS line-gap-override value.', 'gutenberg' ), 'type' => 'string', ), 'sizeAdjust' => array( - 'description' => 'CSS size-adjust value.', + 'description' => __( 'CSS size-adjust value.', 'gutenberg' ), 'type' => 'string', ), 'unicodeRange' => array( - 'description' => 'CSS unicode-range value.', + 'description' => __( 'CSS unicode-range value.', 'gutenberg' ), 'type' => 'string', ), 'preview' => array( - 'description' => 'URL to a preview image of the font face.', + 'description' => __( 'URL to a preview image of the font face.', 'gutenberg' ), 'type' => 'string', ), ), @@ -551,13 +599,31 @@ public function get_item_schema() { * @return array Collection parameters. */ public function get_collection_params() { - $params = parent::get_collection_params(); - - return array( - 'page' => $params['page'], - 'per_page' => $params['per_page'], - 'search' => $params['search'], - ); + $query_params = parent::get_collection_params(); + + $query_params['context']['default'] = 'edit'; + + // 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 ); } /** @@ -571,7 +637,7 @@ public function get_create_params() { $properties = $this->get_item_schema()['properties']; return array( 'theme_json_version' => $properties['theme_json_version'], - // Font face settings is stringified JSON, to work with multipart/form-data used + // 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.', 'gutenberg' ), @@ -583,18 +649,6 @@ public function get_create_params() { ); } - /** - * Allow the font face post type to be managed through the REST API. - * - * @since 6.5.0 - * - * @param WP_Post_Type|string $post_type Post type name or object. - * @return bool Whether the post type is allowed in REST. - */ - protected function check_is_post_type_allowed( $post_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class - return true; - } - /** * Get the parent font family, if the ID is valid. * @@ -603,7 +657,7 @@ protected function check_is_post_type_allowed( $post_type ) { // phpcs:ignore Va * @param int $font_family_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ - protected function get_font_family_post( $font_family_id ) { + protected function get_parent_font_family_post( $font_family_id ) { $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.', 'default' ), @@ -617,7 +671,7 @@ protected function get_font_family_post( $font_family_id ) { $font_family_post = get_post( (int) $font_family_id ); if ( empty( $font_family_post ) || empty( $font_family_post->ID ) - || $this->parent_post_type !== $font_family_post->post_type + || 'wp_font_family' !== $font_family_post->post_type ) { return $error; } @@ -637,13 +691,13 @@ protected function prepare_links( $post ) { // Entity meta. $links = array( 'self' => array( - 'href' => rest_url( $this->namespace . '/' . $this->parent_base . '/' . $post->post_parent . '/' . $this->rest_base . '/' . $post->ID ), + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), ), 'collection' => array( - 'href' => rest_url( $this->namespace . '/' . $this->parent_base . '/' . $post->post_parent . '/' . $this->rest_base ), + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ), ), 'parent' => array( - 'href' => rest_url( $this->namespace . '/' . $this->parent_base . '/' . $post->post_parent ), + 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ), ), ); @@ -725,7 +779,7 @@ 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 ) { + if ( __( 'Sorry, you are not allowed to upload this file type.', 'default' ) === $message ) { $status = 400; $code = 'rest_font_upload_invalid_file_type'; } diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php index a48c9cbe1a776..887a8a5250cc3 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php @@ -18,81 +18,12 @@ */ class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { /** - * Constructor. + * Whether the controller supports batching. * * @since 6.5.0 + * @var false */ - public function __construct() { - $post_type = 'wp_font_family'; - $this->post_type = $post_type; - - $post_type_obj = get_post_type_object( $post_type ); - $this->rest_base = $post_type_obj->rest_base; - $this->namespace = $post_type_obj->rest_namespace; - } - - /** - * 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_font_families_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_edit_params(), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P[\d]+)', - array( - 'id' => array( - 'description' => __( 'Unique identifier for the font family.', 'gutenberg' ), - 'type' => 'integer', - 'required' => true, - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_font_families_permissions_check' ), - 'args' => array(), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'update_item_permissions_check' ), - 'args' => $this->get_create_edit_params(), - ), - 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' ), - ), - ), - ), - ) - ); - } + protected $allow_batch = false; /** * Checks if a given request has access to font families. @@ -102,13 +33,13 @@ 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_font_families_permissions_check() { + 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->edit_posts ) ) { + if ( ! current_user_can( $post_type->cap->read ) ) { return new WP_Error( 'rest_cannot_read', - __( 'Sorry, you are not allowed to access font faces.', 'gutenberg' ), + __( 'Sorry, you are not allowed to access font families.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -116,6 +47,18 @@ public function get_font_families_permissions_check() { 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 ) { + return $this->get_items_permissions_check( $request ); + } + /** * Validates settings when creating or updating a font family. * @@ -213,14 +156,16 @@ public function create_item( $request ) { $settings = $request->get_param( 'font_family_settings' ); // Check that the font family slug is unique. - $existing_font_family = get_posts( + $query = new WP_Query( array( - 'post_type' => $this->post_type, - 'posts_per_page' => 1, - 'name' => $settings['slug'], + 'post_type' => $this->post_type, + 'posts_per_page' => 1, + 'name' => $settings['slug'], + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, ) ); - if ( ! empty( $existing_font_family ) ) { + if ( ! empty( $query->get_posts() ) ) { return new WP_Error( 'rest_duplicate_font_family', /* translators: %s: Font family slug. */ @@ -241,10 +186,9 @@ public function create_item( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { - $font_family_id = $request->get_param( 'id' ); - $force = isset( $request['force'] ) ? (bool) $request['force'] : false; + $force = isset( $request['force'] ) ? (bool) $request['force'] : false; - // We don't support trashing for revisions. + // We don't support trashing for font families. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', @@ -254,17 +198,7 @@ public function delete_item( $request ) { ); } - $deleted = parent::delete_item( $request ); - - if ( is_wp_error( $deleted ) ) { - return $deleted; - } - - foreach ( $this->get_font_face_ids( $font_family_id ) as $font_face_id ) { - wp_delete_post( $font_face_id, true ); - } - - return $deleted; + return parent::delete_item( $request ); } /** @@ -276,19 +210,47 @@ public function delete_item( $request ) { * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ - public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- required by parent class - $data = array(); + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $data = array(); - $data['id'] = $item->ID; - $data['theme_json_version'] = 2; - $data['font_faces'] = $this->get_font_face_ids( $item->ID ); - $data['font_family_settings'] = $this->get_settings_from_post( $item ); + 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'] : 'edit'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); - $links = $this->prepare_links( $item ); - $response->add_links( $links ); - return $response; + 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 ); } /** @@ -312,6 +274,7 @@ public function get_item_schema() { 'id' => array( 'description' => __( 'Unique identifier for the post.', 'default' ), 'type' => 'integer', + 'context' => array( 'edit' ), 'readonly' => true, ), 'theme_json_version' => array( @@ -320,10 +283,12 @@ public function get_item_schema() { 'default' => 2, 'minimum' => 2, 'maximum' => 2, + 'context' => array( 'edit' ), ), 'font_faces' => array( 'description' => __( 'The IDs of the child font faces in the font family.', 'gutenberg' ), 'type' => 'array', + 'context' => array( 'edit' ), 'items' => array( 'type' => 'integer', ), @@ -333,6 +298,7 @@ public function get_item_schema() { 'font_family_settings' => array( 'description' => __( 'font-face declaration in theme.json format.', 'gutenberg' ), 'type' => 'object', + 'context' => array( 'edit' ), 'properties' => array( 'name' => array( 'description' => 'Name of the font family preset, translatable.', @@ -370,58 +336,72 @@ public function get_item_schema() { * @return array Collection parameters. */ public function get_collection_params() { - $params = parent::get_collection_params(); + $query_params = parent::get_collection_params(); - return array( - 'page' => $params['page'], - 'per_page' => $params['per_page'], - 'search' => $params['search'], - 'slug' => $params['slug'], - ); + $query_params['context']['default'] = 'edit'; + + // 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 ); } /** - * Checks if a given request has access to read font families. + * Retrieves the query params for the font family collection, defaulting to the 'edit' context. * * @since 6.5.0 * - * @return true|WP_Error True if the request has read access, otherwise a WP_Error object. + * @param array $args Optional. Additional arguments for context parameter. Default empty array. + * @return array Context parameter details. */ - public function get_font_family_permissions_check() { - $post_type = get_post_type_object( $this->post_type ); - - if ( ! current_user_can( $post_type->cap->edit_posts ) ) { - return new WP_Error( - 'rest_cannot_read', - __( 'Sorry, you are not allowed to access font families.', 'gutenberg' ), - array( 'status' => rest_authorization_required_code() ) - ); + public function get_context_param( $args = array() ) { + if ( isset( $args['default'] ) ) { + $args['default'] = 'edit'; } - - return true; + return parent::get_context_param( $args ); } /** - * Get the params used when creating or updating a font family. + * 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_create_edit_params() { - $properties = $this->get_item_schema()['properties']; - return array( - 'theme_json_version' => $properties['theme_json_version'], - // 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.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'validate_callback' => array( $this, 'validate_font_family_settings' ), - 'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ), - ), - ); + 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.', 'gutenberg' ), + '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 ); } /** @@ -434,19 +414,24 @@ public function get_create_edit_params() { * . */ protected function get_font_face_ids( $font_family_id ) { - $font_face_ids = get_posts( + $query = new WP_Query( array( - 'fields' => 'ids', - 'post_parent' => $font_family_id, - 'post_type' => 'wp_font_face', - 'posts_per_page' => 999, + '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 $font_face_ids; + + return $query->get_posts(); } /** - * Prepares links for the request. + * Prepares font family links for the request. * * @since 6.5.0 * @@ -464,6 +449,12 @@ protected function prepare_links( $post ) { ); } + /** + * 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(); @@ -528,10 +519,10 @@ protected function get_settings_from_post( $post ) { // Default to empty strings if the settings are missing. return array( - 'name' => $post->post_title ?? '', - 'slug' => $post->post_name ?? '', - 'fontFamily' => $settings_json['fontFamily'] ?? '', - 'preview' => $settings_json['preview'] ?? '', + '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/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index eb76cbab19128..a156089a071c5 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -27,8 +27,8 @@ function gutenberg_init_font_library_routes() { 'singular_name' => __( 'Font Family', 'gutenberg' ), ), 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - 'show_in_rest' => true, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'hierarchical' => false, 'capabilities' => array( 'read' => 'edit_theme_options', 'read_post' => 'edit_theme_options', @@ -45,26 +45,25 @@ function gutenberg_init_font_library_routes() { 'delete_published_posts' => 'edit_theme_options', ), 'map_meta_cap' => false, + 'query_var' => false, + 'show_in_rest' => true, 'rest_base' => 'font-families', 'rest_controller_class' => 'WP_REST_Font_Families_Controller', - 'autosave_rest_controller_class' => 'WP_REST_Autosave_Font_Families_Controller', - 'query_var' => false, + 'autosave_rest_controller_class' => 'WP_REST_Autosave_Fonts_Controller', ); register_post_type( 'wp_font_family', $args ); register_post_type( 'wp_font_face', array( - 'labels' => array( + 'labels' => array( 'name' => __( 'Font Faces', 'gutenberg' ), 'singular_name' => __( 'Font Face', 'gutenberg' ), ), - 'public' => false, - '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ - 'hierarchical' => false, - 'show_in_rest' => false, - 'rest_base' => 'font-faces', - '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_post' => 'edit_theme_options', 'read_private_posts' => 'edit_theme_options', @@ -79,17 +78,18 @@ function gutenberg_init_font_library_routes() { 'delete_others_posts' => 'edit_theme_options', 'delete_published_posts' => 'edit_theme_options', ), - 'map_meta_cap' => false, - 'query_var' => false, + 'map_meta_cap' => false, + 'query_var' => false, + 'show_in_rest' => true, + 'rest_base' => 'font-families/(?P[\d]+)/font-faces', + 'rest_controller_class' => 'WP_REST_Font_Faces_Controller', + 'autosave_rest_controller_class' => 'WP_REST_Autosave_Fonts_Controller', ) ); // @core-merge: This code will go into Core's `create_initial_rest_routes()`. $font_collections_controller = new WP_REST_Font_Collections_Controller(); $font_collections_controller->register_routes(); - - $font_faces_controller = new WP_REST_Font_Faces_Controller(); - $font_faces_controller->register_routes(); } add_action( 'rest_api_init', 'gutenberg_init_font_library_routes' ); @@ -190,7 +190,7 @@ function wp_get_font_dir( $defaults = array() ) { // @core-merge: Filters should go in `src/wp-includes/default-filters.php`, // functions in a general file for font library. -if ( ! function_exists( '_wp_delete_font_family' ) ) { +if ( ! function_exists( '_wp_after_delete_font_family' ) ) { /** * Deletes child font faces when a font family is deleted. * @@ -201,7 +201,7 @@ function wp_get_font_dir( $defaults = array() ) { * @param WP_Post $post Post object. * @return void */ - function _wp_delete_font_family( $post_id, $post ) { + function _wp_after_delete_font_family( $post_id, $post ) { if ( 'wp_font_family' !== $post->post_type ) { return; } @@ -217,10 +217,10 @@ function _wp_delete_font_family( $post_id, $post ) { wp_delete_post( $font_face->ID, true ); } } - add_action( 'deleted_post', '_wp_delete_font_family', 10, 2 ); + add_action( 'deleted_post', '_wp_after_delete_font_family', 10, 2 ); } -if ( ! function_exists( '_wp_delete_font_face' ) ) { +if ( ! function_exists( '_wp_before_delete_font_face' ) ) { /** * Deletes associated font files when a font face is deleted. * @@ -231,7 +231,7 @@ function _wp_delete_font_family( $post_id, $post ) { * @param WP_Post $post Post object. * @return void */ - function _wp_delete_font_face( $post_id, $post ) { + function _wp_before_delete_font_face( $post_id, $post ) { if ( 'wp_font_face' !== $post->post_type ) { return; } @@ -242,5 +242,5 @@ function _wp_delete_font_face( $post_id, $post ) { wp_delete_file( wp_get_font_dir()['path'] . '/' . $font_file ); } } - add_action( 'before_delete_post', '_wp_delete_font_face', 10, 2 ); + add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); } diff --git a/lib/load.php b/lib/load.php index b569c52d14e25..81daeef25933f 100644 --- a/lib/load.php +++ b/lib/load.php @@ -157,7 +157,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-families-controller.php'; require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-faces-controller.php'; require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php'; - require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-autosave-font-families-controller.php'; + require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-autosave-fonts-controller.php'; require __DIR__ . '/experimental/fonts/font-library/font-library.php'; } diff --git a/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php index a53289735c429..55db0ce18392d 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontFacesController.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -112,10 +112,24 @@ public function test_register_routes() { } /** - * @doesNotPerformAssertions + * @covers WP_REST_Font_Faces_Controller::get_context_param */ public function test_context_param() { - // Controller does not use get_context_param(). + // Collection. + $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->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] ); + $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertSame( array( 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); + + // Single. + $request = new WP_REST_Request( 'OPTIONS', '/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->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] ); + $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertSame( array( 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); } /** @@ -130,9 +144,9 @@ public function test_get_items() { $this->assertSame( 200, $response->get_status() ); $this->assertCount( 2, $data ); $this->assertArrayHasKey( '_links', $data[0] ); - $this->check_font_face_data( $data[0], self::$font_face_id1, $data[0]['_links'] ); + $this->check_font_face_data( $data[0], self::$font_face_id2, $data[0]['_links'] ); $this->assertArrayHasKey( '_links', $data[1] ); - $this->check_font_face_data( $data[1], self::$font_face_id2, $data[1]['_links'] ); + $this->check_font_face_data( $data[1], self::$font_face_id1, $data[1]['_links'] ); } /** @@ -465,6 +479,21 @@ public function test_create_item_with_all_properties() { wp_delete_post( $data['id'], true ); } + /** + * @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 */ @@ -675,10 +704,10 @@ public function test_update_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['force'] = true; - $response = rest_get_server()->dispatch( $request ); + $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() ); $this->assertNull( get_post( $font_face_id ) ); @@ -710,11 +739,38 @@ public function test_delete_item_no_trash() { */ 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 = 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 ); + + $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 */ diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php index e0fe5e0c8cb93..4fffa879ad56e 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -127,12 +127,27 @@ public function test_register_routes() { } /** - * @doesNotPerformAssertions + * @covers WP_REST_Font_Families_Controller::get_context_param */ public function test_context_param() { - // Controller does not use get_context_param(). + // Collection. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] ); + $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertSame( array( 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); + + // Single. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families/' . self::$font_family_id1 ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertArrayNotHasKey( 'allow_batch', $data['endpoints'][0] ); + $this->assertSame( 'edit', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertSame( array( 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); } + /** * @covers WP_REST_Font_Faces_Controller::get_items */ @@ -145,9 +160,9 @@ public function test_get_items() { $this->assertSame( 200, $response->get_status() ); $this->assertCount( 2, $data ); $this->assertArrayHasKey( '_links', $data[0] ); - $this->check_font_family_data( $data[0], self::$font_family_id1, $data[0]['_links'] ); + $this->check_font_family_data( $data[0], self::$font_family_id2, $data[0]['_links'] ); $this->assertArrayHasKey( '_links', $data[1] ); - $this->check_font_family_data( $data[1], self::$font_family_id2, $data[1]['_links'] ); + $this->check_font_family_data( $data[1], self::$font_family_id1, $data[1]['_links'] ); } /** @@ -195,6 +210,32 @@ public function test_get_item() { $this->check_font_family_data( $data, self::$font_family_id1, $response->get_links() ); } + /** + * @covers WP_REST_Font_Faces_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() ); + $this->assertArrayHasKey( '_embedded', $data ); + $this->assertArrayHasKey( 'font_faces', $data['_embedded'] ); + $this->assertCount( 2, $data['_embedded']['font_faces'] ); + + foreach ( $data['_embedded']['font_faces'] as $font_face ) { + $this->assertArrayHasKey( 'id', $font_face ); + + $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 ); + } + } + /** * @covers WP_REST_Font_Families_Controller::get_item */