fonts_dir = trailingslashit( wp_normalize_path( $uploads['basedir'] ) ) . 'aio-fonts/'; $this->fonts_url = trailingslashit( $uploads['baseurl'] ) . 'aio-fonts/'; } private function ensure_fonts_dir() { if ( ! file_exists( $this->fonts_dir ) ) { wp_mkdir_p( $this->fonts_dir ); @file_put_contents( $this->fonts_dir . 'index.php', "fonts_dir ) && is_writable( $this->fonts_dir ); } /** * Localize a Google Fonts stylesheet URL into wp-content/uploads/aio-fonts/. * Returns array( 'css_url' => string, 'preloads' => string[] ) on success, or false on fallback. * * @param string $raw_google_url Google Fonts CSS or CSS2 URL from HTML . * @return array|false */ public function localize_stylesheet( $raw_google_url ) { $clean_url = html_entity_decode( trim( (string) $raw_google_url ), ENT_QUOTES, 'UTF-8' ); if ( strpos( $clean_url, '//' ) === 0 ) { $clean_url = 'https:' . $clean_url; } if ( ! preg_match( '#^https?://fonts\.googleapis\.com/css2?\?#i', $clean_url ) ) { return false; } // Ensure display=swap is requested from Google if ( stripos( $clean_url, 'display=' ) === false ) { $clean_url .= '&display=swap'; } $hash = substr( md5( $clean_url . '|' . AIO_VERSION ), 0, 16 ); $css_file = $this->fonts_dir . 'gfont-' . $hash . '.css'; $meta_file = $this->fonts_dir . 'gfont-' . $hash . '.json'; $css_url = $this->fonts_url . 'gfont-' . $hash . '.css'; // Fast path: already localized on disk (0 network I/O) if ( is_file( $css_file ) && filesize( $css_file ) > 40 ) { $preloads = array(); if ( is_file( $meta_file ) ) { $meta = json_decode( (string) @file_get_contents( $meta_file ), true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( is_array( $meta ) && ! empty( $meta['preloads'] ) && is_array( $meta['preloads'] ) ) { $preloads = $meta['preloads']; } } return array( 'css_url' => $css_url, 'preloads' => $preloads, ); } // Fail-open cooldown: do not retry failed outbound fetches on every visitor hit $fail_key = 'aio_gf_fail_' . $hash; if ( get_transient( $fail_key ) ) { return false; } // Concurrency lock during initial download $lock_key = 'aio_gf_lock_' . $hash; if ( get_transient( $lock_key ) ) { return false; } set_transient( $lock_key, 1, 30 ); if ( ! $this->ensure_fonts_dir() ) { delete_transient( $lock_key ); return false; } // Request modern WOFF2 stylesheet using desktop Chrome UA (supports vietnamese + latin unicode-range) $res = wp_remote_get( $clean_url, array( 'timeout' => 3.5, 'sslverify' => true, 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', ) ); if ( is_wp_error( $res ) || wp_remote_retrieve_response_code( $res ) !== 200 ) { set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } $css_body = (string) wp_remote_retrieve_body( $res ); if ( $css_body === '' || stripos( $css_body, '@font-face' ) === false ) { set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } // Discover all remote fonts.gstatic.com URLs inside @font-face blocks (cap at 20 files max) if ( ! preg_match_all( '#url\(\s*(["\']?)(https://fonts\.gstatic\.com/[^"\')\s]+\.(?:woff2|woff|ttf))\1\s*\)#i', $css_body, $matches ) ) { set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } $remote_urls = array_slice( array_unique( $matches[2] ), 0, 20 ); $url_replacer = array(); $preloads = array(); foreach ( $remote_urls as $r_url ) { $ext = strtolower( pathinfo( (string) wp_parse_url( $r_url, PHP_URL_PATH ), PATHINFO_EXTENSION ) ); $ext = in_array( $ext, array( 'woff2', 'woff', 'ttf' ), true ) ? $ext : 'woff2'; $font_name = 'font-' . substr( md5( $r_url ), 0, 16 ) . '.' . $ext; $font_path = $this->fonts_dir . $font_name; $font_url = $this->fonts_url . $font_name; if ( ! is_file( $font_path ) || filesize( $font_path ) < 100 ) { $f_res = wp_remote_get( $r_url, array( 'timeout' => 3.5, 'sslverify' => true, ) ); if ( is_wp_error( $f_res ) || wp_remote_retrieve_response_code( $f_res ) !== 200 ) { // Fail-open cleanly if any required subset binary fails to download set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } $bin = (string) wp_remote_retrieve_body( $f_res ); if ( strlen( $bin ) < 100 ) { set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } // Verify WOFF2 / WOFF / TrueType magic signature $magic = substr( $bin, 0, 4 ); if ( ! in_array( $magic, array( 'wOF2', 'wOFF', "\x00\x01\x00\x00", 'OTTO', 'true' ), true ) ) { set_transient( $fail_key, 1, HOUR_IN_SECONDS ); delete_transient( $lock_key ); return false; } if ( @file_put_contents( $font_path, $bin ) === false ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged delete_transient( $lock_key ); return false; } } $url_replacer[ $r_url ] = $font_url; if ( $ext === 'woff2' && count( $preloads ) < 2 ) { $preloads[] = $font_url; } } $localized_css = strtr( $css_body, $url_replacer ); // Guarantee font-display: swap inside every @font-face rule $localized_css = preg_replace_callback( '/@font-face\s*\{[^}]*\}/is', function ( $fm ) { $block = $fm[0]; if ( stripos( $block, 'font-display' ) === false ) { $block = preg_replace( '/\}$/', " font-display: swap;\n}", $block, 1 ); } return $block; }, $localized_css ) ?? $localized_css; // Minify whitespace in generated font CSS $localized_css = preg_replace( '#/\*[^*]*\*+(?:[^/*][^*]*\*+)*/#', '', $localized_css ) ?? $localized_css; $localized_css = trim( preg_replace( '/\s+/', ' ', $localized_css ) ?? $localized_css ); if ( @file_put_contents( $css_file, $localized_css ) === false ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged delete_transient( $lock_key ); return false; } @file_put_contents( $meta_file, wp_json_encode( array( 'preloads' => $preloads ) ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged delete_transient( $lock_key ); return array( 'css_url' => $css_url, 'preloads' => $preloads, ); } /** * Delete all cached local Google Fonts files. */ public static function clear_local_fonts() { $uploads = wp_upload_dir(); $dir = trailingslashit( wp_normalize_path( $uploads['basedir'] ) ) . 'aio-fonts/'; if ( ! is_dir( $dir ) ) { return; } $files = glob( $dir . '*' ); if ( is_array( $files ) ) { foreach ( $files as $f ) { if ( is_file( $f ) && basename( $f ) !== 'index.php' ) { @unlink( $f ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } } } }