Make WordPress Core

Ticket #51052: class-wp-plugins-list-talbe.patch

File class-wp-plugins-list-talbe.patch, 43.2 KB (added by sanzeeb3, 6 years ago)
Line 
1<?php
2/**
3 * List Table API: WP_Plugins_List_Table class
4 *
5 * @package WordPress
6 * @subpackage Administration
7 * @since 3.1.0
8 */
9
10/**
11 * Core class used to implement displaying installed plugins in a list table.
12 *
13 * @since 3.1.0
14 * @access private
15 *
16 * @see WP_List_Table
17 */
18class WP_Plugins_List_Table extends WP_List_Table {
19 /**
20 * Whether to show the auto-updates UI.
21 *
22 * @since 5.5.0
23 *
24 * @var bool True if auto-updates UI is to be shown, false otherwise.
25 */
26 protected $show_autoupdates = true;
27
28 /**
29 * Constructor.
30 *
31 * @since 3.1.0
32 *
33 * @see WP_List_Table::__construct() for more information on default arguments.
34 *
35 * @global string $status
36 * @global int $page
37 *
38 * @param array $args An associative array of arguments.
39 */
40 public function __construct( $args = array() ) {
41 global $status, $page;
42
43 parent::__construct(
44 array(
45 'plural' => 'plugins',
46 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
47 )
48 );
49
50 $allowed_statuses = array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused', 'auto-update-enabled', 'auto-update-disabled' );
51
52 $status = 'all';
53 if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], $allowed_statuses, true ) ) {
54 $status = $_REQUEST['plugin_status'];
55 }
56
57 if ( isset( $_REQUEST['s'] ) ) {
58 $_SERVER['REQUEST_URI'] = add_query_arg( 's', wp_unslash( $_REQUEST['s'] ) );
59 }
60
61 $page = $this->get_pagenum();
62
63 $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'plugin' )
64 && current_user_can( 'update_plugins' )
65 && ( ! is_multisite() || $this->screen->in_admin( 'network' ) )
66 && ! in_array( $status, array( 'mustuse', 'dropins' ), true );
67 }
68
69 /**
70 * @return array
71 */
72 protected function get_table_classes() {
73 return array( 'widefat', $this->_args['plural'] );
74 }
75
76 /**
77 * @return bool
78 */
79 public function ajax_user_can() {
80 return current_user_can( 'activate_plugins' );
81 }
82
83 /**
84 * @global string $status
85 * @global array $plugins
86 * @global array $totals
87 * @global int $page
88 * @global string $orderby
89 * @global string $order
90 * @global string $s
91 */
92 public function prepare_items() {
93 global $status, $plugins, $totals, $page, $orderby, $order, $s;
94
95 wp_reset_vars( array( 'orderby', 'order' ) );
96
97 /**
98 * Filters the full array of plugins to list in the Plugins list table.
99 *
100 * @since 3.0.0
101 *
102 * @see get_plugins()
103 *
104 * @param array $all_plugins An array of plugins to display in the list table.
105 */
106 $all_plugins = apply_filters( 'all_plugins', get_plugins() );
107
108 $plugins = array(
109 'all' => $all_plugins,
110 'search' => array(),
111 'active' => array(),
112 'inactive' => array(),
113 'recently_activated' => array(),
114 'upgrade' => array(),
115 'mustuse' => array(),
116 'dropins' => array(),
117 'paused' => array(),
118 );
119 if ( $this->show_autoupdates ) {
120 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
121
122 $plugins['auto-update-enabled'] = array();
123 $plugins['auto-update-disabled'] = array();
124 }
125
126 $screen = $this->screen;
127
128 if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) {
129
130 /**
131 * Filters whether to display the advanced plugins list table.
132 *
133 * There are two types of advanced plugins - must-use and drop-ins -
134 * which can be used in a single site or Multisite network.
135 *
136 * The $type parameter allows you to differentiate between the type of advanced
137 * plugins to filter the display of. Contexts include 'mustuse' and 'dropins'.
138 *
139 * @since 3.0.0
140 *
141 * @param bool $show Whether to show the advanced plugins for the specified
142 * plugin type. Default true.
143 * @param string $type The plugin type. Accepts 'mustuse', 'dropins'.
144 */
145 if ( apply_filters( 'show_advanced_plugins', true, 'mustuse' ) ) {
146 $plugins['mustuse'] = get_mu_plugins();
147 }
148
149 /** This action is documented in wp-admin/includes/class-wp-plugins-list-table.php */
150 if ( apply_filters( 'show_advanced_plugins', true, 'dropins' ) ) {
151 $plugins['dropins'] = get_dropins();
152 }
153
154 if ( current_user_can( 'update_plugins' ) ) {
155 $current = get_site_transient( 'update_plugins' );
156 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
157 if ( isset( $current->response[ $plugin_file ] ) ) {
158 $plugins['all'][ $plugin_file ]['update'] = true;
159 $plugins['upgrade'][ $plugin_file ] = $plugins['all'][ $plugin_file ];
160 }
161 }
162 }
163 }
164
165 if ( ! $screen->in_admin( 'network' ) ) {
166 $show = current_user_can( 'manage_network_plugins' );
167 /**
168 * Filters whether to display network-active plugins alongside plugins active for the current site.
169 *
170 * This also controls the display of inactive network-only plugins (plugins with
171 * "Network: true" in the plugin header).
172 *
173 * Plugins cannot be network-activated or network-deactivated from this screen.
174 *
175 * @since 4.4.0
176 *
177 * @param bool $show Whether to show network-active plugins. Default is whether the current
178 * user can manage network plugins (ie. a Super Admin).
179 */
180 $show_network_active = apply_filters( 'show_network_active_plugins', $show );
181 }
182
183 if ( $screen->in_admin( 'network' ) ) {
184 $recently_activated = get_site_option( 'recently_activated', array() );
185 } else {
186 $recently_activated = get_option( 'recently_activated', array() );
187 }
188
189 foreach ( $recently_activated as $key => $time ) {
190 if ( $time + WEEK_IN_SECONDS < time() ) {
191 unset( $recently_activated[ $key ] );
192 }
193 }
194
195 if ( $screen->in_admin( 'network' ) ) {
196 update_site_option( 'recently_activated', $recently_activated );
197 } else {
198 update_option( 'recently_activated', $recently_activated );
199 }
200
201 $plugin_info = get_site_transient( 'update_plugins' );
202
203 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
204 // Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide.
205 if ( isset( $plugin_info->response[ $plugin_file ] ) ) {
206 $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
207 } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) {
208 $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
209 } elseif ( empty( $plugin_data['update-supported'] ) ) {
210 $plugin_data['update-supported'] = false;
211 }
212
213 /*
214 * Create the payload that's used for the auto_update_plugin filter.
215 * This is the same data contained within $plugin_info->(response|no_update) however
216 * not all plugins will be contained in those keys, this avoids unexpected warnings.
217 */
218 $filter_payload = array(
219 'id' => $plugin_file,
220 'slug' => '',
221 'plugin' => $plugin_file,
222 'new_version' => '',
223 'url' => '',
224 'package' => '',
225 'icons' => array(),
226 'banners' => array(),
227 'banners_rtl' => array(),
228 'tested' => '',
229 'requires_php' => '',
230 'compatibility' => new stdClass(),
231 );
232 $filter_payload = (object) array_merge( $filter_payload, array_intersect_key( $plugin_data, $filter_payload ) );
233
234 $type = 'plugin';
235 /** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */
236 $auto_update_forced = apply_filters( "auto_update_{$type}", null, $filter_payload );
237
238 if ( ! is_null( $auto_update_forced ) ) {
239 $plugin_data['auto-update-forced'] = $auto_update_forced;
240 }
241
242 $plugins['all'][ $plugin_file ] = $plugin_data;
243 // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade.
244 if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) {
245 $plugins['upgrade'][ $plugin_file ] = $plugin_data;
246 }
247
248 // Filter into individual sections.
249 if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
250 if ( $show_network_active ) {
251 // On the non-network screen, show inactive network-only plugins if allowed.
252 $plugins['inactive'][ $plugin_file ] = $plugin_data;
253 } else {
254 // On the non-network screen, filter out network-only plugins as long as they're not individually active.
255 unset( $plugins['all'][ $plugin_file ] );
256 }
257 } elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
258 if ( $show_network_active ) {
259 // On the non-network screen, show network-active plugins if allowed.
260 $plugins['active'][ $plugin_file ] = $plugin_data;
261 } else {
262 // On the non-network screen, filter out network-active plugins.
263 unset( $plugins['all'][ $plugin_file ] );
264 }
265 } elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
266 || ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
267 // On the non-network screen, populate the active list with plugins that are individually activated.
268 // On the network admin screen, populate the active list with plugins that are network-activated.
269 $plugins['active'][ $plugin_file ] = $plugin_data;
270
271 if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) {
272 $plugins['paused'][ $plugin_file ] = $plugin_data;
273 }
274 } else {
275 if ( isset( $recently_activated[ $plugin_file ] ) ) {
276 // Populate the recently activated list with plugins that have been recently activated.
277 $plugins['recently_activated'][ $plugin_file ] = $plugin_data;
278 }
279 // Populate the inactive list with plugins that aren't activated.
280 $plugins['inactive'][ $plugin_file ] = $plugin_data;
281 }
282
283 if ( $this->show_autoupdates ) {
284 $enabled = in_array( $plugin_file, $auto_updates, true ) && $plugin_data['update-supported'];
285 if ( isset( $plugin_data['auto-update-forced'] ) ) {
286 $enabled = (bool) $plugin_data['auto-update-forced'];
287 }
288
289 if ( $enabled ) {
290 $plugins['auto-update-enabled'][ $plugin_file ] = $plugin_data;
291 } else {
292 $plugins['auto-update-disabled'][ $plugin_file ] = $plugin_data;
293 }
294 }
295 }
296
297 if ( strlen( $s ) ) {
298 $status = 'search';
299 $plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) );
300 }
301
302 $totals = array();
303 foreach ( $plugins as $type => $list ) {
304 $totals[ $type ] = count( $list );
305 }
306
307 if ( empty( $plugins[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) {
308 $status = 'all';
309 }
310
311 $this->items = array();
312 foreach ( $plugins[ $status ] as $plugin_file => $plugin_data ) {
313 // Translate, don't apply markup, sanitize HTML.
314 $this->items[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true );
315 }
316
317 $total_this_page = $totals[ $status ];
318
319 $js_plugins = array();
320 foreach ( $plugins as $key => $list ) {
321 $js_plugins[ $key ] = array_keys( (array) $list );
322 }
323
324 wp_localize_script(
325 'updates',
326 '_wpUpdatesItemCounts',
327 array(
328 'plugins' => $js_plugins,
329 'totals' => wp_get_update_data(),
330 )
331 );
332
333 if ( ! $orderby ) {
334 $orderby = 'Name';
335 } else {
336 $orderby = ucfirst( $orderby );
337 }
338
339 $order = strtoupper( $order );
340
341 uasort( $this->items, array( $this, '_order_callback' ) );
342
343 $plugins_per_page = $this->get_items_per_page( str_replace( '-', '_', $screen->id . '_per_page' ), 999 );
344
345 $start = ( $page - 1 ) * $plugins_per_page;
346
347 if ( $total_this_page > $plugins_per_page ) {
348 $this->items = array_slice( $this->items, $start, $plugins_per_page );
349 }
350
351 $this->set_pagination_args(
352 array(
353 'total_items' => $total_this_page,
354 'per_page' => $plugins_per_page,
355 )
356 );
357 }
358
359 /**
360 * @global string $s URL encoded search term.
361 *
362 * @param array $plugin
363 * @return bool
364 */
365 public function _search_callback( $plugin ) {
366 global $s;
367
368 foreach ( $plugin as $value ) {
369 if ( is_string( $value ) && false !== stripos( strip_tags( $value ), urldecode( $s ) ) ) {
370 return true;
371 }
372 }
373
374 return false;
375 }
376
377 /**
378 * @global string $orderby
379 * @global string $order
380 * @param array $plugin_a
381 * @param array $plugin_b
382 * @return int
383 */
384 public function _order_callback( $plugin_a, $plugin_b ) {
385 global $orderby, $order;
386
387 $a = $plugin_a[ $orderby ];
388 $b = $plugin_b[ $orderby ];
389
390 if ( $a == $b ) {
391 return 0;
392 }
393
394 if ( 'DESC' === $order ) {
395 return strcasecmp( $b, $a );
396 } else {
397 return strcasecmp( $a, $b );
398 }
399 }
400
401 /**
402 * @global array $plugins
403 */
404 public function no_items() {
405 global $plugins;
406
407 if ( ! empty( $_REQUEST['s'] ) ) {
408 $s = esc_html( wp_unslash( $_REQUEST['s'] ) );
409
410 /* translators: %s: Plugin search term. */
411 printf( __( 'No plugins found for &#8220;%s&#8221;.' ), $s );
412
413 // We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link.
414 if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) {
415 echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?tab=search&s=' . urlencode( $s ) ) ) . '">' . __( 'Search for plugins in the WordPress Plugin Directory.' ) . '</a>';
416 }
417 } elseif ( ! empty( $plugins['all'] ) ) {
418 _e( 'No plugins found.' );
419 } else {
420 _e( 'No plugins are currently available.' );
421 }
422 }
423
424 /**
425 * Displays the search box.
426 *
427 * @since 4.6.0
428 *
429 * @param string $text The 'submit' button label.
430 * @param string $input_id ID attribute value for the search input field.
431 */
432 public function search_box( $text, $input_id ) {
433 if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
434 return;
435 }
436
437 $input_id = $input_id . '-search-input';
438
439 if ( ! empty( $_REQUEST['orderby'] ) ) {
440 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
441 }
442 if ( ! empty( $_REQUEST['order'] ) ) {
443 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
444 }
445 ?>
446 <p class="search-box">
447 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
448 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" class="wp-filter-search" name="s" value="<?php _admin_search_query(); ?>" placeholder="<?php esc_attr_e( 'Search installed plugins...' ); ?>"/>
449 <?php submit_button( $text, 'hide-if-js', '', false, array( 'id' => 'search-submit' ) ); ?>
450 </p>
451 <?php
452 }
453
454 /**
455 * @global string $status
456 * @return array
457 */
458 public function get_columns() {
459 global $status;
460
461 $columns = array(
462 'cb' => ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ? '<input type="checkbox" />' : '',
463 'name' => __( 'Plugin' ),
464 'description' => __( 'Description' ),
465 );
466
467 if ( $this->show_autoupdates ) {
468 $columns['auto-updates'] = __( 'Automatic Updates' );
469 }
470
471 return $columns;
472 }
473
474 /**
475 * @return array
476 */
477 protected function get_sortable_columns() {
478 return array();
479 }
480
481 /**
482 * @global array $totals
483 * @global string $status
484 * @return array
485 */
486 protected function get_views() {
487 global $totals, $status;
488
489 $status_links = array();
490 foreach ( $totals as $type => $count ) {
491 if ( ! $count ) {
492 continue;
493 }
494
495 switch ( $type ) {
496 case 'all':
497 /* translators: %s: Number of plugins. */
498 $text = _nx(
499 'All <span class="count">(%s)</span>',
500 'All <span class="count">(%s)</span>',
501 $count,
502 'plugins'
503 );
504 break;
505 case 'active':
506 /* translators: %s: Number of plugins. */
507 $text = _n(
508 'Active <span class="count">(%s)</span>',
509 'Active <span class="count">(%s)</span>',
510 $count
511 );
512 break;
513 case 'recently_activated':
514 /* translators: %s: Number of plugins. */
515 $text = _n(
516 'Recently Active <span class="count">(%s)</span>',
517 'Recently Active <span class="count">(%s)</span>',
518 $count
519 );
520 break;
521 case 'inactive':
522 /* translators: %s: Number of plugins. */
523 $text = _n(
524 'Inactive <span class="count">(%s)</span>',
525 'Inactive <span class="count">(%s)</span>',
526 $count
527 );
528 break;
529 case 'mustuse':
530 /* translators: %s: Number of plugins. */
531 $text = _n(
532 'Must-Use <span class="count">(%s)</span>',
533 'Must-Use <span class="count">(%s)</span>',
534 $count
535 );
536 break;
537 case 'dropins':
538 /* translators: %s: Number of plugins. */
539 $text = _n(
540 'Drop-in <span class="count">(%s)</span>',
541 'Drop-ins <span class="count">(%s)</span>',
542 $count
543 );
544 break;
545 case 'paused':
546 /* translators: %s: Number of plugins. */
547 $text = _n(
548 'Paused <span class="count">(%s)</span>',
549 'Paused <span class="count">(%s)</span>',
550 $count
551 );
552 break;
553 case 'upgrade':
554 /* translators: %s: Number of plugins. */
555 $text = _n(
556 'Update Available <span class="count">(%s)</span>',
557 'Update Available <span class="count">(%s)</span>',
558 $count
559 );
560 break;
561 case 'auto-update-enabled':
562 /* translators: %s: Number of plugins. */
563 $text = _n(
564 'Auto-updates Enabled <span class="count">(%s)</span>',
565 'Auto-updates Enabled <span class="count">(%s)</span>',
566 $count
567 );
568 break;
569 case 'auto-update-disabled':
570 /* translators: %s: Number of plugins. */
571 $text = _n(
572 'Auto-updates Disabled <span class="count">(%s)</span>',
573 'Auto-updates Disabled <span class="count">(%s)</span>',
574 $count
575 );
576 break;
577 }
578
579 if ( 'search' !== $type ) {
580 $status_links[ $type ] = sprintf(
581 "<a href='%s'%s>%s</a>",
582 add_query_arg( 'plugin_status', $type, 'plugins.php' ),
583 ( $type === $status ) ? ' class="current" aria-current="page"' : '',
584 sprintf( $text, number_format_i18n( $count ) )
585 );
586 }
587 }
588
589 return $status_links;
590 }
591
592 /**
593 * @global string $status
594 * @return array
595 */
596 protected function get_bulk_actions() {
597 global $status;
598
599 $actions = array();
600
601 if ( 'active' !== $status ) {
602 $actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' );
603 }
604
605 if ( 'inactive' !== $status && 'recent' !== $status ) {
606 $actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' );
607 }
608
609 if ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) {
610 if ( current_user_can( 'update_plugins' ) ) {
611 $actions['update-selected'] = __( 'Update' );
612 }
613
614 if ( current_user_can( 'delete_plugins' ) && ( 'active' !== $status ) ) {
615 $actions['delete-selected'] = __( 'Delete' );
616 }
617
618 if ( $this->show_autoupdates ) {
619 if ( 'auto-update-enabled' !== $status ) {
620 $actions['enable-auto-update-selected'] = __( 'Enable Auto-updates' );
621 }
622 if ( 'auto-update-disabled' !== $status ) {
623 $actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' );
624 }
625 }
626 }
627
628 return $actions;
629 }
630
631 /**
632 * @global string $status
633 * @param string $which
634 */
635 public function bulk_actions( $which = '' ) {
636 global $status;
637
638 if ( in_array( $status, array( 'mustuse', 'dropins' ), true ) ) {
639 return;
640 }
641
642 parent::bulk_actions( $which );
643 }
644
645 /**
646 * @global string $status
647 * @param string $which
648 */
649 protected function extra_tablenav( $which ) {
650 global $status;
651
652 if ( ! in_array( $status, array( 'recently_activated', 'mustuse', 'dropins' ), true ) ) {
653 return;
654 }
655
656 echo '<div class="alignleft actions">';
657
658 if ( 'recently_activated' === $status ) {
659 submit_button( __( 'Clear List' ), '', 'clear-recent-list', false );
660 } elseif ( 'top' === $which && 'mustuse' === $status ) {
661 echo '<p>' . sprintf(
662 /* translators: %s: mu-plugins directory name. */
663 __( 'Files in the %s directory are executed automatically.' ),
664 '<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>'
665 ) . '</p>';
666 } elseif ( 'top' === $which && 'dropins' === $status ) {
667 echo '<p>' . sprintf(
668 /* translators: %s: wp-content directory name. */
669 __( 'Drop-ins are single files, found in the %s directory, that replace or enhance WordPress features in ways that are not possible for traditional plugins.' ),
670 '<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
671 ) . '</p>';
672 }
673 echo '</div>';
674 }
675
676 /**
677 * @return string
678 */
679 public function current_action() {
680 if ( isset( $_POST['clear-recent-list'] ) ) {
681 return 'clear-recent-list';
682 }
683
684 return parent::current_action();
685 }
686
687 /**
688 * @global string $status
689 */
690 public function display_rows() {
691 global $status;
692
693 if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ), true ) ) {
694 return;
695 }
696
697 foreach ( $this->items as $plugin_file => $plugin_data ) {
698 $this->single_row( array( $plugin_file, $plugin_data ) );
699 }
700 }
701
702 /**
703 * @global string $status
704 * @global int $page
705 * @global string $s
706 * @global array $totals
707 *
708 * @param array $item
709 */
710 public function single_row( $item ) {
711 global $status, $page, $s, $totals;
712 static $plugin_id_attrs = array();
713
714 list( $plugin_file, $plugin_data ) = $item;
715
716 $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_data['Name'] );
717 $plugin_id_attr = $plugin_slug;
718
719 // Ensure the ID attribute is unique.
720 $suffix = 2;
721 while ( in_array( $plugin_id_attr, $plugin_id_attrs, true ) ) {
722 $plugin_id_attr = "$plugin_slug-$suffix";
723 $suffix++;
724 }
725
726 $plugin_id_attrs[] = $plugin_id_attr;
727
728 $context = $status;
729 $screen = $this->screen;
730
731 // Pre-order.
732 $actions = array(
733 'deactivate' => '',
734 'activate' => '',
735 'details' => '',
736 'delete' => '',
737 );
738
739 // Do not restrict by default.
740 $restrict_network_active = false;
741 $restrict_network_only = false;
742
743 if ( 'mustuse' === $context ) {
744 $is_active = true;
745 } elseif ( 'dropins' === $context ) {
746 $dropins = _get_dropins();
747 $plugin_name = $plugin_file;
748 if ( $plugin_file != $plugin_data['Name'] ) {
749 $plugin_name .= '<br/>' . $plugin_data['Name'];
750 }
751 if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant.
752 $is_active = true;
753 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
754 } elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true.
755 $is_active = true;
756 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
757 } else {
758 $is_active = false;
759 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="error-message">' . __( 'Inactive:' ) . '</span></strong> ' .
760 sprintf(
761 /* translators: 1: Drop-in constant name, 2: wp-config.php */
762 __( 'Requires %1$s in %2$s file.' ),
763 "<code>define('" . $dropins[ $plugin_file ][1] . "', true);</code>",
764 '<code>wp-config.php</code>'
765 ) . '</p>';
766 }
767 if ( $plugin_data['Description'] ) {
768 $description .= '<p>' . $plugin_data['Description'] . '</p>';
769 }
770 } else {
771 if ( $screen->in_admin( 'network' ) ) {
772 $is_active = is_plugin_active_for_network( $plugin_file );
773 } else {
774 $is_active = is_plugin_active( $plugin_file );
775 $restrict_network_active = ( is_multisite() && is_plugin_active_for_network( $plugin_file ) );
776 $restrict_network_only = ( is_multisite() && is_network_only_plugin( $plugin_file ) && ! $is_active );
777 }
778
779 if ( $screen->in_admin( 'network' ) ) {
780 if ( $is_active ) {
781 if ( current_user_can( 'manage_network_plugins' ) ) {
782 $actions['deactivate'] = sprintf(
783 '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
784 wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
785 esc_attr( $plugin_id_attr ),
786 /* translators: %s: Plugin name. */
787 esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
788 __( 'Network Deactivate' )
789 );
790 }
791 } else {
792 if ( current_user_can( 'manage_network_plugins' ) ) {
793 $actions['activate'] = sprintf(
794 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
795 wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
796 esc_attr( $plugin_id_attr ),
797 /* translators: %s: Plugin name. */
798 esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
799 __( 'Network Activate' )
800 );
801 }
802
803 if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
804 $actions['delete'] = sprintf(
805 '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
806 wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
807 esc_attr( $plugin_id_attr ),
808 /* translators: %s: Plugin name. */
809 esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
810 __( 'Delete' )
811 );
812 }
813 }
814 } else {
815 if ( $restrict_network_active ) {
816 $actions = array(
817 'network_active' => __( 'Network Active' ),
818 );
819 } elseif ( $restrict_network_only ) {
820 $actions = array(
821 'network_only' => __( 'Network Only' ),
822 );
823 } elseif ( $is_active ) {
824 if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
825 $actions['deactivate'] = sprintf(
826 '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
827 wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ),
828 esc_attr( $plugin_id_attr ),
829 /* translators: %s: Plugin name. */
830 esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ),
831 __( 'Deactivate' )
832 );
833 }
834
835 if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
836 $actions['resume'] = sprintf(
837 '<a href="%s" id="resume-%s" class="resume-link" aria-label="%s">%s</a>',
838 wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'resume-plugin_' . $plugin_file ),
839 esc_attr( $plugin_id_attr ),
840 /* translators: %s: Plugin name. */
841 esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ),
842 __( 'Resume' )
843 );
844 }
845 } else {
846 if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
847 $actions['activate'] = sprintf(
848 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
849 wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ),
850 esc_attr( $plugin_id_attr ),
851 /* translators: %s: Plugin name. */
852 esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ),
853 __( 'Activate' )
854 );
855 }
856
857 if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
858 $actions['delete'] = sprintf(
859 '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
860 wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ),
861 esc_attr( $plugin_id_attr ),
862 /* translators: %s: Plugin name. */
863 esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ),
864 __( 'Delete' )
865 );
866 }
867 } // End if $is_active.
868 } // End if $screen->in_admin( 'network' ).
869 } // End if $context.
870
871 $actions = array_filter( $actions );
872
873 if ( $screen->in_admin( 'network' ) ) {
874
875 /**
876 * Filters the action links displayed for each plugin in the Network Admin Plugins list table.
877 *
878 * @since 3.1.0
879 *
880 * @param string[] $actions An array of plugin action links. By default this can include 'activate',
881 * 'deactivate', and 'delete'.
882 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
883 * @param array $plugin_data An array of plugin data. See `get_plugin_data()`.
884 * @param string $context The plugin context. By default this can include 'all', 'active', 'inactive',
885 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
886 */
887 $actions = apply_filters( 'network_admin_plugin_action_links', $actions, $plugin_file, $plugin_data, $context );
888
889 /**
890 * Filters the list of action links displayed for a specific plugin in the Network Admin Plugins list table.
891 *
892 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
893 * to the plugin file, relative to the plugins directory.
894 *
895 * @since 3.1.0
896 *
897 * @param string[] $actions An array of plugin action links. By default this can include 'activate',
898 * 'deactivate', and 'delete'.
899 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
900 * @param array $plugin_data An array of plugin data. See `get_plugin_data()`.
901 * @param string $context The plugin context. By default this can include 'all', 'active', 'inactive',
902 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
903 */
904 $actions = apply_filters( "network_admin_plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context );
905
906 } else {
907
908 /**
909 * Filters the action links displayed for each plugin in the Plugins list table.
910 *
911 * @since 2.5.0
912 * @since 2.6.0 The `$context` parameter was added.
913 * @since 4.9.0 The 'Edit' link was removed from the list of action links.
914 *
915 * @param string[] $actions An array of plugin action links. By default this can include 'activate',
916 * 'deactivate', and 'delete'. With Multisite active this can also include
917 * 'network_active' and 'network_only' items.
918 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
919 * @param array $plugin_data An array of plugin data. See `get_plugin_data()`.
920 * @param string $context The plugin context. By default this can include 'all', 'active', 'inactive',
921 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
922 */
923 $actions = apply_filters( 'plugin_action_links', $actions, $plugin_file, $plugin_data, $context );
924
925 /**
926 * Filters the list of action links displayed for a specific plugin in the Plugins list table.
927 *
928 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
929 * to the plugin file, relative to the plugins directory.
930 *
931 * @since 2.7.0
932 * @since 4.9.0 The 'Edit' link was removed from the list of action links.
933 *
934 * @param string[] $actions An array of plugin action links. By default this can include 'activate',
935 * 'deactivate', and 'delete'. With Multisite active this can also include
936 * 'network_active' and 'network_only' items.
937 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
938 * @param array $plugin_data An array of plugin data. See `get_plugin_data()`.
939 * @param string $context The plugin context. By default this can include 'all', 'active', 'inactive',
940 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
941 */
942 $actions = apply_filters( "plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context );
943
944 }
945
946 $requires_php = isset( $plugin_data['requires_php'] ) ? $plugin_data['requires_php'] : null;
947 $compatible_php = is_php_version_compatible( $requires_php );
948 $class = $is_active ? 'active' : 'inactive';
949 $checkbox_id = 'checkbox_' . md5( $plugin_data['Name'] );
950 if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) {
951 $checkbox = '';
952 } else {
953 $checkbox = sprintf(
954 '<label class="screen-reader-text" for="%1$s">%2$s</label>' .
955 '<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" />',
956 $checkbox_id,
957 /* translators: %s: Plugin name. */
958 sprintf( __( 'Select %s' ), $plugin_data['Name'] ),
959 esc_attr( $plugin_file )
960 );
961 }
962 if ( 'dropins' !== $context ) {
963 $description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : '&nbsp;' ) . '</p>';
964 $plugin_name = $plugin_data['Name'];
965 }
966
967 if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) ) {
968 $class .= ' update';
969 }
970
971 $paused = ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file );
972
973 if ( $paused ) {
974 $class .= ' paused';
975 }
976
977 if ( is_uninstallable_plugin( $plugin_file ) ) {
978 $class .= ' is-uninstallable';
979 }
980
981 printf(
982 '<tr class="%s" data-slug="%s" data-plugin="%s">',
983 esc_attr( $class ),
984 esc_attr( $plugin_slug ),
985 esc_attr( $plugin_file )
986 );
987
988 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
989
990 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
991 $available_updates = get_site_transient( 'update_plugins' );
992
993 foreach ( $columns as $column_name => $column_display_name ) {
994 $extra_classes = '';
995 if ( in_array( $column_name, $hidden, true ) ) {
996 $extra_classes = ' hidden';
997 }
998
999 switch ( $column_name ) {
1000 case 'cb':
1001 echo "<th scope='row' class='check-column'>$checkbox</th>";
1002 break;
1003 case 'name':
1004 echo "<td class='plugin-title column-primary'><strong>$plugin_name</strong>";
1005 echo $this->row_actions( $actions, true );
1006 echo '</td>';
1007 break;
1008 case 'description':
1009 $classes = 'column-description desc';
1010
1011 echo "<td class='$classes{$extra_classes}'>
1012 <div class='plugin-description'>$description</div>
1013 <div class='$class second plugin-version-author-uri'>";
1014
1015 $plugin_meta = array();
1016 if ( ! empty( $plugin_data['Version'] ) ) {
1017 /* translators: %s: Plugin version number. */
1018 $plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
1019 }
1020 if ( ! empty( $plugin_data['Author'] ) ) {
1021 $author = $plugin_data['Author'];
1022 if ( ! empty( $plugin_data['AuthorURI'] ) ) {
1023 $author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
1024 }
1025 /* translators: %s: Plugin author name. */
1026 $plugin_meta[] = sprintf( __( 'By %s' ), $author );
1027 }
1028
1029 // Details link using API info, if available.
1030 if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) {
1031 $plugin_meta[] = sprintf(
1032 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
1033 esc_url(
1034 network_admin_url(
1035 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
1036 '&TB_iframe=true&width=600&height=550'
1037 )
1038 ),
1039 /* translators: %s: Plugin name. */
1040 esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ),
1041 esc_attr( $plugin_name ),
1042 __( 'View details' )
1043 );
1044 } elseif ( ! empty( $plugin_data['PluginURI'] ) ) {
1045 $plugin_meta[] = sprintf(
1046 '<a href="%s">%s</a>',
1047 esc_url( $plugin_data['PluginURI'] ),
1048 __( 'Visit plugin site' )
1049 );
1050 }
1051
1052 /**
1053 * Filters the array of row meta for each plugin in the Plugins list table.
1054 *
1055 * @since 2.8.0
1056 *
1057 * @param string[] $plugin_meta An array of the plugin's metadata, including
1058 * the version, author, author URI, and plugin URI.
1059 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1060 * @param array $plugin_data An array of plugin data.
1061 * @param string $status Status filter currently applied to the plugin list. Possible
1062 * values are: 'all', 'active', 'inactive', 'recently_activated',
1063 * 'upgrade', 'mustuse', 'dropins', 'search', 'paused',
1064 * 'auto-update-enabled', 'auto-update-disabled'.
1065 */
1066 $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
1067
1068 echo implode( ' | ', $plugin_meta );
1069
1070 echo '</div>';
1071
1072 if ( $paused ) {
1073 $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' );
1074
1075 printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text );
1076
1077 $error = wp_get_plugin_error( $plugin_file );
1078
1079 if ( false !== $error ) {
1080 printf( '<div class="error-display"><p>%s</p></div>', wp_get_extension_error_description( $error ) );
1081 }
1082 }
1083
1084 echo '</td>';
1085 break;
1086 case 'auto-updates':
1087 if ( ! $this->show_autoupdates ) {
1088 break;
1089 }
1090
1091 echo "<td class='column-auto-updates{$extra_classes}'>";
1092
1093 $html = array();
1094
1095 if ( isset( $plugin_data['auto-update-forced'] ) ) {
1096 if ( $plugin_data['auto-update-forced'] ) {
1097 // Forced on
1098 $text = __( 'Auto-updates enabled' );
1099 } else {
1100 $text = __( 'Auto-updates disabled' );
1101 }
1102 $action = 'unavailable';
1103 $time_class = ' hidden';
1104 } elseif ( ! isset( $plugin_data['update-supported'] ) ) {
1105 $text = '';
1106 $action = 'unavailable';
1107 $time_class = ' hidden';
1108 } elseif ( in_array( $plugin_file, $auto_updates, true ) ) {
1109 $text = __( 'Disable auto-updates' );
1110 $action = 'disable';
1111 $time_class = '';
1112 } else {
1113 $text = __( 'Enable auto-updates' );
1114 $action = 'enable';
1115 $time_class = ' hidden';
1116 }
1117
1118 $query_args = array(
1119 'action' => "{$action}-auto-update",
1120 'plugin' => $plugin_file,
1121 'paged' => $page,
1122 'plugin_status' => $status,
1123 );
1124
1125 $url = add_query_arg( $query_args, 'plugins.php' );
1126
1127 if ( 'unavailable' == $action ) {
1128 $html[] = '<span class="label">' . $text . '</span>';
1129 } else {
1130 $html[] = sprintf(
1131 '<a href="%s" class="toggle-auto-update aria-button-if-js" data-wp-action="%s">',
1132 wp_nonce_url( $url, 'updates' ),
1133 $action
1134 );
1135
1136 $html[] = '<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span>';
1137 $html[] = '<span class="label">' . $text . '</span>';
1138 $html[] = '</a>';
1139 }
1140
1141 if ( ! empty( $plugin_data['update'] ) ) {
1142 $html[] = sprintf(
1143 '<div class="auto-update-time%s">%s</div>',
1144 $time_class,
1145 wp_get_auto_update_message()
1146 );
1147 }
1148
1149 $html = implode( '', $html );
1150
1151 /**
1152 * Filters the HTML of the auto-updates setting for each plugin in the Plugins list table.
1153 *
1154 * @since 5.5.0
1155 *
1156 * @param string $html The HTML of the plugin's auto-update column content, including
1157 * toggle auto-update action links and time to next update.
1158 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1159 * @param array $plugin_data An array of plugin data.
1160 */
1161 echo apply_filters( 'plugin_auto_update_setting_html', $html, $plugin_file, $plugin_data );
1162
1163 echo '<div class="notice notice-error notice-alt inline hidden"><p></p></div>';
1164 echo '</td>';
1165
1166 break;
1167 default:
1168 $classes = "$column_name column-$column_name $class";
1169
1170 echo "<td class='$classes{$extra_classes}'>";
1171
1172 /**
1173 * Fires inside each custom column of the Plugins list table.
1174 *
1175 * @since 3.1.0
1176 *
1177 * @param string $column_name Name of the column.
1178 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1179 * @param array $plugin_data An array of plugin data.
1180 */
1181 do_action( 'manage_plugins_custom_column', $column_name, $plugin_file, $plugin_data );
1182
1183 echo '</td>';
1184 }
1185 }
1186
1187 echo '</tr>';
1188
1189 /**
1190 * Fires after each row in the Plugins list table.
1191 *
1192 * @since 2.3.0
1193 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' to possible values for `$status`.
1194 *
1195 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1196 * @param array $plugin_data An array of plugin data.
1197 * @param string $status Status filter currently applied to the plugin list. Possible
1198 * values are: 'all', 'active', 'inactive', 'recently_activated',
1199 * 'upgrade', 'mustuse', 'dropins', 'search', 'paused',
1200 * 'auto-update-enabled', 'auto-update-disabled'.
1201 */
1202 do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
1203
1204 /**
1205 * Fires after each specific row in the Plugins list table.
1206 *
1207 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
1208 * to the plugin file, relative to the plugins directory.
1209 *
1210 * @since 2.7.0
1211 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' to possible values for `$status`.
1212 *
1213 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
1214 * @param array $plugin_data An array of plugin data.
1215 * @param string $status Status filter currently applied to the plugin list. Possible
1216 * values are: 'all', 'active', 'inactive', 'recently_activated',
1217 * 'upgrade', 'mustuse', 'dropins', 'search', 'paused',
1218 * 'auto-update-enabled', 'auto-update-disabled'.
1219 */
1220 do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
1221 }
1222
1223 /**
1224 * Gets the name of the primary column for this specific list table.
1225 *
1226 * @since 4.3.0
1227 *
1228 * @return string Unalterable name for the primary column, in this case, 'name'.
1229 */
1230 protected function get_primary_column_name() {
1231 return 'name';
1232 }
1233}