| @@ -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 |
| @@ -550,13 +566,9 @@ | ||
| 550 | 566 | } |
| 551 | 567 | |
| 552 | 568 | /** Inverse of local_path, for @import + url() resolution. */ |
| 553 | 569 | private static function path_to_url( string $path ): string { |
| 554 | - $root = defined( 'ABSPATH' ) ? rtrim( ABSPATH, '/' ) : ''; | |
| 555 | - if ( '' !== $root && 0 === strpos( $path, $root ) ) { | |
| 556 | - return rtrim( home_url(), '/' ) . str_replace( $root, '', $path ); | |
| 557 | - } | |
| 558 | - return $path; | |
| 570 | + return Asset_Combiner::path_to_url( $path ); | |
| 559 | 571 | } |
| 560 | 572 | |
| 561 | 573 | /** |
| 562 | 574 | * Move any surviving `@import` to the top of the combined file. |