diff options
Diffstat (limited to '')
-rw-r--r-- | wp-includes/fonts/class-wp-font-collection.php | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/wp-includes/fonts/class-wp-font-collection.php b/wp-includes/fonts/class-wp-font-collection.php new file mode 100644 index 0000000..b915e3e --- /dev/null +++ b/wp-includes/fonts/class-wp-font-collection.php @@ -0,0 +1,295 @@ +<?php +/** + * Font Collection class. + * + * This file contains the Font Collection class definition. + * + * @package WordPress + * @subpackage Fonts + * @since 6.5.0 + */ + +/** + * Font Collection class. + * + * @since 6.5.0 + * + * @see wp_register_font_collection() + */ +final class WP_Font_Collection { + /** + * The unique slug for the font collection. + * + * @since 6.5.0 + * @var string + */ + public $slug; + + /** + * Font collection data. + * + * @since 6.5.0 + * @var array|WP_Error|null + */ + private $data; + + /** + * Font collection JSON file path or URL. + * + * @since 6.5.0 + * @var string|null + */ + private $src; + + /** + * WP_Font_Collection constructor. + * + * @since 6.5.0 + * + * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, + * and underscores. See sanitize_title(). + * @param array $args Font collection data. See wp_register_font_collection() for information on accepted arguments. + */ + public function __construct( string $slug, array $args ) { + $this->slug = sanitize_title( $slug ); + if ( $this->slug !== $slug ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Font collection slug. */ + sprintf( __( 'Font collection slug "%s" is not valid. Slugs must use only alphanumeric characters, dashes, and underscores.' ), $slug ), + '6.5.0' + ); + } + + $required_properties = array( 'name', 'font_families' ); + + if ( isset( $args['font_families'] ) && is_string( $args['font_families'] ) ) { + // JSON data is lazy loaded by ::get_data(). + $this->src = $args['font_families']; + unset( $args['font_families'] ); + + $required_properties = array( 'name' ); + } + + $this->data = $this->sanitize_and_validate_data( $args, $required_properties ); + } + + /** + * Retrieves the font collection data. + * + * @since 6.5.0 + * + * @return array|WP_Error An array containing the font collection data, or a WP_Error on failure. + */ + public function get_data() { + if ( is_wp_error( $this->data ) ) { + return $this->data; + } + + // If the collection uses JSON data, load it and cache the data/error. + if ( isset( $this->src ) ) { + $this->data = $this->load_from_json( $this->src ); + } + + if ( is_wp_error( $this->data ) ) { + return $this->data; + } + + // Set defaults for optional properties. + $defaults = array( + 'description' => '', + 'categories' => array(), + ); + + return wp_parse_args( $this->data, $defaults ); + } + + /** + * Loads font collection data from a JSON file or URL. + * + * @since 6.5.0 + * + * @param string $file_or_url File path or URL to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_json( $file_or_url ) { + $url = wp_http_validate_url( $file_or_url ); + $file = file_exists( $file_or_url ) ? wp_normalize_path( realpath( $file_or_url ) ) : false; + + if ( ! $url && ! $file ) { + // translators: %s: File path or URL to font collection JSON file. + $message = __( 'Font collection JSON file is invalid or does not exist.' ); + _doing_it_wrong( __METHOD__, $message, '6.5.0' ); + return new WP_Error( 'font_collection_json_missing', $message ); + } + + $data = $url ? $this->load_from_url( $url ) : $this->load_from_file( $file ); + + if ( is_wp_error( $data ) ) { + return $data; + } + + $data = array( + 'name' => $this->data['name'], + 'font_families' => $data['font_families'], + ); + + if ( isset( $this->data['description'] ) ) { + $data['description'] = $this->data['description']; + } + + if ( isset( $this->data['categories'] ) ) { + $data['categories'] = $this->data['categories']; + } + + return $data; + } + + /** + * Loads the font collection data from a JSON file path. + * + * @since 6.5.0 + * + * @param string $file File path to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_file( $file ) { + $data = wp_json_file_decode( $file, array( 'associative' => true ) ); + if ( empty( $data ) ) { + return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.' ) ); + } + + return $this->sanitize_and_validate_data( $data, array( 'font_families' ) ); + } + + /** + * Loads the font collection data from a JSON file URL. + * + * @since 6.5.0 + * + * @param string $url URL to a JSON file containing the font collection data. + * @return array|WP_Error An array containing the font collection data on success, + * else an instance of WP_Error on failure. + */ + private function load_from_url( $url ) { + // Limit key to 167 characters to avoid failure in the case of a long URL. + $transient_key = substr( 'wp_font_collection_url_' . $url, 0, 167 ); + $data = get_site_transient( $transient_key ); + + if ( false === $data ) { + $response = wp_safe_remote_get( $url ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( + 'font_collection_request_error', + sprintf( + // translators: %s: Font collection URL. + __( 'Error fetching the font collection data from "%s".' ), + $url + ) + ); + } + + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( empty( $data ) ) { + return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection data from the HTTP response JSON.' ) ); + } + + // Make sure the data is valid before storing it in a transient. + $data = $this->sanitize_and_validate_data( $data, array( 'font_families' ) ); + if ( is_wp_error( $data ) ) { + return $data; + } + + set_site_transient( $transient_key, $data, DAY_IN_SECONDS ); + } + + return $data; + } + + /** + * Sanitizes and validates the font collection data. + * + * @since 6.5.0 + * + * @param array $data Font collection data to sanitize and validate. + * @param array $required_properties Required properties that must exist in the passed data. + * @return array|WP_Error Sanitized data if valid, otherwise a WP_Error instance. + */ + private function sanitize_and_validate_data( $data, $required_properties = array() ) { + $schema = self::get_sanitization_schema(); + $data = WP_Font_Utils::sanitize_from_schema( $data, $schema ); + + foreach ( $required_properties as $property ) { + if ( empty( $data[ $property ] ) ) { + $message = sprintf( + // translators: 1: Font collection slug, 2: Missing property name, e.g. "font_families". + __( 'Font collection "%1$s" has missing or empty property: "%2$s".' ), + $this->slug, + $property + ); + _doing_it_wrong( __METHOD__, $message, '6.5.0' ); + return new WP_Error( 'font_collection_missing_property', $message ); + } + } + + return $data; + } + + /** + * Retrieves the font collection sanitization schema. + * + * @since 6.5.0 + * + * @return array Font collection sanitization schema. + */ + private static function get_sanitization_schema() { + return array( + 'name' => 'sanitize_text_field', + 'description' => 'sanitize_text_field', + 'font_families' => array( + array( + 'font_family_settings' => array( + 'name' => 'sanitize_text_field', + 'slug' => static function ( $value ) { + return _wp_to_kebab_case( sanitize_title( $value ) ); + }, + 'fontFamily' => 'WP_Font_Utils::sanitize_font_family', + 'preview' => 'sanitize_url', + 'fontFace' => array( + array( + 'fontFamily' => 'sanitize_text_field', + 'fontStyle' => 'sanitize_text_field', + 'fontWeight' => 'sanitize_text_field', + 'src' => static function ( $value ) { + return is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : sanitize_text_field( $value ); + }, + 'preview' => 'sanitize_url', + 'fontDisplay' => 'sanitize_text_field', + 'fontStretch' => 'sanitize_text_field', + 'ascentOverride' => 'sanitize_text_field', + 'descentOverride' => 'sanitize_text_field', + 'fontVariant' => 'sanitize_text_field', + 'fontFeatureSettings' => 'sanitize_text_field', + 'fontVariationSettings' => 'sanitize_text_field', + 'lineGapOverride' => 'sanitize_text_field', + 'sizeAdjust' => 'sanitize_text_field', + 'unicodeRange' => 'sanitize_text_field', + ), + ), + ), + 'categories' => array( 'sanitize_title' ), + ), + ), + 'categories' => array( + array( + 'name' => 'sanitize_text_field', + 'slug' => 'sanitize_title', + ), + ), + ); + } +} |