PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.1
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-css-combine-buffer.php +38 -5 1.2.4 → 1.4.1 View file →
@@ -276,9 +276,22 @@
276 276 *
277 277 * @param array{media:string,async:bool,tags:string[],urls:string[]} $run Run to merge.
278 278 */
279 279 private static function merge_run( array $run ): ?string {
280 - $key = md5( implode( '|', array_map( static fn( $p ) => $p . ':' . (int) @filemtime( $p ), $run['urls'] ) ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- a missing file contributes 0 to the key; handled below.
280 + // Nowhere to write means nothing to link: a <link> to a file that was
281 + // never written unstyles the page.
282 + if ( ! Minifier::min_dir_writable() ) {
283 + return null;
284 + }
285 + // Named by content and by site. See Asset_Combiner::combined_key().
286 + $parts = array();
287 + foreach ( $run['urls'] as $path ) {
288 + $parts[] = array(
289 + 'path' => $path,
290 + 'url' => self::path_to_url( $path ),
291 + );
292 + }
293 + $key = Asset_Combiner::combined_key( $parts, 'css' );
281 294 $dir = Asset_Combiner::cache_dir();
282 295 $file = $dir . '/combined-' . $key . '.css';
283 296 $url = Asset_Combiner::cache_url() . '/combined-' . $key . '.css';
284 297
@@ -324,10 +337,13 @@
324 337 $css = Asset_Combiner::minify_css_body( $css );
325 338 if ( ! is_dir( $dir ) ) {
326 339 wp_mkdir_p( $dir );
327 340 }
328 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_put_contents_file_put_contents -- WP_Filesystem requires admin context, unavailable on the frontend.
329 - file_put_contents( $file, $css, LOCK_EX );
341 + // Atomic: a concurrent render sees the whole file or none of it.
342 + // A failed write links nothing rather than a file that is not there.
343 + if ( ! Asset_Manifest::write_atomic( $file, $css ) && ! file_exists( $file ) ) {
344 + return null;
345 + }
330 346 }
331 347
332 348 // Carry Async CSS across the merge (issue #330). Every sheet in the
333 349 // run was async'd — that is a condition of grouping them, enforced in
@@ -454,10 +470,27 @@
454 470 // and says the same thing: this sheet is parked under `print` and
455 471 // becomes something else on load. Any plugin using the standard
456 472 // print/swap idiom is read correctly rather than merged into a
457 473 // print-only bundle and stripped of its handler (#335 review, issue 3).
458 - if ( preg_match( '#\bonload\s*=\s*(["\'])\s*this\.media\s*=\s*(["\'])([^"\']*)\2#i', $tag, $m ) ) {
459 - return $m[3];
474 + //
475 + // The assignment can sit anywhere in the handler. loadCSS spells it
476 + // `this.onload=null;this.media='all'`, and matching only a handler
477 + // that STARTS with `this.media` filed two such sheets as real print
478 + // sheets: merged into a `media="print"` bundle with no onload, they
479 + // never applied on screen. (#560)
480 + //
481 + // The attribute is decoded first: a tag built with esc_attr() carries
482 + // `this.media=&#039;all&#039;`. An assignment whose value is not a
483 + // literal (`this.media=this.dataset.media`) is still a swap; it
484 + // restores to the screen, so it is read as `all`.
485 + if ( preg_match( '#(?<![-\w])onload\s*=\s*(?:"([^"]*)"|\'([^\']*)\')#i', $tag, $h ) ) {
486 + $handler = html_entity_decode( '' !== $h[1] ? $h[1] : ( $h[2] ?? '' ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
487 + if ( preg_match( '#\bthis\.media\s*=\s*(["\'])([^"\']*)\1#i', $handler, $m ) ) {
488 + return $m[2];
489 + }
490 + if ( preg_match( '#\bthis\.media\s*=(?!=)#i', $handler ) ) {
491 + return 'all';
492 + }
460 493 }
461 494
462 495 return '';
463 496 }