$property_value ) {
if ( is_string( $property_value ) ) {
$value[ $property ] = $clean( $property_value );
} elseif ( null !== $property_value && ! is_scalar( $property_value ) ) {
unset( $value[ $property ] );
}
}
return $value;
}
/**
* Clean Typography values in ACF block data before the block is saved
*
* ACF runs update_value() for block data sent by the block editor form, but data
* already in ACF's stored format (e.g. typed in the Code editor) is saved as is.
*
* acft_sanitize_block_data()
*
* @since 3.3.0
* @param array $attrs Block attributes, see the acf/pre_save_block filter.
* @return array
*/
add_filter( 'acf/pre_save_block', 'acft_sanitize_block_data' );
function acft_sanitize_block_data( $attrs ) {
if ( is_array( $attrs ) && isset( $attrs['data'] ) && is_array( $attrs['data'] ) ) {
$attrs['data'] = acft_sanitize_typography_values_in( $attrs['data'] );
}
return $attrs;
}
/**
* Clean the Typography values in a list of field values, at any depth
*
* Each value is judged on its own, never the list as a whole: block data is a flat
* name => value list, so a plain field named e.g. font_size sits beside the others.
* A value whose field reference (the _name entry beside it) is a Typography field
* is cleaned. A value with no usable reference that has a Typography property key
* can still be read by its name, so its Typography properties are cleaned; the rest
* of it may belong to another field and is only searched. Anything else is searched.
*
* acft_sanitize_typography_values_in()
*
* @since 3.3.0
* @param array $data Block data, or part of it.
* @return array
*/
function acft_sanitize_typography_values_in( $data ) {
$labels = acft_typography_property_labels();
foreach ( $data as $key => $child ) {
if ( ! is_array( $child ) ) {
continue;
}
$type = acft_block_value_field_type( $data, $key );
if ( 'Typography' === $type ) {
$data[ $key ] = acft_sanitize_typography_value( $child );
} elseif ( null === $type && array_intersect_key( $child, $labels ) ) {
$properties = array_filter( array_intersect_key( $child, $labels ), 'is_string' );
$data[ $key ] = array_replace( acft_sanitize_typography_values_in( $child ), acft_sanitize_typography_value( $properties ) );
} else {
$data[ $key ] = acft_sanitize_typography_values_in( $child );
}
}
return $data;
}
/**
* Get the field type named by a value's field reference in block data
*
* acft_block_value_field_type()
*
* @since 3.3.0
* @param array $data Block data, or part of it.
* @param int|string $key Key of the value.
* @return string|null Field type, or null when there is no known field key beside the value.
*/
function acft_block_value_field_type( $data, $key ) {
$reference = isset( $data[ '_' . $key ] ) ? $data[ '_' . $key ] : '';
// only a field key: acf_get_field() also finds fields by name
if ( ! function_exists( 'acf_is_field_key' ) || ! acf_is_field_key( $reference ) ) {
return null;
}
$field = acf_get_field( $reference );
return is_array( $field ) && isset( $field['type'] ) ? (string) $field['type'] : null;
}
/**
* Check whether a Typography value has a property filled in
*
* 0 is a real value (e.g. letter spacing), so only a missing, empty or
* non-scalar property counts as not filled in.
*
* acft_typography_has_property()
*
* @since 3.3.0
* @param mixed $value Field value.
* @param string $property Property name, e.g. font_size.
* @return bool
*/
function acft_typography_has_property( $value, $property ) {
return is_array( $value ) && isset( $value[ $property ] ) && is_scalar( $value[ $property ] ) && '' !== (string) $value[ $property ];
}
/**
* Get the translated labels of all typography properties, in display order.
*
* @since 3.3.0
* @return array Property name => label.
*/
function acft_typography_property_labels() {
return array(
'font_size' => __( 'Font Size', 'acf-typography-field' ),
'font_family' => __( 'Font Family', 'acf-typography-field' ),
'font_weight' => __( 'Font Weight', 'acf-typography-field' ),
'font_style' => __( 'Font Style', 'acf-typography-field' ),
'font_variant' => __( 'Font Variant', 'acf-typography-field' ),
'font_stretch' => __( 'Font Stretch', 'acf-typography-field' ),
'line_height' => __( 'Line Height', 'acf-typography-field' ),
'letter_spacing' => __( 'Letter Spacing', 'acf-typography-field' ),
'text_align' => __( 'Text Align', 'acf-typography-field' ),
'text_color' => __( 'Text Color', 'acf-typography-field' ),
'text_decoration' => __( 'Text Decoration', 'acf-typography-field' ),
'text_transform' => __( 'Text Transform', 'acf-typography-field' ),
);
}
/**
* Get the translated label for a typography property.
*
* @param string $property Property name.
* @return string
*/
function acft_typography_property_label( $property ) {
$labels = acft_typography_property_labels();
return isset( $labels[ $property ] ) ? $labels[ $property ] : ucwords( str_replace( '_', ' ', $property ) );
}
/**
* Read a list of property names from a field setting
*
* Display and required properties are saved as an array by the field group editor,
* but a field registered in PHP or JSON can give a single name as a string, or an
* empty string. Unknown names are dropped.
*
* acft_typography_property_list()
*
* @since 3.3.0
* @param mixed $setting e.g. $field['display_properties'].
* @return string[] Property names, in the setting's order.
*/
function acft_typography_property_list( $setting ) {
if ( ! is_array( $setting ) && ! is_string( $setting ) ) {
return array();
}
return array_values( array_intersect( (array) $setting, array_keys( acft_typography_property_labels() ) ) );
}
/**
* Font Family choices for the field group's default, keeping a saved default missing from them
*
* A default Google font is missing while the Google list is empty (no key yet, or
* the fetch failed). Without it the select would fall back to its first choice and
* saving the field group would replace the default.
*
* acft_typography_font_family_choices()
*
* @since 3.3.0
* @param array $choices Font Family choices.
* @param mixed $current_default Saved default font family.
* @return array
*/
function acft_typography_font_family_choices( $choices, $current_default ) {
if ( is_string( $current_default ) && '' !== $current_default && ! isset( $choices[ $current_default ] ) ) {
$choices = array( $current_default => $current_default ) + $choices;
}
return $choices;
}
/**
* Print the ';
}
foreach ( (array) $choices as $opt ) {
echo ''; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
}
}
/**
* Correct a font family saved with one of the two old list keys
*
* Up to 3.2.x two web-safe keys were missing a space. A field group stores its
* default as the key, so field groups saved before 3.3.0 can still hold the old
* form. Post values store the option text, which was always correct.
*
* acft_typography_fix_font_family_key()
*
* @since 3.3.0
* @param mixed $font_family Saved font family.
* @return mixed
*/
function acft_typography_fix_font_family_key( $font_family ) {
$old_keys = array(
'Tahoma,Geneva, sans-serif' => 'Tahoma, Geneva, sans-serif',
'"Times New Roman", Times,serif' => '"Times New Roman", Times, serif',
);
return is_string( $font_family ) && isset( $old_keys[ $font_family ] ) ? $old_keys[ $font_family ] : $font_family;
}
/**
* Keep text changes made through the old text domains
*
* Up to 3.2.3 the strings used 'acf-typography' (and 'acf' for the settings field
* label). A theme may have changed them with a gettext filter or its own .mo file
* under those domains. Their version wins over a translation in the new domain.
* Removed in 4.0.
*
* acft_legacy_text_domain()
*
* @since 3.3.0
* @param string $translation Text translated in the acf-typography-field domain.
* @param string $text Original text.
* @return string
*/
add_filter( 'gettext_acf-typography-field', 'acft_legacy_text_domain', 10, 2 );
function acft_legacy_text_domain( $translation, $text ) {
// phpcs:ignore WordPress.WP.I18n -- deliberate lookup of a variable string in the old domain.
$legacy = translate( $text, 'acf-typography' );
// only this string used ACF's domain
if ( $legacy === $text && 'Google Fonts Key' === $text ) {
// phpcs:ignore WordPress.WP.I18n -- deliberate lookup in the old domain.
$legacy = translate( $text, 'acf' );
}
return $legacy !== $text ? $legacy : $translation;
}
/**
* Get the Google Fonts API key saved on the settings page
*
* acft_get_saved_google_api_key()
*
* @since 3.3.0
* @return string Empty string when no key is saved.
*/
function acft_get_saved_google_api_key() {
$acft_options = get_option( 'acft_settings' );
if ( is_array( $acft_options ) && ! empty( $acft_options['google_key'] ) && is_string( $acft_options['google_key'] ) ) {
return $acft_options['google_key'];
}
return '';
}
/**
* Remember whether this plugin defined the legacy YOUR_API_KEY constant itself
*
* acft_legacy_api_key_is_ours()
*
* @since 3.3.0
* @param bool|null $set Pass true once the plugin has defined the constant.
* @return bool
*/
function acft_legacy_api_key_is_ours( $set = null ) {
static $ours = false;
if ( null !== $set ) {
$ours = (bool) $set;
}
return $ours;
}
/**
* Where the Google Fonts API key comes from
*
* Order: ACFT_GOOGLE_API_KEY constant, the legacy YOUR_API_KEY constant (when
* something other than this plugin defined it), then the settings page.
*
* acft_google_api_key_source()
*
* @since 3.3.0
* @return string 'constant', 'legacy_constant', 'option' or '' when no key is set.
*/
function acft_google_api_key_source() {
if ( defined( 'ACFT_GOOGLE_API_KEY' ) && is_string( ACFT_GOOGLE_API_KEY ) && '' !== ACFT_GOOGLE_API_KEY ) {
return 'constant';
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- legacy constant, removed in 4.0.
if ( defined( 'YOUR_API_KEY' ) && ! acft_legacy_api_key_is_ours() && is_string( YOUR_API_KEY ) && '' !== YOUR_API_KEY ) {
return 'legacy_constant';
}
if ( '' !== acft_get_saved_google_api_key() ) {
return 'option';
}
return '';
}
/**
* Get the Google Fonts API key
*
* acft_get_google_api_key()
*
* @since 3.3.0
* @return string Empty string when no key is set.
*/
function acft_get_google_api_key() {
switch ( acft_google_api_key_source() ) {
case 'constant':
return ACFT_GOOGLE_API_KEY;
case 'legacy_constant':
return YOUR_API_KEY; // deprecation notice: acft_legacy_api_key_notice()
case 'option':
return acft_get_saved_google_api_key();
}
return '';
}
/**
* Tell developers that the YOUR_API_KEY constant is deprecated
*
* A debug notice (WP_DEBUG only), once per admin page view. Sent from admin_notices,
* which runs only on pages that are printed: a notice shown by WP_DEBUG_DISPLAY during
* a request that redirects (e.g. saving the settings or a field group), or in a REST or
* admin-ajax response, would break it.
*
* acft_legacy_api_key_notice()
*
* @since 3.3.0
*/
add_action( 'admin_notices', 'acft_legacy_api_key_notice' );
function acft_legacy_api_key_notice() {
if ( 'legacy_constant' !== acft_google_api_key_source() || ! current_user_can( 'manage_options' ) ) {
return;
}
_doing_it_wrong(
'YOUR_API_KEY',
esc_html__( 'The YOUR_API_KEY constant is deprecated and will be removed in 4.0. Define ACFT_GOOGLE_API_KEY instead.', 'acf-typography-field' ),
'3.3.0'
);
}
/**
* Define the legacy YOUR_API_KEY constant for code that still reads it
*
* acft_maybe_define_legacy_api_key()
*
* @since 3.3.0
*/
function acft_maybe_define_legacy_api_key() {
if ( defined( 'YOUR_API_KEY' ) ) {
return;
}
$api_key = acft_get_google_api_key();
if ( '' === $api_key ) {
return;
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- kept for backward compatibility until 4.0.
define( 'YOUR_API_KEY', $api_key );
acft_legacy_api_key_is_ours( true );
}
/**
* Get the cached Google Fonts list and its fetch status
*
* Stored in a non-autoloaded option, so it survives plugin updates and
* works on hosts where the plugin folder is read-only.
*
* acft_get_google_fonts_cache()
*
* @since 3.3.0
* @return array families (string[]), fetched (int), attempted (int), error (string), key_hash (string).
*/
function acft_get_google_fonts_cache() {
$cache = get_option( 'acft_google_fonts' );
if ( ! is_array( $cache ) ) {
$cache = array();
}
$cache = wp_parse_args(
$cache,
array(
'families' => array(),
'fetched' => 0,
'attempted' => 0,
'error' => '',
'key_hash' => '',
)
);
if ( ! is_array( $cache['families'] ) ) {
$cache['families'] = array();
}
return $cache;
}
/**
* Fetch the Google Fonts family list from the Google Fonts Developer API
*
* acft_fetch_google_fonts()
*
* @since 3.3.0
* @param string $api_key Google API key.
* @return string[]|WP_Error Family names, or the reason the request failed.
*/
function acft_fetch_google_fonts( $api_key ) {
$response = wp_remote_get(
'https://www.googleapis.com/webfonts/v1/webfonts?key=' . rawurlencode( $api_key ),
array( 'timeout' => 10 )
);
if ( is_wp_error( $response ) ) {
return $response;
}
$code = (int) wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 !== $code ) {
$message = isset( $body['error']['message'] ) && is_string( $body['error']['message'] ) ? $body['error']['message'] : sprintf( 'HTTP %d', $code );
return new WP_Error( 'acft_google_fonts_http', $message );
}
if ( ! isset( $body['items'] ) || ! is_array( $body['items'] ) ) {
return new WP_Error( 'acft_google_fonts_response', __( 'Unexpected response from the Google Fonts API.', 'acf-typography-field' ) );
}
$families = array();
foreach ( $body['items'] as $item ) {
if ( isset( $item['family'] ) && is_string( $item['family'] ) ) {
$families[] = $item['family'];
}
}
// an empty list would replace the last good one and hold for a week
if ( ! $families ) {
return new WP_Error( 'acft_google_fonts_response', __( 'Unexpected response from the Google Fonts API.', 'acf-typography-field' ) );
}
return $families;
}
/**
* Whether the cached Google Fonts list should be fetched again
*
* True when the list is older than a week, the API key changed, or the last
* request failed. After a failed request it waits an hour before trying again,
* keeping the old list.
*
* acft_google_fonts_needs_refresh()
*
* @since 3.3.0
* @param array $cache From acft_get_google_fonts_cache().
* @param string $key_hash md5 of the current API key.
* @param int $now Current time.
* @return bool
*/
function acft_google_fonts_needs_refresh( $cache, $key_hash, $now ) {
// an error counts as stale, so a failed request is retried even when an older list is still fresh
$stale = $cache['key_hash'] !== $key_hash || $cache['fetched'] < $now - WEEK_IN_SECONDS || '' !== $cache['error'];
if ( ! $stale ) {
return false;
}
// a request for this key failed within the hour
return ! ( '' !== $cache['error'] && $cache['key_hash'] === $key_hash && $cache['attempted'] > $now - HOUR_IN_SECONDS );
}
/**
* Take or release the lock that stops parallel requests from all fetching
*
* A plain options row written with INSERT IGNORE, so only one request can take
* it, with or without a persistent object cache (transients are not atomic).
* A lock older than 30 seconds is left over from a request that died, and is
* taken over.
*
* acft_google_fonts_lock()
*
* @since 3.3.0
* @param bool $take True to take the lock, false to release it.
* @return bool Whether this request now holds the lock (always false on release).
*/
function acft_google_fonts_lock( $take = true ) {
global $wpdb;
// phpcs:disable WordPress.DB.DirectDatabaseQuery -- the options API cannot insert atomically; the row is never read through the cache
if ( ! $take ) {
$wpdb->delete( $wpdb->options, array( 'option_name' => 'acft_google_fonts_lock' ) );
return false;
}
$now = time();
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = 'acft_google_fonts_lock' AND CAST( option_value AS UNSIGNED ) < %d", $now ) );
$taken = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} ( option_name, option_value, autoload ) VALUES ( 'acft_google_fonts_lock', %s, 'no' )", (string) ( $now + 30 ) ) );
// phpcs:enable WordPress.DB.DirectDatabaseQuery
return 1 === $taken;
}
/**
* Refresh the cached Google Fonts list when it is stale
*
* See acft_google_fonts_needs_refresh() for when a fetch happens.
*
* acft_refresh_google_fonts()
*
* @since 3.3.0
* @param bool $force Fetch now, ignoring the cache age, the retry delay and the lock.
*/
function acft_refresh_google_fonts( $force = false ) {
$api_key = acft_get_google_api_key();
if ( '' === $api_key ) {
return;
}
$key_hash = md5( $api_key );
$now = time();
$before = acft_get_google_fonts_cache();
if ( ! $force && ! acft_google_fonts_needs_refresh( $before, $key_hash, $now ) ) {
return;
}
$locked = acft_google_fonts_lock();
if ( ! $force && ! $locked ) {
return; // another request is fetching right now
}
$families = acft_fetch_google_fonts( $api_key );
// re-read: another request may have saved the list while this one waited on Google
wp_cache_delete( 'acft_google_fonts', 'options' );
wp_cache_delete( 'notoptions', 'options' );
$cache = acft_get_google_fonts_cache();
// a result saved while this request waited is newer (e.g. the key was changed and fetched meanwhile);
// only replace it when it was a failure for the same key and this fetch worked, or when this is a
// settings save (forced, so it has the current key) and the newer result is for another key: a
// request that started before the save, with the old key, can finish first
$newer = $cache !== $before;
$replace = ! $newer
|| ( $force && $cache['key_hash'] !== $key_hash )
|| ( $cache['key_hash'] === $key_hash && '' !== $cache['error'] && ! is_wp_error( $families ) );
if ( $replace ) {
$cache['attempted'] = $now;
$cache['key_hash'] = $key_hash;
if ( is_wp_error( $families ) ) {
$cache['error'] = $families->get_error_message();
} else {
$cache['families'] = $families;
$cache['fetched'] = $now;
$cache['error'] = '';
}
update_option( 'acft_google_fonts', $cache, false );
}
if ( $locked ) {
acft_google_fonts_lock( false );
}
}
/**
* Fetch the Google Fonts list right away when the settings are saved
*
* @since 3.3.0
*/
add_action( 'add_option_acft_settings', 'acft_refresh_google_fonts_on_save' );
add_action( 'update_option_acft_settings', 'acft_refresh_google_fonts_on_save' );
function acft_refresh_google_fonts_on_save() {
acft_refresh_google_fonts( true );
}
/**
* Also fetch when the settings are saved unchanged
*
* update_option() skips the hooks above when the value did not change, so saving
* the same key again (e.g. after fixing the key in Google Cloud) would not retry.
*
* @since 3.3.0
* @param mixed $value New value.
* @param mixed $old_value Current value.
* @return mixed The new value, unchanged.
*/
add_filter( 'pre_update_option_acft_settings', 'acft_refresh_google_fonts_on_unchanged_save', 10, 2 );
function acft_refresh_google_fonts_on_unchanged_save( $value, $old_value ) {
if ( $value === $old_value ) {
acft_refresh_google_fonts( true );
}
return $value;
}
/**
* Daily background refresh
*
* Runs acft_refresh_google_fonts(), which only fetches when the list is due (a
* week old, the key changed, or the last request failed), so most runs do not
* call Google.
*
* @since 3.3.0
*/
add_action( 'acft_refresh_google_fonts_event', 'acft_refresh_google_fonts' );
/**
* Keep the daily background refresh scheduled
*
* Plugin updates do not run the activation hook, so this checks on every request
* (the cron list is already in memory). Also replaces a one-off event left by a
* 3.3.0 development build.
*
* acft_schedule_google_fonts_refresh()
*
* @since 3.3.0
*/
add_action( 'init', 'acft_schedule_google_fonts_refresh' );
function acft_schedule_google_fonts_refresh() {
$event = wp_get_scheduled_event( 'acft_refresh_google_fonts_event' );
if ( $event && 'daily' === $event->schedule ) {
return;
}
wp_clear_scheduled_hook( 'acft_refresh_google_fonts_event' );
wp_schedule_event( time(), 'daily', 'acft_refresh_google_fonts_event' );
}
/**
* Stop the background refresh when the plugin is deactivated
*
* Registered in acf-typography.php. Saved data is removed in uninstall.php.
* A network deactivation runs this once, on the current site, so it clears
* the refresh on every site of the network itself.
*
* acft_deactivate()
*
* @since 3.3.0
* @param bool $network_wide Whether the plugin is deactivated for the whole network.
*/
function acft_deactivate( $network_wide = false ) {
if ( ! is_multisite() || ! $network_wide ) {
wp_clear_scheduled_hook( 'acft_refresh_google_fonts_event' );
return;
}
$site_ids = get_sites(
array(
'fields' => 'ids',
'number' => 0,
)
);
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
wp_clear_scheduled_hook( 'acft_refresh_google_fonts_event' );
restore_current_blog();
}
}
/**
* Update Google Fonts JSON file
*
* The list is no longer stored in google_fonts.json. Kept so older theme code
* calling it does not fatal; removed in 4.0.
*
* acft_update_gf_json_file()
*
* @since 3.0.0
* @deprecated 3.3.0 Use acft_refresh_google_fonts() instead.
* @param string $api_key Ignored; the key comes from acft_get_google_api_key().
*/
function acft_update_gf_json_file( $api_key = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- kept for backward compatibility.
_deprecated_function( __FUNCTION__, '3.3.0', 'acft_refresh_google_fonts()' );
acft_refresh_google_fonts();
}
/**
* Get google fonts for Font-Family drop-down subfield
*
* Called when a Typography field or its settings are shown. Reads the cached
* list only: the daily background refresh, ACF screens, the settings page and a
* settings save keep it up to date, so no page with a field waits on Google.
*
* acft_get_google_font_family()
*
* @since 3.0.0
* @return array Family names as both keys and values; empty when no API key is set.
*/
function acft_get_google_font_family() {
if ( '' === acft_get_google_api_key() ) {
return array();
}
$families = acft_get_google_fonts_cache()['families'];
return array_combine( $families, $families );
}
/**
* Collect the field data of ACF blocks, including blocks nested in other blocks
*
* acft_get_acf_blocks_data()
*
* @since 3.3.0
* @param array $blocks Blocks from parse_blocks().
* @return array One entry per ACF block that has field data.
*/
function acft_get_acf_blocks_data( $blocks ) {
$blocks_data = array();
foreach ( $blocks as $block ) {
// freeform content between blocks has no block name; ACF blocks without fields have no data (GH #29)
if ( is_string( $block['blockName'] ) && 0 === strpos( $block['blockName'], 'acf/' ) && ! empty( $block['attrs']['data'] ) && is_array( $block['attrs']['data'] ) ) {
$blocks_data[] = $block['attrs']['data'];
}
if ( ! empty( $block['innerBlocks'] ) ) {
$blocks_data = array_merge( $blocks_data, acft_get_acf_blocks_data( $block['innerBlocks'] ) );
}
}
return $blocks_data;
}
/**
* Collect the font weights used per font family from saved Typography values
*
* Walks nested arrays (repeaters, groups, block data), so each family keeps
* the weights chosen next to it rather than every weight on the page.
*
* acft_collect_font_weights()
*
* @since 3.3.0
* @param mixed $value Field values, as returned by get_fields( ..., false ).
* @param array $weights Family => weight => true, filled in place.
*/
function acft_collect_font_weights( $value, &$weights ) {
if ( ! is_array( $value ) ) {
return;
}
if ( isset( $value['font_family'] ) && is_string( $value['font_family'] ) && '' !== $value['font_family'] ) {
$family = $value['font_family'];
$weight = isset( $value['font_weight'] ) && is_scalar( $value['font_weight'] ) ? (string) $value['font_weight'] : '';
if ( ! isset( $weights[ $family ] ) ) {
$weights[ $family ] = array();
}
// regular and bold always, as in 3.2.x: bold text needs 700, and Google rejects a
// family (e.g. Lobster:700) when none of the requested weights exist
$weights[ $family ]['400'] = true;
$weights[ $family ]['700'] = true;
if ( preg_match( '/^[1-9]00$/', $weight ) ) {
$weights[ $family ][ $weight ] = true;
}
}
foreach ( $value as $child ) {
acft_collect_font_weights( $child, $weights );
}
}
/**
* Whether a font family value should be loaded from Google Fonts
*
* Web-safe values such as "Georgia, serif" and "initial" are not Google fonts.
* A family missing from the cached list (not fetched yet, or since dropped by
* Google) is judged by its name: Google family names are letters, digits, spaces
* and hyphens, web-safe values all contain a comma. Sending an unknown family is
* harmless: the v1 API skips it and still serves the others.
*
* acft_is_google_font_family()
*
* @since 3.3.0
* @param string $family Saved font family value.
* @return bool
*/
function acft_is_google_font_family( $family ) {
static $google = null;
if ( null === $google ) {
$google = array_flip( acft_get_google_fonts_cache()['families'] );
}
if ( isset( $google[ $family ] ) ) {
return true;
}
return ! in_array( $family, array( 'initial', 'inherit' ), true ) && 1 === preg_match( '/^[A-Za-z0-9 -]+$/', $family );
}
/**
* Build the Google Fonts stylesheet URL
*
* acft_google_fonts_url()
*
* @since 3.3.0
* @param array $weights Family => weight => true, from acft_collect_font_weights().
* @return string Empty string when no Google font is used.
*/
function acft_google_fonts_url( $weights ) {
$families = array();
foreach ( $weights as $family => $family_weights ) {
$family = (string) $family; // numeric-looking array keys come back as ints
if ( ! acft_is_google_font_family( $family ) ) {
continue;
}
$family_weights = array_map( 'strval', array_keys( $family_weights ) );
sort( $family_weights, SORT_NUMERIC );
$families[] = str_replace( ' ', '+', $family ) . ':' . implode( ',', $family_weights );
}
if ( ! $families ) {
return '';
}
// display=swap: show fallback text while the font loads; the v1 API accepts it too
return 'https://fonts.googleapis.com/css?family=' . implode( '|', $families ) . '&display=swap';
}
/**
* Enqueue Google Fonts file
*
* acft_enqueue_google_fonts_file()
*
* @since 3.0.0
*/
add_action( 'wp_enqueue_scripts', 'acft_enqueue_google_fonts_file' );
function acft_enqueue_google_fonts_file() {
// ACF inactive: nothing to read, and get_fields() would be undefined
if ( ! function_exists( 'get_fields' ) ) {
return;
}
global $post;
$all_post_fields = array();
$all_option_fields = get_fields( 'option', false ) ?: array();
// 404 and other views can have no post; option fields still apply there
if ( $post instanceof WP_Post ) {
$all_post_fields = get_fields( $post->ID, false ) ?: array();
// for Gutenberg Blocks
$all_post_fields = array_merge( $all_post_fields, acft_get_acf_blocks_data( parse_blocks( $post->post_content ) ) );
}
$weights = array();
acft_collect_font_weights( array( $all_post_fields, $all_option_fields ), $weights );
$url = acft_google_fonts_url( $weights );
if ( '' !== $url ) {
// null: no ?ver= on a third-party URL
wp_enqueue_style( 'acft-gf', $url, array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
}
}