| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Speculative loading functions. 4 * 5 * @package WordPress 6 * @subpackage Speculative Loading 7 * @since 6.8.0 8 */ 9 10 /** 11 * Returns the speculation rules configuration. 12 * 13 * @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() 18 * 19 * @return array<string, string>|null Associative array with 'mode' and 'eagerness' keys, or null if speculative 20 * loading is disabled. 21 * @phpstan-return array{ 22 * mode: 'prefetch'|'prerender', 23 * eagerness: 'conservative'|'moderate'|'eager', 24 * }|null 25 */ 26 function wp_get_speculation_rules_configuration(): ?array { 27 // By default, speculative loading is only enabled for sites with pretty permalinks when no user is logged in. 28 if ( ! is_user_logged_in() && get_option( 'permalink_structure' ) ) { 29 $config = array( 30 'mode' => 'auto', 31 'eagerness' => 'auto', 32 ); 33 } else { 34 $config = null; 35 } 36 37 /** 38 * Filters the way that speculation rules are configured. 39 * 40 * The Speculation Rules API is a web API that allows to automatically prefetch or prerender certain URLs on the 41 * page, which can lead to near-instant page load times. This is also referred to as speculative loading. 42 * 43 * There are two aspects to the configuration: 44 * * The "mode" (whether to "prefetch" or "prerender" URLs). 45 * * The "eagerness" (whether to speculatively load URLs in an "eager", "moderate", or "conservative" way). 46 * 47 * By default, the speculation rules configuration is decided by WordPress Core ("auto"). This filter can be used 48 * to force a certain configuration, which could for instance load URLs more or less eagerly. 49 * 50 * For logged-in users or for sites that are not configured to use pretty permalinks, the default value is `null`, 51 * indicating that speculative loading is entirely disabled. 52 * 53 * @since 6.8.0 54 * @see https://developer.chrome.com/docs/web-platform/prerender-pages 55 * 56 * @param array<string, string>|null $config Associative array with 'mode' and 'eagerness' keys, or `null`. The 57 * default value for both of the keys is 'auto'. Other possible values 58 * for 'mode' are 'prefetch' and 'prerender'. Other possible values for 59 * 'eagerness' are 'eager', 'moderate', and 'conservative'. The value 60 * `null` is used to disable speculative loading entirely. 61 */ 62 $config = apply_filters( 'wp_speculation_rules_configuration', $config ); 63 64 // Allow the value `null` to indicate that speculative loading is disabled. 65 if ( null === $config ) { 66 return null; 67 } 68 69 // Sanitize the configuration and replace 'auto' with current defaults. 70 $defaults = wp_get_speculation_rules_default_configuration(); 71 $default_mode = $defaults['mode']; 72 $default_eagerness = $defaults['eagerness']; 73 74 if ( ! is_array( $config ) ) { 75 return array( 76 'mode' => $default_mode, 77 'eagerness' => $default_eagerness, 78 ); 79 } 80 if ( 81 ! isset( $config['mode'] ) || 82 'auto' === $config['mode'] || 83 ! WP_Speculation_Rules::is_valid_mode( $config['mode'] ) 84 ) { 85 $config['mode'] = $default_mode; 86 } 87 if ( 88 ! isset( $config['eagerness'] ) || 89 'auto' === $config['eagerness'] || 90 ! WP_Speculation_Rules::is_valid_eagerness( $config['eagerness'] ) || 91 // 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules. 92 'immediate' === $config['eagerness'] 93 ) { 94 $config['eagerness'] = $default_eagerness; 95 } 96 97 return array( 98 'mode' => $config['mode'], 99 'eagerness' => $config['eagerness'], 100 ); 101 } 102 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 180 /** 181 * Returns the full speculation rules data based on the configuration. 182 * 183 * Plugins with features that rely on frontend URLs to exclude from prefetching or prerendering should use the 184 * {@see 'wp_speculation_rules_href_exclude_paths'} filter to ensure those URL patterns are excluded. 185 * 186 * Additional speculation rules other than the default rule from WordPress Core can be provided by using the 187 * {@see 'wp_load_speculation_rules'} action and amending the passed WP_Speculation_Rules object. 188 * 189 * @since 6.8.0 190 * @access private 191 * 192 * @return WP_Speculation_Rules|null Object representing the speculation rules to use, or null if speculative loading 193 * is disabled in the current context. 194 */ 195 function wp_get_speculation_rules(): ?WP_Speculation_Rules { 196 $configuration = wp_get_speculation_rules_configuration(); 197 if ( null === $configuration ) { 198 return null; 199 } 200 201 $mode = $configuration['mode']; 202 $eagerness = $configuration['eagerness']; 203 204 $prefixer = new WP_URL_Pattern_Prefixer(); 205 206 $base_href_exclude_paths = array( 207 $prefixer->prefix_path_pattern( '/wp-*.php', 'site' ), 208 $prefixer->prefix_path_pattern( '/wp-admin/*', 'site' ), 209 $prefixer->prefix_path_pattern( '/*', 'uploads' ), 210 $prefixer->prefix_path_pattern( '/*', 'content' ), 211 $prefixer->prefix_path_pattern( '/*', 'plugins' ), 212 $prefixer->prefix_path_pattern( '/*', 'template' ), 213 $prefixer->prefix_path_pattern( '/*', 'stylesheet' ), 214 ); 215 216 /* 217 * If pretty permalinks are enabled, exclude any URLs with query parameters. 218 * Otherwise, exclude specifically the URLs with a `_wpnonce` query parameter or any other query parameter 219 * containing the word `nonce`. 220 */ 221 if ( get_option( 'permalink_structure' ) ) { 222 $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?(.+)', 'home' ); 223 } else { 224 $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?*(^|&)*nonce*=*', 'home' ); 225 } 226 227 /** 228 * Filters the paths for which speculative loading should be disabled. 229 * 230 * All paths should start in a forward slash, relative to the root document. The `*` can be used as a wildcard. 231 * If the WordPress site is in a subdirectory, the exclude paths will automatically be prefixed as necessary. 232 * 233 * Note that WordPress always excludes certain path patterns such as `/wp-login.php` and `/wp-admin/*`, and those 234 * cannot be modified using the filter. 235 * 236 * @since 6.8.0 237 * 238 * @param string[] $href_exclude_paths Additional path patterns to disable speculative loading for. 239 * @param string $mode Mode used to apply speculative loading. Either 'prefetch' or 'prerender'. 240 */ 241 $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' ); 243 244 // Ensure that: 245 // 1. There are no duplicates. 246 // 2. The base paths cannot be removed. 247 // 3. The array has sequential keys (i.e. array_is_list()). 248 $href_exclude_paths = array_values( 249 array_unique( 250 array_merge( 251 $base_href_exclude_paths, 252 array_map( 253 static function ( string $href_exclude_path ) use ( $prefixer ): string { 254 return $prefixer->prefix_path_pattern( $href_exclude_path ); 255 }, 256 $href_exclude_paths 257 ) 258 ) 259 ) 260 ); 261 262 $speculation_rules = new WP_Speculation_Rules(); 263 264 $main_rule_conditions = array( 265 // Include any URLs within the same site. 266 array( 267 'href_matches' => $prefixer->prefix_path_pattern( '/*' ), 268 ), 269 // Except for excluded paths. 270 array( 271 'not' => array( 272 'href_matches' => $href_exclude_paths, 273 ), 274 ), 275 // Also exclude rel=nofollow links, as certain plugins use that on their links that perform an action. 276 array( 277 'not' => array( 278 'selector_matches' => 'a[rel~="nofollow"]', 279 ), 280 ), 281 // Also exclude links that are explicitly marked to opt out, either directly or via a parent element. 282 array( 283 'not' => array( 284 'selector_matches' => ".no-{$mode}, .no-{$mode} a", 285 ), 286 ), 287 ); 288 289 // If using 'prerender', also exclude links that opt out of 'prefetch' because it's part of 'prerender'. 290 if ( 'prerender' === $mode ) { 291 $main_rule_conditions[] = array( 292 'not' => array( 293 'selector_matches' => '.no-prefetch, .no-prefetch a', 294 ), 295 ); 296 } 297 298 $speculation_rules->add_rule( 299 $mode, 300 'main', 301 array( 302 'source' => 'document', 303 'where' => array( 304 'and' => $main_rule_conditions, 305 ), 306 'eagerness' => $eagerness, 307 ) 308 ); 309 310 /** 311 * Fires when speculation rules data is loaded, allowing to amend the rules. 312 * 313 * @since 6.8.0 314 * 315 * @param WP_Speculation_Rules $speculation_rules Object representing the speculation rules to use. 316 */ 317 do_action( 'wp_load_speculation_rules', $speculation_rules ); 318 319 return $speculation_rules; 320 } 321 322 /** 323 * Prints the speculation rules. 324 * 325 * For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored. 326 * 327 * @since 6.8.0 328 * @access private 329 */ 330 function wp_print_speculation_rules(): void { 331 $speculation_rules = wp_get_speculation_rules(); 332 if ( null === $speculation_rules ) { 333 return; 334 } 335 336 wp_print_inline_script_tag( 337 (string) wp_json_encode( 338 $speculation_rules, 339 JSON_HEX_TAG | JSON_UNESCAPED_SLASHES 340 ), 341 array( 'type' => 'speculationrules' ) 342 ); 343 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Jul 29 08:20:18 2026 | Cross-referenced by PHPXref |