| @@ -133,12 +133,12 @@ | ||
| 133 | 133 | // A warm/probe is never scoped, so it downloads every declared font |
| 134 | 134 | // and keeps the shared cache complete. |
| 135 | 135 | $tracker = new UsageTracker(); |
| 136 | 136 | $profile = $this->force |
| 137 | - ? array( 'scope_out' => array(), 'above_fold' => array(), 'measured' => false ) | |
| 137 | + ? array( 'scope_out' => array(), 'fold_faces' => array(), 'measured' => false ) | |
| 138 | 138 | : $tracker->route_render_profile( $route ); |
| 139 | 139 | $scope_out = $profile['scope_out']; |
| 140 | - $above_fold = $profile['above_fold']; | |
| 140 | + $fold_faces = $profile['fold_faces']; | |
| 141 | 141 | $measured = ! empty( $profile['measured'] ); |
| 142 | 142 | |
| 143 | 143 | // 1. Detect + localise. The consolidator is transactional: it only |
| 144 | 144 | // rewrites the page when every font it needs is already hosted |
| @@ -158,9 +158,9 @@ | ||
| 158 | 158 | $buffer = $stripped['html']; |
| 159 | 159 | } |
| 160 | 160 | |
| 161 | 161 | // 3. Inject preloads → consolidated stylesheet → metric fallbacks. |
| 162 | - $head = $this->build_head( $route, $device, $above_fold ); | |
| 162 | + $head = $this->build_head( $route, $device, $fold_faces ); | |
| 163 | 163 | |
| 164 | 164 | if ( '' !== $head ) { |
| 165 | 165 | $buffer = $this->html->before_head_close( $buffer, $head ); |
| 166 | 166 | } |
| @@ -207,10 +207,17 @@ | ||
| 207 | 207 | if ( get_transient( $lock ) ) { |
| 208 | 208 | return; |
| 209 | 209 | } |
| 210 | 210 | |
| 211 | - set_transient( $lock, 1, 60 ); | |
| 211 | + // Claim with a per-request token and re-read: if two requests raced | |
| 212 | + // through the check above, only the one whose token survived proceeds. | |
| 213 | + $claim = wp_generate_password( 8, false, false ); | |
| 214 | + set_transient( $lock, $claim, 60 ); | |
| 212 | 215 | |
| 216 | + if ( get_transient( $lock ) !== $claim ) { | |
| 217 | + return; | |
| 218 | + } | |
| 219 | + | |
| 213 | 220 | $target = add_query_arg( |
| 214 | 221 | array( |
| 215 | 222 | 'easyfonts_probe' => Settings::warm_key(), |
| 216 | 223 | 'efbust' => (string) time(), |
| @@ -235,18 +242,18 @@ | ||
| 235 | 242 | * stylesheet, then the metric-matched fallback faces. |
| 236 | 243 | * |
| 237 | 244 | * @param string $route Canonical route for this page. |
| 238 | 245 | * @param string $device Device ('mobile'|'desktop'). |
| 239 | - * @param array<string,bool> $above_fold Lowercased families above the fold on this route. | |
| 246 | + * @param array<string,array<string,bool>> $fold_faces Above-the-fold faces per family rendered on this route. | |
| 240 | 247 | * @return string |
| 241 | 248 | */ |
| 242 | - private function build_head( string $route = '', string $device = 'any', array $above_fold = array() ): string { | |
| 249 | + private function build_head( string $route = '', string $device = 'any', array $fold_faces = array() ): string { | |
| 243 | 250 | $out = ''; |
| 244 | 251 | $stylesheet = Consolidator::stylesheet_url(); |
| 245 | 252 | |
| 246 | 253 | // Preloads — above the stylesheet, deduped inside SmartPreload, scoped |
| 247 | 254 | // to the fonts actually emitted on this page (Consolidator::touched). |
| 248 | - $out .= ( new SmartPreload() )->build( $route, $device, $this->present_families(), $above_fold ); | |
| 255 | + $out .= ( new SmartPreload() )->build( $route, $device, $this->present_families(), $fold_faces ); | |
| 249 | 256 | |
| 250 | 257 | // The single consolidated stylesheet — either inlined (non-render- |
| 251 | 258 | // blocking: no extra request) or as a normal <link>. |
| 252 | 259 | if ( '' !== $stylesheet ) { |
| @@ -255,13 +262,26 @@ | ||
| 255 | 262 | if ( Settings::get( 'inline_css', 0 ) ) { |
| 256 | 263 | $file = Consolidator::stylesheet_file(); |
| 257 | 264 | |
| 258 | 265 | if ( '' !== $file ) { |
| 259 | - $storage = new Storage(); | |
| 260 | - $css = $storage->exists( $file ) ? (string) @file_get_contents( $storage->path( $file ) ) : ''; // phpcs:ignore WordPress.WP.AlternativeFunctions | |
| 266 | + // The stylesheet content is deterministic for its filename | |
| 267 | + // (content-hash keyed), so cache the minified result instead | |
| 268 | + // of re-reading + re-minifying the file on every request. | |
| 269 | + $cache_key = 'easyfonts_inline_' . md5( $file ); | |
| 270 | + $css = get_transient( $cache_key ); | |
| 261 | 271 | |
| 272 | + if ( ! is_string( $css ) || '' === $css ) { | |
| 273 | + $storage = new Storage(); | |
| 274 | + $raw = $storage->exists( $file ) ? (string) @file_get_contents( $storage->path( $file ) ) : ''; // phpcs:ignore WordPress.WP.AlternativeFunctions | |
| 275 | + $css = '' !== $raw ? $this->minify_css( $raw ) : ''; | |
| 276 | + | |
| 277 | + if ( '' !== $css ) { | |
| 278 | + set_transient( $cache_key, $css, DAY_IN_SECONDS ); | |
| 279 | + } | |
| 280 | + } | |
| 281 | + | |
| 262 | 282 | if ( '' !== $css ) { |
| 263 | - $out .= '<style id="easyfonts-fonts">' . $this->minify_css( $css ) . '</style>' . "\n"; | |
| 283 | + $out .= '<style id="easyfonts-fonts">' . $css . '</style>' . "\n"; | |
| 264 | 284 | $inlined = true; |
| 265 | 285 | } |
| 266 | 286 | } |
| 267 | 287 | } |