Changeset 53282
- Timestamp:
- 04/26/2022 02:46:37 PM (3 years ago)
- Location:
- trunk
- Files:
-
- 16 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/default-filters.php
r53266 r53282 352 352 add_action( 'after_switch_theme', '_wp_sidebars_changed' ); 353 353 add_action( 'wp_print_styles', 'print_emoji_styles' ); 354 add_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); 354 355 355 356 if ( isset( $_GET['replytocom'] ) ) { -
trunk/src/wp-includes/script-loader.php
r53235 r53282 3028 3028 add_action( 'enqueue_block_assets', $callback ); 3029 3029 } 3030 3031 /** 3032 * Runs the theme.json webfonts handler. 3033 * 3034 * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined 3035 * in the `theme.json` for the current selection and style 3036 * variations, validates the font-face properties, generates 3037 * the '@font-face' style declarations, and then enqueues the 3038 * styles for both the editor and front-end. 3039 * 3040 * Design Notes: 3041 * This is not a public API, but rather an internal handler. 3042 * A future public Webfonts API will replace this stopgap code. 3043 * 3044 * This code design is intentional. 3045 * a. It hides the inner-workings. 3046 * b. It does not expose API ins or outs for consumption. 3047 * c. It only works with a theme's `theme.json`. 3048 * 3049 * Why? 3050 * a. To avoid backwards-compatibility issues when 3051 * the Webfonts API is introduced in Core. 3052 * b. To make `fontFace` declarations in `theme.json` work. 3053 * 3054 * @link https://github.com/WordPress/gutenberg/issues/40472 3055 * 3056 * @since 6.0.0 3057 * @access private 3058 */ 3059 function _wp_theme_json_webfonts_handler() { 3060 // Webfonts to be processed. 3061 $registered_webfonts = array(); 3062 3063 /** 3064 * Gets the webfonts from theme.json. 3065 * 3066 * @since 6.0.0 3067 * 3068 * @return array Array of defined webfonts. 3069 */ 3070 $fn_get_webfonts_from_theme_json = static function() { 3071 // Get settings from theme.json. 3072 $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); 3073 3074 // If in the editor, add webfonts defined in variations. 3075 if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { 3076 $variations = WP_Theme_JSON_Resolver::get_style_variations(); 3077 foreach ( $variations as $variation ) { 3078 // Skip if fontFamilies are not defined in the variation. 3079 if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) { 3080 continue; 3081 } 3082 3083 // Initialize the array structure. 3084 if ( empty( $settings['typography'] ) ) { 3085 $settings['typography'] = array(); 3086 } 3087 if ( empty( $settings['typography']['fontFamilies'] ) ) { 3088 $settings['typography']['fontFamilies'] = array(); 3089 } 3090 if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) { 3091 $settings['typography']['fontFamilies']['theme'] = array(); 3092 } 3093 3094 // Combine variations with settings. Remove duplicates. 3095 $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] ); 3096 $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); 3097 } 3098 } 3099 3100 // Bail out early if there are no settings for webfonts. 3101 if ( empty( $settings['typography']['fontFamilies'] ) ) { 3102 return array(); 3103 } 3104 3105 $webfonts = array(); 3106 3107 // Look for fontFamilies. 3108 foreach ( $settings['typography']['fontFamilies'] as $font_families ) { 3109 foreach ( $font_families as $font_family ) { 3110 3111 // Skip if fontFace is not defined. 3112 if ( empty( $font_family['fontFace'] ) ) { 3113 continue; 3114 } 3115 3116 // Skip if fontFace is not an array of webfonts. 3117 if ( ! is_array( $font_family['fontFace'] ) ) { 3118 continue; 3119 } 3120 3121 $webfonts = array_merge( $webfonts, $font_family['fontFace'] ); 3122 } 3123 } 3124 3125 return $webfonts; 3126 }; 3127 3128 /** 3129 * Transforms each 'src' into an URI by replacing 'file:./' 3130 * placeholder from theme.json. 3131 * 3132 * The absolute path to the webfont file(s) cannot be defined in 3133 * theme.json. `file:./` is the placeholder which is replaced by 3134 * the theme's URL path to the theme's root. 3135 * 3136 * @since 6.0.0 3137 * 3138 * @param array $src Webfont file(s) `src`. 3139 * @return array Webfont's `src` in URI. 3140 */ 3141 $fn_transform_src_into_uri = static function( array $src ) { 3142 foreach ( $src as $key => $url ) { 3143 // Tweak the URL to be relative to the theme root. 3144 if ( ! str_starts_with( $url, 'file:./' ) ) { 3145 continue; 3146 } 3147 3148 $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) ); 3149 } 3150 3151 return $src; 3152 }; 3153 3154 /** 3155 * Converts the font-face properties (i.e. keys) into kebab-case. 3156 * 3157 * @since 6.0.0 3158 * 3159 * @param array $font_face Font face to convert. 3160 * @return array Font faces with each property in kebab-case format. 3161 */ 3162 $fn_convert_keys_to_kebab_case = static function( array $font_face ) { 3163 foreach ( $font_face as $property => $value ) { 3164 $kebab_case = _wp_to_kebab_case( $property ); 3165 $font_face[ $kebab_case ] = $value; 3166 if ( $kebab_case !== $property ) { 3167 unset( $font_face[ $property ] ); 3168 } 3169 } 3170 3171 return $font_face; 3172 }; 3173 3174 /** 3175 * Validates a webfont. 3176 * 3177 * @since 6.0.0 3178 * 3179 * @param array $webfont The webfont arguments. 3180 * @return array|false The validated webfont arguments, or false if the webfont is invalid. 3181 */ 3182 $fn_validate_webfont = static function( $webfont ) { 3183 $webfont = wp_parse_args( 3184 $webfont, 3185 array( 3186 'font-family' => '', 3187 'font-style' => 'normal', 3188 'font-weight' => '400', 3189 'font-display' => 'fallback', 3190 'src' => array(), 3191 ) 3192 ); 3193 3194 // Check the font-family. 3195 if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) { 3196 trigger_error( __( 'Webfont font family must be a non-empty string.', 'gutenberg' ) ); 3197 3198 return false; 3199 } 3200 3201 // Check that the `src` property is defined and a valid type. 3202 if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { 3203 trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.', 'gutenberg' ) ); 3204 3205 return false; 3206 } 3207 3208 // Validate the `src` property. 3209 foreach ( (array) $webfont['src'] as $src ) { 3210 if ( ! is_string( $src ) || '' === trim( $src ) ) { 3211 trigger_error( __( 'Each webfont src must be a non-empty string.', 'gutenberg' ) ); 3212 3213 return false; 3214 } 3215 } 3216 3217 // Check the font-weight. 3218 if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { 3219 trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.', 'gutenberg' ) ); 3220 3221 return false; 3222 } 3223 3224 // Check the font-display. 3225 if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'swap' ), true ) ) { 3226 $webfont['font-display'] = 'fallback'; 3227 } 3228 3229 $valid_props = array( 3230 'ascend-override', 3231 'descend-override', 3232 'font-display', 3233 'font-family', 3234 'font-stretch', 3235 'font-style', 3236 'font-weight', 3237 'font-variant', 3238 'font-feature-settings', 3239 'font-variation-settings', 3240 'line-gap-override', 3241 'size-adjust', 3242 'src', 3243 'unicode-range', 3244 ); 3245 3246 foreach ( $webfont as $prop => $value ) { 3247 if ( ! in_array( $prop, $valid_props, true ) ) { 3248 unset( $webfont[ $prop ] ); 3249 } 3250 } 3251 3252 return $webfont; 3253 }; 3254 3255 /** 3256 * Registers webfonts declared in theme.json. 3257 * 3258 * @since 6.0.0 3259 * 3260 * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). 3261 * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json. 3262 * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case. 3263 * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json. 3264 */ 3265 $fn_register_webfonts = static function() use ( &$registered_webfonts, $fn_get_webfonts_from_theme_json, $fn_convert_keys_to_kebab_case, $fn_validate_webfont, $fn_transform_src_into_uri ) { 3266 $registered_webfonts = array(); 3267 3268 foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) { 3269 if ( ! is_array( $webfont ) ) { 3270 continue; 3271 } 3272 3273 $webfont = $fn_convert_keys_to_kebab_case( $webfont ); 3274 3275 $webfont = $fn_validate_webfont( $webfont ); 3276 3277 $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] ); 3278 3279 // Skip if not valid. 3280 if ( empty( $webfont ) ) { 3281 continue; 3282 } 3283 3284 $registered_webfonts[] = $webfont; 3285 } 3286 }; 3287 3288 /** 3289 * Orders 'src' items to optimize for browser support. 3290 * 3291 * @since 6.0.0 3292 * 3293 * @param array $webfont Webfont to process. 3294 * @return array Ordered `src` items. 3295 */ 3296 $fn_order_src = static function( array $webfont ) { 3297 $src = array(); 3298 $src_ordered = array(); 3299 3300 foreach ( $webfont['src'] as $url ) { 3301 // Add data URIs first. 3302 if ( str_starts_with( trim( $url ), 'data:' ) ) { 3303 $src_ordered[] = array( 3304 'url' => $url, 3305 'format' => 'data', 3306 ); 3307 continue; 3308 } 3309 $format = pathinfo( $url, PATHINFO_EXTENSION ); 3310 $src[ $format ] = $url; 3311 } 3312 3313 // Add woff2. 3314 if ( ! empty( $src['woff2'] ) ) { 3315 $src_ordered[] = array( 3316 'url' => sanitize_url( $src['woff2'] ), 3317 'format' => 'woff2', 3318 ); 3319 } 3320 3321 // Add woff. 3322 if ( ! empty( $src['woff'] ) ) { 3323 $src_ordered[] = array( 3324 'url' => sanitize_url( $src['woff'] ), 3325 'format' => 'woff', 3326 ); 3327 } 3328 3329 // Add ttf. 3330 if ( ! empty( $src['ttf'] ) ) { 3331 $src_ordered[] = array( 3332 'url' => sanitize_url( $src['ttf'] ), 3333 'format' => 'truetype', 3334 ); 3335 } 3336 3337 // Add eot. 3338 if ( ! empty( $src['eot'] ) ) { 3339 $src_ordered[] = array( 3340 'url' => sanitize_url( $src['eot'] ), 3341 'format' => 'embedded-opentype', 3342 ); 3343 } 3344 3345 // Add otf. 3346 if ( ! empty( $src['otf'] ) ) { 3347 $src_ordered[] = array( 3348 'url' => sanitize_url( $src['otf'] ), 3349 'format' => 'opentype', 3350 ); 3351 } 3352 $webfont['src'] = $src_ordered; 3353 3354 return $webfont; 3355 }; 3356 3357 /** 3358 * Compiles the 'src' into valid CSS. 3359 * 3360 * @since 6.0.0 3361 * 3362 * @param string $font_family Font family. 3363 * @param array $value Value to process. 3364 * @return string The CSS. 3365 */ 3366 $fn_compile_src = static function( $font_family, array $value ) { 3367 $src = "local($font_family)"; 3368 3369 foreach ( $value as $item ) { 3370 3371 if ( 3372 str_starts_with( $item['url'], site_url() ) || 3373 str_starts_with( $item['url'], home_url() ) 3374 ) { 3375 $item['url'] = wp_make_link_relative( $item['url'] ); 3376 } 3377 3378 $src .= ( 'data' === $item['format'] ) 3379 ? ", url({$item['url']})" 3380 : ", url('{$item['url']}') format('{$item['format']}')"; 3381 } 3382 3383 return $src; 3384 }; 3385 3386 /** 3387 * Compiles the font variation settings. 3388 * 3389 * @since 6.0.0 3390 * 3391 * @param array $font_variation_settings Array of font variation settings. 3392 * @return string The CSS. 3393 */ 3394 $fn_compile_variations = static function( array $font_variation_settings ) { 3395 $variations = ''; 3396 3397 foreach ( $font_variation_settings as $key => $value ) { 3398 $variations .= "$key $value"; 3399 } 3400 3401 return $variations; 3402 }; 3403 3404 /** 3405 * Builds the font-family's CSS. 3406 * 3407 * @since 6.0.0 3408 * 3409 * @uses $fn_compile_src To run the function that compiles the src. 3410 * @uses $fn_compile_variations To run the function that compiles the variations. 3411 * 3412 * @param array $webfont Webfont to process. 3413 * @return string This font-family's CSS. 3414 */ 3415 $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) { 3416 $css = ''; 3417 3418 // Wrap font-family in quotes if it contains spaces. 3419 if ( 3420 str_contains( $webfont['font-family'], ' ' ) && 3421 ! str_contains( $webfont['font-family'], '"' ) && 3422 ! str_contains( $webfont['font-family'], "'" ) 3423 ) { 3424 $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; 3425 } 3426 3427 foreach ( $webfont as $key => $value ) { 3428 /* 3429 * Skip "provider", since it's for internal API use, 3430 * and not a valid CSS property. 3431 */ 3432 if ( 'provider' === $key ) { 3433 continue; 3434 } 3435 3436 // Compile the "src" parameter. 3437 if ( 'src' === $key ) { 3438 $value = $fn_compile_src( $webfont['font-family'], $value ); 3439 } 3440 3441 // If font-variation-settings is an array, convert it to a string. 3442 if ( 'font-variation-settings' === $key && is_array( $value ) ) { 3443 $value = $fn_compile_variations( $value ); 3444 } 3445 3446 if ( ! empty( $value ) ) { 3447 $css .= "$key:$value;"; 3448 } 3449 } 3450 3451 return $css; 3452 }; 3453 3454 /** 3455 * Gets the '@font-face' CSS styles for locally-hosted font files. 3456 * 3457 * @since 6.0.0 3458 * 3459 * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). 3460 * @uses $fn_order_src To run the function that orders the src. 3461 * @uses $fn_build_font_face_css To run the function that builds the font-face CSS. 3462 * 3463 * @return string The `@font-face` CSS. 3464 */ 3465 $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) { 3466 $css = ''; 3467 3468 foreach ( $registered_webfonts as $webfont ) { 3469 // Order the webfont's `src` items to optimize for browser support. 3470 $webfont = $fn_order_src( $webfont ); 3471 3472 // Build the @font-face CSS for this webfont. 3473 $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}'; 3474 } 3475 3476 return $css; 3477 }; 3478 3479 /** 3480 * Generates and enqueues webfonts styles. 3481 * 3482 * @since 6.0.0 3483 * 3484 * @uses $fn_get_css To run the function that gets the CSS. 3485 */ 3486 $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) { 3487 // Generate the styles. 3488 $styles = $fn_get_css(); 3489 3490 // Bail out if there are no styles to enqueue. 3491 if ( '' === $styles ) { 3492 return; 3493 } 3494 3495 // Enqueue the stylesheet. 3496 wp_register_style( 'wp-webfonts', '' ); 3497 wp_enqueue_style( 'wp-webfonts' ); 3498 3499 // Add the styles to the stylesheet. 3500 wp_add_inline_style( 'wp-webfonts', $styles ); 3501 }; 3502 3503 /** 3504 * Generates and enqueues editor styles. 3505 * 3506 * @since 6.0.0 3507 * 3508 * @uses $fn_get_css To run the function that gets the CSS. 3509 */ 3510 $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) { 3511 // Generate the styles. 3512 $styles = $fn_get_css(); 3513 3514 // Bail out if there are no styles to enqueue. 3515 if ( '' === $styles ) { 3516 return; 3517 } 3518 3519 wp_add_inline_style( 'wp-block-library', $styles ); 3520 }; 3521 3522 add_action( 'wp_loaded', $fn_register_webfonts ); 3523 add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles ); 3524 add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles ); 3525 } -
trunk/tests/phpunit/tests/theme/themeDir.php
r52399 r53282 166 166 'Block Theme [0.4.0]', 167 167 'Block Theme [1.0.0] in subdirectory', 168 'Webfonts theme', 169 'Empty `fontFace` in theme.json - no webfonts defined', 168 170 ); 169 171
Note: See TracChangeset
for help on using the changeset viewer.