diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index f096614427288f..1e7b484e439898 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -105,11 +105,9 @@ private function remove_font_family_assets() { */ public function uninstall() { $post = $this->get_data_from_post(); - if ( null === $post ) { - return new WP_Error( - 'font_family_not_found', - __( 'The font family could not be found.', 'gutenberg' ) - ); + + if ( is_wp_error( $post ) ) { + return $post; } if ( @@ -444,11 +442,11 @@ public function get_font_post() { $posts_query = new WP_Query( $args ); - if ( $posts_query->have_posts() ) { - return $posts_query->posts[0]; + if ( ! $posts_query->have_posts() ) { + return null; } - return null; + return $posts_query->posts[0]; } /** @@ -457,17 +455,36 @@ public function get_font_post() { * * @since 6.5.0 * - * @return WP_Post|null The post for this font family object or - * null if the post does not exist. + * @return WP_Post|WP_Error The post for this font family object if it exists and has valid JSON content. */ - private function get_data_from_post() { + public function get_data_from_post() { $post = $this->get_font_post(); - if ( $post ) { - $this->data = json_decode( $post->post_content, true ); - return $post; + + if ( ! $post ) { + return new WP_Error( + 'font_family_slug_not_found', + __( 'Font Family with that slug was not found.' ), + array( + 'status' => 404, + ) + ); + } + + $decoded_content = json_decode( $post->post_content, true ); + + if ( ! $decoded_content ) { + return new WP_Error( + 'font_family_invalid_json_content', + __( 'The JSON content of the font family is invalid.' ), + array( + 'status' => 500, + ) + ); } - return null; + $this->data = $decoded_content; + + return $post; } /** diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 9320a554e510c7..5ae63dd9a15137 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -89,7 +89,7 @@ public static function get_font_collections() { * @since 6.5.0 * * @param string $id Font collection id. - * @return array List of font collections. + * @return WP_Font_Collection|WP_Error Font collection if it exists and a WP_Error otherwise. */ public static function get_font_collection( $id ) { if ( array_key_exists( $id, self::$collections ) ) { diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php new file mode 100644 index 00000000000000..733fdac72f36f4 --- /dev/null +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php @@ -0,0 +1,283 @@ +rest_base = 'font-collections'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.4.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' ), + ), + 'schema' => array( $this, 'get_items_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\/\w-]+)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the post.' ), + 'type' => 'string', + 'required' => true, + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_item_schema' ), + ) + ); + } + + /** + * Gets a font collection. + * + * @since 6.4.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 ) { + $id = $request['id']; + $collection = WP_Font_Library::get_font_collection( $id ); + // If the collection doesn't exist returns a 404. + if ( is_wp_error( $collection ) ) { + $collection->add_data( array( 'status' => 404 ) ); + return $collection; + } + $collection_with_data = $collection->get_data(); + // If there was an error getting the collection data, return the error. + if ( is_wp_error( $collection_with_data ) ) { + $collection_with_data->add_data( array( 'status' => 500 ) ); + return $collection_with_data; + } + + return rest_ensure_response( $collection_with_data ); + } + + /** + * Gets the font collections available. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response Response object. + */ + public function get_items( $request ) { + $collections = array(); + foreach ( WP_Font_Library::get_font_collections() as $collection ) { + $collections[] = $collection->get_config(); + } + return rest_ensure_response( $collections ); + } + + /** + * Checks whether the user has permissions to update the Font Library. + * + * @since 6.4.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 ) { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_get_font_library', + __( 'Sorry, you are not allowed to get the Font Library on this site.' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + return true; + } + + /** + * Retrieves the schema for the font collections item, conforming to JSON Schema. + * + * @since 6.4.0 + * + * @return array Item schema data. + */ + public function get_items_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'font-collections', + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'Name of the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'Description of the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'src' => array( + 'description' => __( 'Source to the list of font families.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ), + ); + + $this->schema = $schema; + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the item's schema, conforming to JSON Schema. + * + * @since 6.4.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'font-collection', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'Name of the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'Description of the font collection.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'data' => array( + 'description' => __( 'Data of the font collection.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'font_families' => array( + 'description' => __( 'List of font families.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'Name of the font family.' ), + 'type' => 'string', + ), + 'font_family' => array( + 'description' => __( 'Font family string.' ), + 'type' => 'string', + ), + 'slug' => array( + 'description' => __( 'Slug of the font family.' ), + 'type' => 'string', + ), + 'category' => array( + 'description' => __( 'Category of the font family.' ), + 'type' => 'string', + ), + 'font_face' => array( + 'description' => __( 'Font face details.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'download_from_url' => array( + 'description' => __( 'URL to download the font.' ), + 'type' => 'string', + ), + 'font_weight' => array( + 'description' => __( 'Font weight.' ), + 'type' => 'string', + ), + 'fonts_style' => array( + 'description' => __( 'Font style.' ), + 'type' => 'string', + ), + 'font_family' => array( + 'description' => __( 'Font family string.' ), + 'type' => 'string', + ), + 'preview' => array( + 'description' => __( 'URL for font preview.' ), + 'type' => 'string', + ), + ), + ), + ), + 'preview' => array( + 'description' => __( 'URL for font family preview.' ), + 'type' => 'string', + ), + ), + ), + ), + ), + ), + ), + ); + + $this->schema = $schema; + return $this->add_additional_fields_schema( $this->schema ); + } +} 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 new file mode 100644 index 00000000000000..0f70429df8d50c --- /dev/null +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php @@ -0,0 +1,503 @@ +rest_base = 'font-families'; + $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, 'update_font_library_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), + 'args' => array( + 'slug' => array( + 'required' => true, + 'type' => 'string', + ), + 'name' => array( + 'required' => true, + 'type' => 'string', + ), + 'font_family' => array( + 'required' => true, + 'type' => 'string', + ), + 'font_face' => array( + 'required' => false, + 'type' => 'string', + 'validate_callback' => array( $this, 'validate_font_faces' ), + ), + ), + ), + 'schema' => array( $this, 'get_items_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, 'update_font_library_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), + ), + 'schema' => array( $this, 'get_item_schema' ), + ) + ); + } + + /** + * Get item (font family). + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error + */ + public function get_item( $request ) { + $font_family = new WP_Font_Family( array( 'slug' => $request['slug'] ) ); + $font_family_data = $font_family->get_data_from_post(); + + if ( is_wp_error( $font_family_data ) ) { + return $font_family_data; + } + + $item = $this->prepare_item_for_response( $font_family_data, $request ); + return rest_ensure_response( $item ); + } + + /** + * Get items (font families). + * + * @since 6.5.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error + */ + public function get_items( $request ) { + $args = array( + 'post_type' => 'wp_font_family', + 'post_status' => 'publish', + 'posts_per_page' => $request['per_page'] ?? 10, + 'paged' => $request['page'] ?? 1, + 'orderby' => $request['orderby'] ?? 'name', + 'order' => $request['order'] ?? 'ASC', + ); + $posts = get_posts( $args ); + $response = array(); + foreach ( $posts as $post ) { + $item = $this->prepare_item_for_response( $post, $request ); + if ( $item ) { + $response[] = $item; + } + } + return rest_ensure_response( $response ); + } + + public function prepare_item_for_response( $post, $request ) { + return json_decode( $post->post_content, true ); + } + + /** + * Returns validation errors in font faces data for installation. + * + * @since 6.5.0 + * + * @param array[] $font_faces Font faces to install. + * @param array $files Files to install. + * @return WP_Error Validation errors. + */ + private function get_validation_errors( $font_faces, $files ) { + $error = new WP_Error(); + + if ( ! is_array( $font_faces ) ) { + $error->add( 'rest_invalid_param', __( 'fontFace should be an array.' ) ); + return $error; + } + + if ( count( $font_faces ) < 1 ) { + $error->add( 'rest_invalid_param', __( 'fontFace should have at least one item.' ) ); + return $error; + } + + for ( $face_index = 0; $face_index < count( $font_faces ); $face_index++ ) { + $font_face = $font_faces[ $face_index ]; + if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { + $error_message = sprintf( + // translators: 1: font face index. + __( 'Font face (%1$s) should have fontWeight and fontStyle properties defined.' ), + $face_index + ); + $error->add( 'rest_invalid_param', $error_message ); + } + + if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { + $error_message = sprintf( + // translators: 1: font face index. + __( 'Font face (%1$s) should have only one of the downloadFromUrl or uploadedFile properties defined and not both.' ), + $face_index + ); + $error->add( 'rest_invalid_param', $error_message ); + } + + if ( isset( $font_face['uploadedFile'] ) ) { + if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { + $error_message = sprintf( + // translators: 1: font face index. + __( 'Font face (%1$s) file is not defined in the request files.' ), + $face_index + ); + $error->add( 'rest_invalid_param', $error_message ); + } + } + } + return $error; + } + + /** + * Validate input for the install endpoint. + * + * @since 6.5.0 + * + * @param string $param The font faces to install. + * @param WP_REST_Request $request The request object. + * @return bool|WP_Error True if the parameter is valid, WP_Error otherwise. + */ + public function validate_font_faces( $param, $request ) { + $font_faces = json_decode( $param, true ); + if ( null === $font_faces ) { + return new WP_Error( + 'rest_invalid_param', + __( 'Invalid font faces parameter.' ), + array( 'status' => 400 ) + ); + } + + $files = $request->get_file_params(); + $validation = $this->get_validation_errors( $font_faces, $files ); + + if ( $validation->has_errors() ) { + $validation->add_data( array( 'status' => 400 ) ); + return $validation; + } + + return true; + } + + /** + * Removes font families from the Font Library and all their assets. + * + * @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 ) { + + $font_family = new WP_Font_Family( array( 'slug' => $request['slug'] ) ); + $result = $font_family->uninstall(); + + if ( is_wp_error( $result ) ) { + if ( 'font_family_slug_not_found' === $result->get_error_code() ) { + $result->add_data( array( 'status' => 404 ) ); + } else { + $result->add_data( array( 'status' => 500 ) ); + } + } + return rest_ensure_response( $result ); + } + + /** + * Checks whether the user has permissions to update the Font Library. + * + * @since 6.5.0 + * + * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. + */ + public function update_font_library_permissions_check() { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_update_font_library', + __( 'Sorry, you are not allowed to update the Font Library on this site.' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + return true; + } + + /** + * Checks whether the user has write permissions to the temp and fonts directories. + * + * @since 6.5.0 + * + * @return true|WP_Error True if the user has write permissions, WP_Error object otherwise. + */ + private function has_write_permission() { + // The update endpoints requires write access to the temp and the fonts directories. + $temp_dir = get_temp_dir(); + $upload_dir = WP_Font_Library::get_fonts_dir(); + if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { + return false; + } + return true; + } + + /** + * Checks whether the request needs write permissions. + * + * @since 6.5.0 + * + * @param array $font_family Font family definition. + * @return bool Whether the request needs write permissions. + */ + private function needs_write_permission( $font_family ) { + if ( isset( $font_family['fontFace'] ) ) { + foreach ( $font_family['fontFace'] as $face ) { + // If the font is being downloaded from a URL or uploaded, it needs write permissions. + if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { + return true; + } + } + } + return false; + } + + /** + * Installs new fonts. + * + * Takes a request containing new fonts to install, downloads their assets, and adds them + * to the Font Library. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request The request object containing the new fonts to install + * in the request parameters. + * @return WP_REST_Response|WP_Error The updated Font Library post content. + */ + public function create_item( $request ) { + $font_family_data = array( + 'slug' => $request->get_param( 'slug' ), + 'name' => $request->get_param( 'name' ), + 'fontFamily' => $request->get_param( 'font_family' ), + ); + + if ( $request->get_param( 'font-face' ) ) { + $font_family_data['fontFace'] = json_decode( $request->get_param( 'font_face' ), true ); + } + + if ( $this->needs_write_permission( $font_family_data ) && ! $this->has_write_permission() ) { + return new WP_Error( + 'cannot_write_fonts_folder', + __( 'Error: WordPress does not have permission to write the fonts folder on your server.' ), + array( + 'status' => 500, + ) + ); + } + + // Get uploaded files (used when installing local fonts). + $files = $request->get_file_params(); + $font_family = new WP_Font_Family( $font_family_data ); + $result = $font_family->install( $files ); + return rest_ensure_response( $result ); + } + + /** + * Retrieves the font family response schema + * + * @since 6.5.0 + * + * @return array Font Family schema data. + */ + private function font_family_schema() { + return array( + 'fontFace' => array( + 'description' => __( 'An array of font face objects.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'fontFamily' => array( + 'description' => __( 'Name of the font family.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'fontStyle' => array( + 'description' => __( 'Style of the font.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'fontWeight' => array( + 'description' => __( 'Weight of the font.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'src' => array( + 'description' => __( 'Source URL of the font.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ), + ), + 'fontFamily' => array( + 'description' => __( 'CSS string for the font family.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'name' => array( + 'description' => __( 'Display name of the font.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'slug' => array( + 'description' => __( 'Slug of the font.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ); + } + + /** + * Retrieves item schema + * + * @since 6.5.0 + * + * @return array Font family schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + $family_schema = array( + 'title' => 'Font family', + 'type' => 'object', + 'properties' => $this->font_family_schema(), + ); + $delete_success_schema = array( + 'title' => 'Font family delete success', + 'type' => 'boolean', + 'description' => 'Indicates a successful response.', + 'enum' => array( true ), + ); + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'Font family item endpoint reponse', + 'oneOf' => array( $family_schema, $delete_success_schema, $this->error_schema() ), + ); + $this->schema = $schema; + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the items schema. + * + * @since 6.5.0 + * + * @return array Font families schema data. + */ + public function get_items_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + $families_schema = array( + 'title' => 'Font families', + 'type' => 'array', + 'items' => ( + array( + 'type' => 'object', + 'properties' => $this->font_family_schema(), + ) + ), + ); + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'Font families items endpoint reponse', + 'oneOf' => array( $families_schema, $this->error_schema() ), + ); + $this->schema = $schema; + return $this->add_additional_fields_schema( $this->schema ); + } + + /** + * Retrieves the error response schema + * + * @since 6.5.0 + * + * @return array Error schema data. + */ + private function error_schema() { + return array( + 'title' => 'Error response', + 'type' => 'object', + 'properties' => array( + 'errors' => array( + 'type' => 'object', + 'description' => 'An associative array of error codes to error messages.', + 'propertyNames' => array( + 'type' => 'string', + ), + 'additionalProperties' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + 'error_data' => array( + 'type' => 'object', + 'description' => 'An associative array of error codes to mixed error data.', + 'propertyNames' => array( + 'type' => 'string', + ), + 'additionalProperties' => array( + 'type' => 'string', + ), + ), + ), + ); + } +} diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php deleted file mode 100644 index 19dfcaab49533c..00000000000000 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ /dev/null @@ -1,457 +0,0 @@ -rest_base = 'fonts'; - $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::EDITABLE, - 'callback' => array( $this, 'install_fonts' ), - 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), - 'args' => array( - 'font_families' => array( - 'required' => true, - 'type' => 'string', - 'validate_callback' => array( $this, 'validate_install_font_families' ), - ), - ), - ), - ) - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( $this, 'uninstall_fonts' ), - 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), - 'args' => $this->uninstall_schema(), - ), - ) - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/collections', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_font_collections' ), - 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), - ), - ) - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/collections' . '/(?P[\/\w-]+)', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_font_collection' ), - 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), - ), - ) - ); - } - - /** - * 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_font_collection( $request ) { - $id = $request->get_param( 'id' ); - $collection = WP_Font_Library::get_font_collection( $id ); - // If the collection doesn't exist returns a 404. - if ( is_wp_error( $collection ) ) { - $collection->add_data( array( 'status' => 404 ) ); - return $collection; - } - $collection_with_data = $collection->get_data(); - // If there was an error getting the collection data, return the error. - if ( is_wp_error( $collection_with_data ) ) { - $collection_with_data->add_data( array( 'status' => 500 ) ); - return $collection_with_data; - } - return new WP_REST_Response( $collection_with_data ); - } - - /** - * 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_font_collections() { - $collections = array(); - foreach ( WP_Font_Library::get_font_collections() as $collection ) { - $collections[] = $collection->get_config(); - } - - return new WP_REST_Response( $collections, 200 ); - } - - /** - * Returns validation errors in font families data for installation. - * - * @since 6.5.0 - * - * @param array[] $font_families Font families to install. - * @param array $files Files to install. - * @return array $error_messages Array of error messages. - */ - private function get_validation_errors( $font_families, $files ) { - $error_messages = array(); - - if ( ! is_array( $font_families ) ) { - $error_messages[] = __( 'font_families should be an array of font families.', 'gutenberg' ); - return $error_messages; - } - - // Checks if there is at least one font family. - if ( count( $font_families ) < 1 ) { - $error_messages[] = __( 'font_families should have at least one font family definition.', 'gutenberg' ); - return $error_messages; - } - - for ( $family_index = 0; $family_index < count( $font_families ); $family_index++ ) { - $font_family = $font_families[ $family_index ]; - - if ( - ! isset( $font_family['slug'] ) || - ! isset( $font_family['name'] ) || - ! isset( $font_family['fontFamily'] ) - ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have slug, name and fontFamily properties defined.', 'gutenberg' ), - $family_index - ); - } - - if ( isset( $font_family['fontFace'] ) ) { - if ( ! is_array( $font_family['fontFace'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have fontFace property defined as an array.', 'gutenberg' ), - $family_index - ); - continue; - } - - if ( count( $font_family['fontFace'] ) < 1 ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have at least one font face definition.', 'gutenberg' ), - $family_index - ); - } - - if ( ! empty( $font_family['fontFace'] ) ) { - for ( $face_index = 0; $face_index < count( $font_family['fontFace'] ); $face_index++ ) { - - $font_face = $font_family['fontFace'][ $face_index ]; - if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have fontWeight and fontStyle properties defined.', 'gutenberg' ), - $family_index, - $face_index - ); - } - - if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have only one of the downloadFromUrl or uploadedFile properties defined and not both.', 'gutenberg' ), - $family_index, - $face_index - ); - } - - if ( isset( $font_face['uploadedFile'] ) ) { - if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] file is not defined in the request files.', 'gutenberg' ), - $family_index, - $face_index - ); - } - } - } - } - } - } - - return $error_messages; - } - - /** - * Validate input for the install endpoint. - * - * @since 6.5.0 - * - * @param string $param The font families to install. - * @param WP_REST_Request $request The request object. - * @return true|WP_Error True if the parameter is valid, WP_Error otherwise. - */ - public function validate_install_font_families( $param, $request ) { - $font_families = json_decode( $param, true ); - $files = $request->get_file_params(); - $error_messages = $this->get_validation_errors( $font_families, $files ); - - if ( empty( $error_messages ) ) { - return true; - } - - return new WP_Error( 'rest_invalid_param', implode( ', ', $error_messages ), array( 'status' => 400 ) ); - } - - /** - * Gets the schema for the uninstall endpoint. - * - * @since 6.5.0 - * - * @return array Schema array. - */ - public function uninstall_schema() { - return array( - 'font_families' => array( - 'type' => 'array', - 'description' => __( 'The font families to install.', 'gutenberg' ), - 'required' => true, - 'minItems' => 1, - 'items' => array( - 'required' => true, - 'type' => 'object', - 'properties' => array( - 'slug' => array( - 'type' => 'string', - 'description' => __( 'The font family slug.', 'gutenberg' ), - 'required' => true, - ), - ), - ), - ), - ); - } - - /** - * Removes font families from the Font Library and all their assets. - * - * @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 uninstall_fonts( $request ) { - $fonts_to_uninstall = $request->get_param( 'font_families' ); - - $errors = array(); - $successes = array(); - - if ( empty( $fonts_to_uninstall ) ) { - $errors[] = new WP_Error( - 'no_fonts_to_install', - __( 'No fonts to uninstall', 'gutenberg' ) - ); - $data = array( - 'successes' => $successes, - 'errors' => $errors, - ); - $response = rest_ensure_response( $data ); - $response->set_status( 400 ); - return $response; - } - - foreach ( $fonts_to_uninstall as $font_data ) { - $font = new WP_Font_Family( $font_data ); - $result = $font->uninstall(); - if ( is_wp_error( $result ) ) { - $errors[] = $result; - } else { - $successes[] = $result; - } - } - $data = array( - 'successes' => $successes, - 'errors' => $errors, - ); - return rest_ensure_response( $data ); - } - - /** - * Checks whether the user has permissions to update the Font Library. - * - * @since 6.5.0 - * - * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. - */ - public function update_font_library_permissions_check() { - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_cannot_update_font_library', - __( 'Sorry, you are not allowed to update the Font Library on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - return true; - } - - /** - * Checks whether the user has write permissions to the temp and fonts directories. - * - * @since 6.5.0 - * - * @return true|WP_Error True if the user has write permissions, WP_Error object otherwise. - */ - private function has_write_permission() { - // The update endpoints requires write access to the temp and the fonts directories. - $temp_dir = get_temp_dir(); - $upload_dir = WP_Font_Library::get_fonts_dir(); - if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { - return false; - } - return true; - } - - /** - * Checks whether the request needs write permissions. - * - * @since 6.5.0 - * - * @param array[] $font_families Font families to install. - * @return bool Whether the request needs write permissions. - */ - private function needs_write_permission( $font_families ) { - foreach ( $font_families as $font ) { - if ( isset( $font['fontFace'] ) ) { - foreach ( $font['fontFace'] as $face ) { - // If the font is being downloaded from a URL or uploaded, it needs write permissions. - if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { - return true; - } - } - } - } - return false; - } - - /** - * Installs new fonts. - * - * Takes a request containing new fonts to install, downloads their assets, and adds them - * to the Font Library. - * - * @since 6.5.0 - * - * @param WP_REST_Request $request The request object containing the new fonts to install - * in the request parameters. - * @return WP_REST_Response|WP_Error The updated Font Library post content. - */ - public function install_fonts( $request ) { - // Get new fonts to install. - $fonts_param = $request->get_param( 'font_families' ); - - /* - * As this is receiving form data, the font families are encoded as a string. - * The form data is used because local fonts need to use that format to - * attach the files in the request. - */ - $fonts_to_install = json_decode( $fonts_param, true ); - - $successes = array(); - $errors = array(); - $response_status = 200; - - if ( empty( $fonts_to_install ) ) { - $errors[] = new WP_Error( - 'no_fonts_to_install', - __( 'No fonts to install', 'gutenberg' ) - ); - $response_status = 400; - } - - if ( $this->needs_write_permission( $fonts_to_install ) && ! $this->has_write_permission() ) { - $errors[] = new WP_Error( - 'cannot_write_fonts_folder', - __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ) - ); - $response_status = 500; - } - - if ( ! empty( $errors ) ) { - $data = array( - 'successes' => $successes, - 'errors' => $errors, - ); - $response = rest_ensure_response( $data ); - $response->set_status( $response_status ); - return $response; - } - - // Get uploaded files (used when installing local fonts). - $files = $request->get_file_params(); - foreach ( $fonts_to_install as $font_data ) { - $font = new WP_Font_Family( $font_data ); - $result = $font->install( $files ); - if ( is_wp_error( $result ) ) { - $errors[] = $result; - } else { - $successes[] = $result; - } - } - - $data = array( - 'successes' => $successes, - 'errors' => $errors, - ); - return rest_ensure_response( $data ); - } -} diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index 6c31c02d409f7a..4f064da82270c1 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -30,8 +30,10 @@ function gutenberg_init_font_library_routes() { register_post_type( 'wp_font_family', $args ); // @core-merge: This code will go into Core's `create_initial_rest_routes()`. - $font_library_controller = new WP_REST_Font_Library_Controller(); - $font_library_controller->register_routes(); + $font_families_controller = new WP_REST_Font_Families_Controller(); + $font_families_controller->register_routes(); + $font_collections_controller = new WP_REST_Font_Collections_Controller(); + $font_collections_controller->register_routes(); } add_action( 'rest_api_init', 'gutenberg_init_font_library_routes' ); diff --git a/lib/load.php b/lib/load.php index 13636b25871303..9592501712d8b1 100644 --- a/lib/load.php +++ b/lib/load.php @@ -148,7 +148,8 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/fonts/font-library/class-wp-font-library.php'; require __DIR__ . '/experimental/fonts/font-library/class-wp-font-family-utils.php'; require __DIR__ . '/experimental/fonts/font-library/class-wp-font-family.php'; - require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-library-controller.php'; + require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-families-controller.php'; + require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php'; require __DIR__ . '/experimental/fonts/font-library/font-library.php'; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/context.js b/packages/edit-site/src/components/global-styles/font-library-modal/context.js index 33a5b0910f0526..daeba942d4e803 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/context.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/context.js @@ -14,8 +14,8 @@ import { * Internal dependencies */ import { - fetchInstallFonts, - fetchUninstallFonts, + fetchInstallFont, + fetchUninstallFont, fetchFontCollections, fetchFontCollection, } from './resolvers'; @@ -26,10 +26,9 @@ import { mergeFontFamilies, loadFontFaceInBrowser, getDisplaySrcFromFontFace, - makeFormDataFromFontFamilies, + makeFormDataFromFontFamily, } from './utils'; import { toggleFont } from './utils/toggleFont'; -import getIntersectingFontFaces from './utils/get-intersecting-font-faces'; export const FontLibraryContext = createContext( {} ); @@ -192,22 +191,15 @@ function FontLibraryProvider( { children } ) { return getActivatedFontsOutline( source )[ slug ] || []; }; - async function installFonts( fonts ) { + async function installFont( fontFamily ) { setIsInstalling( true ); try { // Prepare formData to install. - const formData = makeFormDataFromFontFamilies( fonts ); - // Install the fonts (upload the font files to the server and create the post in the database). - const response = await fetchInstallFonts( formData ); - const fontsInstalled = response?.successes || []; - // Get intersecting font faces between the fonts we tried to installed and the fonts that were installed - // (to avoid activating a non installed font). - const fontToBeActivated = getIntersectingFontFaces( - fontsInstalled, - fonts - ); + const formData = makeFormDataFromFontFamily( fontFamily ); + // Install the font (upload the font files to the server and create the post in the database). + const response = await fetchInstallFont( formData ); // Activate the font families (add the font families to the global styles). - activateCustomFontFamilies( fontToBeActivated ); + activateCustomFontFamilies( [ response ] ); // Save the global styles to the database. saveSpecifiedEntityEdits( 'root', 'globalStyles', globalStylesId, [ 'settings.typography.fontFamilies', @@ -216,29 +208,24 @@ function FontLibraryProvider( { children } ) { setIsInstalling( false ); return response; - } catch ( error ) { + } finally { setIsInstalling( false ); - return { - errors: [ error ], - }; } } async function uninstallFont( font ) { try { // Uninstall the font (remove the font files from the server and the post from the database). - const response = await fetchUninstallFonts( [ font ] ); + const response = await fetchUninstallFont( font ); // Deactivate the font family (remove the font family from the global styles). - if ( ! response.errors ) { - deactivateFontFamily( font ); - // Save the global styles to the database. - await saveSpecifiedEntityEdits( - 'root', - 'globalStyles', - globalStylesId, - [ 'settings.typography.fontFamilies' ] - ); - } + deactivateFontFamily( font ); + // Save the global styles to the database. + await saveSpecifiedEntityEdits( + 'root', + 'globalStyles', + globalStylesId, + [ 'settings.typography.fontFamilies' ] + ); // Refresh the library (the the library font families from database). refreshLibrary(); return response; @@ -358,7 +345,7 @@ function FontLibraryProvider( { children } ) { isFontActivated, getFontFacesActivated, loadFontFaceAsset, - installFonts, + installFont, uninstallFont, toggleActivateFont, getAvailableFontsOutline, diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index 61de80838f6372..38ea3a615354ae 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -30,7 +30,6 @@ import CollectionFontDetails from './collection-font-details'; import { toggleFont } from './utils/toggleFont'; import { getFontsOutline } from './utils/fonts-outline'; import GoogleFontsConfirmDialog from './google-fonts-confirm-dialog'; -import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; const DEFAULT_CATEGORY = { id: 'all', @@ -54,7 +53,7 @@ function FontCollection( { id } ) { const [ renderConfirmDialog, setRenderConfirmDialog ] = useState( requiresPermission && ! getGoogleFontsPermissionFromStorage() ); - const { collections, getFontCollection, installFonts } = + const { collections, getFontCollection, installFont } = useContext( FontLibraryContext ); const selectedCollection = collections.find( ( collection ) => collection.id === id @@ -134,6 +133,7 @@ function FontCollection( { id } ) { }; const handleUnselectFont = () => { + setFontsToInstall( [] ); setSelectedFont( null ); }; @@ -149,10 +149,19 @@ function FontCollection( { id } ) { }; const handleInstall = async () => { - const response = await installFonts( fontsToInstall ); - const installNotice = getNoticeFromInstallResponse( response ); - setNotice( installNotice ); - resetFontsToInstall(); + try { + await installFont( fontsToInstall[ 0 ] ); + resetFontsToInstall(); + setNotice( { + type: 'success', + message: __( 'Fonts were installed successfully.' ), + } ); + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message, + } ); + } }; return ( diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js index 2a8d1e591e084f..1d34582b3c8f81 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js @@ -22,7 +22,6 @@ import FontsGrid from './fonts-grid'; import LibraryFontDetails from './library-font-details'; import LibraryFontCard from './library-font-card'; import ConfirmDeleteDialog from './confirm-delete-dialog'; -import { getNoticeFromUninstallResponse } from './utils/get-notice-from-response'; import { unlock } from '../../../lock-unlock'; const { ProgressBar } = unlock( componentsPrivateApis ); @@ -49,14 +48,21 @@ function InstalledFonts() { const [ notice, setNotice ] = useState( null ); const handleConfirmUninstall = async () => { - const response = await uninstallFont( libraryFontSelected ); - const uninstallNotice = getNoticeFromUninstallResponse( response ); - setNotice( uninstallNotice ); - // If the font was succesfully uninstalled it is unselected - if ( ! response?.errors?.length ) { + try { + await uninstallFont( libraryFontSelected ); handleUnselectFont(); + setNotice( { + type: 'success', + message: __( 'Fonts were uninstalled successfully.' ), + } ); + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message, + } ); + } finally { + setIsConfirmDeleteOpen( false ); } - setIsConfirmDeleteOpen( false ); }; const handleUninstallClick = async () => { diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js index 9612f8be52f5ee..16c00381996239 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js @@ -20,12 +20,11 @@ import { useContext, useState, useEffect } from '@wordpress/element'; import { ALLOWED_FILE_EXTENSIONS } from './utils/constants'; import { FontLibraryContext } from './context'; import { Font } from '../../../../lib/lib-font.browser'; -import makeFamiliesFromFaces from './utils/make-families-from-faces'; +import makeFamilyFromFaces from './utils/make-families-from-faces'; import { loadFontFaceInBrowser } from './utils'; -import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; function LocalFonts() { - const { installFonts } = useContext( FontLibraryContext ); + const { installFont } = useContext( FontLibraryContext ); const [ notice, setNotice ] = useState( null ); const supportedFormats = ALLOWED_FILE_EXTENSIONS.slice( 0, -1 ) @@ -146,10 +145,19 @@ function LocalFonts() { * @return {void} */ const handleInstall = async ( fontFaces ) => { - const fontFamilies = makeFamiliesFromFaces( fontFaces ); - const response = await installFonts( fontFamilies ); - const installNotice = getNoticeFromInstallResponse( response ); - setNotice( installNotice ); + try { + const fontFamily = makeFamilyFromFaces( fontFaces ); + await installFont( fontFamily ); + setNotice( { + type: 'success', + message: __( 'Fonts were installed successfully.' ), + } ); + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message, + } ); + } }; return ( diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js index 425b3afb0e7c3c..303b11c62e61de 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js @@ -7,30 +7,26 @@ */ import apiFetch from '@wordpress/api-fetch'; -export async function fetchInstallFonts( data ) { +export async function fetchInstallFont( data ) { const config = { - path: '/wp/v2/fonts', + path: '/wp/v2/font-families', method: 'POST', body: data, }; return apiFetch( config ); } -export async function fetchUninstallFonts( fonts ) { - const data = { - font_families: fonts, - }; +export async function fetchUninstallFont( fontFamily ) { const config = { - path: '/wp/v2/fonts', + path: `/wp/v2/font-families/${ fontFamily.slug }`, method: 'DELETE', - data, }; return apiFetch( config ); } export async function fetchFontCollections() { const config = { - path: '/wp/v2/fonts/collections', + path: '/wp/v2/font-collections', method: 'GET', }; return apiFetch( config ); @@ -38,7 +34,7 @@ export async function fetchFontCollections() { export async function fetchFontCollection( id ) { const config = { - path: `/wp/v2/fonts/collections/${ id }`, + path: `/wp/v2/font-collections/${ id }`, method: 'GET', }; return apiFetch( config ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js deleted file mode 100644 index b22bd0afe23248..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/get-notice-from-response.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -export function getNoticeFromInstallResponse( response ) { - const { errors = [], successes = [] } = response; - // Everything failed. - if ( errors.length && ! successes.length ) { - return { - type: 'error', - message: __( 'Error installing the fonts.' ), - }; - } - - // Eveerything succeeded. - if ( ! errors.length && successes.length ) { - return { - type: 'success', - message: __( 'Fonts were installed successfully.' ), - }; - } - - // Some succeeded, some failed. - if ( errors.length && successes.length ) { - return { - type: 'warning', - message: __( - 'Some fonts were installed successfully and some failed.' - ), - }; - } -} - -export function getNoticeFromUninstallResponse( response ) { - const { errors = [], successes = [] } = response; - // Everything failed. - if ( errors.length && ! successes.length ) { - return { - type: 'error', - message: __( 'Error uninstalling the fonts.' ), - }; - } - - // Everything succeeded. - if ( ! errors.length && successes.length ) { - return { - type: 'success', - message: __( 'Fonts were uninstalled successfully.' ), - }; - } - - // Some succeeded, some failed. - if ( errors.length && successes.length ) { - return { - type: 'warning', - message: __( - 'Some fonts were uninstalled successfully and some failed.' - ), - }; - } -} diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index d0a57978bcce94..db7b684af28b03 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -129,29 +129,32 @@ export function getDisplaySrcFromFontFace( input, urlPrefix ) { return src; } -export function makeFormDataFromFontFamilies( fontFamilies ) { +export function makeFormDataFromFontFamily( fontFamily ) { const formData = new FormData(); - const newFontFamilies = fontFamilies.map( ( family, familyIndex ) => { - if ( family?.fontFace ) { - family.fontFace = family.fontFace.map( ( face, faceIndex ) => { - if ( face.file ) { - // Slugified file name because the it might contain spaces or characters treated differently on the server. - const fileId = `file-${ familyIndex }-${ faceIndex }`; - // Add the files to the formData - formData.append( fileId, face.file, face.file.name ); - // remove the file object from the face object the file is referenced by the uploadedFile key - const { file, ...faceWithoutFileProperty } = face; - const newFace = { - ...faceWithoutFileProperty, - uploadedFile: fileId, - }; - return newFace; - } - return face; - } ); - } - return family; - } ); - formData.append( 'font_families', JSON.stringify( newFontFamilies ) ); + + formData.append( 'slug', fontFamily.slug ); + formData.append( 'name', fontFamily.name ); + formData.append( 'font_family', fontFamily.fontFamily ); + + if ( fontFamily.fontFace ) { + fontFamily.fontFace = fontFamily.fontFace.map( ( face, faceIndex ) => { + if ( face.file ) { + // Slugified file name because the it might contain spaces or characters treated differently on the server. + const fileId = `file-${ faceIndex }`; + // Add the files to the formData + formData.append( fileId, face.file, face.file.name ); + // remove the file object from the face object the file is referenced by the uploadedFile key + const { file, ...faceWithoutFileProperty } = face; + const newFace = { + ...faceWithoutFileProperty, + uploadedFile: fileId, + }; + return newFace; + } + return face; + } ); + formData.append( 'font_face', JSON.stringify( fontFamily.fontFace ) ); + } + return formData; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/make-families-from-faces.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/make-families-from-faces.js index cbc995bedefd45..cbfa741355f13a 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/make-families-from-faces.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/make-families-from-faces.js @@ -1,15 +1,26 @@ -export default function makeFamiliesFromFaces( fontFaces ) { - const fontFamiliesObject = fontFaces.reduce( ( acc, item ) => { - if ( ! acc[ item.fontFamily ] ) { - acc[ item.fontFamily ] = { - name: item.fontFamily, - fontFamily: item.fontFamily, - slug: item.fontFamily.replace( /\s+/g, '-' ).toLowerCase(), +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export default function makeFamilyFromFaces( fontFaces ) { + let fontFamilyObject = null; + fontFaces.forEach( ( fontFace ) => { + if ( ! fontFamilyObject ) { + fontFamilyObject = { + name: fontFace.fontFamily, + fontFamily: fontFace.fontFamily, + slug: fontFace.fontFamily.replace( /\s+/g, '-' ).toLowerCase(), fontFace: [], }; + } else if ( fontFamilyObject.name !== fontFace.fontFamily ) { + throw new Error( + __( + 'You may only batch upload fonts from the same font family.' + ) + ); } - acc[ item.fontFamily ].fontFace.push( item ); - return acc; - }, {} ); - return Object.values( fontFamiliesObject ); + fontFamilyObject.fontFace.push( fontFace ); + } ); + return fontFamilyObject; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFamiliesFromFaces.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFamiliesFromFaces.spec.js index b8e147116ea6ac..f54ebbb896392d 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFamiliesFromFaces.spec.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFamiliesFromFaces.spec.js @@ -6,28 +6,36 @@ import makeFamiliesFromFaces from '../make-families-from-faces'; describe( 'makeFamiliesFromFaces', () => { it( 'handles empty fontFaces list', () => { const result = makeFamiliesFromFaces( [] ); - expect( result ).toEqual( [] ); + expect( result ).toEqual( null ); } ); - it( 'groups fontFaces by fontFamily', () => { + it( 'collects multiple fontFaces for the same fontFamily', () => { const fontFaces = [ - { fontFamily: 'Lobster' }, + { fontFamily: 'Super Duper' }, { - fontFamily: 'Piazzolla', + fontFamily: 'Super Duper', file: { name: 'piazzolla.ttf' }, }, - { fontFamily: 'Lobster', file: { name: 'piazzolla.ttf' } }, + { fontFamily: 'Super Duper', file: { name: 'piazzolla.ttf' } }, ]; const result = makeFamiliesFromFaces( fontFaces ); - expect( result ).toHaveLength( 2 ); - expect( - result.find( ( family ) => family.name === 'Lobster' ).fontFace - ).toHaveLength( 2 ); - expect( - result.find( ( family ) => family.name === 'Piazzolla' ).fontFace - ).toHaveLength( 1 ); + expect( result.name ).toEqual( 'Super Duper' ); + expect( result.fontFace ).toHaveLength( 3 ); + } ); + + it( 'errors with multiple fontFaces for different fontFamilies', () => { + const fontFaces = [ + { fontFamily: 'Super Duper' }, + { fontFamily: 'Duper Super', file: { name: 'piazzolla.ttf' } }, + ]; + + expect( () => makeFamiliesFromFaces( fontFaces ) ).toThrow( + new Error( + 'You may only batch upload fonts from the same font family.' + ) + ); } ); it( 'generates correct name for fontFamily names', () => { @@ -39,7 +47,7 @@ describe( 'makeFamiliesFromFaces', () => { ]; const result = makeFamiliesFromFaces( fontFaces ); - expect( result[ 0 ].name ).toBe( 'Piazzolla' ); + expect( result.name ).toBe( 'Piazzolla' ); } ); it( 'generates correct slug for fontFamily names', () => { @@ -52,6 +60,19 @@ describe( 'makeFamiliesFromFaces', () => { const result = makeFamiliesFromFaces( fontFaces ); - expect( result[ 0 ].slug ).toBe( 'times-new-roman' ); + expect( result.slug ).toBe( 'times-new-roman' ); + } ); + + it( 'generates correct fontFamily property for fontFamily object', () => { + const fontFaces = [ + { + fontFamily: 'Times New Roman', + file: { name: 'times.ttf' }, + }, + ]; + + const result = makeFamiliesFromFaces( fontFaces ); + + expect( result.fontFamily ).toBe( 'Times New Roman' ); } ); } ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js index 4adae7889cc5e5..485c6da59414ce 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js @@ -1,62 +1,56 @@ /** * Internal dependencies */ -import { makeFormDataFromFontFamilies } from '../index'; +import { makeFormDataFromFontFamily } from '../index'; /* global File */ -describe( 'makeFormDataFromFontFamilies', () => { +describe( 'makeFormDataFromFontFamily', () => { it( 'should process fontFamilies and return FormData', () => { - const mockFontFamilies = [ - { - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', - fontFace: [ - { - file: new File( [ 'content' ], 'test-font1.woff2' ), - fontWeight: '500', - fontStyle: 'normal', - }, - { - file: new File( [ 'content' ], 'test-font2.woff2' ), - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; + const mockFontFamily = { + slug: 'bebas', + name: 'Bebas', + fontFamily: 'Bebas', + fontFace: [ + { + file: new File( [ 'content' ], 'test-font1.woff2' ), + fontWeight: '500', + fontStyle: 'normal', + }, + { + file: new File( [ 'content' ], 'test-font2.woff2' ), + fontWeight: '400', + fontStyle: 'normal', + }, + ], + }; - const formData = makeFormDataFromFontFamilies( mockFontFamilies ); + const formData = makeFormDataFromFontFamily( mockFontFamily ); expect( formData instanceof FormData ).toBeTruthy(); // Check if files are added correctly - expect( formData.get( 'file-0-0' ).name ).toBe( 'test-font1.woff2' ); - expect( formData.get( 'file-0-1' ).name ).toBe( 'test-font2.woff2' ); + expect( formData.get( 'file-0' ).name ).toBe( 'test-font1.woff2' ); + expect( formData.get( 'file-1' ).name ).toBe( 'test-font2.woff2' ); // Check if 'fontFamilies' key in FormData is correct - const expectedFontFamilies = [ + const expectedFontFaces = [ + { + fontWeight: '500', + fontStyle: 'normal', + uploadedFile: 'file-0', + }, { - fontFace: [ - { - fontWeight: '500', - fontStyle: 'normal', - uploadedFile: 'file-0-0', - }, - { - fontWeight: '400', - fontStyle: 'normal', - uploadedFile: 'file-0-1', - }, - ], - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', + fontWeight: '400', + fontStyle: 'normal', + uploadedFile: 'file-1', }, ]; - expect( JSON.parse( formData.get( 'font_families' ) ) ).toEqual( - expectedFontFamilies + expect( formData.get( 'slug' ) ).toBe( 'bebas' ); + expect( formData.get( 'name' ) ).toBe( 'Bebas' ); + expect( formData.get( 'fontFamily' ) ).toBe( 'Bebas' ); + expect( JSON.parse( formData.get( 'fontFace' ) ) ).toEqual( + expectedFontFaces ); } ); } ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/sort-font-faces.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/sort-font-faces.spec.js similarity index 100% rename from packages/edit-site/src/components/global-styles/font-library-modal/utils/test/sort-font-faces.js rename to packages/edit-site/src/components/global-styles/font-library-modal/utils/test/sort-font-faces.spec.js diff --git a/phpunit/data/fonts/DMSans.woff2 b/phpunit/data/fonts/DMSans.woff2 new file mode 100644 index 00000000000000..9a7696df2ade09 Binary files /dev/null and b/phpunit/data/fonts/DMSans.woff2 differ diff --git a/phpunit/data/fonts/Merriweather.ttf b/phpunit/data/fonts/Merriweather.ttf new file mode 100644 index 00000000000000..3fecc77777abf2 Binary files /dev/null and b/phpunit/data/fonts/Merriweather.ttf differ diff --git a/phpunit/data/fonts/cooper-hewitt.woff b/phpunit/data/fonts/cooper-hewitt.woff new file mode 100644 index 00000000000000..8f395dec215013 Binary files /dev/null and b/phpunit/data/fonts/cooper-hewitt.woff differ diff --git a/phpunit/data/fonts/gilbert-color.otf b/phpunit/data/fonts/gilbert-color.otf new file mode 100644 index 00000000000000..f21f9a173f2fe0 Binary files /dev/null and b/phpunit/data/fonts/gilbert-color.otf differ diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php index 5c2b7b5c02793a..a55ab9aaa643f0 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php @@ -3,7 +3,7 @@ * Test WP_Font_Collection::__construct(). * * @package WordPress - * @subpackage Font Library + * @subpackage Fonts * * @group fonts * @group font-library diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php b/phpunit/tests/fonts/font-library/wpFontCollection/getData.php index 4d0b2eb92b595e..9f843205c3a489 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getData.php @@ -3,7 +3,7 @@ * Test WP_Font_Collection::get_data(). * * @package WordPress - * @subpackage Font Library + * @subpackage Fonts * * @group fonts * @group font-library @@ -28,7 +28,7 @@ public function tear_down() { public function mock_request( $preempt, $args, $url ) { // if the URL is not the URL you want to mock, return false. - if ( 'https://localhost/fonts/mock-font-collection.json' !== $url ) { + if ( 'https://wordpress.org/fonts/mock-font-collection.json' !== $url ) { return false; } @@ -86,7 +86,7 @@ public function data_should_get_data() { 'id' => 'my-collection-with-url', 'name' => 'My Collection with URL', 'description' => 'My collection description', - 'src' => 'https://localhost/fonts/mock-font-collection.json', + 'src' => 'https://wordpress.org/fonts/mock-font-collection.json', ), 'expected_data' => array( 'id' => 'my-collection-with-url', diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontCollection/getFontCollection.php similarity index 82% rename from phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php rename to phpunit/tests/fonts/font-library/wpFontCollection/getFontCollection.php index bfdb7258fa11aa..654ed641905003 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getFontCollection.php @@ -1,16 +1,16 @@ 'my-collection', + 'name' => 'My Collection', + 'description' => 'My Collection Description', + 'src' => 'my-collection-data.json', + ); + $collection = WP_Font_Library::register_font_collection( $config ); + $this->assertInstanceOf( 'WP_Font_Collection', $collection ); + } + + public function test_should_return_error_if_id_is_missing() { + $config = array( + 'name' => 'My Collection', + 'description' => 'My Collection Description', + 'src' => 'my-collection-data.json', + ); + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Font Collection config ID is required as a non-empty string.' ); + WP_Font_Library::register_font_collection( $config ); + } + + public function test_should_return_error_if_name_is_missing() { + $config = array( + 'id' => 'my-collection', + 'description' => 'My Collection Description', + 'src' => 'my-collection-data.json', + ); + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Font Collection config name is required as a non-empty string.' ); + WP_Font_Library::register_font_collection( $config ); + } + + public function test_should_return_error_if_config_is_empty() { + $config = array(); + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Font Collection config options is required as a non-empty array.' ); + WP_Font_Library::register_font_collection( $config ); + } + + public function test_should_return_error_if_id_is_repeated() { + $config1 = array( + 'id' => 'my-collection-1', + 'name' => 'My Collection 1', + 'description' => 'My Collection 1 Description', + 'src' => 'my-collection-1-data.json', + ); + $config2 = array( + 'id' => 'my-collection-1', + 'name' => 'My Collection 2', + 'description' => 'My Collection 2 Description', + 'src' => 'my-collection-2-data.json', + ); + + // Register first collection. + $collection1 = WP_Font_Library::register_font_collection( $config1 ); + $this->assertInstanceOf( 'WP_Font_Collection', $collection1, 'A collection should be registered.' ); + + // Try to register a second collection with same id. + $collection2 = WP_Font_Library::register_font_collection( $config2 ); + $this->assertWPError( $collection2, 'Second collection with the same id should fail.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php b/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php index c878dd00fdb5ca..27c12efdebb8f5 100644 --- a/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php +++ b/phpunit/tests/fonts/font-library/wpFontFamily/uninstall.php @@ -24,7 +24,7 @@ public function test_should_return_error_when_font_not_found() { $actual = $font->uninstall(); $this->assertWPError( $actual, 'WP_Error should have been returned' ); $this->assertSame( - array( 'font_family_not_found' => array( 'The font family could not be found.' ) ), + array( 'font_family_slug_not_found' => array( 'Font Family with that slug was not found.' ) ), $actual->errors, 'WP_Error should have "fonts_must_have_same_slug" error' ); diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php index 6bc5fbb8161cee..a56c717dc854c5 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -14,7 +14,7 @@ class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_UnitTestCase { public function test_should_register_font_collection() { $config = array( - 'id' => 'my-collection', + 'id' => 'my-collection-0', 'name' => 'My Collection', 'description' => 'My Collection Description', 'src' => 'my-collection-data.json', @@ -54,13 +54,13 @@ public function test_should_return_error_if_config_is_empty() { public function test_should_return_error_if_id_is_repeated() { $config1 = array( - 'id' => 'my-collection-1', + 'id' => 'my-collection-repeated', 'name' => 'My Collection 1', 'description' => 'My Collection 1 Description', 'src' => 'my-collection-1-data.json', ); $config2 = array( - 'id' => 'my-collection-1', + 'id' => 'my-collection-repeated', 'name' => 'My Collection 2', 'description' => 'My Collection 2 Description', 'src' => 'my-collection-2-data.json', diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/wpRestFontCollectionsController.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/wpRestFontCollectionsController.php new file mode 100644 index 00000000000000..3283ee60ce74e6 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/wpRestFontCollectionsController.php @@ -0,0 +1,300 @@ +factory->user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( $admin_id ); + + if ( in_array( $this->getName(), self::$test_methods_to_skip_setup, true ) ) { + return; + } + + // Mock font collection data file. + $mock_file = wp_tempnam( 'one-collection-' ); + file_put_contents( $mock_file, '{"this is mock data":true}' ); + // Mock the wp_remote_request() function. + add_filter( 'pre_http_request', array( $this, 'mock_response' ), 10, 3 ); + + $config_with_file = array( + 'id' => 'one-collection', + 'name' => 'One Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'src' => $mock_file, + ); + wp_register_font_collection( $config_with_file ); + + $config_with_url = array( + 'id' => 'collection-with-url', + 'name' => 'Another Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'src' => 'https://wordpress.org/fonts/mock-font-collection.json', + ); + + wp_register_font_collection( $config_with_url ); + + $config_with_non_existing_file = array( + 'id' => 'collection-with-non-existing-file', + 'name' => 'Another Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'src' => '/home/non-existing-file.json', + ); + + wp_register_font_collection( $config_with_non_existing_file ); + + $config_with_non_existing_url = array( + 'id' => 'collection-with-non-existing-url', + 'name' => 'Another Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'src' => 'https://non-existing-url-1234x.com.ar/fake-path/missing-file.json', + ); + + wp_register_font_collection( $config_with_non_existing_url ); + } + + public function tear_down() { + + // Remove the mock to not affect other tests. + remove_filter( 'pre_http_request', array( $this, 'mock_response' ) ); + + // Reset $collections static property of WP_Font_Library class. + $reflection = new ReflectionClass( 'WP_Font_Library' ); + $property = $reflection->getProperty( 'collections' ); + $property->setAccessible( true ); + $property->setValue( null, array() ); + + // Clean up the /fonts directory. + foreach ( $this->files_in_dir( static::$fonts_dir ) as $file ) { + @unlink( $file ); + } + + parent::tear_down(); + } + + /** + * @ticket 59166 + * @coversNothing + * + * @param false|array|WP_Error $response A preemptive return value of an HTTP request. + * @param array $parsed_args HTTP request arguments. + * @param string $url The request URL. + * + * @return array Mocked response data. + */ + public function mock_response( $response, $parsed_args, $url ) { + // Check if it's the URL you want to mock. + if ( 'https://wordpress.org/fonts/mock-font-collection.json' !== $url ) { + // For any other URL, return false which ensures the request is made as usual (or you can return other mock data). + return false; + } + + // Mock the response body. + $mock_collection_data = array( + 'fontFamilies' => 'mock', + 'categories' => 'mock', + ); + + return array( + 'body' => json_encode( $mock_collection_data ), + 'response' => array( + 'code' => 200, + ), + ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/wp/v2/font-collections/(?P[\/\w-]+)', + $routes, + "Font collections route doesn't exist." + ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'The REST server does not have the GET method initialized for font collections.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'The REST server does not have the GET method initialized for a specific font collection.' ); + $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'The REST server does not have the font collections path initialized.' ); + $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'The REST server does not have the path initialized for a specific font collection.' ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items() { + // Mock font collection data file. + $mock_file = wp_tempnam( 'my-collection-data-' ); + file_put_contents( $mock_file, '{"this is mock data":true}' ); + + // Add a font collection. + $config = array( + 'id' => 'my-font-collection', + 'name' => 'My Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'src' => $mock_file, + ); + wp_register_font_collection( $config ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + $this->assertCount( 1, $data, 'The response data is not an array with one element.' ); + $this->assertArrayHasKey( 'id', $data[0], 'The response data does not have the key with the collection ID.' ); + $this->assertArrayHasKey( 'name', $data[0], 'The response data does not have the key with the collection name.' ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_items + */ + public function test_get_items_with_no_collection_registered() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( array(), $response->get_data() ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-url' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_item_should_return_font_colllection_from_file() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/one-collection' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' ); + $this->assertSame( array( 'this is mock data' => true ), $data['data'], 'The response data does not have the expected file data.' ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_non_existing_collection_should_return_404() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/non-existing-collection-id' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 404, $response->get_status() ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_non_existing_file_should_return_500() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-non-existing-file' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 500, $response->get_status() ); + } + + /** + * @ticket 59166 + * @covers WP_REST_Font_Collections_Controller::get_item + */ + public function test_get_non_existing_url_should_return_500() { + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/collection-with-non-existing-url' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 500, $response->get_status() ); + } + + public function test_context_param() { + } + + /** + * @ticket 59166 + * @coversNothing + */ + public function test_create_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to create font collection.", + WP_REST_Template_Autosaves_Controller::class + ) + ); + } + + /** + * @ticket 59166 + * @coversNothing + */ + public function test_update_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to update font collection.", + WP_REST_Font_Collections_Controller::class + ) + ); + } + + /** + * @ticket 59166 + * @coversNothing + */ + public function test_delete_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to delete font collection.", + WP_REST_Font_Collections_Controller::class + ) + ); + } + + /** + * @ticket 59166 + * @coversNothing + */ + public function test_prepare_item() { + $this->markTestSkipped( + sprintf( + "The '%s' controller doesn't currently support the ability to prepare font collection.", + WP_REST_Font_Collections_Controller::class + ) + ); + } + + public function test_get_item_schema() { + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php similarity index 57% rename from phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php rename to phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php index a6b02f38a5e817..2562f4fca837fb 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php @@ -1,14 +1,14 @@ getProperty( 'collections' ); - $property->setAccessible( true ); - $property->setValue( null, array() ); - // Clean up the /fonts directory. foreach ( $this->files_in_dir( static::$fonts_dir ) as $file ) { @unlink( $file ); diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/createItem.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/createItem.php new file mode 100644 index 00000000000000..9aac6480231e01 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/createItem.php @@ -0,0 +1,307 @@ +set_param( 'slug', $font_family['slug'] ); + $create_item_request->set_param( 'fontFamily', $font_family['fontFamily'] ); + $create_item_request->set_param( 'name', $font_family['name'] ); + if ( ! empty( $font_family['fontFace'] ) ) { + $create_item_request->set_param( 'fontFace', json_encode( $font_family['fontFace'] ) ); + } + $create_item_request->set_file_params( $files ); + $response = rest_get_server()->dispatch( $create_item_request ); + $installed_font = $response->get_data(); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + + if ( isset( $installed_font['fontFace'] ) || isset( $expected_response['fontFace'] ) ) { + for ( $face_index = 0; $face_index < count( $installed_font['fontFace'] ); $face_index++ ) { + // Checks that the font asset were created correctly. + if ( isset( $installed_font['fontFace'][ $face_index ]['src'] ) ) { + $this->assertStringEndsWith( $expected_response['fontFace'][ $face_index ]['src'], $installed_font['fontFace'][ $face_index ]['src'], 'The src of the fonts were not updated as expected.' ); + } + // Removes the src from the response to compare the rest of the data. + unset( $installed_font['fontFace'][ $face_index ]['src'] ); + unset( $expected_response['fontFace'][ $face_index ]['src'] ); + unset( $installed_font['fontFace'][ $face_index ]['uploadedFile'] ); + } + } + + // Compares if the rest of the data is the same. + $this->assertEquals( $expected_response, $installed_font, 'The endpoint answer is not as expected.' ); + } + + /** + * Data provider for test_install_fonts + */ + public function data_create_item() { + $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); + copy( path_join( GUTENBERG_DIR_TESTDATA, 'fonts/Merriweather.ttf' ), $temp_file_path1 ); + + return array( + + 'google_fonts_to_download' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + 'files' => array(), + 'expected_response' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf', + ), + ), + ), + ), + + 'google_fonts_to_use_as_is' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + 'files' => array(), + 'expected_response' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + ), + + 'fonts_without_font_faces' => array( + 'font_family' => array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', + ), + 'files' => array(), + 'expected_response' => array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', + ), + ), + + 'fonts_with_local_fonts_assets' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', + ), + ), + ), + 'files' => array( + 'files0' => array( + 'name' => 'piazzola1.ttf', + 'type' => 'font/ttf', + 'tmp_name' => $temp_file_path1, + 'error' => 0, + 'size' => 123, + ), + ), + 'expected_response' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf', + ), + ), + ), + ), + ); + } + + /** + * Tests failure when fontFaces has improper inputs + * + * @dataProvider data_create_item_with_improper_inputs + * + * @param array $font_family Font families to install in theme.json format. + * @param array $files Font files to install. + */ + public function test_create_item_with_improper_inputs( $font_family, $files = array() ) { + $create_item_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + + if ( isset( $font_family['slug'] ) ) { + $create_item_request->set_param( 'slug', $font_family['slug'] ); + } + if ( isset( $font_family['fontFamily'] ) ) { + $create_item_request->set_param( 'fontFamily', $font_family['fontFamily'] ); + } + if ( isset( $font_family['name'] ) ) { + $create_item_request->set_param( 'name', $font_family['name'] ); + } + if ( isset( $font_family['fontFace'] ) ) { + $create_item_request->set_param( 'fontFace', json_encode( $font_family['fontFace'] ) ); + } + $create_item_request->set_file_params( $files ); + + $response = rest_get_server()->dispatch( $create_item_request ); + $this->assertSame( 400, $response->get_status() ); + } + + /** + * Data provider for test_install_with_improper_inputs + */ + public function data_create_item_with_improper_inputs() { + + $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); + copy( path_join( DIR_TESTDATA, 'fonts/Merriweather.ttf' ), $temp_file_path1 ); + + return array( + 'empty array' => array( + 'font_family' => array(), + ), + + 'without slug' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + ), + ), + + 'with improper font face property' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => 'This is not an array', + ), + ), + + 'with empty font face property' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array(), + ), + ), + + 'fontface referencing uploaded file without uploaded files' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', + ), + ), + ), + 'files' => array(), + ), + + 'fontface referencing uploaded file without uploaded files' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files666', + ), + ), + ), + 'files' => array( + 'files0' => array( + 'name' => 'piazzola1.ttf', + 'type' => 'font/ttf', + 'tmp_name' => $temp_file_path1, + 'error' => 0, + 'size' => 123, + ), + ), + ), + + 'fontface with incompatible properties (downloadFromUrl and uploadedFile together)' => array( + 'font_family' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'uploadedFile' => 'files0', + ), + ), + ), + ), + ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/deleteItem.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/deleteItem.php new file mode 100644 index 00000000000000..f7402b31d860e7 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/deleteItem.php @@ -0,0 +1,66 @@ + 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + ); + + foreach ( $mock_families as $font_family ) { + + $create_item_request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + + $create_item_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $create_item_request->set_param( 'slug', $font_family['slug'] ); + $create_item_request->set_param( 'fontFamily', $font_family['fontFamily'] ); + $create_item_request->set_param( 'name', $font_family['name'] ); + if ( ! empty( $font_family['fontFace'] ) ) { + $create_item_request->set_param( 'fontFace', json_encode( $font_family['fontFace'] ) ); + } + rest_get_server()->dispatch( $create_item_request ); + } + } + + public function test_delete_item() { + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/piazzolla' ); + $response = rest_get_server()->dispatch( $uninstall_request ); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + } + + + public function test_uninstall_non_existing_fonts() { + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/pizza' ); + $response = rest_get_server()->dispatch( $uninstall_request ); + $this->assertSame( 404, $response->get_status(), 'The response status is not 404.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItem.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItem.php new file mode 100644 index 00000000000000..5ee139d97de13e --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItem.php @@ -0,0 +1,96 @@ + 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/example1.ttf', + ), + ), + ), + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'src' => 'http://example.com/fonts/example2.ttf', + ), + ), + ), + ); + + foreach ( $mock_families as $font_family ) { + + $create_item_request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + + $create_item_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $create_item_request->set_param( 'slug', $font_family['slug'] ); + $create_item_request->set_param( 'fontFamily', $font_family['fontFamily'] ); + $create_item_request->set_param( 'name', $font_family['name'] ); + if ( ! empty( $font_family['fontFace'] ) ) { + $create_item_request->set_param( 'fontFace', json_encode( $font_family['fontFace'] ) ); + } + rest_get_server()->dispatch( $create_item_request ); + } + } + + public function tear_down() { + parent::tear_down(); + + // Delete mock fonts after tests. + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/montserrat' ); + rest_get_server()->dispatch( $uninstall_request ); + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/piazzolla' ); + rest_get_server()->dispatch( $uninstall_request ); + } + + public function test_get_item() { + $get_items_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/piazzolla' ); + $response = rest_get_server()->dispatch( $get_items_request ); + $expected_response = array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/example1.ttf', + ), + ), + ); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + $this->assertEquals( $expected_response, $response->get_data(), 'The response data is not expected.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItems.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItems.php new file mode 100644 index 00000000000000..a1f3ee687ce522 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/getItems.php @@ -0,0 +1,112 @@ + 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/example1.ttf', + ), + ), + ), + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'src' => 'http://example.com/fonts/example2.ttf', + ), + ), + ), + ); + + foreach ( $mock_families as $font_family ) { + + $create_item_request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + + $create_item_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $create_item_request->set_param( 'slug', $font_family['slug'] ); + $create_item_request->set_param( 'fontFamily', $font_family['fontFamily'] ); + $create_item_request->set_param( 'name', $font_family['name'] ); + if ( ! empty( $font_family['fontFace'] ) ) { + $create_item_request->set_param( 'fontFace', json_encode( $font_family['fontFace'] ) ); + } + rest_get_server()->dispatch( $create_item_request ); + } + } + + public function tear_down() { + parent::tear_down(); + + // Delete mock fonts after tests. + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/montserrat' ); + rest_get_server()->dispatch( $uninstall_request ); + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/piazzolla' ); + rest_get_server()->dispatch( $uninstall_request ); + } + + public function test_get_items() { + $get_items_request = new WP_REST_Request( 'GET', '/wp/v2/font-families' ); + $response = rest_get_server()->dispatch( $get_items_request ); + $expected_response = array( + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'src' => 'http://example.com/fonts/example2.ttf', + ), + ), + ), + array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/example1.ttf', + ), + ), + ), + ); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + + $this->assertEquals( $expected_response, $response->get_data(), 'The response data is not expected.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/registerRoutes.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/registerRoutes.php new file mode 100644 index 00000000000000..a58a76b72ea880 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/registerRoutes.php @@ -0,0 +1,25 @@ +get_routes(); + $this->assertArrayHasKey( '/wp/v2/font-families', $routes, 'Rest server has not the fonts path intialized.' ); + $this->assertCount( 2, $routes['/wp/v2/font-families'], 'Rest server has not the 2 fonts paths initialized.' ); + $this->assertCount( 2, $routes['/wp/v2/font-families/(?P[\/\w-]+)'], 'Rest server has not the 2 fonts paths initialized.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-families'][0]['methods'], 'Rest server has not the GET method for fonts intialized.' ); + $this->assertArrayHasKey( 'POST', $routes['/wp/v2/font-families'][1]['methods'], 'Rest server has not the POST method for fonts intialized.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-families/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for fonts intialized.' ); + $this->assertArrayHasKey( 'DELETE', $routes['/wp/v2/font-families/(?P[\/\w-]+)'][1]['methods'], 'Rest server has not the DELETE method for fonts intialized.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php deleted file mode 100644 index f1a0a6a0cd510c..00000000000000 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php +++ /dev/null @@ -1,126 +0,0 @@ - 'one-collection', - 'name' => 'One Font Collection', - 'description' => 'Demo about how to a font collection to your WordPress Font Library.', - 'src' => $mock_file, - ); - wp_register_font_collection( $config_with_file ); - - $config_with_url = array( - 'id' => 'collection-with-url', - 'name' => 'Another Font Collection', - 'description' => 'Demo about how to a font collection to your WordPress Font Library.', - 'src' => 'https://wordpress.org/fonts/mock-font-collection.json', - ); - - wp_register_font_collection( $config_with_url ); - - $config_with_non_existing_file = array( - 'id' => 'collection-with-non-existing-file', - 'name' => 'Another Font Collection', - 'description' => 'Demo about how to a font collection to your WordPress Font Library.', - 'src' => '/home/non-existing-file.json', - ); - - wp_register_font_collection( $config_with_non_existing_file ); - - $config_with_non_existing_url = array( - 'id' => 'collection-with-non-existing-url', - 'name' => 'Another Font Collection', - 'description' => 'Demo about how to a font collection to your WordPress Font Library.', - 'src' => 'https://non-existing-url-1234x.com.ar/fake-path/missing-file.json', - ); - - wp_register_font_collection( $config_with_non_existing_url ); - } - - public function mock_request( $preempt, $args, $url ) { - // Check if it's the URL you want to mock. - if ( 'https://wordpress.org/fonts/mock-font-collection.json' === $url ) { - - // Mock the response body. - $mock_collection_data = array( - 'fontFamilies' => 'mock', - 'categories' => 'mock', - ); - - return array( - 'body' => json_encode( $mock_collection_data ), - 'response' => array( - 'code' => 200, - ), - ); - } - - // For any other URL, return false which ensures the request is made as usual (or you can return other mock data). - return false; - } - - public function tear_down() { - // Remove the mock to not affect other tests. - remove_filter( 'pre_http_request', array( $this, 'mock_request' ) ); - - parent::tear_down(); - } - - public function test_get_font_collection_from_file() { - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections/one-collection' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); - $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' ); - $this->assertSame( array( 'this is mock data' => true ), $data['data'], 'The response data does not have the expected file data.' ); - } - - public function test_get_font_collection_from_url() { - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections/collection-with-url' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); - $this->assertArrayHasKey( 'data', $data, 'The response data does not have the key with the file data.' ); - } - - public function test_get_non_existing_collection_should_return_404() { - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections/non-existing-collection-id' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertSame( 404, $response->get_status() ); - } - - public function test_get_non_existing_file_should_return_500() { - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections/collection-with-non-existing-file' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertSame( 500, $response->get_status() ); - } - - public function test_get_non_existing_url_should_return_500() { - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections/collection-with-non-existing-url' ); - $response = rest_get_server()->dispatch( $request ); - $this->assertSame( 500, $response->get_status() ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php deleted file mode 100644 index ad120ee36fce4d..00000000000000 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php +++ /dev/null @@ -1,45 +0,0 @@ -dispatch( $request ); - $this->assertSame( 200, $response->get_status() ); - $this->assertSame( array(), $response->get_data() ); - } - - public function test_get_font_collections() { - // Mock font collection data file. - $mock_file = wp_tempnam( 'my-collection-data-' ); - file_put_contents( $mock_file, '{"this is mock data":true}' ); - - // Add a font collection. - $config = array( - 'id' => 'my-font-collection', - 'name' => 'My Font Collection', - 'description' => 'Demo about how to a font collection to your WordPress Font Library.', - 'src' => $mock_file, - ); - wp_register_font_collection( $config ); - - $request = new WP_REST_Request( 'GET', '/wp/v2/fonts/collections' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); - $this->assertCount( 1, $data, 'The response data is not an array with one element.' ); - $this->assertArrayHasKey( 'id', $data[0], 'The response data does not have the key with the collection ID.' ); - $this->assertArrayHasKey( 'name', $data[0], 'The response data does not have the key with the collection name.' ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php deleted file mode 100644 index 01ac1ff8436ed7..00000000000000 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php +++ /dev/null @@ -1,433 +0,0 @@ -set_param( 'font_families', $font_families_json ); - $install_request->set_file_params( $files ); - $response = rest_get_server()->dispatch( $install_request ); - $data = $response->get_data(); - $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); - $this->assertCount( count( $expected_response['successes'] ), $data['successes'], 'Not all the font families were installed correctly.' ); - - // Checks that the font families were installed correctly. - for ( $family_index = 0; $family_index < count( $data['successes'] ); $family_index++ ) { - $installed_font = $data['successes'][ $family_index ]; - $expected_font = $expected_response['successes'][ $family_index ]; - - if ( isset( $installed_font['fontFace'] ) || isset( $expected_font['fontFace'] ) ) { - for ( $face_index = 0; $face_index < count( $installed_font['fontFace'] ); $face_index++ ) { - // Checks that the font asset were created correctly. - if ( isset( $installed_font['fontFace'][ $face_index ]['src'] ) ) { - $this->assertStringEndsWith( $expected_font['fontFace'][ $face_index ]['src'], $installed_font['fontFace'][ $face_index ]['src'], 'The src of the fonts were not updated as expected.' ); - } - // Removes the src from the response to compare the rest of the data. - unset( $installed_font['fontFace'][ $face_index ]['src'] ); - unset( $expected_font['fontFace'][ $face_index ]['src'] ); - unset( $installed_font['fontFace'][ $face_index ]['uploadedFile'] ); - } - } - - // Compares if the rest of the data is the same. - $this->assertEquals( $expected_font, $installed_font, 'The endpoint answer is not as expected.' ); - } - } - - /** - * Data provider for test_install_fonts - */ - public function data_install_fonts() { - - $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); - copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $temp_file_path1 ); - - $temp_file_path2 = wp_tempnam( 'Monteserrat-' ); - copy( __DIR__ . '/../../../data/fonts/Merriweather.ttf', $temp_file_path2 ); - - return array( - - 'google_fonts_to_download' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), - ), - ), - ), - 'files' => array(), - 'expected_response' => array( - 'successes' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), - ), - 'errors' => array(), - ), - ), - - 'google_fonts_to_use_as_is' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), - ), - ), - ), - 'files' => array(), - 'expected_response' => array( - 'successes' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - - ), - ), - ), - ), - 'errors' => array(), - ), - ), - - 'fonts_without_font_faces' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Arial', - 'slug' => 'arial', - 'name' => 'Arial', - ), - ), - 'files' => array(), - 'expected_response' => array( - 'successes' => array( - array( - 'fontFamily' => 'Arial', - 'slug' => 'arial', - 'name' => 'Arial', - ), - ), - 'errors' => array(), - ), - ), - - 'fonts_with_local_fonts_assets' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'uploadedFile' => 'files1', - ), - ), - ), - ), - 'files' => array( - 'files0' => array( - 'name' => 'piazzola1.ttf', - 'type' => 'font/ttf', - 'tmp_name' => $temp_file_path1, - 'error' => 0, - 'size' => 123, - ), - 'files1' => array( - 'name' => 'montserrat1.ttf', - 'type' => 'font/ttf', - 'tmp_name' => $temp_file_path2, - 'error' => 0, - 'size' => 123, - ), - ), - 'expected_response' => array( - 'successes' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => '/wp-content/fonts/piazzolla_normal_400.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), - - ), - 'errors' => array(), - ), - ), - ); - } - - /** - * Tests failure when fonfaces has improper inputs - * - * @dataProvider data_install_with_improper_inputs - * - * @param array $font_families Font families to install in theme.json format. - * @param array $files Font files to install. - */ - public function test_install_with_improper_inputs( $font_families, $files = array() ) { - $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); - $font_families_json = json_encode( $font_families ); - $install_request->set_param( 'font_families', $font_families_json ); - $install_request->set_file_params( $files ); - - $response = rest_get_server()->dispatch( $install_request ); - $this->assertSame( 400, $response->get_status() ); - } - - /** - * Data provider for test_install_with_improper_inputs - */ - public function data_install_with_improper_inputs() { - $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); - file_put_contents( $temp_file_path1, 'Mocking file content' ); - - return array( - 'not a font families array' => array( - 'font_families' => 'This is not an array', - ), - - 'empty array' => array( - 'font_families' => array(), - ), - - 'without slug' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - ), - ), - ), - - 'with improper font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => 'This is not an array', - ), - ), - ), - - 'with empty font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array(), - ), - ), - ), - - 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), - ), - ), - ), - 'files' => array(), - ), - - 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files666', - ), - ), - ), - ), - 'files' => array( - 'files0' => array( - 'name' => 'piazzola1.ttf', - 'type' => 'font/ttf', - 'tmp_name' => $temp_file_path1, - 'error' => 0, - 'size' => 123, - ), - ), - ), - - 'fontface with incompatible properties (downloadFromUrl and uploadedFile together)' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'uploadedFile' => 'files0', - ), - ), - ), - ), - ), - ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php deleted file mode 100644 index 2ac7b93c3a4141..00000000000000 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php +++ /dev/null @@ -1,28 +0,0 @@ -get_routes(); - $this->assertArrayHasKey( '/wp/v2/fonts', $routes, 'Rest server has not the fonts path intialized.' ); - $this->assertCount( 2, $routes['/wp/v2/fonts'], 'Rest server has not the 2 fonts paths initialized.' ); - $this->assertCount( 1, $routes['/wp/v2/fonts/collections'], 'Rest server has not the collections path initialized.' ); - $this->assertCount( 1, $routes['/wp/v2/fonts/collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' ); - - $this->assertArrayHasKey( 'POST', $routes['/wp/v2/fonts'][0]['methods'], 'Rest server has not the POST method for fonts intialized.' ); - $this->assertArrayHasKey( 'DELETE', $routes['/wp/v2/fonts'][1]['methods'], 'Rest server has not the DELETE method for fonts intialized.' ); - $this->assertArrayHasKey( 'GET', $routes['/wp/v2/fonts/collections'][0]['methods'], 'Rest server has not the GET method for collections intialized.' ); - $this->assertArrayHasKey( 'GET', $routes['/wp/v2/fonts/collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php deleted file mode 100644 index a3b613e6f983e0..00000000000000 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php +++ /dev/null @@ -1,96 +0,0 @@ - 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), - ), - ), - ); - - $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); - $font_families_json = json_encode( $mock_families ); - $install_request->set_param( 'font_families', $font_families_json ); - rest_get_server()->dispatch( $install_request ); - } - - public function test_uninstall() { - $font_families_to_uninstall = array( - array( - 'slug' => 'piazzolla', - ), - array( - 'slug' => 'montserrat', - ), - ); - - $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/fonts' ); - $uninstall_request->set_param( 'font_families', $font_families_to_uninstall ); - $response = rest_get_server()->dispatch( $uninstall_request ); - $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); - } - - - public function test_uninstall_non_existing_fonts() { - $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/fonts' ); - - $non_existing_font_data = array( - array( - 'slug' => 'non-existing-font', - 'name' => 'Non existing font', - ), - array( - 'slug' => 'another-not-installed-font', - 'name' => 'Another not installed font', - ), - ); - - $uninstall_request->set_param( 'font_families', $non_existing_font_data ); - $response = rest_get_server()->dispatch( $uninstall_request ); - $data = $response->get_data(); - $this->assertCount( 2, $data['errors'], 'The response should have 2 errors, one for each font family uninstall failure.' ); - } -}