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 / plugins.php

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

687 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin settings helper functions.
4 *
5 * @package performance-lab
6 * @noinspection PhpRedundantOptionalArgumentInspection
7 */
8
9 // @codeCoverageIgnoreStart
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13 // @codeCoverageIgnoreEnd
14
15 /**
16 * Gets plugin info for the given plugin slug from WordPress.org.
17 *
18 * @since 2.8.0
19 *
20 * @param string $plugin_slug The string identifier for the plugin in questions slug.
21 * @return array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], version: string}|WP_Error Array of plugin data or WP_Error if failed.
22 */
23 function perflab_query_plugin_info( string $plugin_slug ) {
24 $transient_key = 'perflab_plugins_info';
25 $plugins = get_transient( $transient_key );
26
27 if ( is_array( $plugins ) && isset( $plugins[ $plugin_slug ] ) ) {
28 if ( isset( $plugins[ $plugin_slug ]['error'] ) ) {
29 // Plugin was requested before but an error occurred for it.
30 return new WP_Error(
31 $plugins[ $plugin_slug ]['error']['code'],
32 $plugins[ $plugin_slug ]['error']['message']
33 );
34 }
35 return $plugins[ $plugin_slug ]; // Return cached plugin info if found.
36 }
37
38 $fields = array(
39 'name',
40 'slug',
41 'short_description',
42 'requires',
43 'requires_php',
44 'requires_plugins',
45 'version', // Needed by install_plugin_install_status().
46 );
47
48 // Proceed with API request since no cache hit.
49 $response = plugins_api(
50 'query_plugins',
51 array(
52 'author' => 'wordpressdotorg',
53 'tag' => 'performance',
54 'per_page' => 100,
55 'fields' => array_fill_keys( $fields, true ),
56 )
57 );
58
59 $has_errors = false;
60 $plugins = array();
61
62 if ( is_wp_error( $response ) ) {
63 $plugins[ $plugin_slug ] = array(
64 'error' => array(
65 'code' => 'api_error',
66 'message' => sprintf(
67 /* translators: %s: API error message */
68 __( 'Failed to retrieve plugins data from WordPress.org API: %s', 'performance-lab' ),
69 $response->get_error_message()
70 ),
71 ),
72 );
73
74 foreach ( perflab_get_standalone_plugins() as $standalone_plugin ) {
75 $plugins[ $standalone_plugin ] = $plugins[ $plugin_slug ];
76 }
77
78 $has_errors = true;
79 } elseif ( ! is_object( $response ) || ! property_exists( $response, 'plugins' ) ) {
80 $plugins[ $plugin_slug ] = array(
81 'error' => array(
82 'code' => 'no_plugins',
83 'message' => __( 'No plugins found in the API response.', 'performance-lab' ),
84 ),
85 );
86
87 foreach ( perflab_get_standalone_plugins() as $standalone_plugin ) {
88 $plugins[ $standalone_plugin ] = $plugins[ $plugin_slug ];
89 }
90
91 $has_errors = true;
92 } else {
93 $plugin_queue = perflab_get_standalone_plugins();
94
95 // Index the plugins from the API response by their slug for efficient lookup.
96 $all_performance_plugins = array_column( $response->plugins, null, 'slug' );
97
98 // Start processing the plugins using a queue-based approach.
99 while ( count( $plugin_queue ) > 0 ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
100 $current_plugin_slug = array_shift( $plugin_queue );
101
102 // Skip already-processed plugins.
103 if ( isset( $plugins[ $current_plugin_slug ] ) ) {
104 continue;
105 }
106
107 if ( ! isset( $all_performance_plugins[ $current_plugin_slug ] ) ) {
108 // Cache the fact that the plugin was not found.
109 $plugins[ $current_plugin_slug ] = array(
110 'error' => array(
111 'code' => 'plugin_not_found',
112 'message' => __( 'Plugin not found in API response.', 'performance-lab' ),
113 ),
114 );
115
116 $has_errors = true;
117 } else {
118 $plugin_data = $all_performance_plugins[ $current_plugin_slug ];
119 $plugins[ $current_plugin_slug ] = wp_array_slice_assoc( $plugin_data, $fields );
120
121 // Enqueue the required plugins slug by adding it to the queue.
122 if ( isset( $plugin_data['requires_plugins'] ) && is_array( $plugin_data['requires_plugins'] ) ) {
123 $plugin_queue = array_merge( $plugin_queue, $plugin_data['requires_plugins'] );
124 }
125 }
126 }
127
128 if ( ! isset( $plugins[ $plugin_slug ] ) ) {
129 // Cache the fact that the plugin was not found.
130 $plugins[ $plugin_slug ] = array(
131 'error' => array(
132 'code' => 'plugin_not_found',
133 'message' => __( 'The requested plugin is not part of Performance Lab plugins.', 'performance-lab' ),
134 ),
135 );
136
137 $has_errors = true;
138 }
139 }
140
141 set_transient( $transient_key, $plugins, $has_errors ? MINUTE_IN_SECONDS : HOUR_IN_SECONDS );
142
143 if ( isset( $plugins[ $plugin_slug ]['error'] ) ) {
144 return new WP_Error(
145 $plugins[ $plugin_slug ]['error']['code'],
146 $plugins[ $plugin_slug ]['error']['message']
147 );
148 }
149
150 /**
151 * Validated (mostly) plugin data.
152 *
153 * @var array<string, array{name: string, slug: string, short_description: string, requires: string|false, requires_php: string|false, requires_plugins: string[], version: string}> $plugins
154 */
155 return $plugins[ $plugin_slug ];
156 }
157
158 /**
159 * Returns an array of WPP standalone plugins.
160 *
161 * @since 2.8.0
162 *
163 * @return string[] List of WPP standalone plugins as slugs.
164 */
165 function perflab_get_standalone_plugins(): array {
166 return array_keys(
167 perflab_get_standalone_plugin_data()
168 );
169 }
170
171 /**
172 * Renders plugin UI for managing standalone plugins within PL Settings screen.
173 *
174 * @since 2.8.0
175 */
176 function perflab_render_plugins_ui(): void {
177 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
178 require_once ABSPATH . 'wp-admin/includes/plugin.php';
179
180 $plugins = array();
181 $errors = array();
182
183 $standalone_plugin_data = perflab_get_standalone_plugin_data();
184 foreach ( $standalone_plugin_data as $plugin_slug => $plugin_data ) {
185 $api_data = perflab_query_plugin_info( $plugin_slug ); // Data from wordpress.org.
186
187 // Skip if the plugin is not on WordPress.org or there was a network error.
188 if ( $api_data instanceof WP_Error ) {
189 $errors[ $plugin_slug ] = $api_data;
190 } else {
191 $plugins[ $plugin_slug ] = array_merge(
192 array(
193 'experimental' => false,
194 ),
195 $plugin_data, // Data defined within Performance Lab.
196 $api_data
197 );
198 }
199 }
200
201 if ( count( $errors ) > 0 ) {
202 $active_plugins = array_map(
203 static function ( string $file ) {
204 return strtok( $file, '/' );
205 },
206 array_keys( get_plugins() )
207 );
208 $plugin_list = '<ul>';
209 $error_messages = array();
210 foreach ( $errors as $plugin_slug => $error ) {
211 if ( defined( $standalone_plugin_data[ $plugin_slug ]['constant'] ) ) {
212 $status = __( '(active)', 'performance-lab' );
213 } elseif ( in_array( $plugin_slug, $active_plugins, true ) ) {
214 $status = __( '(installed)', 'performance-lab' );
215 } else {
216 $status = '';
217 }
218
219 $plugin_list .= sprintf(
220 '<li><a target="_blank" href="%s"><code>%s</code></a> %s</li>',
221 esc_url( trailingslashit( __( 'https://wordpress.org/plugins/', 'default' ) . $plugin_slug ) ),
222 esc_html( $plugin_slug ),
223 esc_html( $status )
224 );
225 $error_messages[] = $error->get_error_message();
226 }
227 $plugin_list .= '</ul>';
228
229 $error_messages = array_unique( $error_messages );
230
231 if ( count( $error_messages ) === 1 ) {
232 $error_text = __( 'Failed to query WordPress.org Plugin Directory for the following plugin:', 'performance-lab' );
233 $error_occurred_text = __( 'The following error occurred:', 'performance-lab' );
234 } else {
235 $error_text = __( 'Failed to query WordPress.org Plugin Directory for the following plugins:', 'performance-lab' );
236 $error_occurred_text = __( 'The following errors occurred:', 'performance-lab' );
237 }
238
239 wp_admin_notice(
240 '<p>' . esc_html( $error_text ) . '</p>' .
241 $plugin_list .
242 '<p>' . esc_html( $error_occurred_text ) . '</p>' .
243 '<ul><li>' .
244 join(
245 '</li><li>',
246 array_map(
247 static function ( string $error_message ): string {
248 return wp_kses( $error_message, array( 'a' => array( 'href' => true ) ) );
249 },
250 $error_messages
251 )
252 )
253 . '</li></ul>' .
254 '<p>' . esc_html__( 'Please consider manual plugin installation and activation. You can then access each plugin\'s settings via its respective "Settings" link on the Plugins screen.', 'performance-lab' ) . '</p>',
255 array(
256 'type' => 'error',
257 'paragraph_wrap' => false,
258 )
259 );
260 }
261
262 /*
263 * Sort plugins alphabetically, with experimental ones coming last.
264 * Even though `experimental` is a boolean flag, the underlying
265 * algorithm (`usort` with `strcmp`) makes it possible to sort by it.
266 */
267 $plugins = wp_list_sort(
268 $plugins,
269 array(
270 'experimental' => 'ASC',
271 'name' => 'ASC',
272 )
273 );
274 if ( count( $plugins ) === 0 ) {
275 return;
276 }
277 ?>
278 <div class="wrap plugin-install-php">
279 <h1><?php esc_html_e( 'Performance Features', 'performance-lab' ); ?></h1>
280 <div class="wrap">
281 <form id="plugin-filter" method="post">
282 <div class="wp-list-table widefat plugin-install wpp-standalone-plugins">
283 <h2 class="screen-reader-text"><?php esc_html_e( 'Plugins list', 'default' ); ?></h2>
284 <div id="the-list">
285 <?php
286 foreach ( $plugins as $plugin_data ) {
287 perflab_render_plugin_card( $plugin_data );
288 }
289 ?>
290 </div>
291 </div>
292 </form>
293 </div>
294 <div class="clear"></div>
295 </div>
296 <?php
297 if ( current_user_can( 'activate_plugins' ) ) {
298 ?>
299 <p>
300 <?php
301 $plugins_url = add_query_arg(
302 array(
303 's' => 'WordPress Performance Team',
304 'plugin_status' => 'all',
305 ),
306 admin_url( 'plugins.php' )
307 );
308 echo wp_kses(
309 sprintf(
310 /* translators: %s is the URL to the plugins screen */
311 __( 'Performance features are installed as plugins. To update features or remove them, <a href="%s">manage them on the plugins screen</a>.', 'performance-lab' ),
312 esc_url( $plugins_url )
313 ),
314 array(
315 'a' => array( 'href' => true ),
316 )
317 );
318 ?>
319 </p>
320 <?php
321 }
322 }
323
324 /**
325 * Checks if a given plugin is available.
326 *
327 * @since 3.1.0
328 * @see perflab_install_and_activate_plugin()
329 *
330 * @param array{name: string, slug: string, short_description: string, requires_php: string|false, requires: string|false, requires_plugins: string[], version: string} $plugin_data Plugin data from the WordPress.org API.
331 * @param array<string, array{compatible_php: bool, compatible_wp: bool, can_install: bool, can_activate: bool, activated: bool, installed: bool}> $processed_plugin_availabilities Plugin availabilities already processed. This param is only used by recursive calls.
332 * @return array{compatible_php: bool, compatible_wp: bool, can_install: bool, can_activate: bool, activated: bool, installed: bool} Availability.
333 */
334 function perflab_get_plugin_availability( array $plugin_data, array &$processed_plugin_availabilities = array() ): array {
335 if ( array_key_exists( $plugin_data['slug'], $processed_plugin_availabilities ) ) {
336 // Prevent infinite recursion by returning the previously-computed value.
337 return $processed_plugin_availabilities[ $plugin_data['slug'] ];
338 }
339
340 $availability = array(
341 'compatible_php' => (
342 false === $plugin_data['requires_php'] ||
343 is_php_version_compatible( $plugin_data['requires_php'] )
344 ),
345 'compatible_wp' => (
346 false === $plugin_data['requires'] ||
347 is_wp_version_compatible( $plugin_data['requires'] )
348 ),
349 );
350
351 $plugin_status = install_plugin_install_status( $plugin_data );
352
353 $availability['installed'] = ( 'install' !== $plugin_status['status'] );
354 $availability['activated'] = false !== $plugin_status['file'] && is_plugin_active( $plugin_status['file'] );
355
356 // The plugin is already installed or the user can install plugins.
357 $availability['can_install'] = (
358 $availability['installed'] ||
359 current_user_can( 'install_plugins' )
360 );
361
362 // The plugin is activated or the user can activate plugins.
363 $availability['can_activate'] = (
364 $availability['activated'] ||
365 false !== $plugin_status['file'] // When not false, the plugin is installed.
366 ? current_user_can( 'activate_plugin', $plugin_status['file'] )
367 : current_user_can( 'activate_plugins' )
368 );
369
370 // Store pending availability before recursing.
371 $processed_plugin_availabilities[ $plugin_data['slug'] ] = $availability;
372
373 foreach ( $plugin_data['requires_plugins'] as $requires_plugin ) {
374 $dependency_plugin_data = perflab_query_plugin_info( $requires_plugin );
375 if ( $dependency_plugin_data instanceof WP_Error ) {
376 continue;
377 }
378
379 $dependency_availability = perflab_get_plugin_availability( $dependency_plugin_data );
380 foreach ( array( 'compatible_php', 'compatible_wp', 'can_install', 'can_activate', 'installed', 'activated' ) as $key ) {
381 $availability[ $key ] = $availability[ $key ] && $dependency_availability[ $key ];
382 }
383 }
384
385 $processed_plugin_availabilities[ $plugin_data['slug'] ] = $availability;
386 return $availability;
387 }
388
389 /**
390 * Installs and activates a plugin by its slug.
391 *
392 * Dependencies are recursively installed and activated as well.
393 *
394 * @since 3.1.0
395 * @see perflab_get_plugin_availability()
396 *
397 * @param string $plugin_slug Plugin slug.
398 * @param string[] $processed_plugins Slugs for plugins which have already been processed. This param is only used by recursive calls.
399 * @return WP_Error|null WP_Error on failure.
400 */
401 function perflab_install_and_activate_plugin( string $plugin_slug, array &$processed_plugins = array() ): ?WP_Error {
402 if ( in_array( $plugin_slug, $processed_plugins, true ) ) {
403 // Prevent infinite recursion from possible circular dependency.
404 return null;
405 }
406 $processed_plugins[] = $plugin_slug;
407
408 // Get the freshest data (including the most recent download_link) as opposed what is cached by perflab_query_plugin_info().
409 $plugin_data = plugins_api(
410 'plugin_information',
411 array(
412 'slug' => $plugin_slug,
413 'fields' => array(
414 'download_link' => true,
415 'requires_plugins' => true,
416 'sections' => false, // Omit the bulk of the response which we don't need.
417 ),
418 )
419 );
420
421 if ( $plugin_data instanceof WP_Error ) {
422 return $plugin_data;
423 }
424
425 if ( is_object( $plugin_data ) ) {
426 $plugin_data = (array) $plugin_data;
427 }
428
429 // Add recommended plugins (soft dependencies) to the list of plugins installed and activated.
430 if ( 'embed-optimizer' === $plugin_slug ) {
431 $plugin_data['requires_plugins'][] = 'optimization-detective';
432 }
433
434 // Install and activate plugin dependencies first.
435 foreach ( $plugin_data['requires_plugins'] as $requires_plugin_slug ) {
436 $result = perflab_install_and_activate_plugin( $requires_plugin_slug );
437 if ( $result instanceof WP_Error ) {
438 return $result;
439 }
440 }
441
442 // Install the plugin.
443 $plugin_status = install_plugin_install_status( $plugin_data );
444 $plugin_file = $plugin_status['file'];
445 if ( 'install' === $plugin_status['status'] ) {
446 if ( ! current_user_can( 'install_plugins' ) ) {
447 return new WP_Error( 'cannot_install_plugin', __( 'Sorry, you are not allowed to install plugins on this site.', 'default' ) );
448 }
449
450 // Replace new Plugin_Installer_Skin with new Quiet_Upgrader_Skin when output needs to be suppressed.
451 $skin = new WP_Ajax_Upgrader_Skin( array( 'api' => $plugin_data ) );
452 $upgrader = new Plugin_Upgrader( $skin );
453 $result = $upgrader->install( $plugin_data['download_link'] );
454
455 if ( is_wp_error( $result ) ) {
456 return $result;
457 } elseif ( is_wp_error( $skin->result ) ) {
458 return $skin->result;
459 } elseif ( $skin->get_errors()->has_errors() ) {
460 return $skin->get_errors();
461 }
462
463 $plugins = get_plugins( '/' . $plugin_slug );
464 if ( count( $plugins ) === 0 ) {
465 return new WP_Error(
466 'plugin_not_found',
467 __( 'Plugin not found among installed plugins.', 'performance-lab' )
468 );
469 }
470
471 $plugin_file_names = array_keys( $plugins );
472 $plugin_file = $plugin_slug . '/' . $plugin_file_names[0];
473 }
474
475 // Activate the plugin.
476 if ( ! is_plugin_active( $plugin_file ) ) {
477 if ( ! current_user_can( 'activate_plugin', $plugin_file ) ) {
478 return new WP_Error( 'cannot_activate_plugin', __( 'Sorry, you are not allowed to activate this plugin.', 'default' ) );
479 }
480
481 $result = activate_plugin( $plugin_file );
482 if ( $result instanceof WP_Error ) {
483 return $result;
484 }
485 }
486
487 return null;
488 }
489
490 /**
491 * Renders individual plugin cards.
492 *
493 * This is adapted from `WP_Plugin_Install_List_Table::display_rows()` in core.
494 *
495 * @since 2.8.0
496 *
497 * @see WP_Plugin_Install_List_Table::display_rows()
498 * @link https://github.com/WordPress/wordpress-develop/blob/0b8ca16ea3bd9722bd1a38f8ab68901506b1a0e7/src/wp-admin/includes/class-wp-plugin-install-list-table.php#L467-L830
499 *
500 * @param array{name: string, slug: string, short_description: string, requires_php: string|false, requires: string|false, requires_plugins: string[], version: string, experimental: bool} $plugin_data Plugin data augmenting data from the WordPress.org API.
501 */
502 function perflab_render_plugin_card( array $plugin_data ): void {
503
504 $name = wp_strip_all_tags( $plugin_data['name'] );
505 $description = wp_strip_all_tags( $plugin_data['short_description'] );
506
507 /** This filter is documented in wp-admin/includes/class-wp-plugin-install-list-table.php */
508 $description = apply_filters( 'plugin_install_description', $description, $plugin_data ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentionally applying core filter.
509
510 $availability = perflab_get_plugin_availability( $plugin_data );
511 $compatible_php = $availability['compatible_php'];
512 $compatible_wp = $availability['compatible_wp'];
513
514 $action_links = array();
515
516 if ( $availability['activated'] ) {
517 $action_links[] = sprintf(
518 '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
519 esc_html( _x( 'Active', 'plugin', 'default' ) )
520 );
521 } elseif (
522 $availability['compatible_php'] &&
523 $availability['compatible_wp'] &&
524 $availability['can_install'] &&
525 $availability['can_activate']
526 ) {
527 $url = esc_url_raw(
528 add_query_arg(
529 array(
530 'action' => 'perflab_install_activate_plugin',
531 '_wpnonce' => wp_create_nonce( 'perflab_install_activate_plugin' ),
532 'slug' => $plugin_data['slug'],
533 ),
534 admin_url( 'options-general.php' )
535 )
536 );
537
538 $action_links[] = sprintf(
539 '<a class="button perflab-install-active-plugin" href="%s" data-plugin-slug="%s">%s</a>',
540 esc_url( $url ),
541 esc_attr( $plugin_data['slug'] ),
542 esc_html__( 'Activate', 'default' )
543 );
544 } else {
545 $explanation = $availability['can_install'] ? _x( 'Cannot Activate', 'plugin', 'default' ) : _x( 'Cannot Install', 'plugin', 'default' );
546 $action_links[] = sprintf(
547 '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
548 esc_html( $explanation )
549 );
550 }
551
552 if ( current_user_can( 'install_plugins' ) ) {
553 $title_link_attr = ' class="thickbox open-plugin-details-modal"';
554 $details_link = esc_url_raw(
555 add_query_arg(
556 array(
557 'tab' => 'plugin-information',
558 'plugin' => $plugin_data['slug'],
559 'TB_iframe' => 'true',
560 'width' => 600,
561 'height' => 550,
562 ),
563 admin_url( 'plugin-install.php' )
564 )
565 );
566
567 $action_links[] = sprintf(
568 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
569 esc_url( $details_link ),
570 /* translators: %s: Plugin name and version. */
571 esc_attr( sprintf( __( 'More information about %s', 'default' ), $name ) ),
572 esc_attr( $name ),
573 esc_html__( 'Learn more', 'performance-lab' )
574 );
575 } else {
576 $title_link_attr = ' target="_blank"';
577
578 /* translators: %s: Plugin name. */
579 $aria_label = sprintf( __( 'Visit plugin site for %s', 'default' ), $name );
580
581 $details_link = __( 'https://wordpress.org/plugins/', 'default' ) . $plugin_data['slug'] . '/';
582
583 $action_links[] = sprintf(
584 '<a href="%s" aria-label="%s" target="_blank">%s</a>',
585 esc_url( $details_link ),
586 esc_attr( $aria_label ),
587 esc_html__( 'Visit plugin site', 'default' )
588 );
589 }
590
591 if ( $availability['activated'] ) {
592 $settings_url = perflab_get_plugin_settings_url( $plugin_data['slug'] );
593 if ( null !== $settings_url ) {
594 /* translators: %s is the settings URL */
595 $action_links[] = sprintf( '<a href="%s">%s</a>', esc_url( $settings_url ), esc_html__( 'Settings', 'performance-lab' ) );
596 }
597 }
598 ?>
599 <div class="plugin-card plugin-card-<?php echo sanitize_html_class( $plugin_data['slug'] ); ?>">
600 <?php
601 if ( ! $compatible_php || ! $compatible_wp ) {
602 echo '<div class="notice inline notice-error notice-alt">';
603 if ( ! $compatible_php && ! $compatible_wp ) {
604 echo '<p>' . esc_html__( 'This plugin does not work with your versions of WordPress and PHP.', 'default' ) . '</p>';
605 if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
606 echo wp_kses_post(
607 sprintf(
608 /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */
609 ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.', 'default' ),
610 esc_url( self_admin_url( 'update-core.php' ) ),
611 esc_url( wp_get_update_php_url() )
612 )
613 );
614 wp_update_php_annotation( '<p><em>', '</em></p>' );
615 } elseif ( current_user_can( 'update_core' ) ) {
616 echo wp_kses_post(
617 sprintf(
618 /* translators: %s: URL to WordPress Updates screen. */
619 ' ' . __( '<a href="%s">Please update WordPress</a>.', 'default' ),
620 esc_url( self_admin_url( 'update-core.php' ) )
621 )
622 );
623 } elseif ( current_user_can( 'update_php' ) ) {
624 echo wp_kses_post(
625 sprintf(
626 /* translators: %s: URL to Update PHP page. */
627 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.', 'default' ),
628 esc_url( wp_get_update_php_url() )
629 )
630 );
631 wp_update_php_annotation( '<p><em>', '</em></p>' );
632 }
633 } elseif ( ! $compatible_wp ) {
634 esc_html_e( 'This plugin does not work with your version of WordPress.', 'default' );
635 if ( current_user_can( 'update_core' ) ) {
636 echo wp_kses_post(
637 sprintf(
638 /* translators: %s: URL to WordPress Updates screen. */
639 ' ' . __( '<a href="%s">Please update WordPress</a>.', 'default' ),
640 esc_url( self_admin_url( 'update-core.php' ) )
641 )
642 );
643 }
644 } elseif ( ! $compatible_php ) {
645 esc_html_e( 'This plugin does not work with your version of PHP.', 'default' );
646 if ( current_user_can( 'update_php' ) ) {
647 echo wp_kses_post(
648 sprintf(
649 /* translators: %s: URL to Update PHP page. */
650 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.', 'default' ),
651 esc_url( wp_get_update_php_url() )
652 )
653 );
654 wp_update_php_annotation( '<p><em>', '</em></p>' );
655 }
656 }
657 echo '</div>';
658 }
659 ?>
660 <div class="plugin-card-top">
661 <div class="name column-name">
662 <h3>
663 <a href="<?php echo esc_url( $details_link ); ?>"<?php echo $title_link_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
664 <?php echo wp_kses_post( $name ); ?>
665 </a>
666 <?php if ( $plugin_data['experimental'] ) : ?>
667 <em class="perflab-plugin-experimental">
668 <?php echo esc_html( _x( '(experimental)', 'plugin suffix', 'performance-lab' ) ); ?>
669 </em>
670 <?php endif; ?>
671 </h3>
672 </div>
673 <div class="action-links">
674 <ul class="plugin-action-buttons">
675 <?php foreach ( $action_links as $action_link ) : ?>
676 <li><?php echo wp_kses_post( $action_link ); ?></li>
677 <?php endforeach; ?>
678 </ul>
679 </div>
680 <div class="desc column-description">
681 <p><?php echo wp_kses_post( $description ); ?></p>
682 </div>
683 </div>
684 </div>
685 <?php
686 }
687