PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | modules/google-fonts/current/class-jetpack-google-font-face.php +184 -44 12.9.5 → 16.3-beta View file →
@@ -5,11 +5,11 @@
5 5 * @package automattic/jetpack
6 6 */
7 7
8 8 /**
9 - * Jetpack Google Font Face disables Font Face hooks in Core that prints **ALL** font faces.
10 - * Instead, it collects fonts that are used in global styles or block-level settings and
11 - * print those fonts in use.
9 + * Prints used legacy catalogue faces while native font printers handle theme and user fonts.
10 + *
11 + * @phan-constructor-used-for-side-effects
12 12 */
13 13 class Jetpack_Google_Font_Face {
14 14 /**
15 15 * The fonts that are used in global styles or block-level settings.
@@ -21,31 +21,38 @@
21 21 /**
22 22 * The constructor.
23 23 */
24 24 public function __construct() {
25 - // Turns off hooks to print fonts
25 + // Retire the legacy printer while keeping native font-face printing.
26 26 add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
27 - add_action( 'admin_init', array( $this, 'admin_init' ), 10 );
27 + add_action( 'current_screen', array( $this, 'current_screen' ), 10 );
28 28
29 29 // Collect and print fonts in use
30 - add_action( 'wp_head', array( $this, 'print_font_faces' ), 50 );
30 + if ( wp_is_block_theme() ) {
31 + add_action( 'wp_head', array( $this, 'print_font_faces' ), 50 );
32 + } else {
33 + // In classic themes wp_head runs before the blocks are processed to collect the block fonts.
34 + add_action( 'wp_footer', array( $this, 'print_font_faces' ), 50 );
35 + }
31 36 add_filter( 'pre_render_block', array( $this, 'collect_block_fonts' ), 10, 2 );
32 37 }
33 38
34 39 /**
35 - * Turn off hooks to print fonts on frontend
40 + * Turn off the legacy webfonts printer on the frontend.
36 41 */
37 42 public function wp_loaded() {
38 43 remove_action( 'wp_head', 'wp_print_fonts', 50 );
39 - remove_action( 'wp_head', 'wp_print_font_faces', 50 );
40 44 }
41 45
42 46 /**
43 - * Turn off hooks to print fonts on wp-admin
47 + * Turn off hooks to print fonts on wp-admin, except for GB editor pages.
44 48 */
45 - public function admin_init() {
49 + public function current_screen() {
46 50 remove_action( 'admin_print_styles', 'wp_print_fonts', 50 );
47 - remove_action( 'admin_print_styles', 'wp_print_font_faces', 50 );
51 +
52 + if ( ! $this->is_block_editor() ) {
53 + remove_action( 'admin_print_styles', 'wp_print_font_faces', 50 );
54 + }
48 55 }
49 56
50 57 /**
51 58 * Print fonts that are used in global styles or block-level settings.
@@ -50,19 +57,102 @@
50 57 /**
51 58 * Print fonts that are used in global styles or block-level settings.
52 59 */
53 60 public function print_font_faces() {
54 - $fonts = $this->get_fonts();
55 - $fonts_to_print = array();
61 + if ( jetpack_google_fonts_load_font_faces() ) {
62 + return;
63 + }
56 64
57 65 $this->collect_global_styles_fonts();
58 - $this->fonts_in_use = array_values( array_unique( $this->fonts_in_use, SORT_STRING ) );
59 - foreach ( $fonts as $font_family => $font_faces ) {
60 - if ( in_array( _wp_to_kebab_case( $font_family ), $this->fonts_in_use, true ) ) {
61 - $fonts_to_print[ $font_family ] = $font_faces;
66 + $fonts_in_use = array_fill_keys( $this->fonts_in_use, true );
67 + if ( empty( $fonts_in_use ) ) {
68 + return;
69 + }
70 +
71 + $settings = function_exists( 'gutenberg_get_global_settings' )
72 + ? gutenberg_get_global_settings()
73 + : wp_get_global_settings();
74 + $native_slugs = array();
75 + $native_faces = array();
76 + foreach ( $settings['typography']['fontFamilies'] ?? array() as $origin => $families ) {
77 + foreach ( $families as $family ) {
78 + if ( 'default' === $origin && empty( $family['fontFace'] ) ) {
79 + continue;
80 + }
81 + if ( empty( $family['fontFamily'] ) ) {
82 + continue;
83 + }
84 + $key = $this->format_font( self::get_font_family_name( $family ) );
85 + if ( ! empty( $family['slug'] ) ) {
86 + $native_slugs[ $this->format_font( $family['slug'] ) ] = $key;
87 + }
88 + if ( ! empty( $family['fontFace'] ) ) {
89 + foreach ( $family['fontFace'] as $face ) {
90 + $native_faces[ $key ][] = $face;
91 + }
92 + }
62 93 }
63 94 }
64 95
96 + // Presets without their own faces can still refer to a legacy catalogue family.
97 + $fonts_in_use = array_fill_keys(
98 + array_map(
99 + static function ( $font ) use ( $native_slugs ) {
100 + return $native_slugs[ $font ] ?? $font;
101 + },
102 + array_keys( $fonts_in_use )
103 + ),
104 + true
105 + );
106 +
107 + $data = jetpack_get_google_fonts_data();
108 + if ( empty( $data['fontFamilies'] ) ) {
109 + return;
110 + }
111 + $available_fonts = jetpack_get_available_google_fonts_map( $data );
112 + $fonts_to_print = array();
113 + foreach ( $data['fontFamilies'] as $family ) {
114 + if ( empty( $available_fonts[ $family['name'] ] ) || empty( $family['fontFace'] ) ) {
115 + continue;
116 + }
117 +
118 + $name = self::get_font_family_name( $family );
119 + $key = $this->format_font( $name );
120 + $slug = $this->format_font( $family['slug'] );
121 + // A preset that reuses this slug for another family replaces it.
122 + if ( isset( $native_slugs[ $slug ] ) && $native_slugs[ $slug ] !== $key ) {
123 + continue;
124 + }
125 + if ( ! isset( $fonts_in_use[ $slug ] ) && ! isset( $fonts_in_use[ $key ] ) ) {
126 + continue;
127 + }
128 +
129 + $catalogue_faces = $family['fontFace'];
130 + if ( isset( $native_faces[ $key ] ) ) {
131 + // Native files win; the catalogue only fills styles and weights they lack.
132 + $catalogue_faces = array_filter(
133 + $catalogue_faces,
134 + static function ( $face ) use ( $native_faces, $key ) {
135 + return ! self::is_face_provided( $face, $native_faces[ $key ] );
136 + }
137 + );
138 + if ( empty( $catalogue_faces ) ) {
139 + continue;
140 + }
141 + }
142 +
143 + $faces = array();
144 + foreach ( $catalogue_faces as $face ) {
145 + $face['font-family'] = $name;
146 + $converted = array();
147 + foreach ( $face as $property => $value ) {
148 + $converted[ _wp_to_kebab_case( $property ) ] = $value;
149 + }
150 + $faces[] = $converted;
151 + }
152 + $fonts_to_print[] = $faces;
153 + }
154 +
65 155 if ( ! empty( $fonts_to_print ) ) {
66 156 wp_print_font_faces( $fonts_to_print );
67 157 }
68 158 }
@@ -67,12 +157,49 @@
67 157 }
68 158 }
69 159
70 160 /**
161 + * Whether a native face already covers a catalogue face's style and weight.
162 + *
163 + * @param array $face Catalogue face.
164 + * @param array[] $native_faces Native faces for the same family.
165 + * @return bool
166 + */
167 + private static function is_face_provided( $face, $native_faces ) {
168 + $style = $face['fontStyle'] ?? 'normal';
169 + list( $min, $max ) = self::get_weight_range( $face['fontWeight'] ?? '400' );
170 + foreach ( $native_faces as $native ) {
171 + if ( ( $native['fontStyle'] ?? 'normal' ) !== $style ) {
172 + continue;
173 + }
174 + list( $native_min, $native_max ) = self::get_weight_range( $native['fontWeight'] ?? '400' );
175 + // Overlap, not containment: a later catalogue rule would win at a shared weight.
176 + if ( $native_min <= $max && $min <= $native_max ) {
177 + return true;
178 + }
179 + }
180 + return false;
181 + }
182 +
183 + /**
184 + * Parses a font-weight descriptor such as "400", "bold" or "100 900".
185 + *
186 + * @param string|int $weight Font weight.
187 + * @return int[] Minimum and maximum weight.
188 + */
189 + private static function get_weight_range( $weight ) {
190 + $weight = str_replace( array( 'normal', 'bold' ), array( '400', '700' ), (string) $weight );
191 + $parts = preg_split( '/\s+/', trim( $weight ) );
192 + return array( (int) $parts[0], (int) ( $parts[1] ?? $parts[0] ) );
193 + }
194 +
195 + /**
71 196 * Collect fonts used for global styles settings.
72 197 */
73 198 public function collect_global_styles_fonts() {
74 - $global_styles = wp_get_global_styles();
199 + $global_styles = function_exists( 'gutenberg_get_global_styles' )
200 + ? gutenberg_get_global_styles()
201 + : wp_get_global_styles();
75 202
76 203 $global_styles_font_slug = $this->get_font_slug_from_setting( $global_styles );
77 204 if ( $global_styles_font_slug ) {
78 205 $this->add_font( $global_styles_font_slug );
@@ -121,32 +248,23 @@
121 248 *
122 249 * @param string $font_slug The font slug.
123 250 */
124 251 public function add_font( $font_slug ) {
125 - $this->fonts_in_use[] = _wp_to_kebab_case( $font_slug );
252 + if ( is_string( $font_slug ) ) {
253 + $this->fonts_in_use[] = $this->format_font( $font_slug );
254 + }
126 255 }
127 256
128 257 /**
129 - * Get all font definitions from theme json.
258 + * Format the given font slug.
259 + *
260 + * @example "ABeeZee" formats to "abeezee"
261 + * @example "ADLaM Display" formats to "adlam-display"
262 + * @param string $font_slug The font slug.
263 + * @return string The formatted font slug.
130 264 */
131 - public function get_fonts() {
132 - $fonts = WP_Font_Face_Resolver::get_fonts_from_theme_json();
133 -
134 - // The font definition might define an alias slug name, so we have to add the map from the slug name to font faces.
135 - // See https://github.com/WordPress/twentytwentyfour/blob/df92472089ede6fae5924c124a93c843b84e8cbd/theme.json#L215.
136 - $theme_json = WP_Theme_JSON_Resolver::get_theme_data();
137 - $raw_data = $theme_json->get_data();
138 - if ( ! empty( $raw_data['settings']['typography']['fontFamilies'] ) ) {
139 - foreach ( $raw_data['settings']['typography']['fontFamilies'] as $font ) {
140 - $font_family_name = $this->get_font_family_name( $font );
141 - $font_slug = isset( $font['slug'] ) ? $font['slug'] : '';
142 - if ( $font_slug && ! array_key_exists( $font_slug, $fonts ) && array_key_exists( $font_family_name, $fonts ) ) {
143 - $fonts[ $font_slug ] = $fonts[ $font_family_name ];
144 - }
145 - }
146 - }
147 -
148 - return $fonts;
265 + public function format_font( $font_slug ) {
266 + return _wp_to_kebab_case( strtolower( $font_slug ) );
149 267 }
150 268
151 269 /**
152 270 * Get the font family name from a font.
@@ -152,9 +270,9 @@
152 270 * Get the font family name from a font.
153 271 *
154 272 * @param array $font The font definition object.
155 273 */
156 - public function get_font_family_name( $font ) {
274 + public static function get_font_family_name( $font ) {
157 275 $font_family = $font['fontFamily'];
158 276 if ( str_contains( $font_family, ',' ) ) {
159 277 $font_family = explode( ',', $font_family )[0];
160 278 }
@@ -175,12 +293,18 @@
175 293 }
176 294
177 295 $font_family = $setting['typography']['fontFamily'];
178 296
179 - // Full string: var(--wp--preset--font-family--slug).
180 - // We do not care about the origin of the font, only its slug.
181 - preg_match( '/font-family--(?P<slug>.+)\)$/', $font_family, $matches );
297 + // The font family may be a reference to a path to the value stored at that location,
298 + // e.g.: { "ref": "styles.elements.heading.typography.fontFamily" }.
299 + // Ignore it as we also get the value stored at that location from the setting.
300 + if ( ! is_string( $font_family ) ) {
301 + return null;
302 + }
182 303
304 + // A preset reference can include a var() fallback and a trailing font stack.
305 + preg_match( '/^\s*var\(\s*--wp--preset--font-family--(?P<slug>[^\s,()]+)\s*[,)]/', $font_family, $matches );
306 +
183 307 if ( isset( $matches['slug'] ) ) {
184 308 return $matches['slug'];
185 309 }
186 310
@@ -185,13 +309,29 @@
185 309 }
186 310
187 311 // Full string: var:preset|font-family|slug
188 312 // We do not care about the origin of the font, only its slug.
189 - preg_match( '/font-family\|(?P<slug>.+)$/', $font_family, $matches );
313 + preg_match( '/^\s*var:preset\|font-family\|(?P<slug>[^\s,]+)/', $font_family, $matches );
190 314
191 315 if ( isset( $matches['slug'] ) ) {
192 316 return $matches['slug'];
193 317 }
194 318
195 - return $font_family;
319 + return self::get_font_family_name( $setting['typography'] );
320 + }
321 +
322 + /**
323 + * Check if the current screen is the block editor.
324 + *
325 + * @return bool
326 + */
327 + public function is_block_editor() {
328 + if ( function_exists( 'get_current_screen' ) ) {
329 + $current_screen = get_current_screen();
330 + if ( ! empty( $current_screen ) && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
331 + return true;
332 + }
333 + }
334 +
335 + return false;
196 336 }
197 337 }