PluginProbe
Performance Lab / 4.1.0
Performance Lab v4.1.0
trunk 1.0.0 1.0.0-beta.1 1.0.0-beta.2 1.0.0-beta.3 1.0.0-rc.1 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.7.0 2.8.0 All 44 releases
performance-lab / includes / admin / load.php

load.php in Performance Lab 4.1.0, at includes/admin/load.php

640 lines 19.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin integration file.
4 *
5 * @package performance-lab
6 */
7
8 // @codeCoverageIgnoreStart
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly.
11 }
12 // @codeCoverageIgnoreEnd
13
14 /**
15 * Adds the features page to the Settings menu.
16 *
17 * @since 1.0.0
18 * @since 3.0.0 Renamed to perflab_add_features_page().
19 */
20 function perflab_add_features_page(): void {
21 $hook_suffix = add_options_page(
22 __( 'Performance Features', 'performance-lab' ),
23 __( 'Performance', 'performance-lab' ),
24 'manage_options',
25 PERFLAB_SCREEN,
26 'perflab_render_settings_page'
27 );
28
29 // Add the following hooks only if the screen was successfully added.
30 if ( false !== $hook_suffix ) {
31 add_action( "load-{$hook_suffix}", 'perflab_load_features_page', 10, 0 );
32 add_filter( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ), 'perflab_plugin_action_links_add_settings' );
33 }
34 }
35
36 add_action( 'admin_menu', 'perflab_add_features_page' );
37
38 /**
39 * Initializes functionality for the features page.
40 *
41 * @since 1.0.0
42 * @since 3.0.0 Renamed to perflab_load_features_page(), and the
43 * $module and $hook_suffix parameters were removed.
44 */
45 function perflab_load_features_page(): void {
46 // Handle script enqueuing for settings page.
47 add_action( 'admin_enqueue_scripts', 'perflab_enqueue_features_page_scripts' );
48
49 // Handle admin notices for settings page.
50 add_action( 'admin_notices', 'perflab_plugin_admin_notices' );
51
52 // Handle style for settings page.
53 add_action( 'admin_head', 'perflab_print_features_page_style' );
54 }
55
56 /**
57 * Renders the plugin page.
58 *
59 * @since 1.0.0
60 * @since 3.0.0 Renamed to perflab_render_settings_page().
61 */
62 function perflab_render_settings_page(): void {
63 ?>
64 <div class="wrap">
65 <?php perflab_render_plugins_ui(); ?>
66 </div>
67 <?php
68 }
69
70 /**
71 * Gets dismissed admin pointer IDs.
72 *
73 * @since 4.0.0
74 *
75 * @return non-empty-string[] Dismissed admin pointer IDs.
76 */
77 function perflab_get_dismissed_admin_pointer_ids(): array {
78 return array_filter(
79 explode(
80 ',',
81 (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true )
82 )
83 );
84 }
85
86 /**
87 * Gets the admin pointers.
88 *
89 * @since 4.0.0
90 *
91 * @return array<non-empty-string, array{ content: string, plugin: non-empty-string, dismiss_if_installed: bool }> Keys are the admin pointer IDs.
92 */
93 function perflab_get_admin_pointers(): array {
94 $pointers = array(
95 'perflab-admin-pointer' => array(
96 'content' => __( 'You can now test upcoming WordPress performance features.', 'performance-lab' ),
97 'plugin' => 'performance-lab',
98 'dismiss_if_installed' => false,
99 ),
100 'perflab-feature-view-transitions' => array(
101 'content' => __( 'New <strong>View Transitions</strong> feature now available.', 'performance-lab' ),
102 'plugin' => 'view-transitions',
103 'dismiss_if_installed' => true,
104 ),
105 'perflab-feature-nocache-bfcache' => array(
106 'content' => __( 'New <strong>Instant Back/Forward</strong> feature now available.', 'performance-lab' ),
107 'plugin' => 'nocache-bfcache',
108 'dismiss_if_installed' => true,
109 ),
110 );
111
112 $installed_plugins = get_plugins();
113 if (
114 isset( $installed_plugins['speculation-rules/load.php']['Version'] )
115 &&
116 version_compare( $installed_plugins['speculation-rules/load.php']['Version'], '1.6.0', '>=' )
117 ) {
118 $pointers['perflab-feature-speculation-rules-auth'] = array(
119 'content' => __( '<strong>Speculative Loading</strong> now includes an opt-in setting for logged-in users.', 'performance-lab' ),
120 'plugin' => 'speculative-loading',
121 'dismiss_if_installed' => false,
122 );
123 }
124
125 return $pointers;
126 }
127
128 /**
129 * Initializes admin pointer.
130 *
131 * Handles the bootstrapping of the admin pointer.
132 * Mainly jQuery code that is self-initialising.
133 *
134 * @since 1.0.0
135 *
136 * @param string|null $hook_suffix The current admin page. Note this can be null because `iframe_header()` does not
137 * ensure that `$hook_suffix` is a string when it calls `do_action( 'admin_enqueue_scripts', $hook_suffix )`.
138 */
139 function perflab_admin_pointer( ?string $hook_suffix = '' ): void {
140 // See get_plugin_page_hookname().
141 $is_performance_screen = 'settings_page_' . PERFLAB_SCREEN === $hook_suffix;
142
143 // Do not show admin pointer in multisite Network admin, User admin UI, dashboard, or plugins list table. However,
144 // do proceed on the Performance screen so that all pointers can be auto-dismissed.
145 if (
146 is_network_admin() ||
147 is_user_admin() ||
148 (
149 ! in_array( $hook_suffix, array( 'index.php', 'plugins.php' ), true ) &&
150 ! $is_performance_screen
151 )
152 ) {
153 return;
154 }
155
156 $admin_pointers = perflab_get_admin_pointers();
157 $admin_pointer_ids = array_keys( $admin_pointers );
158 $dismissed_pointer_ids = perflab_get_dismissed_admin_pointer_ids();
159
160 // And if we're on the Performance screen, automatically dismiss all the pointers.
161 $auto_dismissed_pointer_ids = array();
162 if ( $is_performance_screen ) {
163 $auto_dismissed_pointer_ids = array_merge( $auto_dismissed_pointer_ids, $admin_pointer_ids );
164 }
165
166 // List of pointer IDs that are tied to feature plugin slugs.
167 $plugin_pointers_dismissed_if_installed = array();
168 foreach ( $admin_pointers as $pointer_id => $admin_pointer ) {
169 if ( $admin_pointer['dismiss_if_installed'] ) {
170 $plugin_pointers_dismissed_if_installed[ $pointer_id ] = $admin_pointer['plugin'];
171 }
172 }
173
174 // Preemptively dismiss plugin-specific pointers for plugins which are already installed.
175 $plugin_dependent_pointers_undismissed = array_diff( array_keys( $plugin_pointers_dismissed_if_installed ), $dismissed_pointer_ids );
176 if ( count( $plugin_dependent_pointers_undismissed ) > 0 ) {
177 /**
178 * Installed plugin slugs.
179 *
180 * @var non-empty-string[] $installed_plugin_slugs
181 */
182 $installed_plugin_slugs = array_map(
183 static function ( $name ) {
184 return strtok( $name, '/' );
185 },
186 array_keys( get_plugins() )
187 );
188
189 foreach ( $plugin_dependent_pointers_undismissed as $pointer_id ) {
190 if (
191 in_array( $plugin_pointers_dismissed_if_installed[ $pointer_id ], $installed_plugin_slugs, true ) &&
192 ! in_array( $pointer_id, $dismissed_pointer_ids, true )
193 ) {
194 $auto_dismissed_pointer_ids[] = $pointer_id;
195 }
196 }
197 }
198
199 // Persist the automatically-dismissed pointers.
200 if ( count( $auto_dismissed_pointer_ids ) > 0 ) {
201 $dismissed_pointer_ids = array_unique( array_merge( $dismissed_pointer_ids, $auto_dismissed_pointer_ids ) );
202 update_user_meta(
203 get_current_user_id(),
204 'dismissed_wp_pointers',
205 implode( ',', $dismissed_pointer_ids )
206 );
207 }
208
209 // Determine which admin pointers we need.
210 $new_install_pointer_id = 'perflab-admin-pointer';
211 if ( ! in_array( $new_install_pointer_id, $dismissed_pointer_ids, true ) ) {
212 $needed_pointer_ids = array( $new_install_pointer_id );
213 } else {
214 $needed_pointer_ids = $admin_pointer_ids;
215 }
216 $needed_pointer_ids = array_diff( $needed_pointer_ids, $dismissed_pointer_ids );
217
218 // No admin pointers are needed, so abort.
219 if ( count( $needed_pointer_ids ) === 0 ) {
220 return;
221 }
222
223 // Enqueue pointer CSS and JS.
224 wp_enqueue_style( 'wp-pointer' );
225 wp_enqueue_script( 'wp-pointer' );
226
227 $args = array(
228 'heading' => __( 'Performance Lab', 'performance-lab' ),
229 );
230
231 $args['content'] = implode(
232 '',
233 array_map(
234 static function ( string $needed_pointer ) use ( $admin_pointers ): string {
235 return '<p>' . $admin_pointers[ $needed_pointer ]['content'] . '</p>';
236 },
237 $needed_pointer_ids
238 )
239 );
240
241 $args['content'] .= '<p>' . sprintf(
242 /* translators: %s: settings page link */
243 esc_html__( 'Open %s to individually toggle the performance features and access any relevant settings.', 'performance-lab' ),
244 '<a href="' . esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) ) . '">' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '</a>'
245 ) . '</p>';
246
247 $wp_kses_options = array(
248 'a' => array(
249 'href' => array(),
250 ),
251 'p' => array(),
252 'strong' => array(),
253 );
254
255 $pointer_ids_to_dismiss = array_values( array_diff( $admin_pointer_ids, $dismissed_pointer_ids ) );
256
257 ob_start();
258 ?>
259 <script>
260 jQuery( function() {
261 const pointerIdsToDismiss = <?php echo wp_json_encode( $pointer_ids_to_dismiss, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_OBJECT_AS_ARRAY ); ?>;
262 const nonce = <?php echo wp_json_encode( wp_create_nonce( 'dismiss_pointer' ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>;
263
264 function dismissNextPointer() {
265 const pointerId = pointerIdsToDismiss.shift();
266 if ( ! pointerId ) {
267 return;
268 }
269
270 jQuery.post(
271 window.ajaxurl,
272 {
273 pointer: pointerId,
274 action: 'dismiss-wp-pointer',
275 _wpnonce: nonce,
276 }
277 ).then( dismissNextPointer );
278 }
279
280 // Pointer Options.
281 const options = {
282 content: <?php echo wp_json_encode( '<h3>' . esc_html( $args['heading'] ) . '</h3>' . wp_kses( $args['content'], $wp_kses_options ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>,
283 position: {
284 edge: 'left',
285 align: 'right',
286 },
287 pointerClass: 'wp-pointer arrow-top',
288 pointerWidth: 420,
289 close: dismissNextPointer
290 };
291
292 jQuery( '#menu-settings' ).pointer( options ).pointer( 'open' );
293 } );
294 </script>
295 <?php
296 $processor = new WP_HTML_Tag_Processor( (string) ob_get_clean() );
297 if ( $processor->next_tag( array( 'tag_name' => 'SCRIPT' ) ) ) {
298 wp_add_inline_script( 'wp-pointer', $processor->get_modifiable_text() );
299 }
300 }
301 add_action( 'admin_enqueue_scripts', 'perflab_admin_pointer' );
302
303 /**
304 * Adds a link to the features page to the plugin's entry in the plugins list table.
305 *
306 * This function is only used if the features page exists and is accessible.
307 *
308 * @since 1.0.0
309 *
310 * @see perflab_add_features_page()
311 *
312 * @param string[]|mixed $links List of plugin action links HTML.
313 * @return string[]|mixed Modified list of plugin action links HTML.
314 */
315 function perflab_plugin_action_links_add_settings( $links ) {
316 if ( ! is_array( $links ) ) {
317 return $links;
318 }
319
320 // Add link as the first plugin action link.
321 $settings_link = sprintf(
322 '<a href="%s">%s</a>',
323 esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) ),
324 esc_html__( 'Settings', 'performance-lab' )
325 );
326
327 return array_merge(
328 array( 'settings' => $settings_link ),
329 $links
330 );
331 }
332
333 /**
334 * Dismisses notification pointer after verifying nonce.
335 *
336 * This function adds a nonce check before dismissing perflab-admin-pointer
337 * It runs before the dismiss-wp-pointer AJAX action is performed.
338 *
339 * @since 2.3.0
340 */
341 function perflab_dismiss_wp_pointer_wrapper(): void {
342 if (
343 isset( $_POST['pointer'] )
344 &&
345 ! in_array( $_POST['pointer'], array_keys( perflab_get_admin_pointers() ), true )
346 ) {
347 // Another plugin's pointer, do nothing.
348 return;
349 }
350 check_ajax_referer( 'dismiss_pointer' );
351 }
352 add_action( 'wp_ajax_dismiss-wp-pointer', 'perflab_dismiss_wp_pointer_wrapper', 0 );
353
354 /**
355 * Gets the path to a script or stylesheet.
356 *
357 * @since 3.7.0
358 *
359 * @param string $src_path Source path.
360 * @param string|null $min_path Minified path. If not supplied, then '.min' is injected before the file extension in the source path.
361 * @return string URL to script or stylesheet.
362 */
363 function perflab_get_asset_path( string $src_path, ?string $min_path = null ): string {
364 if ( null === $min_path ) {
365 // Note: wp_scripts_get_suffix() is not used here because we need access to both the source and minified paths.
366 $min_path = (string) preg_replace( '/(?=\.\w+$)/', '.min', $src_path );
367 }
368
369 $force_src = false;
370 if ( WP_DEBUG && ! file_exists( trailingslashit( PERFLAB_PLUGIN_DIR_PATH ) . $min_path ) ) {
371 $force_src = true;
372 wp_trigger_error(
373 __FUNCTION__,
374 sprintf(
375 /* translators: %s is the minified asset path */
376 __( 'Minified asset has not been built: %s', 'performance-lab' ),
377 $min_path
378 ),
379 E_USER_WARNING
380 );
381 }
382
383 if ( SCRIPT_DEBUG || $force_src ) {
384 return $src_path;
385 }
386
387 return $min_path;
388 }
389
390 /**
391 * Callback function to handle admin scripts.
392 *
393 * @since 2.8.0
394 * @since 3.0.0 Renamed to perflab_enqueue_features_page_scripts().
395 */
396 function perflab_enqueue_features_page_scripts(): void {
397 // These assets are needed for the "Learn more" popover.
398 wp_enqueue_script( 'thickbox' );
399 wp_enqueue_style( 'thickbox' );
400 wp_enqueue_script( 'plugin-install' );
401
402 // Enqueue plugin activate AJAX script and localize script data.
403 wp_enqueue_script(
404 'perflab-plugin-activate-ajax',
405 plugins_url( perflab_get_asset_path( 'includes/admin/plugin-activate-ajax.js' ), PERFLAB_MAIN_FILE ),
406 array( 'wp-i18n', 'wp-a11y', 'wp-api-fetch' ),
407 PERFLAB_VERSION,
408 true
409 );
410 }
411
412 /**
413 * Sanitizes a plugin slug.
414 *
415 * @since 3.1.0
416 *
417 * @param mixed $unsanitized_plugin_slug Unsanitized plugin slug.
418 * @return string|null Validated and sanitized slug or else null.
419 */
420 function perflab_sanitize_plugin_slug( $unsanitized_plugin_slug ): ?string {
421 if ( in_array( $unsanitized_plugin_slug, perflab_get_standalone_plugins(), true ) ) {
422 return $unsanitized_plugin_slug;
423 }
424 return null;
425 }
426
427 /**
428 * Callback for handling installation/activation of plugin.
429 *
430 * @since 3.0.0
431 */
432 function perflab_install_activate_plugin_callback(): void {
433 check_admin_referer( 'perflab_install_activate_plugin' );
434
435 require_once ABSPATH . 'wp-admin/includes/plugin.php';
436 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
437 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
438 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
439
440 if ( ! isset( $_GET['slug'] ) ) {
441 wp_die( esc_html__( 'Missing required parameter.', 'performance-lab' ) );
442 }
443
444 $plugin_slug = perflab_sanitize_plugin_slug( wp_unslash( $_GET['slug'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- perflab_sanitize_plugin_slug() is a sanitizing function.
445 if ( null === $plugin_slug ) {
446 wp_die( esc_html__( 'Invalid plugin.', 'performance-lab' ) );
447 }
448
449 // Install and activate the plugin and its dependencies.
450 $result = perflab_install_and_activate_plugin( $plugin_slug );
451 if ( $result instanceof WP_Error ) {
452 wp_die( wp_kses_post( $result->get_error_message() ) );
453 }
454
455 $url = add_query_arg(
456 array(
457 'page' => PERFLAB_SCREEN,
458 'activate' => $plugin_slug,
459 ),
460 admin_url( 'options-general.php' )
461 );
462
463 if ( wp_safe_redirect( $url ) ) {
464 exit;
465 }
466 }
467 add_action( 'admin_action_perflab_install_activate_plugin', 'perflab_install_activate_plugin_callback' );
468
469 /**
470 * Callback function to handle admin inline style.
471 *
472 * @since 3.0.0
473 */
474 function perflab_print_features_page_style(): void {
475 ?>
476 <style>
477 .plugin-card .name,
478 .plugin-card .desc, /* For WP <6.5 versions */
479 .plugin-card .desc > p {
480 margin-left: 0;
481 }
482 .plugin-card-top {
483 /* This is required to ensure the Settings link does not extend below the bottom of a plugin card on a wide screen. */
484 min-height: 90px;
485 }
486 @media screen and (max-width: 782px) {
487 .plugin-card-top {
488 /* Same reason as above, but now the button is taller to make it easier to tap on touch screens. */
489 min-height: 110px;
490 }
491 }
492 .plugin-card .perflab-plugin-experimental {
493 font-size: 80%;
494 font-weight: normal;
495 }
496
497 @media screen and (max-width: 1100px) and (min-width: 782px), (max-width: 480px) {
498 .plugin-card .action-links {
499 margin-left: auto;
500 }
501 /* Make sure the settings link gets spaced out from the Learn more link. */
502 .plugin-card .plugin-action-buttons > li:nth-child(3) {
503 margin-left: 2ex;
504 border-left: solid 1px;
505 padding-left: 2ex;
506 }
507 }
508 </style>
509 <?php
510 }
511
512 /**
513 * Callback function hooked to admin_notices to render admin notices on the plugin's screen.
514 *
515 * @since 2.8.0
516 */
517 function perflab_plugin_admin_notices(): void {
518 if ( ! current_user_can( 'install_plugins' ) ) {
519 $are_all_plugins_installed = true;
520 $installed_plugin_slugs = array_map(
521 static function ( $name ) {
522 return strtok( $name, '/' );
523 },
524 array_keys( get_plugins() )
525 );
526 foreach ( perflab_get_standalone_plugin_version_constants() as $plugin_slug => $constant_name ) {
527 if ( ! in_array( $plugin_slug, $installed_plugin_slugs, true ) ) {
528 $are_all_plugins_installed = false;
529 break;
530 }
531 }
532
533 if ( ! $are_all_plugins_installed ) {
534 wp_admin_notice(
535 esc_html__( 'Due to your site\'s configuration, you may not be able to activate the performance features, unless the underlying plugin is already installed. Please install the relevant plugins manually.', 'performance-lab' ),
536 array(
537 'type' => 'warning',
538 )
539 );
540 return;
541 }
542 }
543
544 $activated_plugin_slug = null;
545 if ( isset( $_GET['activate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
546 $activated_plugin_slug = perflab_sanitize_plugin_slug( wp_unslash( $_GET['activate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- perflab_sanitize_plugin_slug() is a sanitizing function.
547 }
548
549 if ( null !== $activated_plugin_slug ) {
550 $message = __( 'Feature activated.', 'performance-lab' );
551
552 $plugin_settings_url = perflab_get_plugin_settings_url( $activated_plugin_slug );
553 if ( null !== $plugin_settings_url ) {
554 /* translators: %s is the settings URL */
555 $message .= ' ' . sprintf( __( 'Review <a href="%s">settings</a>.', 'performance-lab' ), esc_url( $plugin_settings_url ) );
556 }
557
558 wp_admin_notice(
559 wp_kses(
560 $message,
561 array(
562 'a' => array(
563 'href' => array(),
564 ),
565 )
566 ),
567 array(
568 'type' => 'success',
569 'dismissible' => true,
570 )
571 );
572 }
573 }
574
575 /**
576 * Gets the URL to the plugin settings screen if one exists.
577 *
578 * @since 3.1.0
579 *
580 * @param string $plugin_slug Plugin slug passed to generate the settings link.
581 * @return string|null Either the plugin settings URL or null if not available.
582 */
583 function perflab_get_plugin_settings_url( string $plugin_slug ): ?string {
584 $plugin_file = null;
585
586 foreach ( array_keys( get_plugins() ) as $file ) {
587 if ( strtok( $file, '/' ) === $plugin_slug ) {
588 $plugin_file = $file;
589 break;
590 }
591 }
592
593 if ( null === $plugin_file ) {
594 return null;
595 }
596
597 /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
598 $plugin_links = apply_filters( "plugin_action_links_{$plugin_file}", array() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentionally applying core filter.
599
600 if ( ! is_array( $plugin_links ) || ! array_key_exists( 'settings', $plugin_links ) ) {
601 return null;
602 }
603
604 $p = new WP_HTML_Tag_Processor( $plugin_links['settings'] );
605 if ( ! $p->next_tag( array( 'tag_name' => 'A' ) ) ) {
606 return null;
607 }
608 $href = $p->get_attribute( 'href' );
609 if ( is_string( $href ) && '' !== $href ) {
610 return $href;
611 }
612
613 return null;
614 }
615
616 /**
617 * Prints the Performance Lab install notice after each feature plugin's row meta.
618 *
619 * @since 3.2.0
620 *
621 * @param string $plugin_file Plugin file.
622 */
623 function perflab_print_row_meta_install_notice( string $plugin_file ): void {
624 if ( ! in_array( strtok( $plugin_file, '/' ), perflab_get_standalone_plugins(), true ) ) {
625 return;
626 }
627
628 $message = sprintf(
629 /* translators: %s: link to Performance Lab settings screen */
630 __( 'This plugin is installed by <a href="%s">Performance Lab</a>.', 'performance-lab' ),
631 esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) )
632 );
633
634 printf(
635 '<div class="requires"><p>%1$s</p></div>',
636 wp_kses( $message, array( 'a' => array( 'href' => array() ) ) )
637 );
638 }
639 add_action( 'after_plugin_row_meta', 'perflab_print_row_meta_install_notice' );
640