PluginProbe
Speculative Loading / 1.7.0
Speculative Loading v1.7.0
1.7.0 1.1.0 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 trunk 1.0.0 1.0.1
speculation-rules / settings.php

settings.php in Speculative Loading 1.7.0, at settings.php

397 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings functions used for Speculative Loading.
4 *
5 * @package speculation-rules
6 * @since 1.0.0
7 */
8
9 declare( strict_types = 1 );
10
11 // @codeCoverageIgnoreStart
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15 // @codeCoverageIgnoreEnd
16
17 /**
18 * Returns the available options for the Speculative Loading mode and their labels.
19 *
20 * @since 1.0.0
21 *
22 * @return array{ prefetch: string, prerender: string } Associative array of `$mode => $label` pairs.
23 */
24 function plsr_get_mode_labels(): array {
25 return array(
26 'prefetch' => _x( 'Prefetch', 'setting label', 'speculation-rules' ),
27 'prerender' => _x( 'Prerender', 'setting label', 'speculation-rules' ),
28 );
29 }
30
31 /**
32 * Returns the available options for the Speculative Loading eagerness and their labels.
33 *
34 * @since 1.0.0
35 *
36 * @return array{ conservative: string, moderate: string, eager: string } Associative array of `$eagerness => $label` pairs.
37 */
38 function plsr_get_eagerness_labels(): array {
39 return array(
40 'conservative' => _x( 'Conservative (typically on click)', 'setting label', 'speculation-rules' ),
41 'moderate' => _x( 'Moderate (typically on hover)', 'setting label', 'speculation-rules' ),
42 'eager' => _x( 'Eager (on slightest suggestion)', 'setting label', 'speculation-rules' ),
43 );
44 }
45
46 /**
47 * Returns the available options for the Speculative Loading authentication and their labels.
48 *
49 * @since 1.6.0
50 *
51 * @return array{ logged_out: string, logged_out_and_admins: string, any: string } Associative array of `$authentication => $label` pairs.
52 */
53 function plsr_get_authentication_labels(): array {
54 return array(
55 'logged_out' => _x( 'Logged-out visitors only (default)', 'setting label', 'speculation-rules' ),
56 'logged_out_and_admins' => _x( 'Administrators and logged-out visitors', 'setting label', 'speculation-rules' ),
57 'any' => _x( 'Any user (logged-in or logged-out)', 'setting label', 'speculation-rules' ),
58 );
59 }
60
61 /**
62 * Returns translated description strings for settings fields.
63 *
64 * @since 1.6.0
65 * @access private
66 *
67 * @param 'mode'|'eagerness'|'authentication' $field The field name to get description for.
68 * @return string The translated description string.
69 */
70 function plsr_get_field_description( string $field ): string {
71 $descriptions = array(
72 'mode' => __( 'Prerendering will lead to faster load times than prefetching. However, in case of interactive content, prefetching may be a safer choice.', 'speculation-rules' ),
73 'eagerness' => __( 'The eagerness setting defines the heuristics based on which the loading is triggered. "Eager" will have the minimum delay to start speculative loads, "Conservative" increases the chance that only URLs the user actually navigates to are loaded.', 'speculation-rules' ),
74 'authentication' => sprintf(
75 /* translators: %s: URL to persistent object cache documentation */
76 __( 'Only unauthenticated pages are typically served from cache. So in order to reduce load on the server, speculative loading is not enabled by default for logged-in users. If your server can handle the additional load, you can opt in to speculative loading for all logged-in users or just administrator users only. For optimal performance, regardless of the user authentication status but <em>especially</em> when logged-in, ensure you have a <a href="%s" target="_blank">persistent object cache</a> configured. This only applies to pages on the frontend; admin screens remain excluded.', 'speculation-rules' ),
77 'https://developer.wordpress.org/advanced-administration/performance/optimization/#object-caching'
78 ),
79 );
80 return $descriptions[ $field ] ?? '';
81 }
82
83 /**
84 * Returns the default setting value for Speculative Loading configuration.
85 *
86 * @since 1.0.0
87 *
88 * @return array{ mode: 'prerender', eagerness: 'moderate', authentication: 'logged_out' } {
89 * Default setting value.
90 *
91 * @type string $mode Mode.
92 * @type string $eagerness Eagerness.
93 * @type string $authentication Authentication.
94 * }
95 */
96 function plsr_get_setting_default(): array {
97 return array(
98 'mode' => 'prerender',
99 'eagerness' => 'moderate',
100 'authentication' => 'logged_out',
101 );
102 }
103
104 /**
105 * Returns the stored setting value for Speculative Loading configuration.
106 *
107 * @since 1.4.0
108 *
109 * @return array{ mode: 'prefetch'|'prerender', eagerness: 'conservative'|'moderate'|'eager', authentication: 'logged_out'|'logged_out_and_admins'|'any' } {
110 * Stored setting value.
111 *
112 * @type string $mode Mode.
113 * @type string $eagerness Eagerness.
114 * @type string $authentication Authentication.
115 * }
116 */
117 function plsr_get_stored_setting_value(): array {
118 return plsr_sanitize_setting( get_option( 'plsr_speculation_rules' ) );
119 }
120
121 /**
122 * Sanitizes the setting for Speculative Loading configuration.
123 *
124 * @since 1.0.0
125 * @todo Consider whether the JSON schema for the setting could be reused here.
126 *
127 * @param mixed $input Setting to sanitize.
128 * @return array{ mode: 'prefetch'|'prerender', eagerness: 'conservative'|'moderate'|'eager', authentication: 'logged_out'|'logged_out_and_admins'|'any' } {
129 * Sanitized setting.
130 *
131 * @type string $mode Mode.
132 * @type string $eagerness Eagerness.
133 * @type string $authentication Authentication.
134 * }
135 */
136 function plsr_sanitize_setting( $input ): array {
137 $default_value = plsr_get_setting_default();
138
139 if ( ! is_array( $input ) ) {
140 return $default_value;
141 }
142
143 // Ensure only valid keys are present.
144 $value = array_intersect_key( array_merge( $default_value, $input ), $default_value );
145
146 // Constrain values to what is allowed.
147 if ( ! in_array( $value['mode'], array_keys( plsr_get_mode_labels() ), true ) ) {
148 $value['mode'] = $default_value['mode'];
149 }
150 if ( ! in_array( $value['eagerness'], array_keys( plsr_get_eagerness_labels() ), true ) ) {
151 $value['eagerness'] = $default_value['eagerness'];
152 }
153 if ( ! in_array( $value['authentication'], array_keys( plsr_get_authentication_labels() ), true ) ) {
154 $value['authentication'] = $default_value['authentication'];
155 }
156
157 // Return an explicit array literal so the sealed return shape is preserved (array_intersect_key() above yields a loose remainder).
158 return array(
159 'mode' => $value['mode'],
160 'eagerness' => $value['eagerness'],
161 'authentication' => $value['authentication'],
162 );
163 }
164
165 /**
166 * Registers setting to control Speculative Loading configuration.
167 *
168 * @since 1.0.0
169 * @access private
170 */
171 function plsr_register_setting(): void {
172 register_setting(
173 'reading',
174 'plsr_speculation_rules',
175 array(
176 'type' => 'object',
177 'description' => __( 'Configuration for the Speculation Rules API.', 'speculation-rules' ),
178 'sanitize_callback' => 'plsr_sanitize_setting',
179 'default' => plsr_get_setting_default(),
180 'show_in_rest' => array(
181 'schema' => array(
182 'type' => 'object',
183 'properties' => array(
184 'mode' => array(
185 'description' => wp_strip_all_tags( plsr_get_field_description( 'mode' ) ),
186 'type' => 'string',
187 'enum' => array_keys( plsr_get_mode_labels() ),
188 ),
189 'eagerness' => array(
190 'description' => wp_strip_all_tags( plsr_get_field_description( 'eagerness' ) ),
191 'type' => 'string',
192 'enum' => array_keys( plsr_get_eagerness_labels() ),
193 ),
194 'authentication' => array(
195 'description' => wp_strip_all_tags( plsr_get_field_description( 'authentication' ) ),
196 'type' => 'string',
197 'enum' => array_keys( plsr_get_authentication_labels() ),
198 ),
199 ),
200 'additionalProperties' => false,
201 ),
202 ),
203 )
204 );
205 }
206 add_action( 'init', 'plsr_register_setting' );
207
208 /**
209 * Adds the settings sections and fields for the Speculative Loading configuration.
210 *
211 * @since 1.0.0
212 * @access private
213 */
214 function plsr_add_setting_ui(): void {
215 add_settings_section(
216 'plsr_speculation_rules',
217 __( 'Speculative Loading', 'speculation-rules' ),
218 static function (): void {
219 ?>
220 <p class="description">
221 <?php esc_html_e( 'This section allows you to control how URLs that your users navigate to are speculatively loaded to improve performance.', 'speculation-rules' ); ?>
222 </p>
223 <?php
224 },
225 'reading',
226 array(
227 'before_section' => '<div id="speculative-loading">',
228 'after_section' => '</div>',
229 )
230 );
231
232 $fields = array(
233 'mode' => array(
234 'title' => __( 'Speculation Mode', 'speculation-rules' ),
235 'description' => plsr_get_field_description( 'mode' ),
236 ),
237 'eagerness' => array(
238 'title' => __( 'Eagerness', 'speculation-rules' ),
239 'description' => plsr_get_field_description( 'eagerness' ),
240 ),
241 'authentication' => array(
242 'title' => __( 'User Authentication Status', 'speculation-rules' ),
243 'description' => plsr_get_field_description( 'authentication' ),
244 ),
245 );
246 foreach ( $fields as $slug => $args ) {
247 add_settings_field(
248 "plsr_speculation_rules_{$slug}",
249 $args['title'],
250 'plsr_render_settings_field',
251 'reading',
252 'plsr_speculation_rules',
253 array_merge( // @phpstan-ignore argument.type (WordPress documents add_settings_field()'s $args as arbitrary extra arguments forwarded to the field callback, but php-stubs/wordpress-stubs types it as a sealed array{label_for?, class?}. TODO: Fix upstream in php-stubs/wordpress-stubs and remove.)
254 array( 'field' => $slug ),
255 $args
256 )
257 );
258 }
259 }
260 add_action( 'load-options-reading.php', 'plsr_add_setting_ui' );
261
262 /**
263 * Renders a settings field for the Speculative Loading configuration.
264 *
265 * @since 1.0.0
266 * @access private
267 *
268 * @param array{ field: 'mode'|'eagerness'|'authentication', title: non-empty-string, description: non-empty-string } $args {
269 * Associative array of arguments.
270 *
271 * @type string $field The slug of the sub setting controlled by the field.
272 * @type string $title The title for the field.
273 * @type string $description Optional. A description to show for the field.
274 * }
275 */
276 function plsr_render_settings_field( array $args ): void {
277 $option = plsr_get_stored_setting_value();
278
279 switch ( $args['field'] ) {
280 case 'mode':
281 $choices = plsr_get_mode_labels();
282 break;
283 case 'eagerness':
284 $choices = plsr_get_eagerness_labels();
285 break;
286 case 'authentication':
287 $choices = plsr_get_authentication_labels();
288 break;
289 default:
290 // Invalid (and this case should never occur).
291 return; // @codeCoverageIgnore
292 }
293
294 $value = $option[ $args['field'] ];
295 ?>
296 <fieldset id="<?php echo esc_attr( 'plsr-' . $args['field'] . '-setting' ); ?>">
297 <legend class="screen-reader-text"><?php echo esc_html( $args['title'] ); ?></legend>
298 <?php foreach ( $choices as $slug => $label ) : ?>
299 <p>
300 <label>
301 <input
302 name="<?php echo esc_attr( "plsr_speculation_rules[{$args['field']}]" ); ?>"
303 type="radio"
304 value="<?php echo esc_attr( $slug ); ?>"
305 <?php checked( $value, $slug ); ?>
306 >
307 <?php echo esc_html( $label ); ?>
308 </label>
309 </p>
310 <?php endforeach; ?>
311
312 <?php if ( 'authentication' === $args['field'] && ! wp_using_ext_object_cache() ) : ?>
313 <div id="plsr-auth-notice" class="notice <?php echo esc_attr( 'logged_out' !== $value ? 'notice-warning' : 'notice-info' ); ?> inline">
314 <p>
315 <?php
316 echo wp_kses(
317 sprintf(
318 /* translators: %s: URL to persistent object cache documentation */
319 __( 'Enabling speculative loading for authenticated users may significantly increase the server load. Consider setting up a <a href="%s" target="_blank">persistent object cache</a> before enabling this feature for logged-in users.', 'speculation-rules' ),
320 'https://developer.wordpress.org/advanced-administration/performance/optimization/#object-caching'
321 ),
322 array(
323 'a' => array(
324 'href' => array(),
325 'target' => array(),
326 ),
327 )
328 );
329 ?>
330 </p>
331 </div>
332 <?php
333 // phpcs:ignore Squiz.PHP.Heredoc.NotAllowed -- Part of the PCP ruleset. Appealed in <https://github.com/WordPress/plugin-check/issues/792#issuecomment-3214985527>.
334 $js = <<<'JS'
335 const authOptions = document.getElementById( 'plsr-authentication-setting' );
336 const noticeDiv = document.getElementById( 'plsr-auth-notice' );
337 if ( authOptions && noticeDiv ) {
338 authOptions.addEventListener( 'change', ( /** @type {Event} */ event ) => {
339 const target = event.target;
340 if ( ! ( target instanceof HTMLInputElement && 'radio' === target.type ) ) {
341 return;
342 }
343 const isLoggedOut = ( target.value === 'logged_out' );
344 noticeDiv.classList.toggle( 'notice-info', isLoggedOut );
345 noticeDiv.classList.toggle( 'notice-warning', ! isLoggedOut );
346 } );
347 }
348 JS;
349 $js .= "\n//# sourceURL=speculation-rules-auth-admin-notice";
350 wp_print_inline_script_tag( $js, array( 'type' => 'module' ) );
351 ?>
352 <?php endif; ?>
353
354 <p class="description" style="max-width: 800px;">
355 <?php
356 echo wp_kses(
357 $args['description'],
358 array(
359 'a' => array(
360 'href' => array(),
361 'target' => array(),
362 ),
363 'em' => array(),
364 )
365 );
366 ?>
367 </p>
368 </fieldset>
369 <?php
370 }
371
372 /**
373 * Adds a settings link to the plugin's action links.
374 *
375 * @since 1.2.1
376 *
377 * @param string[]|mixed $links An array of plugin action links.
378 * @return string[]|mixed The modified list of actions.
379 */
380 function plsr_add_settings_action_link( $links ) {
381 if ( ! is_array( $links ) ) {
382 return $links;
383 }
384
385 return array_merge(
386 array(
387 'settings' => sprintf(
388 '<a href="%1$s">%2$s</a>',
389 esc_url( admin_url( 'options-reading.php#speculative-loading' ) ),
390 esc_html__( 'Settings', 'speculation-rules' )
391 ),
392 ),
393 $links
394 );
395 }
396 add_filter( 'plugin_action_links_' . SPECULATION_RULES_MAIN_FILE, 'plsr_add_settings_action_link' );
397