tring( $webfont['font-family'] ) ) { trigger_error( __( 'Webfont font family must be a non-empty string.' ) ); return false; } // Check that the `src` property is defined and a valid type. if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) ); return false; } // Validate the `src` property. foreach ( (array) $webfont['src'] as $src ) { if ( ! is_string( $src ) || '' === trim( $src ) ) { trigger_error( __( 'Each webfont src must be a non-empty string.' ) ); return false; } } // Check the font-weight. if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) ); return false; } // Check the font-display. if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'swap' ), true ) ) { $webfont['font-display'] = 'fallback'; } $valid_props = array( 'ascend-override', 'descend-override', 'font-display', 'font-family', 'font-stretch', 'font-style', 'font-weight', 'font-variant', 'font-feature-settings', 'font-variation-settings', 'line-gap-override', 'size-adjust', 'src', 'unicode-range', ); foreach ( $webfont as $prop => $value ) { if ( ! in_array( $prop, $valid_props, true ) ) { unset( $webfont[ $prop ] ); } } return $webfont; }; /** * Registers webfonts declared in theme.json. * * @since 6.0.0 * * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json. * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case. * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json. */ $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 ) { $registered_webfonts = array(); foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) { if ( ! is_array( $webfont ) ) { continue; } $webfont = $fn_convert_keys_to_kebab_case( $webfont ); $webfont = $fn_validate_webfont( $webfont ); $webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] ); // Skip if not valid. if ( empty( $webfont ) ) { continue; } $registered_webfonts[] = $webfont; } }; /** * Orders 'src' items to optimize for browser support. * * @since 6.0.0 * * @param array $webfont Webfont to process. * @return array Ordered `src` items. */ $fn_order_src = static function( array $webfont ) { $src = array(); $src_ordered = array(); foreach ( $webfont['src'] as $url ) { // Add data URIs first. if ( str_starts_with( trim( $url ), 'data:' ) ) { $src_ordered[] = array( 'url' => $url, 'format' => 'data', ); continue; } $format = pathinfo( $url, PATHINFO_EXTENSION ); $src[ $format ] = $url; } // Add woff2. if ( ! empty( $src['woff2'] ) ) { $src_ordered[] = array( 'url' => sanitize_url( $src['woff2'] ), 'format' => 'woff2', ); } // Add woff. if ( ! empty( $src['woff'] ) ) { $src_ordered[] = array( 'url' => sanitize_url( $src['woff'] ), 'format' => 'woff', ); } // Add ttf. if ( ! empty( $src['ttf'] ) ) { $src_ordered[] = array( 'url' => sanitize_url( $src['ttf'] ), 'format' => 'truetype', ); } // Add eot. if ( ! empty( $src['eot'] ) ) { $src_ordered[] = array( 'url' => sanitize_url( $src['eot'] ), 'format' => 'embedded-opentype', ); } // Add otf. if ( ! empty( $src['otf'] ) ) { $src_ordered[] = array( 'url' => sanitize_url( $src['otf'] ), 'format' => 'opentype', ); } $webfont['src'] = $src_ordered; return $webfont; }; /** * Compiles the 'src' into valid CSS. * * @since 6.0.0 * @since 6.2.0 Removed local() CSS. * * @param string $font_family Font family. * @param array $value Value to process. * @return string The CSS. */ $fn_compile_src = static function( $font_family, array $value ) { $src = ''; foreach ( $value as $item ) { $src .= ( 'data' === $item['format'] ) ? ", url({$item['url']})" : ", url('{$item['url']}') format('{$item['format']}')"; } $src = ltrim( $src, ', ' ); return $src; }; /** * Compiles the font variation settings. * * @since 6.0.0 * * @param array $font_variation_settings Array of font variation settings. * @return string The CSS. */ $fn_compile_variations = static function( array $font_variation_settings ) { $variations = ''; foreach ( $font_variation_settings as $key => $value ) { $variations .= "$key $value"; } return $variations; }; /** * Builds the font-family's CSS. * * @since 6.0.0 * * @uses $fn_compile_src To run the function that compiles the src. * @uses $fn_compile_variations To run the function that compiles the variations. * * @param array $webfont Webfont to process. * @return string This font-family's CSS. */ $fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) { $css = ''; // Wrap font-family in quotes if it contains spaces. if ( str_contains( $webfont['font-family'], ' ' ) && ! str_contains( $webfont['font-family'], '"' ) && ! str_contains( $webfont['font-family'], "'" ) ) { $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; } foreach ( $webfont as $key => $value ) { /* * Skip "provider", since it's for internal API use, * and not a valid CSS property. */ if ( 'provider' === $key ) { continue; } // Compile the "src" parameter. if ( 'src' === $key ) { $value = $fn_compile_src( $webfont['font-family'], $value ); } // If font-variation-settings is an array, convert it to a string. if ( 'font-variation-settings' === $key && is_array( $value ) ) { $value = $fn_compile_variations( $value ); } if ( ! empty( $value ) ) { $css .= "$key:$value;"; } } return $css; }; /** * Gets the '@font-face' CSS styles for locally-hosted font files. * * @since 6.0.0 * * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference). * @uses $fn_order_src To run the function that orders the src. * @uses $fn_build_font_face_css To run the function that builds the font-face CSS. * * @return string The `@font-face` CSS. */ $fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) { $css = ''; foreach ( $registered_webfonts as $webfont ) { // Order the webfont's `src` items to optimize for browser support. $webfont = $fn_order_src( $webfont ); // Build the @font-face CSS for this webfont. $css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}'; } return $css; }; /** * Generates and enqueues webfonts styles. * * @since 6.0.0 * * @uses $fn_get_css To run the function that gets the CSS. */ $fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) { // Generate the styles. $styles = $fn_get_css(); // Bail out if there are no styles to enqueue. if ( '' === $styles ) { return; } // Enqueue the stylesheet. wp_register_style( 'wp-webfonts', '' ); wp_enqueue_style( 'wp-webfonts' ); // Add the styles to the stylesheet. wp_add_inline_style( 'wp-webfonts', $styles ); }; /** * Generates and enqueues editor styles. * * @since 6.0.0 * * @uses $fn_get_css To run the function that gets the CSS. */ $fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) { // Generate the styles. $styles = $fn_get_css(); // Bail out if there are no styles to enqueue. if ( '' === $styles ) { return; } wp_add_inline_style( 'wp-block-library', $styles ); }; add_action( 'wp_loaded', $fn_register_webfonts ); add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles ); add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles ); } /** * Loads classic theme styles on classic themes in the frontend. * * This is needed for backwards compatibility for button blocks specifically. * * @since 6.1.0 */ function wp_enqueue_classic_theme_styles() { if ( ! wp_theme_has_theme_json() ) { $suffix = wp_scripts_get_suffix(); wp_register_style( 'classic-theme-styles', '/' . WPINC . "/css/classic-themes$suffix.css" ); wp_style_add_data( 'classic-theme-styles', 'path', ABSPATH . WPINC . "/css/classic-themes$suffix.css" ); wp_enqueue_style( 'classic-theme-styles' ); } } /** * Loads classic theme styles on classic themes in the editor. * * This is needed for backwards compatibility for button blocks specifically. * * @since 6.1.0 * * @param array $editor_settings The array of editor settings. * @return array A filtered array of editor settings. */ function wp_add_editor_classic_theme_styles( $editor_settings ) { if ( wp_theme_has_theme_json() ) { return $editor_settings; } $suffix = wp_scripts_get_suffix(); $classic_theme_styles = ABSPATH . WPINC . "/css/classic-themes$suffix.css"; /* * This follows the pattern of get_block_editor_theme_styles, * but we can't use get_block_editor_theme_styles directly as it * only handles external files or theme files. */ $classic_theme_styles_settings = array( 'css' => file_get_contents( $classic_theme_styles ), '__unstableType' => 'core', 'isGlobalStyles' => false, ); // Add these settings to the start of the array so that themes can override them. array_unshift( $editor_settings['styles'], $classic_theme_styles_settings ); return $editor_settings; }