diff --git a/lib/experimental/fonts/font-library/class-wp-font-collection.php b/lib/experimental/fonts/font-library/class-wp-font-collection.php index e8cc7c98fe730f..74a4372826c6b9 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-collection.php +++ b/lib/experimental/fonts/font-library/class-wp-font-collection.php @@ -27,6 +27,7 @@ class WP_Font_Collection { * * @var array */ + //TODO: Why is this a config object and not discreen properties? private $config; /** @@ -43,16 +44,16 @@ public function __construct( $config ) { throw new Exception( 'Font Collection config options is required as a non-empty array.' ); } - if ( empty( $config['id'] ) || ! is_string( $config['id'] ) ) { - throw new Exception( 'Font Collection config ID is required as a non-empty string.' ); + if ( empty( $config['slug'] ) || ! is_string( $config['slug'] ) ) { + throw new Exception( 'Font Collection config slug is required as a non-empty string.' ); } if ( empty( $config['name'] ) || ! is_string( $config['name'] ) ) { throw new Exception( 'Font Collection config name is required as a non-empty string.' ); } - if ( empty( $config['src'] ) || ! is_string( $config['src'] ) ) { - throw new Exception( 'Font Collection config "src" option is required as a non-empty string.' ); + if ( ( empty( $config['src'] ) || ! is_string( $config['src'] ) ) && ( empty( $config['data'] ) ) ) { + throw new Exception( 'Font Collection config "src" option OR "data" option is required.' ); } $this->config = $config; @@ -78,6 +79,11 @@ public function get_config() { * else an instance of WP_Error on failure. */ public function get_data() { + + if( ! empty( $this->config['data'] ) ) { + return $this->get_config(); + } + // If the src is a URL, fetch the data from the URL. if ( str_contains( $this->config['src'], 'http' ) && str_contains( $this->config['src'], '://' ) ) { if ( ! wp_http_validate_url( $this->config['src'] ) ) { 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 58d4f476e834d1..bddddb4fbc5b48 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -27,6 +27,8 @@ class WP_Font_Family { * * @var array */ + + public $id; private $data; /** @@ -35,256 +37,337 @@ class WP_Font_Family { * @since 6.5.0 * * @param array $font_data Font family data. - * @throws Exception If the font family data is missing the slug. + * @throws Exception If the font family data is missing the slug or if there are errors installing font face resourdces. */ public function __construct( $font_data = array() ) { if ( empty( $font_data['slug'] ) ) { throw new Exception( 'Font family data is missing the slug.' ); } - $this->data = $font_data; + + $this->data = array( + 'slug' => $font_data['slug'], + ); + + if( isset( $font_data['id'] ) ) { + $this->id = $font_data['id']; + } + + $update_response = $this->update( $font_data, null, true ); + + if ( is_wp_error( $update_response ) ) { + throw new Exception( $update_response->get_error_message() ); + } } /** - * Gets the font family data. + * Gets the font family objects that have been persisted. * * @since 6.5.0 * - * @return array An array in fontFamily theme.json format. + * @return WP_Font_Family The Font Family objects that have been persisted */ - public function get_data() { - return $this->data; + public static function get_font_families () { + $args = array( + 'post_type' => 'wp_font_family', + ); + + $posts_query = new WP_Query( $args ); + + if ( ! $posts_query->have_posts() ) { + return array(); + } + + $font_families = array(); + foreach( $posts_query->posts as $post ) { + $post_data = json_decode( $post->post_content, true ); + $post_data['id'] = $post->ID; + $font_families[] = new WP_Font_Family( $post_data ); + } + + return $font_families; } /** - * Gets the font family data. + * Gets the font family object that has been persisted. * * @since 6.5.0 * - * @return string fontFamily in theme.json format as stringified JSON. + * @return null|WP_Font_Family The Font Family object or null if the font family does not exist. */ - public function get_data_as_json() { - return wp_json_encode( $this->get_data() ); + public static function get_font_family_by_id ( $id ) { + $post = get_post( $id ); + + if ( ! $post ) { + return null; + } + + $post_data = json_decode( $post->post_content, true ); + $post_data['id'] = $post->ID; + return new WP_Font_Family( $post_data ); } /** - * Checks whether the font family has font faces defined. + * Gets the font family object that has been persisted. * * @since 6.5.0 * - * @return bool True if the font family has font faces defined, false otherwise. + * @return null|WP_Font_Family The Font Family object or null if the font family does not exist. */ - public function has_font_faces() { - return ! empty( $this->data['fontFace'] ) && is_array( $this->data['fontFace'] ); + public static function get_font_family_by_slug ( $slug ) { + $args = array( + 'post_type' => 'wp_font_family', + 'post_name' => $slug, + 'name' => $slug, + 'posts_per_page' => 1, + ); + + $posts_query = new WP_Query( $args ); + + if ( ! $posts_query->have_posts() ) { + return null; + } + + $post = $posts_query->posts[0]; + $post_data = json_decode( $post->post_content, true ); + $post_data['id'] = $post->ID; + return new WP_Font_Family( $post_data ); } /** - * Removes font family assets. + * Gets the font family data. * * @since 6.5.0 * - * @return bool True if assets were removed, false otherwise. + * @return array An array in fontFamily theme.json format. */ - private function remove_font_family_assets() { - if ( $this->has_font_faces() ) { - foreach ( $this->data['fontFace'] as $font_face ) { - $were_assets_removed = $this->delete_font_face_assets( $font_face ); - if ( false === $were_assets_removed ) { - return false; - } - } - } - return true; + public function get_data() { + return $this->data; } /** - * Removes a font family from the database and deletes its assets. + * Update and sanitize the font family data. * * @since 6.5.0 * - * @return bool|WP_Error True if the font family was uninstalled, WP_Error otherwise. + * @return array An array in fontFamily theme.json format. */ - 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' ) - ); + public function update( $data, $files=null, $patch=false ) { + + $current_font_faces = array(); + $font_faces_to_add = array(); + $font_faces_to_remove = array(); + + if ( array_key_exists( 'fontFace', $this->data ) && ! empty( $this->data['fontFace'] ) ) { + $current_font_faces = $this->data['fontFace']; } - if ( - ! $this->remove_font_family_assets() || - ! wp_delete_post( $post->ID, true ) - ) { - return new WP_Error( - 'font_family_not_deleted', - __( 'The font family could not be deleted.', 'gutenberg' ) - ); + if ( array_key_exists( 'fontFace', $data ) && ! empty( $data['fontFace'] ) ) { + $font_faces_to_add = $data['fontFace']; } - return true; - } + if ( false === $patch ) { + //If we are UPDATING this object then we need to remove any font faces that were not sent + foreach ( $current_font_faces as $font_face ) { + $existing_font_face = $this->get_font_face_from_collection( $font_face['fontWeight'], $font_face['fontStyle'], $font_faces_to_add ); + if ( ! $existing_font_face ) { + $font_faces_to_remove[] = $font_face; + } + } + } - /** - * Deletes a specified font asset file from the fonts directory. - * - * @since 6.5.0 - * - * @param string $src The path of the font asset file to delete. - * @return bool Whether the file was deleted. - */ - private static function delete_asset( $src ) { - $filename = basename( $src ); - $file_path = path_join( WP_Font_Library::get_fonts_dir(), $filename ); + //Skip any font faces that are ALREADY installed + $non_redundant_font_faces_to_add = array(); + $skipped_font_faces = array(); + foreach ( $font_faces_to_add as $font_face ) { + $existing_font_face = $this->get_font_face_from_collection( $font_face['fontWeight'], $font_face['fontStyle'], $current_font_faces ); + if ( $existing_font_face ) { + $skipped_font_faces[] = $existing_font_face; + } + else { + $non_redundant_font_faces_to_add[] = $font_face; + } + } + $font_faces_to_add = $non_redundant_font_faces_to_add; + + + // Install any assets needed by font faces. + $prepared_font_faces = array(); + if ( ! empty( $font_faces_to_add ) ) { + $prepared_font_faces = $this->prepare_font_faces( $font_faces_to_add, $files); + if ( is_wp_error( $prepared_font_faces ) ) { + return $prepared_font_faces; + } + } + + // Remember to keep the font faces that were already installed that need to stay installed + $prepared_font_faces = array_merge( $skipped_font_faces, $prepared_font_faces ); + + if ( true === $patch ) { + //If we are PATCHING this object then we need to merge the collection + $data['fontFace'] = array_merge( $current_font_faces, $prepared_font_faces ); + } + else { + //If we are UPDATING this object then we need to use only the items that were sent + $data['fontFace'] = $prepared_font_faces; + } + + // If after all that installing and uninstalling our fontFace collection is empty just remove it. + if ( empty( $data['fontFace'] ) ) { + unset( $data['fontFace'] ); + } + + // Remove the assets for any font faces that were flagged to be removed. + //NOTE: This is a 'nice-to-have' operation. If we got here we have ALREADY sucessfully installed the necessary font faces. + //If we fail deleting the assets (for some reason) failing the request isn't very nice. + if ( ! empty( $font_faces_to_remove ) ) { + foreach ( $font_faces_to_remove as $font_face ) { + $this->delete_font_face_assets( $font_face ); + } + } - wp_delete_file( $file_path ); + $merged_data = array_merge( $this->data, $data); + $sanitized_data = $this->sanitize( $merged_data ); - return ! file_exists( $file_path ); + $this->data = $sanitized_data; + + return $this->get_data(); } /** - * Deletes all font face asset files associated with a given font face. + * Given a font weight and style, find the font face in the collection. * * @since 6.5.0 * - * @param array $font_face The font face array containing the 'src' attribute - * with the file path(s) to be deleted. - * @return bool True if delete was successful, otherwise false. + * @return Object|null The Font Face object if it exists in the collection. Otherwise null. */ - private static function delete_font_face_assets( $font_face ) { - $sources = (array) $font_face['src']; - foreach ( $sources as $src ) { - $was_asset_removed = self::delete_asset( $src ); - if ( ! $was_asset_removed ) { - // Bail if any of the assets could not be removed. - return false; + private function get_font_face_from_collection ( $font_weight, $font_style, $collection ) { + if ( ! $collection ) { + return null; + } + foreach ( $collection as $font_face ) { + if ( $font_weight === $font_face['fontWeight'] && $font_style === $font_face['fontStyle']) { + return $font_face; } } - return true; + return null; } + private function prepare_font_faces( $font_faces, $files ) { + $ready_font_faces = array(); + + foreach ( $font_faces as $font_face ) { + + if ( empty( $font_face['src'] ) ) { + return new WP_Error( + 'font_face_asset_missing', + __( 'The font face asset was not defined.', 'gutenberg' ) + ); + } + + if ( $files ) { + + if ( ! $this->has_write_permission() ) { + return new WP_Error( + 'font_face_download_failed', + __( 'The font face assets could not be written.', 'gutenberg' ) + ); + } + + if ( ! $files || ! array_key_exists( $font_face['src'], $files )) { + return new WP_Error( + 'font_face_upload_file_missing', + __( 'The font face assets was not provided.', 'gutenberg' ) + ); + } + $downloaded_font_face_src = $this->move_font_face_asset( $font_face, $files[ $font_face[ 'src' ] ] ); + + if ( is_wp_error( $downloaded_font_face_src ) ) { + return $downloaded_font_face_src; + } + + $font_face['src'] = $downloaded_font_face_src; + } + else if ( str_starts_with( $font_face['src'], 'file' ) ) { + return new WP_Error( + 'font_face_upload_file_missing', + __( 'The font face assets was not provided.', 'gutenberg' ) + ); + } + + $ready_font_faces[] = $font_face; + } + + return $ready_font_faces; + } /** - * Gets the overrides for the 'wp_handle_upload' function. + * Store the Font Family information in the database. * * @since 6.5.0 * - * @param string $filename The filename to be used for the uploaded file. - * @return array The overrides for the 'wp_handle_upload' function. + * @return int|WP_Error Post ID if the post was created or updated sucessfully, WP_Error otherwise. */ - private function get_upload_overrides( $filename ) { - return array( - // Arbitrary string to avoid the is_uploaded_file() check applied - // when using 'wp_handle_upload'. - 'action' => 'wp_handle_font_upload', - // Not testing a form submission. - 'test_form' => false, - // Seems mime type for files that are not images cannot be tested. - // See wp_check_filetype_and_ext(). - 'test_type' => true, - 'mimes' => WP_Font_Library::get_expected_font_mime_types_per_php_version(), - 'unique_filename_callback' => static function () use ( $filename ) { - // Keep the original filename. - return $filename; - }, - ); + public function persist() { + if ( ! empty( $this->id ) ) { + return $this->update_font_post( $this->id, wp_json_encode( $this->get_data() ) ); + } + return $this->create_font_post( wp_json_encode( $this->get_data() ) ); } /** - * Downloads a font asset from a specified source URL and saves it to - * the font directory. + * Creates a post for a font family. * * @since 6.5.0 * - * @param string $url The source URL of the font asset to be downloaded. - * @param string $filename The filename to save the downloaded font asset as. - * @return string|bool The relative path to the downloaded font asset. - * False if the download failed. + * @return int|WP_Error Post ID if the post was created, WP_Error otherwise. */ - private function download_asset( $url, $filename ) { - // Checks if the file to be downloaded has a font mime type. - if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { - return false; - } - - // Include file with download_url() if function doesn't exist. - if ( ! function_exists( 'download_url' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - - // Downloads the font asset or returns false. - $temp_file = download_url( $url ); - if ( is_wp_error( $temp_file ) ) { - return false; - } - - $overrides = $this->get_upload_overrides( $filename ); - - $file = array( - 'tmp_name' => $temp_file, - 'name' => $filename, + private function create_font_post( $post_content ) { + $post = array( + 'post_title' => $this->data['slug'], + 'post_name' => $this->data['slug'], + 'post_type' => 'wp_font_family', + 'post_content' => $post_content, + 'post_status' => 'publish', ); - $handled_file = wp_handle_upload( $file, $overrides ); - - // Cleans the temp file. - @unlink( $temp_file ); - - if ( ! isset( $handled_file['url'] ) ) { - return false; + $post_id = wp_insert_post( $post ); + if ( 0 === $post_id || is_wp_error( $post_id ) ) { + return new WP_Error( + 'font_post_creation_failed', + __( 'Font post creation failed', 'gutenberg' ) + ); } - // Returns the relative path to the downloaded font asset to be used as - // font face src. - return $handled_file['url']; + $this->id = $post_id; + + return $post_id; } /** - * Moves an uploaded font face asset from temp folder to the fonts directory. - * - * This is used when uploading local fonts. + * Updates a post for a font family. * * @since 6.5.0 * - * @param array $font_face Font face to download. - * @param array $file Uploaded file. - * @return array New font face with all assets downloaded and referenced in - * the font face definition. + * @param WP_Post $post The post to update. + * @return int|WP_Error Post ID if the update was successful, WP_Error otherwise. */ - private function move_font_face_asset( $font_face, $file ) { - $new_font_face = $font_face; - $filename = WP_Font_Family_Utils::get_filename_from_font_face( - $this->data['slug'], - $font_face, - $file['name'] - ); + private function update_font_post( $id, $post_content ) { - // Remove the uploaded font asset reference from the font face definition - // because it is no longer needed. - unset( $new_font_face['uploadedFile'] ); - - // If the filename has no font mime type, don't move the file and - // return the font face definition without src to be ignored later. - if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { - return $new_font_face; - } - - // Move the uploaded font asset from the temp folder to the fonts directory. - if ( ! function_exists( 'wp_handle_upload' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - - $overrides = $this->get_upload_overrides( $filename ); + $post = array( + 'ID' => $id, + 'post_content' => $post_content, + ); - $handled_file = wp_handle_upload( $file, $overrides ); + $post_id = wp_update_post( $post ); - if ( isset( $handled_file['url'] ) ) { - // If the file was successfully moved, update the font face definition - // to reference the new file location. - $new_font_face['src'] = $handled_file['url']; + if ( 0 === $post_id || is_wp_error( $post_id ) ) { + return new WP_Error( + 'font_post_update_failed', + __( 'Font post update failed', 'gutenberg' ) + ); } - return $new_font_face; + return $post_id; } /** @@ -294,7 +377,7 @@ private function move_font_face_asset( $font_face, $file ) { * * @return array A sanitized font family definition. */ - private function sanitize() { + private function sanitize( $font_data ) { // Creates the structure of theme.json array with the new fonts. $fonts_json = array( 'version' => '2', @@ -302,7 +385,7 @@ private function sanitize() { 'typography' => array( 'fontFamilies' => array( 'custom' => array( - $this->data, + $font_data, ), ), ), @@ -320,8 +403,35 @@ private function sanitize() { $sanitized_font['slug'] = _wp_to_kebab_case( $sanitized_font['slug'] ); $sanitized_font['fontFamily'] = WP_Font_Family_Utils::format_font_family( $sanitized_font['fontFamily'] ); - $this->data = $sanitized_font; - return $this->data; + + return $sanitized_font; + } + + /** + * 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(); + + // The Fonts directory doesn't exists, so let's make it + if ( ! is_dir( $upload_dir ) ) { + if ( ! wp_mkdir_p( $upload_dir ) ) { + return false; + } + } + + // Checks if the temp and fonts directories are writable. + if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { + return false; + } + + return true; } /** @@ -338,11 +448,9 @@ private function sanitize() { * the downloaded assets. */ private function download_font_face_assets( $font_face ) { - $new_font_face = $font_face; $sources = (array) $font_face['downloadFromUrl']; - $new_font_face['src'] = array(); $index = 0; - + $new_src_collection = array(); foreach ( $sources as $src ) { $suffix = $index++ > 0 ? $index : ''; $filename = WP_Font_Family_Utils::get_filename_from_font_face( @@ -351,283 +459,204 @@ private function download_font_face_assets( $font_face ) { $src, $suffix ); + + $new_src = $this->download_asset( $src, $filename ); - if ( $new_src ) { - $new_font_face['src'][] = $new_src; + + if( is_wp_error($new_src)) { + return $new_src; } + $new_src_collection[] = $new_src; } - if ( count( $new_font_face['src'] ) === 1 ) { - $new_font_face['src'] = $new_font_face['src'][0]; + if ( count( $new_src_collection ) === 0 ) { + return ''; + } + else if ( count( $new_src_collection ) === 1 ) { + return $new_src_collection[0]; } - // Remove the download url reference from the font face definition - // because it is no longer needed. - unset( $new_font_face['downloadFromUrl'] ); - - return $new_font_face; + return $new_src_collection; } - /** - * Downloads font face assets if the font family is a Google font, - * or moves them if it is a local font. + * Downloads a font asset from a specified source URL and saves it to + * the font directory. * * @since 6.5.0 * - * @param array $files An array of files to be installed. - * @return bool True if the font faces were downloaded or moved successfully, false otherwise. + * @param string $url The source URL of the font asset to be downloaded. + * @param string $filename The filename to save the downloaded font asset as. + * @return string|WP_Error The relative path to the downloaded font asset. + * WP_Error if the download failed. */ - private function download_or_move_font_faces( $files ) { - if ( ! $this->has_font_faces() ) { - return true; + private function download_asset( $url, $filename ) { + // Checks if the file to be downloaded has a font mime type. + if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { + return false; } - $new_font_faces = array(); - foreach ( $this->data['fontFace'] as $font_face ) { - // If the fonts are not meant to be downloaded or uploaded - // (for example to install fonts that use a remote url). - $new_font_face = $font_face; - $font_face_is_repeated = false; - - // If the font face has the same fontStyle and fontWeight as an existing, continue. - foreach ( $new_font_faces as $font_to_compare ) { - if ( $new_font_face['fontStyle'] === $font_to_compare['fontStyle'] && - $new_font_face['fontWeight'] === $font_to_compare['fontWeight'] ) { - $font_face_is_repeated = true; - } - } + // Include file with download_url() if function doesn't exist. + if ( ! function_exists( 'download_url' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } - if ( $font_face_is_repeated ) { - continue; - } + // Downloads the font asset or returns false. + $temp_file = download_url( $url ); + if ( is_wp_error( $temp_file ) ) { + return $temp_file; + } - // If the font face requires the use of the filesystem, create the fonts dir if it doesn't exist. - if ( ! empty( $font_face['downloadFromUrl'] ) && ! empty( $font_face['uploadedFile'] ) ) { - wp_mkdir_p( WP_Font_Library::get_fonts_dir() ); - } + $overrides = $this->get_upload_overrides( $filename ); - // If installing google fonts, download the font face assets. - if ( ! empty( $font_face['downloadFromUrl'] ) ) { - $new_font_face = $this->download_font_face_assets( $new_font_face ); - } + $file = array( + 'tmp_name' => $temp_file, + 'name' => $filename, + ); - // If installing local fonts, move the font face assets from - // the temp folder to the wp fonts directory. - if ( ! empty( $font_face['uploadedFile'] ) && ! empty( $files ) ) { - $new_font_face = $this->move_font_face_asset( - $new_font_face, - $files[ $new_font_face['uploadedFile'] ] - ); - } + add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); + add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + $handled_file = wp_handle_upload( $file, $overrides ); + remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - /* - * If the font face assets were successfully downloaded, add the font face - * to the new font. Font faces with failed downloads are not added to the - * new font. - */ - if ( ! empty( $new_font_face['src'] ) ) { - $new_font_faces[] = $new_font_face; - } + if ( array_key_exists( 'error', $handled_file ) ) { + return new WP_Error( $handled_file['error'] . ' ' . $filename ); } - if ( ! empty( $new_font_faces ) ) { - $this->data['fontFace'] = $new_font_faces; - return true; - } + // Cleans the temp file. + @unlink( $temp_file ); - return false; + // Returns the relative path to the downloaded font asset to be used as + // font face src. + return $handled_file['url']; } /** - * Gets the post for a font family. + * Moves an uploaded font face asset from temp folder to the fonts directory. + * + * This is used when uploading local fonts. * * @since 6.5.0 * - * @return WP_Post|null The post for this font family object or - * null if the post does not exist. + * @param array $font_face Font face to download. + * @param array $file Uploaded file. + * @return array New font face with all assets downloaded and referenced in + * the font face definition. */ - public function get_font_post() { - $args = array( - 'post_type' => 'wp_font_family', - 'post_name' => $this->data['slug'], - 'name' => $this->data['slug'], - 'posts_per_page' => 1, - ); + private function move_font_face_asset( $font_face, $file ) { - $posts_query = new WP_Query( $args ); + $new_font_face = $font_face; + $filename = WP_Font_Family_Utils::get_filename_from_font_face( + $this->data['slug'], + $font_face, + $file['name'] + ); - if ( $posts_query->have_posts() ) { - return $posts_query->posts[0]; + // If the filename has no font mime type, don't move the file and + // return the font face definition without src to be ignored later. + if ( ! WP_Font_Family_Utils::has_font_mime_type( $filename ) ) { + return $new_font_face; } - return null; - } - - /** - * Gets the data for this object from the database and - * sets it to the data property. - * - * @since 6.5.0 - * - * @return WP_Post|null The post for this font family object or - * null if the post does not exist. - */ - private function get_data_from_post() { - $post = $this->get_font_post(); - if ( $post ) { - $this->data = json_decode( $post->post_content, true ); - return $post; + // Move the uploaded font asset from the temp folder to the fonts directory. + if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; } - return null; - } + $overrides = $this->get_upload_overrides( $filename ); - /** - * Creates a post for a font family. - * - * @since 6.5.0 - * - * @return int|WP_Error Post ID if the post was created, WP_Error otherwise. - */ - private function create_font_post() { - $post = array( - 'post_title' => $this->data['name'], - 'post_name' => $this->data['slug'], - 'post_type' => 'wp_font_family', - 'post_content' => $this->get_data_as_json(), - 'post_status' => 'publish', - ); + add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); + add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + $handled_file = wp_handle_upload( $file, $overrides ); + remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - $post_id = wp_insert_post( $post ); - if ( 0 === $post_id || is_wp_error( $post_id ) ) { - return new WP_Error( - 'font_post_creation_failed', - __( 'Font post creation failed', 'gutenberg' ) - ); + if ( array_key_exists( 'error', $handled_file ) ) { + return new WP_Error( $handled_file['error'] . ' ' . $filename ); } - return $post_id; + return $handled_file['url']; } /** - * Gets the font faces that are in both the existing and incoming font families. + * Gets the overrides for the 'wp_handle_upload' function. * * @since 6.5.0 * - * @param array $existing The existing font faces. - * @param array $incoming The incoming font faces. - * @return array The font faces that are in both the existing and incoming font families. + * @param string $filename The filename to be used for the uploaded file. + * @return array The overrides for the 'wp_handle_upload' function. */ - private function get_intersecting_font_faces( $existing, $incoming ) { - $intersecting = array(); - foreach ( $existing as $existing_face ) { - foreach ( $incoming as $incoming_face ) { - if ( $incoming_face['fontStyle'] === $existing_face['fontStyle'] && - $incoming_face['fontWeight'] === $existing_face['fontWeight'] && - $incoming_face['src'] !== $existing_face['src'] ) { - $intersecting[] = $existing_face; - } - } - } - return $intersecting; + private function get_upload_overrides( $filename ) { + return array( + // Arbitrary string to avoid the is_uploaded_file() check applied + // when using 'wp_handle_upload'. + 'action' => 'wp_handle_font_upload', + // Not testing a form submission. + 'test_form' => false, + // Seems mime type for files that are not images cannot be tested. + // See wp_check_filetype_and_ext(). + 'test_type' => true, + 'unique_filename_callback' => static function () use ( $filename ) { + // Keep the original filename. + return $filename; + }, + ); } /** - * Updates a post for a font family. + * Deletes all font face asset files associated with a given font face. * * @since 6.5.0 * - * @param WP_Post $post The post to update. - * @return int|WP_Error Post ID if the update was successful, WP_Error otherwise. + * @param array $font_face The font face array containing the 'src' attribute + * with the file path(s) to be deleted. + * @return bool True if delete was successful, otherwise false. */ - private function update_font_post( $post ) { - $post_font_data = json_decode( $post->post_content, true ); - $new_data = WP_Font_Family_Utils::merge_fonts_data( $post_font_data, $this->data ); - if ( isset( $post_font_data['fontFace'] ) && ! empty( $post_font_data['fontFace'] ) ) { - $intersecting = $this->get_intersecting_font_faces( $post_font_data['fontFace'], $new_data['fontFace'] ); - } - - if ( isset( $intersecting ) && ! empty( $intersecting ) ) { - $serialized_font_faces = array_map( 'serialize', $new_data['fontFace'] ); - $serialized_intersecting = array_map( 'serialize', $intersecting ); - - $diff = array_diff( $serialized_font_faces, $serialized_intersecting ); - - $new_data['fontFace'] = array_values( array_map( 'unserialize', $diff ) ); - - foreach ( $intersecting as $intersect ) { - $this->delete_font_face_assets( $intersect ); + private function delete_font_face_assets( $font_face ) { + $sources = (array) $font_face['src']; + foreach ( $sources as $src ) { + //Skip if it's remotely hosted + if( ! str_contains( $src, WP_Font_Library::get_fonts_dir() ) ){ + continue; } - } - $this->data = $new_data; - - $post = array( - 'ID' => $post->ID, - 'post_content' => $this->get_data_as_json(), - ); - $post_id = wp_update_post( $post ); - - if ( 0 === $post_id || is_wp_error( $post_id ) ) { - return new WP_Error( - 'font_post_update_failed', - __( 'Font post update failed', 'gutenberg' ) - ); + $filename = basename( $src ); + $file_path = path_join( WP_Font_Library::get_fonts_dir(), $filename ); + wp_delete_file( $file_path ); } - return $post_id; } /** - * Creates a post for a font in the Font Library if it doesn't exist, - * or updates it if it does. + * Removes a font family from the database and deletes its assets. * * @since 6.5.0 * - * @return int|WP_Error Post id if the post was created or updated successfully, - * WP_Error otherwise. + * @param bool $force Whether to force the deletion and bypass trass. + * + * @return bool|WP_Error True if the font family was uninstalled, WP_Error otherwise. */ - private function create_or_update_font_post() { - $this->sanitize(); + public function uninstall( $force ) { - $post = $this->get_font_post(); - if ( $post ) { - return $this->update_font_post( $post ); + // Delete any font face assets. + if ( array_key_exists( 'fontFace', $this->data ) && ! empty( $this->data['fontFace'] ) ) { + foreach ( $this->data['fontFace'] as $font_face ) { + $this->delete_font_face_assets( $font_face ); + } } - return $this->create_font_post(); - } - - /** - * Installs the font family into the library. - * - * @since 6.5.0 - * - * @param array $files Optional. An array of files to be installed. Default null. - * @return array|WP_Error An array of font family data on success, WP_Error otherwise. - */ - public function install( $files = null ) { - add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); - $were_assets_written = $this->download_or_move_font_faces( $files ); - remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); - remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); + $delete_post_response = wp_delete_post( $this->id, $force ); - if ( ! $were_assets_written ) { + if ( ! $delete_post_response ) { return new WP_Error( - 'font_face_download_failed', - __( 'The font face assets could not be written.', 'gutenberg' ) + 'font_family_not_deleted', + __( 'The font family could not be deleted.', 'gutenberg' ) ); } - $post_id = $this->create_or_update_font_post(); - - if ( is_wp_error( $post_id ) ) { - return $post_id; - } - - return $this->get_data(); + return true; } + } 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..2a8c3fdc086de2 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -64,11 +64,11 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio public static function register_font_collection( $config ) { $new_collection = new WP_Font_Collection( $config ); - if ( isset( self::$collections[ $config['id'] ] ) ) { + if ( isset( self::$collections[ $config['slug'] ] ) ) { return new WP_Error( 'font_collection_registration_error', 'Font collection already registered.' ); } - self::$collections[ $config['id'] ] = $new_collection; + self::$collections[ $config['slug'] ] = $new_collection; return $new_collection; } @@ -89,13 +89,13 @@ public static function get_font_collections() { * @since 6.5.0 * * @param string $id Font collection id. - * @return array List of font collections. + * @return array WP_Font_Collection|null Font Collection Object or null if not found. */ - public static function get_font_collection( $id ) { - if ( array_key_exists( $id, self::$collections ) ) { - return self::$collections[ $id ]; + public static function get_font_collection( $slug ) { + if ( array_key_exists( $slug, self::$collections ) ) { + return self::$collections[ $slug ]; } - return new WP_Error( 'font_collection_not_found', 'Font collection not found.' ); + return null; } /** diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-collection-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-collection-controller.php new file mode 100644 index 00000000000000..9404da6ae80c19 --- /dev/null +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-collection-controller.php @@ -0,0 +1,126 @@ +rest_base = 'font-collections'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.5.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + //TODO: Is this permission check needed? Is it correct? + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + //TODO: Is this permission check needed? Is it correct? + '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_item( $request ) { + $slug = $request->get_param( 'slug' ); + $font_collection = WP_Font_Library::get_font_collection( $slug ); + + if ( is_null($font_collection) ) { + return new WP_Error( 'rest_font_collection_not_found', 'Font collection not found.', array( 'status' => 404 ) ); + } + + $collection_with_data = $font_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_items( $request ) { + $collections = array(); + foreach ( WP_Font_Library::get_font_collections() as $collection ) { + $collections[] = $collection->get_config(); + } + return new WP_REST_Response( $collections, 200 ); + } + + /** + * 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; + } +} diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-family-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-family-controller.php new file mode 100644 index 00000000000000..269d70b5d50cf7 --- /dev/null +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-family-controller.php @@ -0,0 +1,558 @@ +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 . '/(?P[\d]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), + ), + ), + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array($this, 'create_item'), + 'permission_callback' => array($this, 'update_font_library_permissions_check'), + 'args' => array( + 'data' => array( + 'required' => true, + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'required' => true, + 'type' => 'string', + ), + 'slug' => array( + 'required' => true, + 'type' => 'string', + ), + 'fontFamily' => array( + 'required' => true, + 'type' => 'string', + ), + 'fontFace' => array( + 'type' => 'array', + ) + ), + + ) + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + array( + 'methods' => 'PUT', + 'callback' => array($this, 'update_item'), + 'permission_callback' => array($this, 'update_font_library_permissions_check'), + 'args' => array( + 'data' => array( + 'required' => true, + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'required' => true, + 'type' => 'string', + ), + 'slug' => array( + 'required' => true, + 'type' => 'string', + ), + 'fontFamily' => array( + 'required' => true, + 'type' => 'string', + ), + 'fontFace' => array( + 'type' => 'array', + ), + ), + + ) + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + array( + 'methods' => 'PATCH', + 'callback' => array($this, 'patch_item'), + 'permission_callback' => array($this, 'update_font_library_permissions_check'), + 'args' => array( + 'data' => array( + 'required' => true, + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'type' => 'string', + ), + 'slug' => array( + 'type' => 'string', + ), + 'fontFamily' => array( + 'type' => 'string', + ), + 'fontFace' => array( + 'type' => 'array', + ), + ), + + ) + ), + ), + ) + ); + 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' ), + ), + ), + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), + ), + ), + ); + } + + /** + * 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; + } + + /** + * Returns a collection of all Font Families. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request The request object + * @return WP_REST_Response|WP_Error A response with the collection of Font Families. + */ + public function get_items( $request ) { + $font_families = WP_Font_Family::get_font_families(); + $font_family_data = array(); + foreach ( $font_families as $font_family) { + $font_family_data[] = array ( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ); + } + return new WP_REST_Response( $font_family_data ); + } + + /** + * Returns a Font Family item. + * + * @since 6.5.0 + * + * @param WP_REST_Request $request The request object containing the id. + * @return WP_REST_Response|WP_Error A response with the Font Family requested. Otherwise a WP_Error. + */ + public function get_item( $request ) { + $id = $request->get_param( 'id' ); + + $font_family = WP_Font_Family::get_font_family_by_id( $id ); + + if($font_family) { + return new WP_REST_Response( array( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ) ); + } + + return new WP_Error( + 'rest_font_family_not_found', + __( 'Font Family not found.', 'gutenberg' ), + array( 'status' => 404 ) + ); + } + + /** + * 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 = $request->get_param( 'data' ); + $files = $request->get_file_params(); + + // In addition to creating a new object this services has the potential to update an existing item. + // https://www.rfc-editor.org/rfc/rfc7231#section-4.3.3 + // In that scenario the Font Family with a matching SLUG will be looked for and PATCHED with the included information. + // This allows the service to include ONLY THE FONT FACES that are to be ADDED, doing so won't remove existing Font Faces. + $font_family = WP_Font_Family::get_font_family_by_slug( $font_family_data['slug'] ); + + + if ( ! $font_family ) { + // A new Font Family is to be created. + try { + $font_family = new WP_Font_Family(array( + 'slug' => $font_family_data['slug'], + 'name' => $font_family_data['name'], + 'fontFamily' => $font_family_data['fontFamily'], + )); + } catch (Exception $exception) { + return new WP_Error( + 'rest_font_family_not_created', + __('Font Family not created. ' . $exception, 'gutenberg'), + array('status' => 500) + ); + } + } + + $update_response = $font_family->update( $font_family_data, $files, true ); + if ( is_wp_error( $update_response ) ) { + return $update_response; + } + + $font_family->persist(); + + return new WP_REST_Response( array( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ) ); + } + + /** + * Deletes an existing font family. + * + * Takes a font family id and deletes it from 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 delete_item( $request ) { + $id = $request->get_param( 'id' ); + $force = $request->get_param( 'force' ) || true; + + $font_family = WP_Font_Family::get_font_family_by_id( $id ); + + if( ! $font_family) { + return new WP_Error( + 'rest_font_family_not_found', + __( 'Font Family not found.', 'gutenberg' ), + array( 'status' => 404 ) + ); + } + + $uninstall_response = $font_family->uninstall( $force ); + + if ( is_wp_error( $uninstall_response ) ) { + return $uninstall_response; + } + + return new WP_REST_Response( array( + 'deleted' => $force, + 'previous' => array( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ) + ) ); + } + + /** + * Updates an existing Font Family + * + * @since 6.5.0 + * + * @param WP_REST_Request $request The request object containing the information of the font family to update + * in the request parameters and ID of the font family to update. + * + * @return WP_REST_Response|WP_Error The updated Font Library post content. + + */ + public function update_item( $request ) { + + $id = $request->get_param( 'id' ); + $files = $request->get_file_params(); + + $font_family_data = $request->get_param( 'data' ); + $font_family = WP_Font_Family::get_font_family_by_id( $id ); + + if( ! $font_family) { + return new WP_Error( + 'rest_font_family_not_found', + __( 'Font Family not found.', 'gutenberg' ), + array( 'status' => 404 ) + ); + } + + $update_response = $font_family->update( $font_family_data, $files ); + if ( is_wp_error( $update_response ) ) { + return $update_response; + } + + $font_family->persist(); + + return new WP_REST_Response( array( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ) ); + } + + /** + * Patches an existing Font Family + * + * @since 6.5.0 + * + * @param WP_REST_Request $request The request object containing the information of the font family to update + * in the request parameters and ID of the font family to update. + * + * @return WP_REST_Response|WP_Error The updated Font Library post content. + + */ + public function patch_item( $request ) { + + $id = $request->get_param( 'id' ); + $files = $request->get_file_params(); + + $font_family_data = $request->get_param( 'data' ); + $font_family = WP_Font_Family::get_font_family_by_id( $id ); + + if( ! $font_family) { + return new WP_Error( + 'rest_font_family_not_found', + __( 'Font Family not found.', 'gutenberg' ), + array( 'status' => 404 ) + ); + } + + $update_response = $font_family->update( $font_family_data, $files, true ); + if ( is_wp_error( $update_response ) ) { + return $update_response; + } + + $font_family->persist(); + + return new WP_REST_Response( array( + 'id' => $font_family->id, + 'data' => $font_family->get_data(), + ) ); + } + + + + + + + + + + + + + + + + + + + + + + /** + * 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 ) ); + } + +} 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 9655178d706679..00000000000000 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ /dev/null @@ -1,486 +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 font directory exists or not. - * - * @since 6.5.0 - * - * @return bool Whether the font directory exists. - */ - private function has_upload_directory() { - $upload_dir = WP_Font_Library::get_fonts_dir(); - return is_dir( $upload_dir ); - } - - /** - * 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 ) ) { - $upload_dir = WP_Font_Library::get_fonts_dir(); - if ( ! $this->has_upload_directory() ) { - if ( ! wp_mkdir_p( $upload_dir ) ) { - $errors[] = new WP_Error( - 'cannot_create_fonts_folder', - sprintf( - /* translators: %s: Directory path. */ - __( 'Error: Unable to create directory %s.', 'gutenberg' ), - esc_html( $upload_dir ) - ) - ); - $response_status = 500; - } - } - - if ( $this->has_upload_directory() && ! $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..fa54cbdc42ce1d 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -30,7 +30,9 @@ 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_collection_controller = new WP_REST_Font_Collection_Controller(); + $font_collection_controller->register_routes(); + $font_library_controller = new WP_REST_Font_Family_Controller(); $font_library_controller->register_routes(); } @@ -57,12 +59,15 @@ function wp_register_font_collection( $config ) { } } +// This would be a great plugin for wordpress.org to offer... +// Having this installed as part of the library (instead of a plugin) is making testing tricky. +// Taking this out for now -$default_font_collection = array( - 'id' => 'default-font-collection', - 'name' => 'Google Fonts', - 'description' => __( 'Add from Google Fonts. Fonts are copied to and served from your site.', 'gutenberg' ), - 'src' => 'https://s.w.org/images/fonts/16.7/collections/google-fonts-with-preview.json', -); +// $default_font_collection = array( +// 'slug' => 'default-font-collection', +// 'name' => 'Google Fonts', +// 'description' => __( 'Add from Google Fonts. Fonts are copied to and served from your site.', 'gutenberg' ), +// 'src' => 'https://s.w.org/images/fonts/16.7/collections/google-fonts-with-preview.json', +// ); -wp_register_font_collection( $default_font_collection ); +// wp_register_font_collection( $default_font_collection ); diff --git a/lib/load.php b/lib/load.php index 59fb75541ac41e..9f0534df844b6d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -149,7 +149,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-collection-controller.php'; + require __DIR__ . '/experimental/fonts/font-library/class-wp-rest-font-family-controller.php'; require __DIR__ . '/experimental/fonts/font-library/font-library.php'; } diff --git a/phpunit-watcher.yml.dist b/phpunit-watcher.yml.dist index f2f9da5fbcdbfd..ddd81e23a16419 100644 --- a/phpunit-watcher.yml.dist +++ b/phpunit-watcher.yml.dist @@ -6,3 +6,6 @@ watch: notifications: passingTests: false failingTests: false + +phpunit: + arguments: '--group font-library-refactor' diff --git a/phpunit/tests/fonts/font-library/wpFontCollectionController/wpRestFontCollectionController.php b/phpunit/tests/fonts/font-library/wpFontCollectionController/wpRestFontCollectionController.php new file mode 100644 index 00000000000000..70255b82589221 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontCollectionController/wpRestFontCollectionController.php @@ -0,0 +1,242 @@ +factory->user->create( + array( + 'role' => 'administrator', + ) + ); + wp_set_current_user( $admin_id ); + } + + public function tear_down() { + + // 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() ); + + // Remove the mock to not affect other tests. + remove_filter( 'pre_http_request', array( $this, 'mock_request' ) ); + parent::tear_down(); + } + + 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 ) { + return array( + 'body' => $this->get_test_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 test_get_font_collections_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() ); + } + + public function test_get_font_collections_with_a_single_simple_collection() { + + // Add a font collection with included data. + $config = array( + 'slug' => 'my-font-collection', + 'name' => 'My Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'data' => array('this is mock data'=>true), + ); + + 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( 'slug', $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.' ); + } + + public function test_get_font_collection_with_included_data() { + + $collection_data = json_decode( $this->get_test_collection_data(), true ); + + // Add a font collection with included data. + $config = array( + 'slug' => 'included-data-font-collection', + 'name' => 'Included Data Font Collection', + 'description' => 'Demo about how to a font collection to your WordPress Font Library.', + 'data' => $collection_data, + ); + + wp_register_font_collection( $config ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/included-data-font-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( 'ABeeZee', $data['data']['fontFamilies'][0]['name'], 'The response data does not have the expected data.' ); + $this->assertSame( 'ABeeZee', $data['data']['fontFamilies'][0]['fontFace'][0]['fontFamily'], 'The response data does not have the expected data.' ); + } + + + public function test_get_font_collection_with_data_from_file() { + + $mock_file = wp_tempnam( 'one-collection-' ); + file_put_contents( $mock_file, $this->get_test_collection_data() ); + + $config = array( + 'slug' => 'file-data-font-collection', + 'name' => 'File Data 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/file-data-font-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( 'ABeeZee', $data['data']['fontFamilies'][0]['name'], 'The response data does not have the expected data.' ); + $this->assertSame( 'ABeeZee', $data['data']['fontFamilies'][0]['fontFace'][0]['fontFamily'], 'The response data does not have the expected data.' ); + } + + public function test_get_font_collection_with_data_from_missing_file() { + + $config = array( + 'slug' => 'missing-file-data-font-collection', + 'name' => 'File Data 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 ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/missing-file-data-font-collection' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 500, $response->get_status(), 'The response status is not 500.' ); + } + + public function test_get_font_collection_with_data_from_missing_url() { + + $config = array( + 'slug' => 'missing-url-data-font-collection', + 'name' => 'File Data 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 ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/missing-url-data-font-collection' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 500, $response->get_status(), 'The response status is not 500.' ); + } + + public function test_get_font_collection_with_data_from_url() { + + $config = array( + 'slug' => 'url-data-font-collection', + 'name' => 'File Data 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 ); + + // Mock the wp_remote_request() function. + // add_filter( 'pre_http_request', array( $this, 'mock_request' ), 10, 3 ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/url-data-font-collection' ); + + // Remove the mock to not affect other tests. + // remove_filter( 'pre_http_request', array( $this, 'mock_request' ) ); + + + $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( 'ABeeZee', $data['data']['fontFamilies'][0]['name'], 'The response data does not have the expected data.' ); + $this->assertSame( 'ABeeZee', $data['data']['fontFamilies'][0]['fontFace'][0]['fontFamily'], 'The response data does not have the expected data.' ); + } + + private function get_test_collection_data() { + return '{ + "fontFamilies": [ + { + "name": "ABeeZee", + "fontFamily": "ABeeZee, sans-serif", + "slug": "abeezee", + "category": "sans-serif", + "fontFace": [ + { + "downloadFromUrl": "https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf", + "fontWeight": "400", + "fontStyle": "normal", + "fontFamily": "ABeeZee", + "preview": "https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg" + }, + { + "downloadFromUrl": "https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf", + "fontWeight": "400", + "fontStyle": "italic", + "fontFamily": "ABeeZee", + "preview": "https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg" + } + ], + "preview": "https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee.svg" + }, + { + "name": "ADLaM Display", + "fontFamily": "ADLaM Display, system-ui", + "slug": "adlam-display", + "category": "display", + "fontFace": [ + { + "downloadFromUrl": "https://fonts.gstatic.com/s/adlamdisplay/v1/KFOhCnGXkPOLlhx6jD8_b1ZECsHYkYBPY3o.ttf", + "fontWeight": "400", + "fontStyle": "normal", + "fontFamily": "ADLaM Display", + "preview": "https://s.w.org/images/fonts/16.7/previews/adlam-display/adlam-display-400-normal.svg" + } + ], + "preview": "https://s.w.org/images/fonts/16.7/previews/adlam-display/adlam-display.svg" + } + ] + }'; + } + + +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php index a6b02f38a5e817..2f955f4a0dd576 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/base.php @@ -7,19 +7,10 @@ */ abstract class WP_REST_Font_Library_Controller_UnitTestCase extends WP_UnitTestCase { - /** - * Fonts directory. - * - * @var string - */ - protected static $fonts_dir; - public function set_up() { parent::set_up(); - static::$fonts_dir = WP_Font_Library::get_fonts_dir(); - // Create a user with administrator role. $admin_id = $this->factory->user->create( array( @@ -35,14 +26,8 @@ public function set_up() { public function tear_down() { parent::tear_down(); - // 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 ) { + foreach ( $this->files_in_dir( WP_Font_Library::get_fonts_dir() ) as $file ) { @unlink( $file ); } } diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/create_item.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/create_item.php new file mode 100644 index 00000000000000..60a134e69447d3 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/create_item.php @@ -0,0 +1,360 @@ +set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + $response = rest_get_server()->dispatch( $install_request ); + $this->assertSame( $expected_response['data']['status'], $response->get_status() ); + $this->assertSame( $expected_response['code'], $response->get_data()['code'], 'Error Response Unexpected' ); + $this->assertSame( $expected_response['message'], $response->get_data()['message'], 'Error Response Unexpected' ); + } + + /** + * Data provider for test_install_with_improper_inputs + */ + public function data_create_font_family_with_improper_inputs() { + + $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); + file_put_contents( $temp_file_path1, 'Mocking file content' ); + + return array( + 'no valid parameters' => array( + 'request' => array( + 'apples' => 'bananas', + ), + 'expected_response' => array( + 'code' => 'rest_missing_callback_param', + 'message' => __( 'Missing parameter(s): data', 'wp-font-library' ), + 'data' => array( + 'status' => 400, + ), + ), + ), + 'missing slug' => array( + 'request' => array( + 'data' => array( + 'name' => 'Name', + 'fontFamily' => 'Font Family', + ) + ), + 'expected_response' => array( + 'code' => 'rest_invalid_param', + 'message' => __( 'Invalid parameter(s): data', 'wp-font-library' ), + 'data' => array( + 'status' => 400, + ), + ), + ), + 'missing name' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'fontFamily' => 'Font Family', + ), + ), + 'expected_response' => array( + 'code' => 'rest_invalid_param', + 'message' => __( 'Invalid parameter(s): data', 'wp-font-library' ), + 'data' => array( + 'status' => 400, + ), + ), + ), + 'missing fontFamily' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + ), + ), + 'expected_response' => array( + 'code' => 'rest_invalid_param', + 'message' => __( 'Invalid parameter(s): data', 'wp-font-library' ), + 'data' => array( + 'status' => 400, + ), + ), + ), + + 'fontface referencing uploaded file without uploaded files' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Font Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Font Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'files0', + ), + ), + ), + ), + 'expected_response' => array( + 'code' => 'font_face_upload_file_missing', + 'message' => __( 'The font face assets was not provided.', 'wp-font-library' ), + 'data' => array( + 'status' => 500, + ), + ), + ), + + 'fontface referencing uploaded file without correct uploaded file' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Font Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Font Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'filesNotCorrectReference', + ), + ), + ), + ), + 'expected_response' => array( + 'code' => 'font_face_upload_file_missing', + 'message' => __( 'The font face assets was not provided.', 'wp-font-library' ), + 'data' => array( + 'status' => 500, + ), + ), + 'files' => array( + 'files0' => array( + 'name' => 'piazzola1.ttf', + 'type' => 'font/ttf', + 'tmp_name' => $temp_file_path1, + 'error' => 0, + 'size' => 123, + ), + ), + ), + ); + + } + + /** + * Tests responses when sucessfully creating Font Families + * + * @dataProvider data_create_font_family + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_create_font_family_success( $request, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + $response = rest_get_server()->dispatch( $install_request ); + $response_data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + } + + /** + * Data provider for test_create_font_family_success + */ + public function data_create_font_family() { + + $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( + 'font family with no font faces' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + ), + + 'font family with a font face that needs to be wrapped in quotes' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Font Family, serif', + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => "'Font Family', serif", + ) + ), + ), + + 'Font Family with Font Faces with hosted resources' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ), + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ), + ), + ), + + 'Font Family with Font Faces with uploaded resources' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'files0', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'files1', + ), + ), + ), + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://localhost:8889/wp-content/fonts/slug_normal_400.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://localhost:8889/wp-content/fonts/slug_italic_400.ttf', + ), + ), + ), + ), + '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, + ), + ), + ), + + ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/delete_item.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/delete_item.php new file mode 100644 index 00000000000000..4ca6c4b8781ca7 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/delete_item.php @@ -0,0 +1,74 @@ +dispatch( $request ); + + $this->assertSame( 404, $response->get_status() ); + } + + /** + * Tests responses when sucessfully deleting Font Families + * + * @dataProvider data_get_font_family + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_get_font_family_success( $request, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + //TODO: adding . '?force=true' to the end doesn't work like it is supposed to. + $delete_request = new WP_REST_Request( 'DELETE', '/wp/v2/font-families/' . $installed_font_id); + $delete_response = rest_get_server()->dispatch( $delete_request ); + $delete_data = $delete_response->get_data(); + + $this->assertSame( 200, $delete_response->get_status(), 'The response status is not 200.' ); + $this->assertSame( $expected_response['data']['slug'], $delete_data['previous']['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $delete_data['previous']['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $delete_data['previous']['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertSame( $installed_font_id, $delete_data['previous']['id'], 'The id response did not match expected.' ); + $this->assertTrue( $delete_data['deleted'], 'The response did not flag deleted status as expected.' ); + + $verify_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $installed_font_id ); + $verify_response = rest_get_server()->dispatch( $verify_request ); + + $this->assertSame( 404, $verify_response->get_status(), 'The deleted resource can still be found.' ); + } + + public function data_get_font_family() { + // Use all the same successful tests for creating a font family + $create_item_tests = new Tests_Fonts_Font_Family_Controller_create_item(); + return $create_item_tests->data_create_font_family(); + } +} 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/get_item.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/get_item.php new file mode 100644 index 00000000000000..5e3d8f8d6f7510 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/get_item.php @@ -0,0 +1,71 @@ +dispatch( $request ); + + $this->assertSame( 404, $response->get_status() ); + } + + /** + * Tests responses when sucessfully creating Font Families + * + * @dataProvider data_get_font_family + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_get_font_family_success( $request, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + // Get the font family. + $verify_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $installed_font_id ); + $verify_response = rest_get_server()->dispatch( $verify_request ); + $response_data = $verify_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + } + + public function data_get_font_family() { + // Use all the same successful tests for creating a font family + $create_item_tests = new Tests_Fonts_Font_Family_Controller_create_item(); + return $create_item_tests->data_create_font_family(); + } +} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/get_items.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/get_items.php new file mode 100644 index 00000000000000..b9e07c868230e9 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/get_items.php @@ -0,0 +1,57 @@ + array( + 'slug' => 'slugone', + 'name' => 'Name One', + 'fontFamily' => 'FontFamily1', + ), + ), + array( + 'data' => array( + 'slug' => 'slugtwo', + 'name' => 'Name Two', + 'fontFamily' => 'FontFamily2', + ), + ), + ); + + foreach ($sample_fonts as $font_family) { + $install_request = new WP_REST_Request('POST', '/wp/v2/font-families'); + $install_request->set_param('data', $font_family['data']); + rest_get_server()->dispatch($install_request); + } + + $verify_request = new WP_REST_Request('GET', '/wp/v2/font-families'); + $verify_response = rest_get_server()->dispatch($verify_request); + $verify_data = $verify_response->get_data(); + + $this->assertSame(200, $verify_response->get_status(), 'The response status is not 200.'); + + for ( $i = 0; $i < count( $sample_fonts ); $i++ ) { + $this->assertSame( $sample_fonts[ $i ]['data']['slug'], $verify_data[ $i ]['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $sample_fonts[ $i ]['data']['name'], $verify_data[ $i ]['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $sample_fonts[ $i ]['data']['fontFamily'], $verify_data[ $i ]['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $verify_data[ $i ]['id'], 'The id response did not match expected.' ); + } + } +} 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/patch_item.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/patch_item.php new file mode 100644 index 00000000000000..3f01c3a87f74b2 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/patch_item.php @@ -0,0 +1,409 @@ +set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + // Patch the font family. + $update_request = new WP_REST_Request( 'PATCH', '/wp/v2/font-families/' . $installed_font_id ); + if( array_key_exists( 'data', $update ) ){ + $update_request->set_param( 'data', $update['data'] ); + } + if( $files ){ + $update_request->set_file_params( $files ); + } + + $update_response = rest_get_server()->dispatch( $update_request ); + $response_data = $update_response->get_data(); + + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + + // Get the font family. + $verify_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $installed_font_id ); + $verify_response = rest_get_server()->dispatch( $verify_request ); + $response_data = $verify_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + } + + /** + * Tests responses when sucessfully patching Font Families VIA POST CALL + * + * @dataProvider data_patch_font_family_via_post + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_patch_font_family_success_via_post( $request, $update, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + // Patch the font family (via another call to post). + $update_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $update ) ){ + $update_request->set_param( 'data', $update['data'] ); + } + if( $files ){ + $update_request->set_file_params( $files ); + } + + $update_response = rest_get_server()->dispatch( $update_request ); + $this->assertSame( 200, $update_response->get_status(), 'The response status is not 200.' ); + + $response_data = $update_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + + // Get the font family. + $verify_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $installed_font_id ); + $verify_response = rest_get_server()->dispatch( $verify_request ); + $response_data = $verify_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + } + + public function data_patch_font_family() { + return array( + 'font family change name' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'name' => 'New Name', + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + ) + ), + ), + 'Add Font Faces to Font Family' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + ), + 'Add a Font Face to a Font Family without removing existing' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'update' => array( + 'data' => array( + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + ), + ); + } + + public function data_patch_font_family_via_post() { + return array( + 'font family change name' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'fontFamily' => 'Family', + 'name' => 'New Name', + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + ) + ), + ), + 'Add Font Faces to Font Family' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + ), + 'Add a Font Face to a Font Family without removing existing' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + ), + ); + } +} 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.' ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/update_item.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/update_item.php new file mode 100644 index 00000000000000..0285eb524606c0 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/update_item.php @@ -0,0 +1,293 @@ +dispatch( $request ); + + $this->assertSame( 404, $response->get_status() ); + } + + /** + * Tests responses when sucessfully creating Font Families + * + * @dataProvider data_update_font_family_failure + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_update_font_family_failure( $request, $update, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + // Update the font family. + $update_request = new WP_REST_Request( 'PUT', '/wp/v2/font-families/' . $installed_font_id ); + if( array_key_exists( 'data', $update ) ){ + $update_request->set_param( 'data', $update['data'] ); + } + if( $files ){ + $update_request->set_file_params( $files ); + } + + $response = rest_get_server()->dispatch( $update_request ); + + $this->assertSame( $expected_response['data']['status'], $response->get_status() ); + $this->assertSame( $expected_response['code'], $response->get_data()['code'], 'Error Response Unexpected' ); + $this->assertSame( $expected_response['message'], $response->get_data()['message'], 'Error Response Unexpected' ); + + } + + public function data_update_font_family_failure() { + return array( + 'font family missing slug' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'name' => 'New Name', + 'fontFamily' => 'Family', + ) + ), + 'expected_response' => array( + 'code' => 'rest_invalid_param', + 'message' => __( 'Invalid parameter(s): data', 'wp-font-library' ), + 'data' => array( + 'status' => 400, + ), + ), + ), + ); + } + + /** + * Tests responses when sucessfully creating Font Families + * + * @dataProvider data_update_font_family + * + * @param array $font_family Font families to install in theme.json format. + * @param array $expected_response Expected response data. + */ + public function test_update_font_family_success( $request, $update, $expected_response, $files=null ) { + + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + if( array_key_exists( 'data', $request ) ){ + $install_request->set_param( 'data', $request['data'] ); + } + if( $files ){ + $install_request->set_file_params( $files ); + } + + // Create the font family. + $install_response = rest_get_server()->dispatch( $install_request ); + $install_data = $install_response->get_data(); + $installed_font_id = $install_data['id']; + + // Update the font family. + $update_request = new WP_REST_Request( 'PUT', '/wp/v2/font-families/' . $installed_font_id ); + if( array_key_exists( 'data', $update ) ){ + $update_request->set_param( 'data', $update['data'] ); + } + if( $files ){ + $update_request->set_file_params( $files ); + } + + $update_response = rest_get_server()->dispatch( $update_request ); + $response_data = $update_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + + // Get the font family. + $verify_request = new WP_REST_Request( 'GET', '/wp/v2/font-families/' . $installed_font_id ); + $verify_response = rest_get_server()->dispatch( $verify_request ); + $response_data = $verify_response->get_data(); + + $this->assertSame( $expected_response['data']['slug'], $response_data['data']['slug'], 'The slug response did not match expected.' ); + $this->assertSame( $expected_response['data']['name'], $response_data['data']['name'], 'The name response did not match expected.' ); + $this->assertSame( $expected_response['data']['fontFamily'], $response_data['data']['fontFamily'], 'The font_family response did not match expected.' ); + $this->assertIsInt( $response_data['id'], 'The id response did not match expected.' ); + + if( array_key_exists( 'data', $request ) && array_key_exists( 'fontFace', $request['data'] ) ){ + $this->assertSame( $expected_response['data']['fontFace'], $response_data['data']['fontFace'], 'The font_family response did not match expected.' ); + } + } + + public function data_update_font_family() { + return array( + 'font family change name' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + ) + ), + ), + 'Add Font Face to Font Family' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + ), + 'Remove a Font Face from Font Family' => array( + 'request' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-italic.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDT31xSG-6AGleN2tCklZUCGpG-GQ.ttf', + ), + ), + ) + ), + 'update' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + 'expected_response' => array( + 'data' => array( + 'slug' => 'slug', + 'name' => 'New Name', + 'fontFamily' => 'Family', + 'fontFace' => array( + array( + 'fontFamily' => 'Family', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'preview' => 'https://s.w.org/images/fonts/16.7/previews/abeezee/abeezee-400-normal.svg', + 'src' => 'https://fonts.gstatic.com/s/abeezee/v22/esDR31xSG-6AGleN6tKukbcHCpE.ttf', + ), + ), + ) + ), + ), + ); + } +}