Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
49.58% covered (danger)
49.58%
59 / 119
50.00% covered (danger)
50.00%
4 / 8
CRAP
n/a
0 / 0
jetpack_get_google_fonts_data
10.71% covered (danger)
10.71%
3 / 28
0.00% covered (danger)
0.00%
0 / 1
97.13
jetpack_get_available_google_fonts_map
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
jetpack_google_fonts_is_rest_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
jetpack_google_fonts_load_font_faces
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
6
jetpack_register_google_fonts_to_theme_json
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
5
jetpack_google_fonts_filter_out_deprecated_font_data
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
jetpack_unregister_deprecated_google_fonts_from_theme_json_data
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
2.69
jetpack_unregister_google_fonts
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Load the google fonts by the new Font Library. See pNEWy-hhx-p2.
4 *
5 * @package automattic/jetpack
6 */
7
8if ( ! defined( 'ABSPATH' ) ) {
9    exit( 0 );
10}
11
12if ( ! class_exists( 'Jetpack_Google_Font_Face' ) ) {
13    /**
14     * Load Jetpack Google Font Face
15     */
16    require_once __DIR__ . '/class-jetpack-google-font-face.php';
17}
18
19/**
20 * Gets the Google Fonts data
21 *
22 * @return array|null The collection data of the Google Fonts.
23 */
24function jetpack_get_google_fonts_data() {
25    /**
26     * Filters the Google Fonts data before the default retrieval process.
27     *
28     * This filter allows short-circuiting the default Google Fonts data retrieval process.
29     * Returning a non-null value from this filter will bypass the default retrieval
30     * and return the filtered value instead.
31     *
32     * @module google-fonts
33     *
34     * @since 13.7
35     *
36     * @param null|array $pre The pre-filtered Google Fonts data, default null.
37     */
38    $pre = apply_filters( 'pre_jetpack_get_google_fonts_data', null );
39    if ( null !== $pre ) {
40        return $pre;
41    }
42
43    $default_google_fonts_api_url        = 'https://fonts.gstatic.com';
44    $jetpack_google_fonts_collection_url = 'https://s0.wp.com/i/font-collections/jetpack-google-fonts.json';
45    $cache_key                           = 'jetpack_google_fonts_' . md5( $jetpack_google_fonts_collection_url );
46    $data                                = get_transient( $cache_key );
47    if ( $data === false ) {
48        $response = wp_remote_get( $jetpack_google_fonts_collection_url );
49        if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
50            return null;
51        }
52
53        $data = json_decode( wp_remote_retrieve_body( $response ), true );
54        if ( $data === null ) {
55            return null;
56        }
57
58        set_transient( $cache_key, $data, DAY_IN_SECONDS );
59    }
60
61    // Replace the google fonts api url if the custom one is provided.
62    $custom_google_fonts_api_url = \esc_url(
63        /**
64         * Filters the Google Fonts API URL.
65         *
66         * @module google-fonts
67         *
68         * @since 12.8
69         *
70         * @param string $url The Google Fonts API URL.
71         */
72        apply_filters( 'jetpack_google_fonts_api_url', $default_google_fonts_api_url )
73    );
74    if ( $custom_google_fonts_api_url !== $default_google_fonts_api_url ) {
75        foreach ( $data['fontFamilies'] as &$font_family ) {
76            foreach ( $font_family['fontFace'] as &$font_face ) {
77                $font_face['src'] = str_replace(
78                    $default_google_fonts_api_url,
79                    $custom_google_fonts_api_url,
80                    $font_face['src']
81                );
82            }
83        }
84    }
85
86    if ( is_array( $data ) && is_array( $data['fontFamilies'] ) ) {
87        return $data;
88    }
89}
90
91/**
92 * Gets the map of the available Google Fonts
93 *
94 * @param array $google_fonts_data The collection data of the Google Fonts.
95 * @return array The map of the the available Google Fonts.
96 */
97function jetpack_get_available_google_fonts_map( $google_fonts_data ) {
98    $jetpack_google_fonts_list = array_map(
99        function ( $font_family ) {
100            return $font_family['name'];
101        },
102        $google_fonts_data['fontFamilies']
103    );
104
105    /**
106     * Curated list of Google Fonts.
107     *
108     * @module google-fonts
109     *
110     * @since 10.8
111     *
112     * @param array $fonts_to_register Array of Google Font names to register.
113     */
114    $google_font_list           = apply_filters( 'jetpack_google_fonts_list', $jetpack_google_fonts_list );
115    $available_google_fonts_map = array();
116
117    foreach ( $google_font_list as $google_font ) {
118        $available_google_fonts_map[ $google_font ] = true;
119    }
120
121    return $available_google_fonts_map;
122}
123
124/**
125 * Whether the request URL is a REST route.
126 *
127 * Theme JSON can resolve before REST_REQUEST is defined, and the Accept header also matches front-end requests.
128 *
129 * @since 16.3
130 *
131 * @return bool
132 */
133function jetpack_google_fonts_is_rest_url() {
134    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only used to classify the request.
135    if ( isset( $_GET['rest_route'] ) ) {
136        return true;
137    }
138    $path = (string) wp_parse_url( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '', PHP_URL_PATH ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Parsed for path comparison only.
139    return str_contains( $path, '/' . rest_get_url_prefix() . '/' );
140}
141
142/**
143 * Whether catalogue faces are needed in theme JSON for an editor or API request.
144 *
145 * @since 16.3
146 *
147 * @return bool
148 */
149function jetpack_google_fonts_load_font_faces() {
150    $load_font_faces = is_admin()
151        || ( defined( 'REST_REQUEST' ) && REST_REQUEST )
152        || jetpack_google_fonts_is_rest_url()
153        || ( defined( 'WP_CLI' ) && WP_CLI );
154
155    /**
156     * Filters whether catalogue font faces are loaded into theme JSON.
157     *
158     * When true, the catalogue's files reach theme JSON and the native font printer prints all of them.
159     *
160     * @module google-fonts
161     *
162     * @since 16.3
163     *
164     * @param bool $load_font_faces Whether to load catalogue font faces. Default true for editor, API and CLI requests.
165     */
166    return (bool) apply_filters( 'jetpack_google_fonts_load_font_faces', $load_font_faces );
167}
168
169/**
170 * Register google fonts to the theme json data
171 *
172 * @param WP_Theme_JSON_Data $theme_json The theme json data of core.
173 * @return WP_Theme_JSON_Data The theme json data with registered google fonts.
174 */
175function jetpack_register_google_fonts_to_theme_json( $theme_json ) {
176    $google_fonts_data = jetpack_get_google_fonts_data();
177    if ( ! $google_fonts_data ) {
178        return $theme_json;
179    }
180
181    $available_google_fonts_map = jetpack_get_available_google_fonts_map( $google_fonts_data );
182    $google_fonts_families      = array_values(
183        array_filter(
184            $google_fonts_data['fontFamilies'],
185            function ( $google_fonts_family ) use ( $available_google_fonts_map ) {
186                $name = $google_fonts_family['name'];
187                return $available_google_fonts_map[ $name ] ?? false;
188            }
189        )
190    );
191
192    $raw_data = $theme_json->get_data();
193    $origin   = 'default';
194    if ( empty( $raw_data['settings']['typography']['fontFamilies'][ $origin ] ) ) {
195        $raw_data['settings']['typography']['fontFamilies'][ $origin ] = array();
196    }
197
198    $load_font_faces = jetpack_google_fonts_load_font_faces();
199    foreach ( $google_fonts_families as $font_family ) {
200        if ( ! $load_font_faces ) {
201            unset( $font_family['fontFace'] );
202        }
203        $raw_data['settings']['typography']['fontFamilies'][ $origin ][] = $font_family;
204    }
205
206    $theme_json_class = get_class( $theme_json );
207    return new $theme_json_class( $raw_data, $origin );
208}
209
210add_filter( 'wp_theme_json_data_default', 'jetpack_register_google_fonts_to_theme_json' );
211
212/**
213 * Filter out the deprecated font families that are from the jetpack-google-fonts provider.
214 *
215 * @param array[] $font_families The font families.
216 * @return array[] The filtered font families.
217 */
218function jetpack_google_fonts_filter_out_deprecated_font_data( $font_families ) {
219    return array_values(
220        array_filter(
221            $font_families,
222            function ( $font_family ) {
223                $has_deprecated_google_fonts_data = false;
224
225                if ( isset( $font_family['fontFace'] ) ) {
226                    foreach ( $font_family['fontFace'] as $font_face ) {
227                        $provider = $font_face['provider'] ?? '';
228                        if ( $provider === 'jetpack-google-fonts' ) {
229                            $has_deprecated_google_fonts_data = true;
230                            break;
231                        }
232                    }
233                }
234
235                return ! $has_deprecated_google_fonts_data;
236            }
237        )
238    );
239}
240
241/**
242 * Unregister the deprecated jetpack-google-fonts provider from theme json data that were stored
243 * before we moved to the Font Library.
244 *
245 * @param WP_Theme_JSON_Data $theme_json The theme json data.
246 * @return WP_Theme_JSON_Data The filtered theme json data.
247 */
248function jetpack_unregister_deprecated_google_fonts_from_theme_json_data( $theme_json ) {
249    $raw_data = $theme_json->get_data();
250    $origin   = 'theme';
251    if ( empty( $raw_data['settings']['typography']['fontFamilies'][ $origin ] ) ) {
252        return $theme_json;
253    }
254
255    // Filter out the font definitions that are from the jetpack-google-fonts provider.
256    $raw_data['settings']['typography']['fontFamilies'][ $origin ] = jetpack_google_fonts_filter_out_deprecated_font_data(
257        $raw_data['settings']['typography']['fontFamilies'][ $origin ]
258    );
259
260    $theme_json_class = get_class( $theme_json );
261    return new $theme_json_class( $raw_data, 'custom' );
262}
263
264add_filter( 'wp_theme_json_data_theme', 'jetpack_unregister_deprecated_google_fonts_from_theme_json_data' );
265add_filter( 'wp_theme_json_data_user', 'jetpack_unregister_deprecated_google_fonts_from_theme_json_data' );
266
267/**
268 * Clean up the Google Fonts data if either google fonts module is disabled or Jetpack is disabled.
269 */
270function jetpack_unregister_google_fonts() {
271    $post_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
272
273    // Get user config
274    $user_config          = WP_Theme_JSON_Resolver::get_user_data();
275    $user_config_raw_data = $user_config->get_raw_data();
276    $user_config_raw_data['isGlobalStylesUserThemeJSON'] = true;
277
278    // Prepare data for saving
279    if ( ! empty( $user_config_raw_data['settings']['typography']['fontFamilies']['default'] ) ) {
280        $user_config_raw_data['settings']['typography']['fontFamilies']['default'] = array();
281    }
282
283    if ( ! empty( $user_config_raw_data['settings']['typography']['fontFamilies']['theme'] ) ) {
284        $user_config_raw_data['settings']['typography']['fontFamilies']['theme'] = jetpack_google_fonts_filter_out_deprecated_font_data(
285            $user_config_raw_data['settings']['typography']['fontFamilies']['theme'] // @phan-suppress-current-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument
286        );
287    }
288
289    // Prepare changes
290    $changes               = new stdClass();
291    $changes->ID           = $post_id;
292    $changes->post_content = wp_json_encode( $user_config_raw_data, JSON_UNESCAPED_SLASHES );
293
294    // Update user config
295    wp_update_post( wp_slash( (array) $changes ), true );
296}
297add_action( 'jetpack_deactivate_module_google-fonts', 'jetpack_unregister_google_fonts' );
298
299// Print legacy catalogue faces separately from native theme and user fonts.
300new Jetpack_Google_Font_Face();