Skip to content

Commit a7bbea0

Browse files
authored
Merge branch 'WordPress:trunk' into trunk
2 parents bf0215c + 9e94fe6 commit a7bbea0

11 files changed

Lines changed: 516 additions & 61 deletions

src/wp-includes/class-wp-speculation-rules.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,24 +259,30 @@ private function is_valid_id( string $id ): bool {
259259
* Checks whether the given speculation rules mode is valid.
260260
*
261261
* @since 6.8.0
262+
* @since 7.1.0 The $mode param now allows mixed and not just string.
262263
*
263-
* @param string $mode Speculation rules mode.
264+
* @param mixed $mode Speculation rules mode.
264265
* @return bool True if valid, false otherwise.
266+
*
267+
* @phpstan-assert-if-true 'prefetch'|'prerender' $mode
265268
*/
266-
public static function is_valid_mode( string $mode ): bool {
267-
return isset( self::$mode_allowlist[ $mode ] );
269+
public static function is_valid_mode( $mode ): bool {
270+
return is_string( $mode ) && isset( self::$mode_allowlist[ $mode ] );
268271
}
269272

270273
/**
271274
* Checks whether the given speculation rules eagerness is valid.
272275
*
273276
* @since 6.8.0
277+
* @since 7.1.0 The $eagerness param now allows mixed and not just string.
274278
*
275-
* @param string $eagerness Speculation rules eagerness.
279+
* @param mixed $eagerness Speculation rules eagerness.
276280
* @return bool True if valid, false otherwise.
281+
*
282+
* @phpstan-assert-if-true 'conservative'|'moderate'|'eager'|'immediate' $eagerness
277283
*/
278-
public static function is_valid_eagerness( string $eagerness ): bool {
279-
return isset( self::$eagerness_allowlist[ $eagerness ] );
284+
public static function is_valid_eagerness( $eagerness ): bool {
285+
return is_string( $eagerness ) && isset( self::$eagerness_allowlist[ $eagerness ] );
280286
}
281287

282288
/**

src/wp-includes/speculative-loading.php

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@
1111
* Returns the speculation rules configuration.
1212
*
1313
* @since 6.8.0
14+
* @since 7.1.0 The `WP_SPECULATIVE_LOADING_DEFAULT_MODE` and `WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS` constants and
15+
* environment variables can now specify the default mode and eagerness, respectively.
16+
*
17+
* @see wp_get_speculation_rules_default_configuration()
1418
*
1519
* @return array<string, string>|null Associative array with 'mode' and 'eagerness' keys, or null if speculative
1620
* loading is disabled.
21+
* @phpstan-return array{
22+
* mode: 'prefetch'|'prerender',
23+
* eagerness: 'conservative'|'moderate'|'eager',
24+
* }|null
1725
*/
1826
function wp_get_speculation_rules_configuration(): ?array {
1927
// By default, speculative loading is only enabled for sites with pretty permalinks when no user is logged in.
@@ -59,8 +67,10 @@ function wp_get_speculation_rules_configuration(): ?array {
5967
}
6068

6169
// Sanitize the configuration and replace 'auto' with current defaults.
62-
$default_mode = 'prefetch';
63-
$default_eagerness = 'conservative';
70+
$defaults = wp_get_speculation_rules_default_configuration();
71+
$default_mode = $defaults['mode'];
72+
$default_eagerness = $defaults['eagerness'];
73+
6474
if ( ! is_array( $config ) ) {
6575
return array(
6676
'mode' => $default_mode,
@@ -90,6 +100,83 @@ function wp_get_speculation_rules_configuration(): ?array {
90100
);
91101
}
92102

103+
/**
104+
* Returns the default speculation rules configuration that the value 'auto' resolves to.
105+
*
106+
* WordPress Core defaults to a mode of 'prefetch' and an eagerness of 'conservative'. Hosting providers can override
107+
* either default by way of the `WP_SPECULATIVE_LOADING_DEFAULT_MODE` and `WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS`
108+
* constants or environment variables. This only changes what the 'auto' value resolves to, so a plugin which supplies
109+
* an explicit mode or eagerness via the {@see 'wp_speculation_rules_configuration'} filter continues to take
110+
* precedence.
111+
*
112+
* Note that an eagerness of 'immediate' is not permitted as a default, since WordPress does not allow it for the
113+
* document-level rules that it generates.
114+
*
115+
* @since 7.1.0
116+
* @access private
117+
*
118+
* @return array<string, string> Associative array with 'mode' and 'eagerness' keys.
119+
* @phpstan-return array{
120+
* mode: 'prefetch'|'prerender',
121+
* eagerness: 'conservative'|'moderate'|'eager',
122+
* }
123+
*/
124+
function wp_get_speculation_rules_default_configuration(): array {
125+
$default_mode = 'prefetch';
126+
$mode = wp_get_speculative_loading_override( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE' );
127+
if ( WP_Speculation_Rules::is_valid_mode( $mode ) ) {
128+
$default_mode = $mode;
129+
}
130+
131+
$default_eagerness = 'conservative';
132+
$eagerness = wp_get_speculative_loading_override( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' );
133+
if (
134+
WP_Speculation_Rules::is_valid_eagerness( $eagerness ) &&
135+
// 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules.
136+
'immediate' !== $eagerness
137+
) {
138+
$default_eagerness = $eagerness;
139+
}
140+
141+
return array(
142+
'mode' => $default_mode,
143+
'eagerness' => $default_eagerness,
144+
);
145+
}
146+
147+
/**
148+
* Returns the value of a speculative loading override, as supplied by a constant or an environment variable.
149+
*
150+
* The constant takes precedence over the environment variable, consistent with {@see wp_get_environment_type()}.
151+
*
152+
* @since 7.1.0
153+
* @access private
154+
*
155+
* @param string $name Name of the constant and environment variable to look up.
156+
* @return string|null The override value, or null if neither is set.
157+
*/
158+
function wp_get_speculative_loading_override( string $name ): ?string {
159+
$value = null;
160+
161+
// Check if the environment variable has been set, if `getenv` is available on the system.
162+
if ( function_exists( 'getenv' ) ) {
163+
$has_env = getenv( $name );
164+
if ( false !== $has_env ) {
165+
$value = $has_env;
166+
}
167+
}
168+
169+
// Fetch the value from a constant, which overrides the environment variable.
170+
if ( defined( $name ) ) {
171+
$has_constant = constant( $name );
172+
if ( is_string( $has_constant ) ) {
173+
$value = $has_constant;
174+
}
175+
}
176+
177+
return $value;
178+
}
179+
93180
/**
94181
* Returns the full speculation rules data based on the configuration.
95182
*
@@ -152,6 +239,7 @@ function wp_get_speculation_rules(): ?WP_Speculation_Rules {
152239
* @param string $mode Mode used to apply speculative loading. Either 'prefetch' or 'prerender'.
153240
*/
154241
$href_exclude_paths = (array) apply_filters( 'wp_speculation_rules_href_exclude_paths', array(), $mode );
242+
$href_exclude_paths = array_filter( $href_exclude_paths, 'is_string' );
155243

156244
// Ensure that:
157245
// 1. There are no duplicates.

tests/phpunit/tests/post/getPages.php

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,22 @@ public function test_get_pages_cache() {
5555
$time1 = wp_cache_get( 'last_changed', 'posts' );
5656
$this->assertNotEmpty( $time1 );
5757
$num_queries = get_num_queries();
58-
foreach ( $pages as $page ) {
59-
$this->assertInstanceOf( 'WP_Post', $page );
60-
}
58+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
6159

6260
// Again. num_queries and last_changed should remain the same.
6361
$pages = get_pages();
6462
$this->assertCount( 3, $pages );
6563
$this->assertSame( $time1, wp_cache_get( 'last_changed', 'posts' ) );
6664
$this->assertSame( $num_queries, get_num_queries() );
67-
foreach ( $pages as $page ) {
68-
$this->assertInstanceOf( 'WP_Post', $page );
69-
}
65+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
7066

7167
// Again with different args. last_changed should not increment because of
7268
// different args to get_pages(). num_queries should bump by 1.
7369
$pages = get_pages( array( 'number' => 2 ) );
7470
$this->assertCount( 2, $pages );
7571
$this->assertSame( $time1, wp_cache_get( 'last_changed', 'posts' ) );
7672
$this->assertSame( $num_queries + 1, get_num_queries() );
77-
foreach ( $pages as $page ) {
78-
$this->assertInstanceOf( 'WP_Post', $page );
79-
}
73+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
8074

8175
$num_queries = get_num_queries();
8276

@@ -85,18 +79,14 @@ public function test_get_pages_cache() {
8579
$this->assertCount( 2, $pages );
8680
$this->assertSame( $time1, wp_cache_get( 'last_changed', 'posts' ) );
8781
$this->assertSame( $num_queries, get_num_queries() );
88-
foreach ( $pages as $page ) {
89-
$this->assertInstanceOf( 'WP_Post', $page );
90-
}
82+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
9183

9284
// Do the first query again. The interim queries should not affect it.
9385
$pages = get_pages();
9486
$this->assertCount( 3, $pages );
9587
$this->assertSame( $time1, wp_cache_get( 'last_changed', 'posts' ) );
9688
$this->assertSame( $num_queries, get_num_queries() );
97-
foreach ( $pages as $page ) {
98-
$this->assertInstanceOf( 'WP_Post', $page );
99-
}
89+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
10090

10191
// Force last_changed to increment.
10292
clean_post_cache( $pages[0]->ID );
@@ -109,9 +99,7 @@ public function test_get_pages_cache() {
10999
$this->assertCount( 2, $pages );
110100
$this->assertSame( $time2, wp_cache_get( 'last_changed', 'posts' ) );
111101
$this->assertSame( $num_queries + 1, get_num_queries() );
112-
foreach ( $pages as $page ) {
113-
$this->assertInstanceOf( 'WP_Post', $page );
114-
}
102+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
115103

116104
$last_changed = wp_cache_get( 'last_changed', 'posts' );
117105

@@ -129,9 +117,7 @@ public function test_get_pages_cache() {
129117
$this->assertCount( 2, $pages );
130118
$this->assertSame( $last_changed, wp_cache_get( 'last_changed', 'posts' ) );
131119
$this->assertSame( $num_queries + 1, get_num_queries() );
132-
foreach ( $pages as $page ) {
133-
$this->assertInstanceOf( 'WP_Post', $page );
134-
}
120+
$this->assertContainsOnlyInstancesOf( 'WP_Post', $pages );
135121
}
136122

137123
/**

tests/phpunit/tests/post/wpPostType.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ public function test_instances() {
99

1010
$this->assertNotEmpty( $wp_post_types );
1111

12-
foreach ( $wp_post_types as $post_type ) {
13-
$this->assertInstanceOf( 'WP_Post_Type', $post_type );
14-
}
12+
$this->assertContainsOnlyInstancesOf( 'WP_Post_Type', $wp_post_types );
1513
}
1614

1715
public function test_add_supports_defaults() {

tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,20 @@ class Tests_Speculative_Loading_wpGetSpeculationRules extends WP_UnitTestCase {
2121
'eagerness' => 'conservative',
2222
);
2323

24-
public function set_up() {
24+
/**
25+
* Stores the original speculative loading env values for cleanup.
26+
*
27+
* @var array<string, string|false>
28+
*/
29+
private array $original_env = array();
30+
31+
public function set_up(): void {
2532
parent::set_up();
2633

34+
foreach ( array( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE', 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' ) as $name ) {
35+
$this->original_env[ $name ] = getenv( $name );
36+
}
37+
2738
add_filter(
2839
'template_directory_uri',
2940
static function () {
@@ -41,6 +52,18 @@ static function () {
4152
update_option( 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/' );
4253
}
4354

55+
public function tear_down(): void {
56+
foreach ( $this->original_env as $name => $value ) {
57+
if ( false === $value ) {
58+
putenv( $name );
59+
} else {
60+
putenv( "{$name}={$value}" );
61+
}
62+
}
63+
64+
parent::tear_down();
65+
}
66+
4467
/**
4568
* Tests speculation rules output with prefetch for the different eagerness levels.
4669
*
@@ -558,4 +581,43 @@ function () {
558581
$this->assertSame( 'moderate', $rules['prerender'][0]['eagerness'] );
559582
$this->assertSame( 'eager', $rules['prerender'][1]['eagerness'] );
560583
}
584+
585+
/**
586+
* Tests that an overridden default eagerness is applied to the main document-level rule.
587+
*
588+
* @ticket 65624
589+
*/
590+
public function test_wp_get_speculation_rules_with_overridden_default_eagerness(): void {
591+
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS=moderate' );
592+
593+
$rules = wp_get_speculation_rules();
594+
$this->assertInstanceOf( WP_Speculation_Rules::class, $rules );
595+
596+
$rules = $rules->jsonSerialize();
597+
598+
$this->assertArrayHasKey( 'prefetch', $rules );
599+
$this->assertCount( 1, $rules['prefetch'] );
600+
$this->assertSame( 'moderate', $rules['prefetch'][0]['eagerness'] );
601+
}
602+
603+
/**
604+
* Tests that an eagerness of 'immediate' cannot be forced as the default.
605+
*
606+
* WordPress does not allow 'immediate' for the document-level rules it generates, so allowing it as a default
607+
* would cause the main rule to be rejected, leaving no speculation rules at all.
608+
*
609+
* @ticket 65624
610+
*/
611+
public function test_wp_get_speculation_rules_with_immediate_default_eagerness(): void {
612+
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS=immediate' );
613+
614+
$rules = wp_get_speculation_rules();
615+
$this->assertInstanceOf( WP_Speculation_Rules::class, $rules );
616+
617+
$rules = $rules->jsonSerialize();
618+
619+
$this->assertArrayHasKey( 'prefetch', $rules );
620+
$this->assertCount( 1, $rules['prefetch'] );
621+
$this->assertSame( 'conservative', $rules['prefetch'][0]['eagerness'] );
622+
}
561623
}

0 commit comments

Comments
 (0)