<?php
/**
* FontFace class file
*/
namespace Automattic\WooCommerce\Internal\Font;
// IMPORTANT: We have to switch to the WordPress API to create the FontFace post type when they will be implemented: https://github.com/WordPress/gutenberg/issues/58670!
/**
* Helper class for font face related functionality.
*
* @internal Just for internal use.
*/
class FontFace {
const POST_TYPE = 'wp_font_face';
/**
* Gets the installed font face by slug.
*
* @param string $slug The font face slug.
* @return \WP_Post|null The font face post or null if not found.
*/
public static function get_installed_font_faces_by_slug( $slug ) {
$query = new \WP_Query(
array(
'post_type' => self::POST_TYPE,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'name' => $slug,
)
);
if ( ! empty( $query->get_posts() ) ) {
return $query->get_posts()[0];
}
return null;
}
/**
* Sanitizes a single src value for a font face.
*
* Copied from Gutenberg: https://github.com/WordPress/gutenberg/blob/8d94c3bd7af977d998466b56bd773f9b19de8d03/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php/#L837-L840
*
* @param string $value Font face src that is a URL or the key for a $_FILES array item.
*
* @return string Sanitized value.
*/
private static function sanitize_src( $value ) {
$value = ltrim( $value );
return false === wp_http_validate_url( $value ) ? (string) $value : esc_url_raw( $value );
}
/**
* Handles file upload error.
*
* Copied from Gutenberg: https://github.com/WordPress/gutenberg/blob/b283c47dba96d74dd7589a823d8ab84c9e5a4765/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php/#L859-L883
*
* @param array $file File upload data.
* @param string $message Error message from wp_handle_upload().
* @return WP_Error WP_Error object.
*/
private static function handle_font_file_upload_error( $file, $message ) {
$status = 500;
$code = 'rest_font_upload_unknown_error';
if ( __( 'Sorry, you are not allowed to upload this file type.', 'woocommerce' ) === $message ) {
$status = 400;
$code = 'rest_font_upload_invalid_file_type';
}
return new \WP_Error( $code, $message, array( 'status' => $status ) );
}
/**
* Handles the upload of a font file using wp_handle_upload().
*
* Copied from Gutenberg: https://github.com/WordPress/gutenberg/blob/b283c47dba96d74dd7589a823d8ab84c9e5a4765/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php/#L859-L883
*
* @param array $file Single file item from $_FILES.
* @return array Array containing uploaded file attributes on success, or error on failure.
*/
private static function handle_font_file_upload( $file ) {
add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
add_filter( 'upload_dir', 'wp_get_font_dir' );
$overrides = array(
'upload_error_handler' => array( self::class, 'handle_font_file_upload_error' ),
// Arbitrary string to avoid the is_uploaded_file() check applied
// when using 'wp_handle_upload'.
'action' => 'wp_handle_font_upload',
// Not testing a form submission.
'test_form' => false,
// Seems mime type for files that are not images cannot be tested.
// See wp_check_filetype_and_ext().
'test_type' => true,
// Only allow uploading font files for this request.
'mimes' => \WP_Font_Utils::get_allowed_font_mime_types(),
);
$uploaded_file = wp_handle_upload( $file, $overrides );
remove_filter( 'upload_dir', 'wp_get_font_dir' );
remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
return $uploaded_file;
}
/**
* Downloads a file from a URL.
*
* @param string $file_url The file URL.
**/
private static function download_file( $file_url ) {
if ( ! function_exists( 'download_url' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$allowed_extensions = array( 'ttf', 'otf', 'woff', 'woff2', 'eot' );
$allowed_extensions = array_map( 'preg_quote', $allowed_extensions );
// Set variables for storage, fix file filename for query strings.
preg_match( '/[^\?]+\.(' . implode( '|', $allowed_extensions ) . ')\b/i', $file_url, $matches );
$file_array = array();
$file_array['name'] = wp_basename( $matches[0] );
// Download file to temp location.
$file_array['tmp_name'] = download_url( $file_url );
return $file_array;
}
/**
* Inserts a font face.
*
* @param array $font_face The font face settings.
* @param int $parent_font_family_id The parent font family ID.
* @return \WP_Error|\WP_Post The inserted font face post or an error if the font face already exists.
*/
public static function insert_font_face( array $font_face, int $parent_font_family_id ) {
$slug = \WP_Font_Utils::get_font_face_slug( $font_face );
// Check that the font face slug is unique.
$query = new \WP_Query(
array(
'post_type' => self::POST_TYPE,
'posts_per_page' => 1,
'name' => $slug,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
if ( ! empty( $query->get_posts() ) ) {
return new \WP_Error(
'duplicate_font_face',
/* translators: %s: Font face slug. */
sprintf( __( 'A font face with slug "%s" already exists.', 'woocommerce' ), $slug ),
);
}
// Validate the font face settings.
$validation_error = self::validate_font_face( $font_face );
if ( is_wp_error( $validation_error ) ) {
return $validation_error;
}
$parsed_font_face['fontFamily'] = addslashes( \WP_Font_Utils::sanitize_font_family( $font_face['fontFamily'] ) );
$parsed_font_face['fontStyle'] = sanitize_text_field( $font_face['fontStyle'] );
$parsed_font_face['fontWeight'] = sanitize_text_field( $font_face['fontWeight'] );
$file = self::download_file( $font_face['src'] );
$uploaded_file = self::handle_font_file_upload( $file );
$parsed_font_face['src'] = self::sanitize_src( $uploaded_file['url'] );
$parsed_font_face['preview'] = esc_url_raw( $font_face['preview'] );
// Insert the font face.
wp_insert_post(
array(
'post_type' => self::POST_TYPE,
'post_parent' => $parent_font_family_id,
'post_title' => $slug,
'post_name' => sanitize_title( $slug ),
'post_content' => wp_json_encode( $parsed_font_face ),
'post_status' => 'publish',
)
);
}
/**
* Validates a font face.
*
* @param array $font_face The font face settings.
* @return \WP_Error|null The error if the font family is invalid, null otherwise.
*/
private static function validate_font_face( $font_face ) {
// Validate the font face family name.
if ( empty( $font_face['fontFamily'] ) ) {
return new \WP_Error(
'invalid_font_face_font_family',
__( 'The font face family name is required.', 'woocommerce' ),
);
}
// Validate the font face font style.
if ( empty( $font_face['fontStyle'] ) ) {
return new \WP_Error(
'invalid_font_face_font_style',
__( 'The font face font style is required.', 'woocommerce' ),
);
}
// Validate the font face weight.
if ( empty( $font_face['fontWeight'] ) ) {
return new \WP_Error(
'invalid_font_face_font_weight',
__( 'The font face weight is required.', 'woocommerce' ),
);
}
// Validate the font face src.
if ( empty( $font_face['src'] ) ) {
return new \WP_Error(
'invalid_font_face_src',
__( 'The font face src is required.', 'woocommerce' ),
);
}
}
}