PluginProbe
Gutenberg / 17.2.1
Gutenberg v17.2.1
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / lib / experimental / fonts-api / bc-layer / class-wp-web-fonts.php

class-wp-web-fonts.php in Gutenberg 17.2.1, at lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php

704 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Web Fonts API class.
4 *
5 * @package WordPress
6 * @subpackage Fonts API
7 * @since X.X.X
8 */
9
10 if ( class_exists( 'WP_Web_Fonts' ) ) {
11 return;
12 }
13
14 /**
15 * Class WP_Web_Fonts
16 *
17 * @since GB 14.9.1
18 * @deprecated GB 15.1 Use WP_Fonts instead.
19 */
20 class WP_Web_Fonts extends WP_Webfonts {
21
22 /**
23 * Constructor.
24 *
25 * @since X.X.X
26 */
27 public function __construct() {
28 _deprecated_function( __METHOD__, 'GB 15.1', 'WP_Fonts' );
29
30 /**
31 * Filters the web font variation's property defaults.
32 *
33 * @since X.X.X
34 *
35 * @param array $defaults {
36 * An array of required web font properties and defaults.
37 *
38 * @type string $provider The provider ID. Default 'local'.
39 * @type string $font-family The font-family property. Default empty string.
40 * @type string $font-style The font-style property. Default 'normal'.
41 * @type string $font-weight The font-weight property. Default '400'.
42 * @type string $font-display The font-display property. Default 'fallback'.
43 * }
44 */
45 $this->variation_property_defaults = apply_filters( 'wp_webfont_variation_defaults', $this->variation_property_defaults );
46
47 /**
48 * Fires when the WP_Webfonts instance is initialized.
49 *
50 * @since X.X.X
51 *
52 * @param WP_Web_Fonts $wp_webfonts WP_Web_Fonts instance (passed by reference).
53 */
54 do_action_ref_array( 'wp_default_webfonts', array( &$this ) );
55 }
56
57 /**
58 * Get the list of registered providers.
59 *
60 * @since X.X.X
61 *
62 * @return array $providers {
63 * An associative array of registered providers, keyed by their unique ID.
64 *
65 * @type string $provider_id => array {
66 * An associate array of provider's class name and fonts.
67 *
68 * @type string $class_name Fully qualified name of the provider's class.
69 * @type string[] $fonts An array of enqueued font handles for this provider.
70 * }
71 * }
72 */
73 public function get_providers() {
74 return $this->providers;
75 }
76
77 /**
78 * Register a provider.
79 *
80 * @since X.X.X
81 *
82 * @param string $provider_id The provider's unique ID.
83 * @param string $class_name The provider class name.
84 * @return bool True if successfully registered, else false.
85 */
86 public function register_provider( $provider_id, $class_name ) {
87 if ( empty( $provider_id ) || empty( $class_name ) || ! class_exists( $class_name ) ) {
88 return false;
89 }
90
91 $this->providers[ $provider_id ] = array(
92 'class' => $class_name,
93 'fonts' => array(),
94 );
95 return true;
96 }
97
98 /**
99 * Get the list of all registered font family handles.
100 *
101 * @since X.X.X
102 *
103 * @return string[]
104 */
105 public function get_registered_font_families() {
106 $font_families = array();
107 foreach ( $this->registered as $handle => $obj ) {
108 if ( $obj->extra['is_font_family'] ) {
109 $font_families[] = $handle;
110 }
111 }
112 return $font_families;
113 }
114
115 /**
116 * Get the list of all registered font families and their variations.
117 *
118 * @since X.X.X
119 *
120 * @return string[]
121 */
122 public function get_registered() {
123 return array_keys( $this->registered );
124 }
125
126 /**
127 * Get the list of enqueued font families and their variations.
128 *
129 * @since X.X.X
130 *
131 * @return array[]
132 */
133 public function get_enqueued() {
134 return $this->queue;
135 }
136
137 /**
138 * Registers a font family.
139 *
140 * @since X.X.X
141 *
142 * @param string $font_family Font family name to register.
143 * @return string|null Font family handle when registration successes. Null on failure.
144 */
145 public function add_font_family( $font_family ) {
146 $font_family_handle = WP_Fonts_Utils::convert_font_family_into_handle( $font_family );
147 if ( ! $font_family_handle ) {
148 return null;
149 }
150
151 if ( isset( $this->registered[ $font_family_handle ] ) ) {
152 return $font_family_handle;
153 }
154
155 $registered = $this->add( $font_family_handle, false );
156 if ( ! $registered ) {
157 return null;
158 }
159
160 $this->add_data( $font_family_handle, 'font-properties', array( 'font-family' => $font_family ) );
161 $this->add_data( $font_family_handle, 'is_font_family', true );
162
163 return $font_family_handle;
164 }
165
166 /**
167 * Removes a font family and all registered variations.
168 *
169 * @since X.X.X
170 *
171 * @param string $font_family_handle The font family to remove.
172 */
173 public function remove_font_family( $font_family_handle ) {
174 if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
175 return;
176 }
177
178 $variations = $this->registered[ $font_family_handle ]->deps;
179
180 foreach ( $variations as $variation ) {
181 $this->remove( $variation );
182 }
183
184 $this->remove( $font_family_handle );
185 }
186
187 /**
188 * Add a variation to an existing family or register family if none exists.
189 *
190 * @since X.X.X
191 *
192 * @param string $font_family_handle The font family's handle for this variation.
193 * @param array $variation An array of variation properties to add.
194 * @param string $variation_handle Optional. The variation's handle. When none is provided, the
195 * handle will be dynamically generated.
196 * Default empty string.
197 * @return string|null Variation handle on success. Else null.
198 */
199 public function add_variation( $font_family_handle, array $variation, $variation_handle = '' ) {
200 if ( ! WP_Fonts_Utils::is_defined( $font_family_handle ) ) {
201 trigger_error( 'Font family handle must be a non-empty string.' );
202 return null;
203 }
204
205 // When there is a variation handle, check it.
206 if ( '' !== $variation_handle && ! WP_Fonts_Utils::is_defined( $variation_handle ) ) {
207 trigger_error( 'Variant handle must be a non-empty string.' );
208 return null;
209 }
210
211 // Register the font family when it does not yet exist.
212 if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
213 if ( ! $this->add_font_family( $font_family_handle ) ) {
214 return null;
215 }
216 }
217
218 $variation = $this->validate_variation( $variation );
219
220 // Variation validation failed.
221 if ( ! $variation ) {
222 return null;
223 }
224
225 // When there's no variation handle, attempt to create one.
226 if ( '' === $variation_handle ) {
227 $variation_handle = WP_Fonts_Utils::convert_variation_into_handle( $font_family_handle, $variation );
228 if ( is_null( $variation_handle ) ) {
229 return null;
230 }
231 }
232
233 // Bail out if the variant is already registered.
234 if ( $this->is_variation_registered( $font_family_handle, $variation_handle ) ) {
235 return $variation_handle;
236 }
237
238 $variation_src = array_key_exists( 'src', $variation ) ? $variation['src'] : false;
239 $result = $this->add( $variation_handle, $variation_src );
240
241 // Bail out if the registration failed.
242 if ( ! $result ) {
243 return null;
244 }
245
246 $this->add_data( $variation_handle, 'font-properties', $variation );
247 $this->add_data( $variation_handle, 'is_font_family', false );
248
249 // Add the font variation as a dependency to the registered font family.
250 $this->add_dependency( $font_family_handle, $variation_handle );
251
252 $this->providers[ $variation['provider'] ]['fonts'][] = $variation_handle;
253
254 return $variation_handle;
255 }
256
257 /**
258 * Removes a variation.
259 *
260 * @since X.X.X
261 *
262 * @param string $font_family_handle The font family for this variation.
263 * @param string $variation_handle The variation's handle to remove.
264 */
265 public function remove_variation( $font_family_handle, $variation_handle ) {
266 if ( isset( $this->registered[ $variation_handle ] ) ) {
267 $this->remove( $variation_handle );
268 }
269
270 if ( ! $this->is_variation_registered( $font_family_handle, $variation_handle ) ) {
271 return;
272 }
273
274 // Remove the variation as a dependency from its font family.
275 $this->registered[ $font_family_handle ]->deps = array_values(
276 array_diff(
277 $this->registered[ $font_family_handle ]->deps,
278 array( $variation_handle )
279 )
280 );
281 }
282
283 /**
284 * Checks if the variation is registered.
285 *
286 * @since X.X.X
287 *
288 * @param string $font_family_handle The font family's handle for this variation.
289 * @param string $variation_handle Variation's handle.
290 * @return bool True when registered to the given font family. Else false.
291 */
292 private function is_variation_registered( $font_family_handle, $variation_handle ) {
293 if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
294 return false;
295 }
296
297 return in_array( $variation_handle, $this->registered[ $font_family_handle ]->deps, true );
298 }
299
300 /**
301 * Adds a variation as a dependency to the given font family.
302 *
303 * @since X.X.X
304 *
305 * @param string $font_family_handle The font family's handle for this variation.
306 * @param string $variation_handle The variation's handle.
307 */
308 private function add_dependency( $font_family_handle, $variation_handle ) {
309 $this->registered[ $font_family_handle ]->deps[] = $variation_handle;
310 }
311
312 /**
313 * Validates and sanitizes a variation.
314 *
315 * @since X.X.X
316 *
317 * @param array $variation Variation properties to add.
318 * @return false|array Validated variation on success. Else, false.
319 */
320 private function validate_variation( $variation ) {
321 $variation = wp_parse_args( $variation, $this->variation_property_defaults );
322
323 // Check the font-family.
324 if ( empty( $variation['font-family'] ) || ! is_string( $variation['font-family'] ) ) {
325 trigger_error( 'Webfont font-family must be a non-empty string.' );
326 return false;
327 }
328
329 // Local fonts need a "src".
330 if ( 'local' === $variation['provider'] ) {
331 // Make sure that local fonts have 'src' defined.
332 if ( empty( $variation['src'] ) || ( ! is_string( $variation['src'] ) && ! is_array( $variation['src'] ) ) ) {
333 trigger_error( 'Webfont src must be a non-empty string or an array of strings.' );
334 return false;
335 }
336 } elseif ( ! isset( $this->providers[ $variation['provider'] ] ) ) {
337 trigger_error( sprintf( 'The provider "%s" is not registered', $variation['provider'] ) );
338 return false;
339 } elseif ( ! class_exists( $this->providers[ $variation['provider'] ]['class'] ) ) {
340 trigger_error( sprintf( 'The provider class "%s" does not exist', $variation['provider'] ) );
341 return false;
342 }
343
344 // Validate the 'src' property.
345 if ( ! empty( $variation['src'] ) ) {
346 foreach ( (array) $variation['src'] as $src ) {
347 if ( empty( $src ) || ! is_string( $src ) ) {
348 trigger_error( 'Each webfont src must be a non-empty string.' );
349 return false;
350 }
351 }
352 }
353
354 // Check the font-weight.
355 if ( ! is_string( $variation['font-weight'] ) && ! is_int( $variation['font-weight'] ) ) {
356 trigger_error( 'Webfont font-weight must be a properly formatted string or integer.' );
357 return false;
358 }
359
360 // Check the font-display.
361 if ( ! in_array( $variation['font-display'], array( 'auto', 'block', 'fallback', 'swap', 'optional' ), true ) ) {
362 $variation['font-display'] = 'fallback';
363 }
364
365 $valid_props = array(
366 'ascent-override',
367 'descent-override',
368 'font-display',
369 'font-family',
370 'font-stretch',
371 'font-style',
372 'font-weight',
373 'font-variant',
374 'font-feature-settings',
375 'font-variation-settings',
376 'line-gap-override',
377 'size-adjust',
378 'src',
379 'unicode-range',
380
381 // Exceptions.
382 'provider',
383 );
384
385 foreach ( $variation as $prop => $value ) {
386 if ( ! in_array( $prop, $valid_props, true ) ) {
387 unset( $variation[ $prop ] );
388 }
389 }
390
391 return $variation;
392 }
393
394 /**
395 * Processes the items and dependencies.
396 *
397 * Processes the items passed to it or the queue, and their dependencies.
398 *
399 * @since X.X.X
400 *
401 * @param string|string[]|bool $handles Optional. Items to be processed: queue (false),
402 * single item (string), or multiple items (array of strings).
403 * Default false.
404 * @param int|false $group Optional. Group level: level (int), no group (false).
405 *
406 * @return array|string[] Array of web font handles that have been processed.
407 * An empty array if none were processed.
408 */
409 public function do_items( $handles = false, $group = false ) {
410 $handles = $this->prepare_handles_for_printing( $handles );
411
412 if ( empty( $handles ) ) {
413 return $this->done;
414 }
415
416 $this->all_deps( $handles );
417 if ( empty( $this->to_do ) ) {
418 return $this->done;
419 }
420
421 $this->to_do_keyed_handles = array_flip( $this->to_do );
422
423 foreach ( $this->get_providers() as $provider_id => $provider ) {
424 // Alert and skip if the provider class does not exist.
425 if ( ! class_exists( $provider['class'] ) ) {
426 /* translators: %s is the provider name. */
427 trigger_error(
428 sprintf(
429 'Class "%s" not found for "%s" web font provider',
430 $provider['class'],
431 $provider_id
432 )
433 );
434 continue;
435 }
436
437 $this->do_item( $provider_id, $group );
438 }
439
440 $this->process_font_families_after_printing( $handles );
441
442 return $this->done;
443 }
444
445 /**
446 * Prepares the given handles for printing.
447 *
448 * @since X.X.X
449 *
450 * @param string|string[]|bool $handles Optional. Handles to prepare.
451 * Default false.
452 * @return array Array of handles.
453 */
454 private function prepare_handles_for_printing( $handles = false ) {
455 if ( false !== $handles ) {
456 $handles = $this->validate_handles( $handles );
457 // Bail out when invalid.
458 if ( empty( $handles ) ) {
459 return array();
460 }
461 }
462
463 // Use the enqueued queue.
464 if ( empty( $handles ) ) {
465 if ( empty( $this->queue ) ) {
466 return array();
467 }
468 $handles = $this->queue;
469 }
470
471 return $handles;
472 }
473
474 /**
475 * Validates handle(s) to ensure each is a non-empty string.
476 *
477 * @since X.X.X
478 *
479 * @param string|string[] $handles Handles to prepare.
480 * @return string[]|null Array of handles on success. Else null.
481 */
482 private function validate_handles( $handles ) {
483 // Validate each element is a non-empty string handle.
484 $handles = array_filter( (array) $handles, array( WP_Fonts_Utils::class, 'is_defined' ) );
485
486 if ( empty( $handles ) ) {
487 trigger_error( 'Handles must be a non-empty string or array of non-empty strings' );
488 return null;
489 }
490
491 return $handles;
492 }
493
494 /**
495 * Invokes each provider to process and print its styles.
496 *
497 * @since X.X.X
498 *
499 * @see WP_Dependencies::do_item()
500 *
501 * @param string $provider_id The provider to process.
502 * @param int|false $group Not used.
503 * @return bool
504 */
505 public function do_item( $provider_id, $group = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
506 // Bail out if the provider is not registered.
507 if ( ! isset( $this->providers[ $provider_id ] ) ) {
508 return false;
509 }
510
511 $font_handles = $this->get_enqueued_fonts_for_provider( $provider_id );
512 if ( empty( $font_handles ) ) {
513 return false;
514 }
515
516 $properties_by_font = $this->get_font_properties_for_provider( $font_handles );
517 if ( empty( $properties_by_font ) ) {
518 return false;
519 }
520
521 // Invoke provider to print its styles.
522 $provider = $this->get_provider_instance( $provider_id );
523 $provider->set_fonts( $properties_by_font );
524 $provider->print_styles();
525
526 // Clean up.
527 $this->update_queues_for_printed_fonts( $font_handles );
528
529 return true;
530 }
531
532 /**
533 * Retrieves a list of enqueued web font variations for a provider.
534 *
535 * @since X.X.X
536 *
537 * @param string $provider_id The provider to process.
538 * @return array[] Webfonts organized by providers.
539 */
540 private function get_enqueued_fonts_for_provider( $provider_id ) {
541 $providers = $this->get_providers();
542
543 if ( empty( $providers[ $provider_id ] ) ) {
544 return array();
545 }
546
547 return array_intersect(
548 $providers[ $provider_id ]['fonts'],
549 $this->to_do
550 );
551 }
552
553 /**
554 * Gets a list of font properties for each of the given font handles.
555 *
556 * @since X.X.X
557 *
558 * @param array $font_handles Font handles to get properties.
559 * @return array A list of fonts with each font's properties.
560 */
561 private function get_font_properties_for_provider( array $font_handles ) {
562 $font_properties = array();
563
564 foreach ( $font_handles as $font_handle ) {
565 $properties = $this->get_data( $font_handle, 'font-properties' );
566 if ( ! $properties ) {
567 continue;
568 }
569 $font_properties[ $font_handle ] = $properties;
570 }
571
572 return $font_properties;
573 }
574
575 /**
576 * Gets the instance of the provider from the WP_Webfonts::$provider_instance store.
577 *
578 * @since X.X.X
579 *
580 * @param string $provider_id The provider to get.
581 * @return object Instance of the provider.
582 */
583 private function get_provider_instance( $provider_id ) {
584 if ( ! isset( $this->provider_instances[ $provider_id ] ) ) {
585 $this->provider_instances[ $provider_id ] = new $this->providers[ $provider_id ]['class']();
586 }
587 return $this->provider_instances[ $provider_id ];
588 }
589
590 /**
591 * Update queues for the given printed fonts.
592 *
593 * @since X.X.X
594 *
595 * @param array $font_handles Font handles to get properties.
596 */
597 private function update_queues_for_printed_fonts( array $font_handles ) {
598 foreach ( $font_handles as $font_handle ) {
599 $this->set_as_done( $font_handle );
600 $this->remove_from_to_do_queues( $font_handle );
601 }
602 }
603
604 /**
605 * Processes the font families after printing the variations.
606 *
607 * For each queued font family:
608 *
609 * a. if any of their variations were printed, the font family is added to the `done` list.
610 * b. removes each from the to_do queues.
611 *
612 * @since X.X.X
613 *
614 * @param array $handles Handles to process.
615 */
616 private function process_font_families_after_printing( array $handles ) {
617 foreach ( $handles as $handle ) {
618 if (
619 ! $this->get_data( $handle, 'is_font_family' ) ||
620 ! isset( $this->to_do_keyed_handles[ $handle ] )
621 ) {
622 continue;
623 }
624 $font_family = $this->registered[ $handle ];
625
626 // Add the font family to `done` list if any of its variations were printed.
627 if ( ! empty( $font_family->deps ) ) {
628 $processed = array_intersect( $font_family->deps, $this->done );
629 if ( ! empty( $processed ) ) {
630 $this->set_as_done( $handle );
631 }
632 }
633
634 $this->remove_from_to_do_queues( $handle );
635 }
636 }
637
638 /**
639 * Removes the handle from the `to_do` and `to_do_keyed_handles` lists.
640 *
641 * @since X.X.X
642 *
643 * @param string $handle Handle to remove.
644 */
645 private function remove_from_to_do_queues( $handle ) {
646 unset(
647 $this->to_do[ $this->to_do_keyed_handles[ $handle ] ],
648 $this->to_do_keyed_handles[ $handle ]
649 );
650 }
651
652 /**
653 * Sets the given handle to done by adding it to the `done` list.
654 *
655 * @since X.X.X
656 *
657 * @param string $handle Handle to set as done.
658 */
659 private function set_as_done( $handle ) {
660 if ( ! is_array( $this->done ) ) {
661 $this->done = array();
662 }
663 $this->done[] = $handle;
664 }
665
666 /**
667 * Converts the font family and its variations into theme.json structural format.
668 *
669 * @since X.X.X
670 *
671 * @param string $font_family_handle Font family to convert.
672 * @return array Webfonts in theme.json structural format.
673 */
674 public function to_theme_json( $font_family_handle ) {
675 if ( ! isset( $this->registered[ $font_family_handle ] ) ) {
676 return array();
677 }
678
679 $font_family_name = $this->registered[ $font_family_handle ]->extra['font-properties']['font-family'];
680 $theme_json_format = array(
681 'fontFamily' => str_contains( $font_family_name, ' ' ) ? "'{$font_family_name}'" : $font_family_name,
682 'name' => $font_family_name,
683 'slug' => $font_family_handle,
684 'fontFace' => array(),
685 );
686
687 foreach ( $this->registered[ $font_family_handle ]->deps as $variation_handle ) {
688 if ( ! isset( $this->registered[ $variation_handle ] ) ) {
689 continue;
690 }
691
692 $variation_obj = $this->registered[ $variation_handle ];
693 $variation_properties = array( 'origin' => 'gutenberg_wp_fonts_api' );
694 foreach ( $variation_obj->extra['font-properties'] as $property_name => $property_value ) {
695 $property_in_camelcase = lcfirst( str_replace( '-', '', ucwords( $property_name, '-' ) ) );
696 $variation_properties[ $property_in_camelcase ] = $property_value;
697 }
698 $theme_json_format['fontFace'][ $variation_obj->handle ] = $variation_properties;
699 }
700
701 return $theme_json_format;
702 }
703 }
704