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