balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-plugins-list-table.php (42875B)


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