PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 2.0.4
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v2.0.4
2.0.4 2.0.3 2.0.2 2.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.3 2.0.0
← All changes | src/Fonts/UsageTracker.php +88 -32 2.0.3 → 2.0.4 View file →
@@ -216,15 +216,20 @@
216 216 * are family-level and based on the page's authorial font-family, so they're
217 217 * robust to weight/style differences and self-correcting.
218 218 *
219 219 * - `rendered` drives page scoping (which fonts load on this page).
220 - * - `above_fold` drives preload (which of them to fetch early).
220 + * - `above_fold` the families above the fold.
221 + * - `fold_faces` drives preload: for every family rendered here, the faces
222 + * ('weight|style') the beacon saw above the fold — empty when the family
223 + * renders only further down. (2.0.4) Preload used to take the whole
224 + * family, so a family with many weights filled every slot with weights
225 + * the page never shows.
221 226 *
222 227 * Empty `rendered` means the route hasn't been measured yet — callers must
223 228 * neither scope nor preload from no data.
224 229 *
225 230 * @param string $route Canonical route (path).
226 - * @return array{rendered:array<string,bool>,above_fold:array<string,bool>,scope_out:array<string,bool>,measured:bool}
231 + * @return array{rendered:array<string,bool>,above_fold:array<string,bool>,fold_faces:array<string,array<string,bool>>,scope_out:array<string,bool>,measured:bool}
227 232 */
228 233 public function route_render_profile( string $route ): array {
229 234 global $wpdb;
230 235
@@ -230,8 +235,9 @@
230 235
231 236 $out = array(
232 237 'rendered' => array(),
233 238 'above_fold' => array(),
239 + 'fold_faces' => array(),
234 240 'scope_out' => array(),
235 241 'measured' => false,
236 242 );
237 243
@@ -246,12 +252,13 @@
246 252 // confirmed scope-out ONLY when a beacon has actually measured it and
247 253 // never saw it render. A family that hasn't been beacon-measured yet
248 254 // (e.g. a font just added/replaced in the Customizer) is NEVER scoped
249 255 // out — it loads normally until a beacon has had a chance to judge it.
256 + // Grouped per face (still one query); folded per family below.
250 257 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
251 258 $rows = $wpdb->get_results(
252 259 $wpdb->prepare(
253 - "SELECT family,
260 + "SELECT family, weight, style,
254 261 MAX(rendered) AS rnd,
255 262 MAX(CASE WHEN rendered = 1 THEN above_fold ELSE 0 END) AS af,
256 263 MAX(CASE WHEN origin = 'beacon' THEN 1 ELSE 0 END) AS beaconed,
257 264 MAX(beacon_misses) AS misses
@@ -256,14 +263,32 @@
256 263 MAX(CASE WHEN origin = 'beacon' THEN 1 ELSE 0 END) AS beaconed,
257 264 MAX(beacon_misses) AS misses
258 265 FROM {$this->usage_table()}
259 266 WHERE page_url = %s
260 - GROUP BY family",
267 + GROUP BY family, weight, style",
261 268 $route
262 269 ),
263 270 ARRAY_A
264 271 );
265 272
273 + $families = array();
274 +
275 + foreach ( (array) $rows as $r ) {
276 + $fam = strtolower( (string) $r['family'] );
277 + $agg = $families[ $fam ] ?? array( 'rnd' => 0, 'af' => 0, 'beaconed' => 0, 'misses' => 0, 'faces' => array() );
278 +
279 + $agg['rnd'] = max( $agg['rnd'], (int) $r['rnd'] );
280 + $agg['af'] = max( $agg['af'], (int) $r['af'] );
281 + $agg['beaconed'] = max( $agg['beaconed'], (int) $r['beaconed'] );
282 + $agg['misses'] = max( $agg['misses'], (int) $r['misses'] );
283 +
284 + if ( ! empty( $r['af'] ) ) {
285 + $agg['faces'][ $r['weight'] . '|' . $r['style'] ] = true;
286 + }
287 +
288 + $families[ $fam ] = $agg;
289 + }
290 +
266 291 /**
267 292 * Consistent beacon measurements required before a never-rendered
268 293 * family is scoped off a page. Corroboration means one bad or forged
269 294 * report can't remove a font (beacons are throttled hourly, so this is
@@ -272,29 +297,27 @@
272 297 * @param int $min
273 298 */
274 299 $min_misses = max( 1, (int) apply_filters( 'easyfonts_scope_out_min_measurements', 2 ) );
275 300
276 - if ( $rows ) {
277 - foreach ( $rows as $r ) {
278 - $fam = strtolower( (string) $r['family'] );
279 - $rendered = ! empty( $r['rnd'] );
280 - $beaconed = ! empty( $r['beaconed'] );
301 + foreach ( $families as $fam => $r ) {
302 + $rendered = ! empty( $r['rnd'] );
303 + $beaconed = ! empty( $r['beaconed'] );
281 304
282 - if ( $beaconed ) {
283 - $out['measured'] = true;
284 - }
305 + if ( $beaconed ) {
306 + $out['measured'] = true;
307 + }
285 308
286 - if ( $rendered ) {
287 - $out['rendered'][ $fam ] = true;
309 + if ( $rendered ) {
310 + $out['rendered'][ $fam ] = true;
311 + $out['fold_faces'][ $fam ] = $r['faces'];
288 312
289 - if ( ! empty( $r['af'] ) ) {
290 - $out['above_fold'][ $fam ] = true;
291 - }
292 - } elseif ( $beaconed && (int) $r['misses'] >= $min_misses ) {
293 - // Multiple beacons measured it and never saw it render →
294 - // confirmed scope-out.
295 - $out['scope_out'][ $fam ] = true;
313 + if ( ! empty( $r['af'] ) ) {
314 + $out['above_fold'][ $fam ] = true;
296 315 }
316 + } elseif ( $beaconed && (int) $r['misses'] >= $min_misses ) {
317 + // Multiple beacons measured it and never saw it render →
318 + // confirmed scope-out.
319 + $out['scope_out'][ $fam ] = true;
297 320 }
298 321 }
299 322
300 323 return $out;
@@ -306,14 +329,27 @@
306 329 * wherever they appear without consulting per-route data (and even before a
307 330 * given route has been measured), which is the cheap path: no per-route
308 331 * check needed for the fonts whose answer is the same everywhere.
309 332 *
310 - * @return array<string,bool> Lowercased family => true.
333 + * (2.0.4) With the faces ('weight|style') those families render in. A value
334 + * of `true` is a family stored by 2.0.3 without its faces — it stands for
335 + * the whole family until the next recompute (next beacon, or weekly GC).
336 + *
337 + * @return array<string,array<string,bool>|true> Lowercased family => faces.
311 338 */
312 - public function global_preload_families(): array {
339 + public function global_preload_faces(): array {
313 340 $list = get_option( 'easyfonts_global_preload', array() );
341 + $out = array();
314 342
315 - return is_array( $list ) ? array_fill_keys( array_map( 'strval', $list ), true ) : array();
343 + foreach ( is_array( $list ) ? $list : array() as $key => $value ) {
344 + if ( is_array( $value ) ) {
345 + $out[ (string) $key ] = array_fill_keys( array_map( 'strval', $value ), true );
346 + } elseif ( is_int( $key ) ) {
347 + $out[ strtolower( (string) $value ) ] = true;
348 + }
349 + }
350 +
351 + return $out;
316 352 }
317 353
318 354 /**
319 355 * Recompute and cache the uniformly-above-the-fold family set. A family
@@ -318,10 +354,11 @@
318 354 /**
319 355 * Recompute and cache the uniformly-above-the-fold family set. A family
320 356 * qualifies when every measured row for it (across all routes/variants where
321 357 * it renders) is above the fold — so its preload answer doesn't vary by page
322 - * and we can skip per-route checks for it. Cheap: one grouped scan of the
323 - * (capped) usage table; called when a beacon is accepted and from GC.
358 + * and we can skip per-route checks for it. Stored with the faces it renders
359 + * in, so preload stays weight-level. Cheap: one grouped scan of the (capped)
360 + * usage table; called when a beacon is accepted and from GC.
324 361 *
325 362 * @return void
326 363 */
327 364 public function recompute_global_preload(): void {
@@ -327,18 +364,37 @@
327 364 public function recompute_global_preload(): void {
328 365 global $wpdb;
329 366
330 367 // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
331 - $families = $wpdb->get_col(
332 - "SELECT family FROM {$this->usage_table()}
368 + $rows = $wpdb->get_results(
369 + "SELECT family, weight, style, MIN(above_fold) AS af FROM {$this->usage_table()}
333 370 WHERE rendered = 1
334 - GROUP BY family
335 - HAVING MIN(above_fold) = 1"
371 + GROUP BY family, weight, style",
372 + ARRAY_A
336 373 );
337 374
338 - $families = $families ? array_values( array_unique( array_map( 'strtolower', $families ) ) ) : array();
375 + $faces = array();
376 + $below = array();
339 377
340 - update_option( 'easyfonts_global_preload', $families, true );
378 + foreach ( (array) $rows as $r ) {
379 + $fam = strtolower( (string) $r['family'] );
380 +
381 + if ( empty( $r['af'] ) ) {
382 + $below[ $fam ] = true;
383 + }
384 +
385 + $faces[ $fam ][ $r['weight'] . '|' . $r['style'] ] = true;
386 + }
387 +
388 + $global = array();
389 +
390 + foreach ( $faces as $fam => $set ) {
391 + if ( ! isset( $below[ $fam ] ) ) {
392 + $global[ $fam ] = array_keys( $set );
393 + }
394 + }
395 +
396 + update_option( 'easyfonts_global_preload', $global, true );
341 397 }
342 398
343 399 /**
344 400 * Forget the stored decision for a route (all devices), so the next beacon