| @@ -214,9 +214,26 @@ | ||
| 214 | 214 | $scanned = 0; |
| 215 | 215 | $capped = false; |
| 216 | 216 | |
| 217 | 217 | // Flat tree — md5-keyed, URL recovered from the markup. |
| 218 | - $flat = defined( 'XSPEED_CACHE_DIR' ) ? (array) glob( XSPEED_CACHE_DIR . '/*.html' ) : array(); | |
| 218 | + // Flat entries live in per-site buckets since #6 | |
| 219 | + // (XSPEED_CACHE_DIR/<host>/<md5>.html); the legacy top-level layout | |
| 220 | + // is still globbed so pre-#6 entries remain visible until they age out. | |
| 221 | + $flat = defined( 'XSPEED_CACHE_DIR' ) | |
| 222 | + ? array_merge( | |
| 223 | + (array) glob( XSPEED_CACHE_DIR . '/*.html' ), | |
| 224 | + (array) glob( XSPEED_CACHE_DIR . '/*/*.html' ) | |
| 225 | + ) | |
| 226 | + : array(); | |
| 227 | + $flat = array_values( | |
| 228 | + array_filter( | |
| 229 | + $flat, | |
| 230 | + static function ( $f ) { | |
| 231 | + // min/ and rest/ are separate buckets, not page entries. | |
| 232 | + return ! in_array( basename( dirname( (string) $f ) ), array( 'min', 'rest', 'combined' ), true ); | |
| 233 | + } | |
| 234 | + ) | |
| 235 | + ); | |
| 219 | 236 | foreach ( $flat as $file ) { |
| 220 | 237 | if ( $scanned >= self::SCAN_CAP ) { |
| 221 | 238 | $capped = true; |
| 222 | 239 | break; |
| @@ -368,11 +385,15 @@ | ||
| 368 | 385 | ), |
| 369 | 386 | ); |
| 370 | 387 | |
| 371 | 388 | if ( defined( 'XSPEED_CACHE_DIR' ) ) { |
| 389 | + // Both layouts: per-site buckets (#6) and pre-#6 top level. | |
| 372 | 390 | self::add_glob( $buckets['pages_flat'], XSPEED_CACHE_DIR . '/*.html' ); |
| 373 | 391 | self::add_glob( $buckets['metadata'], XSPEED_CACHE_DIR . '/*.meta' ); |
| 374 | 392 | self::add_glob( $buckets['precompressed'], XSPEED_CACHE_DIR . '/*.br' ); |
| 393 | + self::add_glob( $buckets['pages_flat'], XSPEED_CACHE_DIR . '/*/*.html' ); | |
| 394 | + self::add_glob( $buckets['metadata'], XSPEED_CACHE_DIR . '/*/*.meta' ); | |
| 395 | + self::add_glob( $buckets['precompressed'], XSPEED_CACHE_DIR . '/*/*.br' ); | |
| 375 | 396 | self::add_glob( $buckets['rest'], XSPEED_CACHE_DIR . '/rest/*.json' ); |
| 376 | 397 | self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/*.css' ); |
| 377 | 398 | self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/*.js' ); |
| 378 | 399 | self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/combined/*.css' ); |
| @@ -506,18 +527,27 @@ | ||
| 506 | 527 | |
| 507 | 528 | /** |
| 508 | 529 | * The last-purge drill-down. |
| 509 | 530 | * |
| 510 | - * @return array{events:array<int,array<string,mixed>>,last_purge:int} | |
| 531 | + * `last_gc` / `gc_removed` / `gc_removed_total` come from the daily | |
| 532 | + * `xspeed_gc` sweep (Cache_GC) so the collector is verifiable from the | |
| 533 | + * dashboard instead of over SSH. | |
| 534 | + * | |
| 535 | + * @return array{events:array<int,array<string,mixed>>,last_purge:int,last_gc:int,gc_removed:int,gc_removed_total:int,gc_next_run:int} | |
| 511 | 536 | */ |
| 512 | 537 | public static function purge_log( int $limit = 25 ): array { |
| 513 | 538 | $limit = max( 1, min( 50, $limit ) ); |
| 514 | 539 | $events = self::filter_purges( Activity_Log::entries(), $limit ); |
| 515 | 540 | $stats = get_option( 'xspeed_stats', array() ); |
| 541 | + $stats = is_array( $stats ) ? $stats : array(); | |
| 516 | 542 | |
| 517 | 543 | return array( |
| 518 | - 'events' => $events, | |
| 519 | - 'last_purge' => is_array( $stats ) && isset( $stats['last_purge'] ) ? (int) $stats['last_purge'] : 0, | |
| 544 | + 'events' => $events, | |
| 545 | + 'last_purge' => (int) ( $stats['last_purge'] ?? 0 ), | |
| 546 | + 'last_gc' => (int) ( $stats['last_gc'] ?? 0 ), | |
| 547 | + 'gc_removed' => (int) ( $stats['gc_removed'] ?? 0 ), | |
| 548 | + 'gc_removed_total' => (int) ( $stats['gc_removed_total'] ?? 0 ), | |
| 549 | + 'gc_next_run' => (int) wp_next_scheduled( Cache_GC::CRON_HOOK ), | |
| 520 | 550 | ); |
| 521 | 551 | } |
| 522 | 552 | |
| 523 | 553 | /** Drop the memoized scan — called after a purge so the list can't lie. */ |