PluginProbe
Performance Lab / trunk
Performance Lab vtrunk
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 trunk, at includes/admin/plugins.php

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