Skip to content

Commit a3520d2

Browse files
committed
Script Loader: Refactor Etag generation for concatenated assets.
Move Etag HTTP header generation in `load-scripts.php` and `load-styles.php` to `WP_Dependencies`. Introduces the method `WP_Dependencies::get_etag()` and associated unit tests. Follow up to [57943]. Props vrajadas, martinkrcho, mukesh27. Fixes #61485. git-svn-id: https://develop.svn.wordpress.org/trunk@58935 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 24e8775 commit a3520d2

4 files changed

Lines changed: 130 additions & 36 deletions

File tree

src/wp-admin/load-scripts.php

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,7 @@
5252
wp_default_packages_vendor( $wp_scripts );
5353
wp_default_packages_scripts( $wp_scripts );
5454

55-
$etag = "WP:{$wp_version};";
56-
57-
foreach ( $load as $handle ) {
58-
if ( ! array_key_exists( $handle, $wp_scripts->registered ) ) {
59-
continue;
60-
}
61-
62-
$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_version;
63-
$etag .= "{$handle}:{$ver};";
64-
}
65-
66-
/*
67-
* This is not intended to be cryptographically secure, just a fast way to get
68-
* a fixed length string based on the script versions. As this file does not
69-
* load the full WordPress environment, it is not possible to use the salted
70-
* wp_hash() function.
71-
*/
72-
$etag = 'W/"' . md5( $etag ) . '"';
55+
$etag = $wp_scripts->get_etag( $load );
7356

7457
if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) === $etag ) {
7558
header( "$protocol 304 Not Modified" );

src/wp-admin/load-styles.php

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,7 @@
5555
$wp_styles = new WP_Styles();
5656
wp_default_styles( $wp_styles );
5757

58-
$etag = "WP:{$wp_version};";
59-
60-
foreach ( $load as $handle ) {
61-
if ( ! array_key_exists( $handle, $wp_styles->registered ) ) {
62-
continue;
63-
}
64-
65-
$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_version;
66-
$etag .= "{$handle}:{$ver};";
67-
}
68-
69-
/*
70-
* This is not intended to be cryptographically secure, just a fast way to get
71-
* a fixed length string based on the script versions. As this file does not
72-
* load the full WordPress environment, it is not possible to use the salted
73-
* wp_hash() function.
74-
*/
75-
$etag = 'W/"' . md5( $etag ) . '"';
58+
$etag = $wp_styles->get_etag( $wp_version, $load );
7659

7760
if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) === $etag ) {
7861
header( "$protocol 304 Not Modified" );

src/wp-includes/class-wp-dependencies.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,4 +490,42 @@ public function set_group( $handle, $recursion, $group ) {
490490

491491
return true;
492492
}
493+
494+
/**
495+
* Get etag header for cache validation.
496+
*
497+
* @since 6.7.0
498+
*
499+
* @global string $wp_version The WordPress version string.
500+
*
501+
* @param string[] $load Array of script or style handles to load.
502+
* @return string Etag header.
503+
*/
504+
public function get_etag( $load ) {
505+
/*
506+
* Note: wp_get_wp_version() is not used here, as this file can be included
507+
* via wp-admin/load-scripts.php or wp-admin/load-styles.php, in which case
508+
* wp-includes/functions.php is not loaded.
509+
*/
510+
global $wp_version;
511+
512+
$etag = "WP:{$wp_version};";
513+
514+
foreach ( $load as $handle ) {
515+
if ( ! array_key_exists( $handle, $this->registered ) ) {
516+
continue;
517+
}
518+
519+
$ver = $this->registered[ $handle ]->ver ?? $wp_version;
520+
$etag .= "{$handle}:{$ver};";
521+
}
522+
523+
/*
524+
* This is not intended to be cryptographically secure, just a fast way to get
525+
* a fixed length string based on the script versions. As this file does not
526+
* load the full WordPress environment, it is not possible to use the salted
527+
* wp_hash() function.
528+
*/
529+
return 'W/"' . md5( $etag ) . '"';
530+
}
493531
}

tests/phpunit/tests/dependencies.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,94 @@ public function test_enqueue_before_register() {
148148

149149
$this->assertContains( 'one', $dep->queue );
150150
}
151+
152+
/**
153+
* Data provider for test_get_etag.
154+
*
155+
* @return array[]
156+
*/
157+
public function data_provider_get_etag() {
158+
return array(
159+
'should accept one dependency' => array(
160+
'load' => array(
161+
'abcd' => '1.0.2',
162+
),
163+
'hash_source_string' => 'WP:6.7;abcd:1.0.2;',
164+
'expected' => 'W/"8145d7e3c41d5a9cc2bccba4afa861fc"',
165+
),
166+
'should accept empty array of dependencies' => array(
167+
'load' => array(),
168+
'hash_source_string' => 'WP:6.7;',
169+
'expected' => 'W/"7ee896c19250a3d174f11469a4ad0b1e"',
170+
),
171+
);
172+
}
173+
174+
/**
175+
* Tests get_etag method for WP_Scripts.
176+
*
177+
* @ticket 58433
178+
* @ticket 61485
179+
*
180+
* @covers WP_Dependencies::get_etag
181+
*
182+
* @dataProvider data_provider_get_etag
183+
*
184+
* @param array $load List of scripts to load.
185+
* @param string $hash_source_string Hash source string.
186+
* @param string $expected Expected etag.
187+
*/
188+
public function test_get_etag_scripts( $load, $hash_source_string, $expected ) {
189+
global $wp_version;
190+
// Modify global to avoid tests needing to change with each new version of WordPress.
191+
$original_wp_version = $wp_version;
192+
$wp_version = '6.7';
193+
$instance = wp_scripts();
194+
195+
foreach ( $load as $handle => $ver ) {
196+
// The src should not be empty.
197+
wp_enqueue_script( $handle, 'https://example.org', array(), $ver );
198+
}
199+
200+
$result = $instance->get_etag( array_keys( $load ) );
201+
202+
// Restore global prior to making assertions.
203+
$wp_version = $original_wp_version;
204+
205+
$this->assertSame( $expected, $result, "Expected MD hash: $expected for $hash_source_string, but got: $result." );
206+
}
207+
208+
/**
209+
* Tests get_etag method for WP_Styles.
210+
*
211+
* @ticket 58433
212+
* @ticket 61485
213+
*
214+
* @covers WP_Dependencies::get_etag
215+
*
216+
* @dataProvider data_provider_get_etag
217+
*
218+
* @param array $load List of styles to load.
219+
* @param string $hash_source_string Hash source string.
220+
* @param string $expected Expected etag.
221+
*/
222+
public function test_get_etag_styles( $load, $hash_source_string, $expected ) {
223+
global $wp_version;
224+
// Modify global to avoid tests needing to change with each new version of WordPress.
225+
$original_wp_version = $wp_version;
226+
$wp_version = '6.7';
227+
$instance = wp_scripts();
228+
229+
foreach ( $load as $handle => $ver ) {
230+
// The src should not be empty.
231+
wp_enqueue_style( $handle, 'https://example.cdn', array(), $ver );
232+
}
233+
234+
$result = $instance->get_etag( array_keys( $load ) );
235+
236+
// Restore global prior to making assertions.
237+
$wp_version = $original_wp_version;
238+
239+
$this->assertSame( $expected, $result, "Expected MD hash: $expected for $hash_source_string, but got: $result." );
240+
}
151241
}

0 commit comments

Comments
 (0)