wp/wp-includes/fonts/class-wp-font-collection.php
changeset 21 48c4eec2b7e6
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 <?php
       
     2 /**
       
     3  * Font Collection class.
       
     4  *
       
     5  * This file contains the Font Collection class definition.
       
     6  *
       
     7  * @package    WordPress
       
     8  * @subpackage Fonts
       
     9  * @since      6.5.0
       
    10  */
       
    11 
       
    12 /**
       
    13  * Font Collection class.
       
    14  *
       
    15  * @since 6.5.0
       
    16  *
       
    17  * @see wp_register_font_collection()
       
    18  */
       
    19 final class WP_Font_Collection {
       
    20 	/**
       
    21 	 * The unique slug for the font collection.
       
    22 	 *
       
    23 	 * @since 6.5.0
       
    24 	 * @var string
       
    25 	 */
       
    26 	public $slug;
       
    27 
       
    28 	/**
       
    29 	 * Font collection data.
       
    30 	 *
       
    31 	 * @since 6.5.0
       
    32 	 * @var array|WP_Error|null
       
    33 	 */
       
    34 	private $data;
       
    35 
       
    36 	/**
       
    37 	 * Font collection JSON file path or URL.
       
    38 	 *
       
    39 	 * @since 6.5.0
       
    40 	 * @var string|null
       
    41 	 */
       
    42 	private $src;
       
    43 
       
    44 	/**
       
    45 	 * WP_Font_Collection constructor.
       
    46 	 *
       
    47 	 * @since 6.5.0
       
    48 	 *
       
    49 	 * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes,
       
    50 	 *                     and underscores. See sanitize_title().
       
    51 	 * @param array  $args Font collection data. See wp_register_font_collection() for information on accepted arguments.
       
    52 	 */
       
    53 	public function __construct( string $slug, array $args ) {
       
    54 		$this->slug = sanitize_title( $slug );
       
    55 		if ( $this->slug !== $slug ) {
       
    56 			_doing_it_wrong(
       
    57 				__METHOD__,
       
    58 				/* translators: %s: Font collection slug. */
       
    59 				sprintf( __( 'Font collection slug "%s" is not valid. Slugs must use only alphanumeric characters, dashes, and underscores.' ), $slug ),
       
    60 				'6.5.0'
       
    61 			);
       
    62 		}
       
    63 
       
    64 		$required_properties = array( 'name', 'font_families' );
       
    65 
       
    66 		if ( isset( $args['font_families'] ) && is_string( $args['font_families'] ) ) {
       
    67 			// JSON data is lazy loaded by ::get_data().
       
    68 			$this->src = $args['font_families'];
       
    69 			unset( $args['font_families'] );
       
    70 
       
    71 			$required_properties = array( 'name' );
       
    72 		}
       
    73 
       
    74 		$this->data = $this->sanitize_and_validate_data( $args, $required_properties );
       
    75 	}
       
    76 
       
    77 	/**
       
    78 	 * Retrieves the font collection data.
       
    79 	 *
       
    80 	 * @since 6.5.0
       
    81 	 *
       
    82 	 * @return array|WP_Error An array containing the font collection data, or a WP_Error on failure.
       
    83 	 */
       
    84 	public function get_data() {
       
    85 		if ( is_wp_error( $this->data ) ) {
       
    86 			return $this->data;
       
    87 		}
       
    88 
       
    89 		// If the collection uses JSON data, load it and cache the data/error.
       
    90 		if ( isset( $this->src ) ) {
       
    91 			$this->data = $this->load_from_json( $this->src );
       
    92 		}
       
    93 
       
    94 		if ( is_wp_error( $this->data ) ) {
       
    95 			return $this->data;
       
    96 		}
       
    97 
       
    98 		// Set defaults for optional properties.
       
    99 		$defaults = array(
       
   100 			'description' => '',
       
   101 			'categories'  => array(),
       
   102 		);
       
   103 
       
   104 		return wp_parse_args( $this->data, $defaults );
       
   105 	}
       
   106 
       
   107 	/**
       
   108 	 * Loads font collection data from a JSON file or URL.
       
   109 	 *
       
   110 	 * @since 6.5.0
       
   111 	 *
       
   112 	 * @param string $file_or_url File path or URL to a JSON file containing the font collection data.
       
   113 	 * @return array|WP_Error An array containing the font collection data on success,
       
   114 	 *                        else an instance of WP_Error on failure.
       
   115 	 */
       
   116 	private function load_from_json( $file_or_url ) {
       
   117 		$url  = wp_http_validate_url( $file_or_url );
       
   118 		$file = file_exists( $file_or_url ) ? wp_normalize_path( realpath( $file_or_url ) ) : false;
       
   119 
       
   120 		if ( ! $url && ! $file ) {
       
   121 			// translators: %s: File path or URL to font collection JSON file.
       
   122 			$message = __( 'Font collection JSON file is invalid or does not exist.' );
       
   123 			_doing_it_wrong( __METHOD__, $message, '6.5.0' );
       
   124 			return new WP_Error( 'font_collection_json_missing', $message );
       
   125 		}
       
   126 
       
   127 		$data = $url ? $this->load_from_url( $url ) : $this->load_from_file( $file );
       
   128 
       
   129 		if ( is_wp_error( $data ) ) {
       
   130 			return $data;
       
   131 		}
       
   132 
       
   133 		$data = array(
       
   134 			'name'          => $this->data['name'],
       
   135 			'font_families' => $data['font_families'],
       
   136 		);
       
   137 
       
   138 		if ( isset( $this->data['description'] ) ) {
       
   139 			$data['description'] = $this->data['description'];
       
   140 		}
       
   141 
       
   142 		if ( isset( $this->data['categories'] ) ) {
       
   143 			$data['categories'] = $this->data['categories'];
       
   144 		}
       
   145 
       
   146 		return $data;
       
   147 	}
       
   148 
       
   149 	/**
       
   150 	 * Loads the font collection data from a JSON file path.
       
   151 	 *
       
   152 	 * @since 6.5.0
       
   153 	 *
       
   154 	 * @param string $file File path to a JSON file containing the font collection data.
       
   155 	 * @return array|WP_Error An array containing the font collection data on success,
       
   156 	 *                        else an instance of WP_Error on failure.
       
   157 	 */
       
   158 	private function load_from_file( $file ) {
       
   159 		$data = wp_json_file_decode( $file, array( 'associative' => true ) );
       
   160 		if ( empty( $data ) ) {
       
   161 			return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.' ) );
       
   162 		}
       
   163 
       
   164 		return $this->sanitize_and_validate_data( $data, array( 'font_families' ) );
       
   165 	}
       
   166 
       
   167 	/**
       
   168 	 * Loads the font collection data from a JSON file URL.
       
   169 	 *
       
   170 	 * @since 6.5.0
       
   171 	 *
       
   172 	 * @param string $url URL to a JSON file containing the font collection data.
       
   173 	 * @return array|WP_Error An array containing the font collection data on success,
       
   174 	 *                        else an instance of WP_Error on failure.
       
   175 	 */
       
   176 	private function load_from_url( $url ) {
       
   177 		// Limit key to 167 characters to avoid failure in the case of a long URL.
       
   178 		$transient_key = substr( 'wp_font_collection_url_' . $url, 0, 167 );
       
   179 		$data          = get_site_transient( $transient_key );
       
   180 
       
   181 		if ( false === $data ) {
       
   182 			$response = wp_safe_remote_get( $url );
       
   183 			if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
       
   184 				return new WP_Error(
       
   185 					'font_collection_request_error',
       
   186 					sprintf(
       
   187 						// translators: %s: Font collection URL.
       
   188 						__( 'Error fetching the font collection data from "%s".' ),
       
   189 						$url
       
   190 					)
       
   191 				);
       
   192 			}
       
   193 
       
   194 			$data = json_decode( wp_remote_retrieve_body( $response ), true );
       
   195 			if ( empty( $data ) ) {
       
   196 				return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection data from the HTTP response JSON.' ) );
       
   197 			}
       
   198 
       
   199 			// Make sure the data is valid before storing it in a transient.
       
   200 			$data = $this->sanitize_and_validate_data( $data, array( 'font_families' ) );
       
   201 			if ( is_wp_error( $data ) ) {
       
   202 				return $data;
       
   203 			}
       
   204 
       
   205 			set_site_transient( $transient_key, $data, DAY_IN_SECONDS );
       
   206 		}
       
   207 
       
   208 		return $data;
       
   209 	}
       
   210 
       
   211 	/**
       
   212 	 * Sanitizes and validates the font collection data.
       
   213 	 *
       
   214 	 * @since 6.5.0
       
   215 	 *
       
   216 	 * @param array $data                Font collection data to sanitize and validate.
       
   217 	 * @param array $required_properties Required properties that must exist in the passed data.
       
   218 	 * @return array|WP_Error Sanitized data if valid, otherwise a WP_Error instance.
       
   219 	 */
       
   220 	private function sanitize_and_validate_data( $data, $required_properties = array() ) {
       
   221 		$schema = self::get_sanitization_schema();
       
   222 		$data   = WP_Font_Utils::sanitize_from_schema( $data, $schema );
       
   223 
       
   224 		foreach ( $required_properties as $property ) {
       
   225 			if ( empty( $data[ $property ] ) ) {
       
   226 				$message = sprintf(
       
   227 					// translators: 1: Font collection slug, 2: Missing property name, e.g. "font_families".
       
   228 					__( 'Font collection "%1$s" has missing or empty property: "%2$s".' ),
       
   229 					$this->slug,
       
   230 					$property
       
   231 				);
       
   232 				_doing_it_wrong( __METHOD__, $message, '6.5.0' );
       
   233 				return new WP_Error( 'font_collection_missing_property', $message );
       
   234 			}
       
   235 		}
       
   236 
       
   237 		return $data;
       
   238 	}
       
   239 
       
   240 	/**
       
   241 	 * Retrieves the font collection sanitization schema.
       
   242 	 *
       
   243 	 * @since 6.5.0
       
   244 	 *
       
   245 	 * @return array Font collection sanitization schema.
       
   246 	 */
       
   247 	private static function get_sanitization_schema() {
       
   248 		return array(
       
   249 			'name'          => 'sanitize_text_field',
       
   250 			'description'   => 'sanitize_text_field',
       
   251 			'font_families' => array(
       
   252 				array(
       
   253 					'font_family_settings' => array(
       
   254 						'name'       => 'sanitize_text_field',
       
   255 						'slug'       => static function ( $value ) {
       
   256 							return _wp_to_kebab_case( sanitize_title( $value ) );
       
   257 						},
       
   258 						'fontFamily' => 'WP_Font_Utils::sanitize_font_family',
       
   259 						'preview'    => 'sanitize_url',
       
   260 						'fontFace'   => array(
       
   261 							array(
       
   262 								'fontFamily'            => 'sanitize_text_field',
       
   263 								'fontStyle'             => 'sanitize_text_field',
       
   264 								'fontWeight'            => 'sanitize_text_field',
       
   265 								'src'                   => static function ( $value ) {
       
   266 									return is_array( $value )
       
   267 										? array_map( 'sanitize_text_field', $value )
       
   268 										: sanitize_text_field( $value );
       
   269 								},
       
   270 								'preview'               => 'sanitize_url',
       
   271 								'fontDisplay'           => 'sanitize_text_field',
       
   272 								'fontStretch'           => 'sanitize_text_field',
       
   273 								'ascentOverride'        => 'sanitize_text_field',
       
   274 								'descentOverride'       => 'sanitize_text_field',
       
   275 								'fontVariant'           => 'sanitize_text_field',
       
   276 								'fontFeatureSettings'   => 'sanitize_text_field',
       
   277 								'fontVariationSettings' => 'sanitize_text_field',
       
   278 								'lineGapOverride'       => 'sanitize_text_field',
       
   279 								'sizeAdjust'            => 'sanitize_text_field',
       
   280 								'unicodeRange'          => 'sanitize_text_field',
       
   281 							),
       
   282 						),
       
   283 					),
       
   284 					'categories'           => array( 'sanitize_title' ),
       
   285 				),
       
   286 			),
       
   287 			'categories'    => array(
       
   288 				array(
       
   289 					'name' => 'sanitize_text_field',
       
   290 					'slug' => 'sanitize_title',
       
   291 				),
       
   292 			),
       
   293 		);
       
   294 	}
       
   295 }