diff --git a/lib/experimental/fonts-library/class-wp-font-family-utils.php b/lib/experimental/fonts-library/class-wp-font-family-utils.php new file mode 100644 index 00000000000000..ef239936b8e5a3 --- /dev/null +++ b/lib/experimental/fonts-library/class-wp-font-family-utils.php @@ -0,0 +1,92 @@ +data = $font_data; + } + + /** + * Gets the font family data. + * + * @since 6.4.0 + * + * @return array An array in fontFamily theme.json format. + */ + public function get_data() { + return $this->data; + } + + /** + * Gets the font family data. + * + * @since 6.4.0 + * + * @return string fontFamily in theme.json format as stringified JSON. + */ + public function get_data_as_json() { + return wp_json_encode( $this->get_data() ); + } + + /** + * Checks whether the font family has font faces defined. + * + * @since 6.4.0 + * + * @return bool True if the font family has font faces defined, false otherwise. + */ + public function has_font_faces() { + return ! empty( $this->data['fontFace'] ) && is_array( $this->data['fontFace'] ); + } + + /** + * Removes font family assets. + * + * @since 6.4.0 + * + * @return bool True if assets were removed, false otherwise. + */ + 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; + } + + /** + * Removes a font family from the database and deletes its assets. + * + * @since 6.4.0 + * + * @return bool|WP_Error True if the font family was uninstalled, WP_Error otherwise. + */ + public function uninstall() { + $post = $this->get_data_from_post(); + if ( null === $post ) { + return new WP_Error( + 'font_family_not_found', + __( 'The font family could not be found.', 'gutenberg' ) + ); + } + + if ( + ! $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' ) + ); + } + + return true; + } + + /** + * Deletes a specified font asset file from the fonts directory. + * + * @since 6.4.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_Fonts_Library::get_fonts_dir(), $filename ); + + wp_delete_file( $file_path ); + + return ! file_exists( $file_path ); + } + + /** + * Deletes all font face asset files associated with a given font face. + * + * @since 6.4.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. + */ + 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; + } + } + return true; + } + + + /** + * Gets the overrides for the 'wp_handle_upload' function. + * + * @since 6.4.0 + * + * @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_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' => false, + 'unique_filename_callback' => static function() use ( $filename ) { + // Keep the original filename. + return $filename; + }, + ); + } + + /** + * Downloads a font asset from a specified source URL and saves it to + * the font directory. + * + * @since 6.4.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. + */ + 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, + ); + + $handled_file = wp_handle_upload( $file, $overrides ); + + // Cleans the temp file. + @unlink( $temp_file ); + + if ( ! isset( $handled_file['url'] ) ) { + return false; + } + + // Returns the relative path to the downloaded font asset to be used as + // font face src. + return $handled_file['url']; + } + + /** + * Moves an uploaded font face asset from temp folder to the fonts directory. + * + * This is used when uploading local fonts. + * + * @since 6.4.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. + */ + 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'] + ); + + // Remove the uploaded font asset reference from the font face definition + // because it is no longer needed. + unset( $new_font_face['uploaded_file'] ); + + // 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 ); + + $handled_file = wp_handle_upload( $file, $overrides ); + + 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']; + } + + return $new_font_face; + } + + /** + * Sanitizes the font family data using WP_Theme_JSON. + * + * @since 6.4.0 + * + * @return array A sanitized font family definition. + */ + private function sanitize() { + // Creates the structure of theme.json array with the new fonts. + $fonts_json = array( + 'version' => '2', + 'settings' => array( + 'typography' => array( + 'fontFamilies' => array( $this->data ), + ), + ), + ); + // Creates a new WP_Theme_JSON object with the new fonts to + // leverage sanitization and validation. + $theme_json = new WP_Theme_JSON_Gutenberg( $fonts_json ); + $theme_data = $theme_json->get_data(); + $sanitized_font = ! empty( $theme_data['settings']['typography']['fontFamilies'] ) + ? $theme_data['settings']['typography']['fontFamilies'][0] + : array(); + $this->data = $sanitized_font; + return $this->data; + } + + /** + * Downloads font face assets. + * + * Downloads the font face asset(s) associated with a font face. It works with + * both single source URLs and arrays of multiple source URLs. + * + * @since 6.4.0 + * + * @param array $font_face The font face array containing the 'src' attribute + * with the source URL(s) of the assets. + * @return array The modified font face array with the new source URL(s) to + * the downloaded assets. + */ + private function download_font_face_assets( $font_face ) { + $new_font_face = $font_face; + $sources = (array) $font_face['download_from_url']; + $new_font_face['src'] = array(); + $index = 0; + + foreach ( $sources as $src ) { + $suffix = $index++ > 0 ? $index : ''; + $filename = WP_Font_Family_Utils::get_filename_from_font_face( + $this->data['slug'], + $font_face, + $src, + $suffix + ); + $new_src = $this->download_asset( $src, $filename ); + if ( $new_src ) { + $new_font_face['src'][] = $new_src; + } + } + + if ( count( $new_font_face['src'] ) === 1 ) { + $new_font_face['src'] = $new_font_face['src'][0]; + } + + // Remove the download url reference from the font face definition + // because it is no longer needed. + unset( $new_font_face['download_from_url'] ); + + return $new_font_face; + } + + + /** + * Downloads font face assets if the font family is a Google font, + * or moves them if it is a local font. + * + * @since 6.4.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. + */ + private function download_or_move_font_faces( $files ) { + if ( ! $this->has_font_faces() ) { + return true; + } + + $new_font_faces = array(); + foreach ( $this->data['fontFace'] as $font_face ) { + // If the fonts are not meant to be dowloaded or uploaded + // (for example to install fonts that use a remote url). + $new_font_face = $font_face; + + // If installing google fonts, download the font face assets. + if ( ! empty( $font_face['download_from_url'] ) ) { + $new_font_face = $this->download_font_face_assets( $new_font_face ); + } + + // If installing local fonts, move the font face assets from + // the temp folder to the wp fonts directory. + if ( ! empty( $font_face['uploaded_file'] ) && ! empty( $files ) ) { + $new_font_face = $this->move_font_face_asset( + $new_font_face, + $files[ $new_font_face['uploaded_file'] ] + ); + } + + /* + * 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 ( ! empty( $new_font_faces ) ) { + $this->data['fontFace'] = $new_font_faces; + return true; + } + + return false; + } + + /** + * Gets the post for a font family. + * + * @since 6.4.0 + * + * @return WP_Post|null The post for this font family object or + * null if the post does not exist. + */ + 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, + ); + + $posts_query = new WP_Query( $args ); + + if ( $posts_query->have_posts() ) { + return $posts_query->posts[0]; + } + + return null; + } + + /** + * Gets the data for this object from the database and + * sets it to the data property. + * + * @since 6.4.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; + } + + return null; + } + + /** + * Creates a post for a font family. + * + * @since 6.4.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', + ); + + $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' ) + ); + } + + return $post_id; + } + + /** + * Updates a post for a font family. + * + * @since 6.4.0 + * + * @param WP_Post $post The post to update. + * @return int|WP_Error Post ID if the update was successful, WP_Error otherwise. + */ + 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 ); + $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' ) + ); + } + + return $post_id; + } + + /** + * Creates a post for a font in the fonts library if it doesn't exist, + * or updates it if it does. + * + * @since 6.4.0 + * + * @return int|WP_Error Post id if the post was created or updated successfully, + * WP_Error otherwise. + */ + private function create_or_update_font_post() { + $this->sanitize(); + + $post = $this->get_font_post(); + if ( $post ) { + return $this->update_font_post( $post ); + } + + return $this->create_font_post(); + } + + /** + * Installs the font family into the library. + * + * @since 6.4.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_dir', array( 'WP_Fonts_Library', 'set_upload_dir' ) ); + $were_assets_written = $this->download_or_move_font_faces( $files ); + remove_filter( 'upload_dir', array( 'WP_Fonts_Library', 'set_upload_dir' ) ); + + if ( ! $were_assets_written ) { + return new WP_Error( + 'font_face_download_failed', + __( 'The font face assets could not be written.', 'gutenberg' ) + ); + } + + $post_id = $this->create_or_update_font_post(); + + if ( is_wp_error( $post_id ) ) { + return $post_id; + } + + return $this->get_data(); + } +} diff --git a/lib/experimental/fonts-library/class-wp-fonts-library.php b/lib/experimental/fonts-library/class-wp-fonts-library.php new file mode 100644 index 00000000000000..f0482ae9a864af --- /dev/null +++ b/lib/experimental/fonts-library/class-wp-fonts-library.php @@ -0,0 +1,64 @@ + 'font/otf', + 'ttf' => 'font/ttf', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + ); + + /** + * Gets the upload directory for fonts. + * + * @since 6.4.0 + * + * @return string Path of the upload directory for fonts. + */ + public static function get_fonts_dir() { + return wp_upload_dir()['basedir'] . '/fonts'; + } + + /** + * Sets the upload directory for fonts. + * + * @since 6.4.0 + * + * @param array $defaults { + * Default upload directory. + * + * @type string $path Path to the directory. + * @type string $url URL for the directory. + * @type string $subdir Sub-directory of the directory. + * @type string $basedir Base directory. + * @type string $baseurl Base URL. + * } + * @return array Modified upload directory. + */ + public static function set_upload_dir( $defaults ) { + $defaults['subdir'] = '/fonts'; + $defaults['path'] = $defaults['basedir'] . $defaults['subdir']; + $defaults['url'] = $defaults['baseurl'] . $defaults['subdir']; + + return $defaults; + } +} diff --git a/lib/experimental/fonts-library/class-wp-rest-fonts-library-controller.php b/lib/experimental/fonts-library/class-wp-rest-fonts-library-controller.php new file mode 100644 index 00000000000000..c49560ea32c160 --- /dev/null +++ b/lib/experimental/fonts-library/class-wp-rest-fonts-library-controller.php @@ -0,0 +1,332 @@ +rest_base = 'fonts'; + $this->namespace = 'wp/v2'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.4.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'install_fonts' ), + 'permission_callback' => array( $this, 'update_fonts_library_permissions_check' ), + 'args' => array( + 'fontFamilies' => 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_fonts_library_permissions_check' ), + 'args' => $this->uninstall_schema(), + ), + ) + ); + } + + /** + * Returns validation errors in font families data for installation. + * + * @since 6.4.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[] = __( 'fontFamilies 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[] = __( 'fontFamilies 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 + ); + } + + 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['download_from_url'] ) && isset( $font_face['uplodaded_file'] ) ) { + $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 download_from_url or uploaded_file properties defined and not both.', 'gutenberg' ), + $family_index, + $face_index + ); + } + + if ( isset( $font_face['uploaded_file'] ) ) { + if ( ! isset( $files[ $font_face['uploaded_file'] ] ) ) { + $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.4.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.4.0 + * + * @return array Schema array. + */ + public function uninstall_schema() { + return array( + 'fontFamilies' => 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 fonts library and all their assets. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function uninstall_fonts( $request ) { + $fonts_param = $request->get_param( 'fontFamilies' ); + + foreach ( $fonts_param as $font_data ) { + $font = new WP_Font_Family( $font_data ); + $result = $font->uninstall(); + + // If there was an error uninstalling the font, return the error. + if ( is_wp_error( $result ) ) { + return $result; + } + } + + return new WP_REST_Response( __( 'Font family uninstalled successfully.', 'gutenberg' ), 200 ); + } + + /** + * Checks whether the user has permissions to update the fonts library. + * + * @since 6.4.0 + * + * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. + */ + public function update_fonts_library_permissions_check() { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_update_fonts_library', + __( 'Sorry, you are not allowed to update the fonts library on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + + // The update endpoints requires write access to the temp and the fonts directories. + $temp_dir = get_temp_dir(); + $upload_dir = wp_upload_dir()['basedir']; + if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { + return new WP_Error( + 'rest_cannot_write_fonts_folder', + __( 'Error: WordPress does not have permission to write the fonts folder on your server.', 'gutenberg' ), + array( + 'status' => 500, + ) + ); + } + + return true; + } + + /** + * Installs new fonts. + * + * Takes a request containing new fonts to install, downloads their assets, and adds them + * to the fonts library. + * + * @since 6.4.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 fonts library post content. + */ + public function install_fonts( $request ) { + // Get new fonts to install. + $fonts_param = $request->get_param( 'fontFamilies' ); + + /* + * 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 ); + + if ( empty( $fonts_to_install ) ) { + return new WP_Error( + 'no_fonts_to_install', + __( 'No fonts to install', 'gutenberg' ), + array( 'status' => 400 ) + ); + } + + // Get uploaded files (used when installing local fonts). + $files = $request->get_file_params(); + + // Iterates the fonts data received and creates a new WP_Font_Family object for each one. + $fonts_installed = array(); + foreach ( $fonts_to_install as $font_data ) { + $font = new WP_Font_Family( $font_data ); + $font->install( $files ); + $fonts_installed[] = $font; + } + + if ( empty( $fonts_installed ) ) { + return new WP_Error( + 'error_installing_fonts', + __( 'Error installing fonts. No font was installed.', 'gutenberg' ), + array( 'status' => 500 ) + ); + } + + $response = array(); + foreach ( $fonts_installed as $font ) { + $response[] = $font->get_data(); + } + + return new WP_REST_Response( $response ); + } +} diff --git a/lib/experimental/fonts-library/fonts-library.php b/lib/experimental/fonts-library/fonts-library.php new file mode 100644 index 00000000000000..359442f231fc70 --- /dev/null +++ b/lib/experimental/fonts-library/fonts-library.php @@ -0,0 +1,37 @@ + true, + 'label' => 'Font Library', + 'show_in_rest' => true, + ); + register_post_type( 'wp_font_family', $args ); + + // @core-merge: This code will go into Core's `create_initial_rest_routes()`. + $fonts_library_controller = new WP_REST_Fonts_Library_Controller(); + $fonts_library_controller->register_routes(); +} + +add_action( 'rest_api_init', 'gutenberg_init_fonts_library' ); + diff --git a/lib/load.php b/lib/load.php index 85e9f9575e6e6f..e7da8de58c1b48 100644 --- a/lib/load.php +++ b/lib/load.php @@ -28,7 +28,7 @@ function gutenberg_is_experiment_enabled( $name ) { return ! empty( $experiments[ $name ] ); } -// These files only need to be loaded if within a rest server instance +// These files only need to be loaded if within a rest server instance. // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { if ( ! class_exists( 'WP_REST_Block_Editor_Settings_Controller' ) ) { @@ -144,7 +144,14 @@ function gutenberg_is_experiment_enabled( $name ) { * the Font Face (redesigned Fonts API) to be merged before the Fonts Library while * keeping Fonts API available for sites that are using it. */ -if ( class_exists( 'WP_Fonts_Library' ) || class_exists( 'WP_Fonts_Library_Controller' ) ) { +if ( defined( 'FONTS_LIBRARY_ENABLE' ) && FONTS_LIBRARY_ENABLE ) { + // Loads the Fonts Library. + require __DIR__ . '/experimental/fonts-library/class-wp-fonts-library.php'; + require __DIR__ . '/experimental/fonts-library/class-wp-font-family-utils.php'; + require __DIR__ . '/experimental/fonts-library/class-wp-font-family.php'; + require __DIR__ . '/experimental/fonts-library/class-wp-rest-fonts-library-controller.php'; + require __DIR__ . '/experimental/fonts-library/fonts-library.php'; + if ( ! class_exists( 'WP_Font_Face' ) ) { require __DIR__ . '/experimental/fonts/class-wp-font-face.php'; require __DIR__ . '/experimental/fonts/class-wp-font-face-resolver.php'; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2bc6b7b29900de..5cf01a02fef695 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,6 +9,7 @@ > + @@ -18,6 +19,7 @@ ms-required + fontsapi diff --git a/phpunit/fonts-library/class-wp-font-family-test.php b/phpunit/fonts-library/class-wp-font-family-test.php new file mode 100644 index 00000000000000..85e5c2c6e5fdec --- /dev/null +++ b/phpunit/fonts-library/class-wp-font-family-test.php @@ -0,0 +1,340 @@ + 'Piazzolla', + 'name' => 'Piazzolla', + ); + $this->expectException( 'Exception' ); + $this->expectExceptionMessage( 'Font family data is missing the slug.' ); + new WP_Font_Family( $font_data ); + } + + /** + * Tests that data is set by the constructor and retrieved by the get_data() method. + * + * @covers ::__construct + * @covers ::get_data + * + * @dataProvider data_font_fixtures + * + * @param array $font_data Font family data in theme.json format. + */ + public function test_get_data( $font_data ) { + $font = new WP_Font_Family( $font_data ); + $this->assertSame( $font_data, $font->get_data() ); + } + + /** + * Tests that the get_data_as_json() method returns the expected data in JSON format. + * + * @covers ::get_data_as_json + * + * @dataProvider data_get_data_as_json + * + * @param array $font_data Font family data in theme.json format. + * @param string $expected Expected font family data as JSON string. + */ + public function test_get_data_as_json( $font_data, $expected ) { + $font = new WP_Font_Family( $font_data ); + $this->assertSame( $expected, $font->get_data_as_json() ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_get_data_as_json() { + return array( + 'piazzolla' => array( + 'font_data' => array( + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'src' => 'https://example.com/fonts/piazzolla_italic_400.ttf', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + ), + ), + 'expected' => '{"slug":"piazzolla","fontFamily":"Piazzolla","name":"Piazzolla","fontFace":[{"fontFamily":"Piazzolla","src":"https:\/\/example.com\/fonts\/piazzolla_italic_400.ttf","fontStyle":"italic","fontWeight":"400"}]}', + ), + 'piazzolla2' => array( + 'font_data' => array( + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'src' => 'https://example.com/fonts/piazzolla_italic_400.ttf', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + ), + ), + 'expected' => '{"slug":"piazzolla","fontFamily":"Piazzolla","name":"Piazzolla","fontFace":[{"fontFamily":"Piazzolla","src":"https:\/\/example.com\/fonts\/piazzolla_italic_400.ttf","fontStyle":"italic","fontWeight":"400"}]}', + ), + ); + } + + /** + * Tests that the has_font_faces() method correctly determines whether a font family has font faces. + * + * @covers ::has_font_faces + * + * @dataProvider data_has_font_faces + * + * @param array $font_data Font family data in theme.json format. + * @param bool $expected Expected result. + */ + public function test_has_font_faces( $font_data, $expected ) { + $font = new WP_Font_Family( $font_data ); + $this->assertSame( $expected, $font->has_font_faces() ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_has_font_faces() { + return array( + 'with font faces' => array( + 'font_data' => array( + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + ), + ), + ), + 'expected' => true, + ), + + 'empty font faces' => array( + 'font_data' => array( + 'slug' => 'piazzolla', + 'fontFace' => array(), + ), + 'expected' => false, + ), + + 'without font faces' => array( + 'font_data' => array( + 'slug' => 'piazzolla', + ), + 'expected' => false, + ), + ); + } + + /** + * Tests that the install() and uninstall() methods work as expected + * It uses different types of font families: with local, remote or no files. + * + * @covers ::install + * @covers ::uninstall + * @covers ::get_font_post + * + * @dataProvider data_font_fixtures + * + * @param array $font_data Font family data in theme.json format. + * @param array $installed_font_data Font family data in theme.json format expected data after installation. + * @param array $files_data Optional. Files data in $_FILES format (Used only if the font has local files). Default: empty array. + */ + public function test_install_and_uninstall( $font_data, $installed_font_data, $files_data = array() ) { + $font = new WP_Font_Family( $font_data ); + $font->install( $files_data ); + + // Check that the post was created. + $post = $font->get_font_post(); + $this->assertInstanceof( 'WP_Post', $post, 'The font post was not created.' ); + + // Check that the post has the correct data. + $this->assertSame( $installed_font_data['name'], $post->post_title, 'The font post has the wrong title.' ); + $this->assertSame( $installed_font_data['slug'], $post->post_name, 'The font post has the wrong slug.' ); + + $content = json_decode( $post->post_content, true ); + $this->assertSame( $installed_font_data['fontFamily'], $content['fontFamily'], 'The font post content has the wrong font family.' ); + $this->assertSame( $installed_font_data['slug'], $content['slug'], 'The font post content has the wrong slug.' ); + + $this->assertArrayNotHasKey( 'download_from_url', $content, 'The installed font should not have the url from where it was downloaded.' ); + $this->assertArrayNotHasKey( 'uploaded_file', $content, 'The installed font should not have the reference to the file from it was installed.' ); + + $this->assertCount( count( $installed_font_data['fontFace'] ), $content['fontFace'], 'One or more font faces could not be installed.' ); + + $font->uninstall(); + + // Check that the post was deleted. + $post = $font->get_font_post(); + $this->assertNull( $post, 'The font post was not deleted' ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_font_fixtures() { + $temp_file_path1 = wp_tempnam( 'Inter-' ); + file_put_contents( $temp_file_path1, 'Mocking file content' ); + $temp_file_path2 = wp_tempnam( 'Inter-' ); + file_put_contents( $temp_file_path2, 'Mocking file content' ); + + return array( + 'with_one_google_font_face_to_be_downloaded' => array( + 'font_data' => array( + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'download_from_url' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + 'installed_font_data' => array( + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'piazzolla_italic_400.ttf', // This is just filename of the font asset and not the entire URL because we can't know the URL of the asset in the test. + ), + ), + ), + 'files_data' => null, + ), + 'with_one_google_font_face_to_not_be_downloaded' => array( + 'font_data' => array( + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + ), + ), + ), + 'installed_font_data' => array( + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'piazzolla_italic_400.ttf', // This is just filename of the font asset and not the entire URL because we can't know the URL of the asset in the test. + ), + ), + ), + 'files_data' => null, + ), + 'without_font_faces' => array( + 'font_data' => array( + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial', + 'fontFace' => array(), + ), + 'installed_font_data' => array( + 'name' => 'Arial', + 'slug' => 'arial', + 'fontFamily' => 'Arial', + 'fontFace' => array(), + ), + 'files_data' => null, + ), + 'with_local_files' => array( + 'font_data' => array( + 'name' => 'Inter', + 'slug' => 'inter', + 'fontFamily' => 'Inter', + 'fontFace' => array( + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploaded_file' => 'files0', + ), + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'normal', + 'fontWeight' => '500', + 'uploaded_file' => 'files1', + ), + ), + ), + 'installed_font_data' => array( + 'name' => 'Inter', + 'slug' => 'inter', + 'fontFamily' => 'Inter', + 'fontFace' => array( + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'inter_normal_400.ttf', + ), + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'normal', + 'fontWeight' => '500', + 'src' => 'inter_normal_500.ttf', + ), + ), + ), + 'files_data' => array( + 'files0' => array( + 'name' => 'inter1.ttf', + 'type' => 'font/ttf', + 'tmp_name' => $temp_file_path1, + 'error' => 0, + 'size' => 123, + ), + 'files1' => array( + 'name' => 'inter2.ttf', + 'type' => 'font/ttf', + 'tmp_name' => $temp_file_path2, + 'error' => 0, + 'size' => 123, + ), + ), + ), + ); + } +} diff --git a/phpunit/fonts-library/class-wp-font-family-utils-test.php b/phpunit/fonts-library/class-wp-font-family-utils-test.php new file mode 100644 index 00000000000000..775c88ce8b596c --- /dev/null +++ b/phpunit/fonts-library/class-wp-font-family-utils-test.php @@ -0,0 +1,320 @@ +assertSame( $expected, WP_Font_Family_Utils::has_font_mime_type( $font_file ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_has_font_mime_type_fixtures() { + return array( + 'ttf' => array( + 'font_file' => '/temp/piazzolla_400_italic.ttf', + 'expected' => true, + ), + 'otf' => array( + 'font_file' => '/temp/piazzolla_400_italic.otf', + 'expected' => true, + ), + 'woff' => array( + 'font_file' => '/temp/piazzolla_400_italic.woff', + 'expected' => true, + ), + 'woff2' => array( + 'font_file' => '/temp/piazzolla_400_italic.woff2', + 'expected' => true, + ), + 'exe' => array( + 'font_file' => '/temp/piazzolla_400_italic.exe', + 'expected' => false, + ), + 'php' => array( + 'font_file' => '/temp/piazzolla_400_italic.php', + 'expected' => false, + ), + ); + } + + /** + * @covers ::get_filename_from_font_face + * + * @dataProvider data_get_filename_from_font_face_fixtures + * + * @param string $slug Font slug. + * @param array $font_face Font face data in theme.json format. + * @param string $suffix Suffix added to the resulting filename. Default empty string. + * @param string $expected_file_name Expected file name. + */ + public function test_get_filename_from_font_face( $slug, $font_face, $suffix, $expected_file_name ) { + + $this->assertSame( + $expected_file_name, + WP_Font_Family_Utils::get_filename_from_font_face( + $slug, + $font_face, + $font_face['src'], + $suffix + ) + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_get_filename_from_font_face_fixtures() { + return array( + 'piazzolla' => array( + 'slug' => 'piazzolla', + 'font_face' => array( + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/font_file.ttf', + ), + 'suffix' => '', + 'expected_file_name' => 'piazzolla_italic_400.ttf', + ), + 'inter' => array( + 'slug' => 'inter', + 'font_face' => array( + 'fontStyle' => 'normal', + 'fontWeight' => '600', + 'src' => 'http://example.com/fonts/font_file.otf', + ), + 'suffix' => '', + 'expected_file_name' => 'inter_normal_600.otf', + ), + ); + } + + /** + * @covers ::merge_fonts_data + * + * @dataProvider data_merge_fonts_data_fixtures + * + * @param bool $are_mergeable Whether the fonts are mergeable. + * @param array $font1 First font data in theme.json format. + * @param array $font2 Second font data in theme.json format. + * @param array $expected_result Expected result. + */ + public function test_merge_fonts_data( $are_mergeable, $font1, $font2, $expected_result ) { + // Fonts with same slug should be merged. + $merged_font = WP_Font_Family_Utils::merge_fonts_data( $font1, $font2 ); + + if ( $are_mergeable ) { + $this->assertNotWPError( $merged_font, 'Fonts could not be merged' ); + $this->assertSame( $expected_result, $merged_font, 'The font family data and font faces merged not as expected' ); + } else { + $this->assertWPError( $merged_font, 'Merging non mergeable fonts (diifferent slug) should have failed.' ); + } + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_merge_fonts_data_fixtures() { + return array( + + 'mergeable_fonts' => array( + 'are_mergeable' => true, + 'font1' => array( + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '500', + 'src' => 'http://example.com/fonts/piazzolla_500_italic.ttf', + ), + ), + ), + 'font2' => array( + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '600', + 'src' => 'http://example.com/fonts/piazzolla_600_normal.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '700', + 'src' => 'http://example.com/fonts/piazzolla_700_normal.ttf', + ), + ), + ), + 'expected_result' => array( + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '500', + 'src' => 'http://example.com/fonts/piazzolla_500_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '600', + 'src' => 'http://example.com/fonts/piazzolla_600_normal.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '700', + 'src' => 'http://example.com/fonts/piazzolla_700_normal.ttf', + ), + ), + ), + ), + + 'mergeable_fonts_with_repeated_font_faces' => array( + 'are_mergeable' => true, + 'font1' => array( + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '500', + 'src' => 'http://example.com/fonts/piazzolla_500_italic.ttf', + ), + ), + ), + 'font2' => array( + 'slug' => 'piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '600', + 'src' => 'http://example.com/fonts/piazzolla_600_normal.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '500', + 'src' => 'http://example.com/fonts/piazzolla_500_italic.ttf', + ), + ), + ), + 'expected_result' => array( + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '500', + 'src' => 'http://example.com/fonts/piazzolla_500_italic.ttf', + ), + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '600', + 'src' => 'http://example.com/fonts/piazzolla_600_normal.ttf', + ), + ), + ), + ), + + 'non_mergeable_fonts' => array( + 'are_mergeable' => false, + 'font1' => array( + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFamily' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'italic', + 'fontWeight' => '400', + 'src' => 'http://example.com/fonts/piazzolla_400_italic.ttf', + ), + ), + ), + 'font2' => array( + 'slug' => 'inter', + 'fontFamily' => 'Inter', + 'fontFace' => array( + array( + 'fontFamily' => 'Inter', + 'fontStyle' => 'normal', + 'fontWeight' => '700', + 'src' => 'http://example.com/fonts/inter_700_normal.ttf', + ), + ), + ), + 'expected_result' => 'WP_Error', + ), + + ); + } + +} diff --git a/phpunit/fonts-library/class-wp-fonts-library-test.php b/phpunit/fonts-library/class-wp-fonts-library-test.php new file mode 100644 index 00000000000000..5e802fb6e3685d --- /dev/null +++ b/phpunit/fonts-library/class-wp-fonts-library-test.php @@ -0,0 +1,52 @@ +assertStringEndsWith( '/wp-content/uploads/fonts', WP_Fonts_Library::get_fonts_dir() ); + } + + /** + * @covers ::set_upload_dir + * + * @dataProvider data_set_upload_dir + * + * @param array $defaults Default upload directory data. + * @param array $expected Modified upload directory data. + */ + public function test_set_upload_dir( $defaults, $expected ) { + $this->assertSame( $expected, WP_Fonts_Library::set_upload_dir( $defaults ) ); + } + + public function data_set_upload_dir() { + return array( + 'fonts_subdir' => array( + 'defaults' => array( + 'subdir' => '/abc', + 'basedir' => '/var/www/html/wp-content/uploads', + 'baseurl' => 'http://example.com/wp-content/uploads', + ), + 'expected' => array( + 'subdir' => '/fonts', + 'basedir' => '/var/www/html/wp-content/uploads', + 'baseurl' => 'http://example.com/wp-content/uploads', + 'path' => '/var/www/html/wp-content/uploads/fonts', + 'url' => 'http://example.com/wp-content/uploads/fonts', + ), + ), + ); + } + +} diff --git a/phpunit/fonts-library/class-wp-rest-fonts-library-controller-test.php b/phpunit/fonts-library/class-wp-rest-fonts-library-controller-test.php new file mode 100644 index 00000000000000..ddf61aeb41ea6c --- /dev/null +++ b/phpunit/fonts-library/class-wp-rest-fonts-library-controller-test.php @@ -0,0 +1,343 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + } + + /** + * @covers ::register_routes + */ + public function test_register_routes() { + $routes = rest_get_server()->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->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.' ); + } + + /** + * @covers ::uninstall_fonts + */ + public function test_uninstall_non_existing_fonts() { + wp_set_current_user( self::$admin_id ); + $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( 'fontFamilies', $non_existing_font_data ); + $response = rest_get_server()->dispatch( $uninstall_request ); + $response->get_data(); + $this->assertSame( 500, $response->get_status(), 'The response status is not 500.' ); + } + + + /** + * @covers ::install_fonts + * @covers ::uninstall_fonts + * + * @dataProvider data_install_and_uninstall_fonts + * + * @param array $font_families Font families to install in theme.json format. + * @param array $files Font files to install. + * @param array $expected_response Expected response data. + */ + public function test_install_and_uninstall_fonts( $font_families, $files, $expected_response ) { + wp_set_current_user( self::$admin_id ); + $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); + $font_families_json = json_encode( $font_families ); + $install_request->set_param( 'fontFamilies', $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 ), $data, 'Not all the font families were installed correctly.' ); + + // Checks that the font families were installed correctly. + for ( $family_index = 0; $family_index < count( $data ); $family_index++ ) { + $installed_font = $data[ $family_index ]; + $expected_font = $expected_response[ $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. + $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'] ); + } + } + + // Compares if the rest of the data is the same. + $this->assertEquals( $expected_font, $installed_font, 'The endpoint answer is not as expected.' ); + } + + $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/fonts' ); + $uninstall_request->set_param( 'fontFamilies', $font_families ); + $response = rest_get_server()->dispatch( $uninstall_request ); + $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); + } + + /** + * Data provider for test_install_and_uninstall_fonts + */ + public function data_install_and_uninstall_fonts() { + + $temp_file_path1 = wp_tempnam( 'Piazzola1-' ); + file_put_contents( $temp_file_path1, 'Mocking file content' ); + $temp_file_path2 = wp_tempnam( 'Monteserrat-' ); + file_put_contents( $temp_file_path2, 'Mocking file content' ); + + 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', + 'download_from_url' => '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', + 'download_from_url' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', + ), + ), + ), + ), + 'files' => array(), + 'expected_response' => array( + array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => '/wp-content/uploads/fonts/piazzolla_normal_400.ttf', + ), + ), + ), + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'src' => '/wp-content/uploads/fonts/montserrat_normal_100.ttf', + ), + ), + ), + ), + ), + + '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( + 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', + + ), + ), + ), + ), + ), + + 'fonts_without_font_faces' => array( + 'font_families' => array( + array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', + ), + ), + 'files' => array(), + 'expected_response' => array( + array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', + ), + ), + ), + + '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', + 'uploaded_file' => 'files0', + ), + ), + ), + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'uploaded_file' => '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( + array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => '/wp-content/uploads/fonts/piazzolla_normal_400.ttf', + ), + ), + ), + array( + 'fontFamily' => 'Montserrat', + 'slug' => 'montserrat', + 'name' => 'Montserrat', + 'fontFace' => array( + array( + 'fontFamily' => 'Montserrat', + 'fontStyle' => 'normal', + 'fontWeight' => '100', + 'src' => '/wp-content/uploads/fonts/montserrat_normal_100.ttf', + ), + ), + ), + ), + ), + ); + } +} diff --git a/phpunit/multisite.xml b/phpunit/multisite.xml index 7a6117948547d3..23de57aa1d9bd8 100644 --- a/phpunit/multisite.xml +++ b/phpunit/multisite.xml @@ -10,6 +10,7 @@ + @@ -19,6 +20,7 @@ ms-excluded + fontsapi