PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.0
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.0
1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 All 35 releases
← All changes | includes/class-cache-inventory.php +44 -5 1.1.2 → 1.4.0 View file →
@@ -86,8 +86,12 @@
86 86 return null;
87 87 }
88 88
89 89 $path = '' === implode( '/', $segments ) ? '/' : '/' . implode( '/', $segments ) . '/';
90 + // The tree stores decoded names (Cache::static_path()). Encode them
91 + // back the way a permalink is spelled, so the row merges with the
92 + // flat entry's canonical URL instead of listing the page twice.
93 + $path = Cache::normalize_path( $path );
90 94
91 95 return ( $https ? 'https://' : 'http://' ) . $host . $path;
92 96 }
93 97
@@ -142,9 +146,12 @@
142 146 $merged = array();
143 147
144 148 foreach ( $rows as $row ) {
145 149 $url = isset( $row['url'] ) && is_string( $row['url'] ) ? $row['url'] : null;
146 - $key = null !== $url ? 'u:' . $url : 'p:' . (string) ( $row['path'] ?? '' );
150 + // A flat entry's URL comes from the page's canonical link, which
151 + // may carry either escape case; compare in the cache key's one
152 + // spelling so a page's flat and static copies stay one row.
153 + $key = null !== $url ? 'u:' . Cache::normalize_path( $url ) : 'p:' . (string) ( $row['path'] ?? '' );
147 154
148 155 if ( ! isset( $merged[ $key ] ) ) {
149 156 $merged[ $key ] = $row;
150 157 continue;
@@ -214,9 +221,26 @@
214 221 $scanned = 0;
215 222 $capped = false;
216 223
217 224 // Flat tree — md5-keyed, URL recovered from the markup.
218 - $flat = defined( 'XSPEED_CACHE_DIR' ) ? (array) glob( XSPEED_CACHE_DIR . '/*.html' ) : array();
225 + // Flat entries live in per-site buckets since #6
226 + // (XSPEED_CACHE_DIR/<host>/<md5>.html); the legacy top-level layout
227 + // is still globbed so pre-#6 entries remain visible until they age out.
228 + $flat = defined( 'XSPEED_CACHE_DIR' )
229 + ? array_merge(
230 + (array) glob( XSPEED_CACHE_DIR . '/*.html' ),
231 + (array) glob( XSPEED_CACHE_DIR . '/*/*.html' )
232 + )
233 + : array();
234 + $flat = array_values(
235 + array_filter(
236 + $flat,
237 + static function ( $f ) {
238 + // min/ and rest/ are separate buckets, not page entries.
239 + return ! in_array( basename( dirname( (string) $f ) ), array( 'min', 'rest', 'combined' ), true );
240 + }
241 + )
242 + );
219 243 foreach ( $flat as $file ) {
220 244 if ( $scanned >= self::SCAN_CAP ) {
221 245 $capped = true;
222 246 break;
@@ -368,16 +392,22 @@
368 392 ),
369 393 );
370 394
371 395 if ( defined( 'XSPEED_CACHE_DIR' ) ) {
396 + // Both layouts: per-site buckets (#6) and pre-#6 top level.
372 397 self::add_glob( $buckets['pages_flat'], XSPEED_CACHE_DIR . '/*.html' );
373 398 self::add_glob( $buckets['metadata'], XSPEED_CACHE_DIR . '/*.meta' );
374 399 self::add_glob( $buckets['precompressed'], XSPEED_CACHE_DIR . '/*.br' );
400 + self::add_glob( $buckets['pages_flat'], XSPEED_CACHE_DIR . '/*/*.html' );
401 + self::add_glob( $buckets['metadata'], XSPEED_CACHE_DIR . '/*/*.meta' );
402 + self::add_glob( $buckets['precompressed'], XSPEED_CACHE_DIR . '/*/*.br' );
375 403 self::add_glob( $buckets['rest'], XSPEED_CACHE_DIR . '/rest/*.json' );
376 404 self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/*.css' );
377 405 self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/*.js' );
378 406 self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/combined/*.css' );
379 407 self::add_glob( $buckets['assets'], XSPEED_CACHE_DIR . '/min/combined/*.js' );
408 + // One manifest per minified source, per blog (Asset_Manifest).
409 + self::add_glob( $buckets['metadata'], XSPEED_CACHE_DIR . '/min/manifests/*/*.json' );
380 410 }
381 411
382 412 if ( defined( 'XSPEED_CACHE_STATIC_DIR' ) && is_dir( XSPEED_CACHE_STATIC_DIR ) ) {
383 413 $static = self::measure_static( XSPEED_CACHE_STATIC_DIR );
@@ -506,18 +536,27 @@
506 536
507 537 /**
508 538 * The last-purge drill-down.
509 539 *
510 - * @return array{events:array<int,array<string,mixed>>,last_purge:int}
540 + * `last_gc` / `gc_removed` / `gc_removed_total` come from the daily
541 + * `xspeed_gc` sweep (Cache_GC) so the collector is verifiable from the
542 + * dashboard instead of over SSH.
543 + *
544 + * @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 545 */
512 546 public static function purge_log( int $limit = 25 ): array {
513 547 $limit = max( 1, min( 50, $limit ) );
514 548 $events = self::filter_purges( Activity_Log::entries(), $limit );
515 549 $stats = get_option( 'xspeed_stats', array() );
550 + $stats = is_array( $stats ) ? $stats : array();
516 551
517 552 return array(
518 - 'events' => $events,
519 - 'last_purge' => is_array( $stats ) && isset( $stats['last_purge'] ) ? (int) $stats['last_purge'] : 0,
553 + 'events' => $events,
554 + 'last_purge' => (int) ( $stats['last_purge'] ?? 0 ),
555 + 'last_gc' => (int) ( $stats['last_gc'] ?? 0 ),
556 + 'gc_removed' => (int) ( $stats['gc_removed'] ?? 0 ),
557 + 'gc_removed_total' => (int) ( $stats['gc_removed_total'] ?? 0 ),
558 + 'gc_next_run' => (int) wp_next_scheduled( Cache_GC::CRON_HOOK ),
520 559 );
521 560 }
522 561
523 562 /** Drop the memoized scan — called after a purge so the list can't lie. */