wp/wp-admin/includes/class-wp-plugins-list-table.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    14  * @access private
    14  * @access private
    15  *
    15  *
    16  * @see WP_List_Table
    16  * @see WP_List_Table
    17  */
    17  */
    18 class WP_Plugins_List_Table extends WP_List_Table {
    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;
    19 
    27 
    20 	/**
    28 	/**
    21 	 * Constructor.
    29 	 * Constructor.
    22 	 *
    30 	 *
    23 	 * @since 3.1.0
    31 	 * @since 3.1.0
    37 				'plural' => 'plugins',
    45 				'plural' => 'plugins',
    38 				'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
    46 				'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
    39 			)
    47 			)
    40 		);
    48 		);
    41 
    49 
       
    50 		$allowed_statuses = array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused', 'auto-update-enabled', 'auto-update-disabled' );
       
    51 
    42 		$status = 'all';
    52 		$status = 'all';
    43 		if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) {
    53 		if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], $allowed_statuses, true ) ) {
    44 			$status = $_REQUEST['plugin_status'];
    54 			$status = $_REQUEST['plugin_status'];
    45 		}
    55 		}
    46 
    56 
    47 		if ( isset( $_REQUEST['s'] ) ) {
    57 		if ( isset( $_REQUEST['s'] ) ) {
    48 			$_SERVER['REQUEST_URI'] = add_query_arg( 's', wp_unslash( $_REQUEST['s'] ) );
    58 			$_SERVER['REQUEST_URI'] = add_query_arg( 's', wp_unslash( $_REQUEST['s'] ) );
    49 		}
    59 		}
    50 
    60 
    51 		$page = $this->get_pagenum();
    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 );
    52 	}
    67 	}
    53 
    68 
    54 	/**
    69 	/**
    55 	 * @return array
    70 	 * @return array
    56 	 */
    71 	 */
    99 			'upgrade'            => array(),
   114 			'upgrade'            => array(),
   100 			'mustuse'            => array(),
   115 			'mustuse'            => array(),
   101 			'dropins'            => array(),
   116 			'dropins'            => array(),
   102 			'paused'             => array(),
   117 			'paused'             => array(),
   103 		);
   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 		}
   104 
   125 
   105 		$screen = $this->screen;
   126 		$screen = $this->screen;
   106 
   127 
   107 		if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) {
   128 		if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) {
   108 
   129 
   157 			 *                   user can manage network plugins (ie. a Super Admin).
   178 			 *                   user can manage network plugins (ie. a Super Admin).
   158 			 */
   179 			 */
   159 			$show_network_active = apply_filters( 'show_network_active_plugins', $show );
   180 			$show_network_active = apply_filters( 'show_network_active_plugins', $show );
   160 		}
   181 		}
   161 
   182 
   162 		set_transient( 'plugin_slugs', array_keys( $plugins['all'] ), DAY_IN_SECONDS );
       
   163 
       
   164 		if ( $screen->in_admin( 'network' ) ) {
   183 		if ( $screen->in_admin( 'network' ) ) {
   165 			$recently_activated = get_site_option( 'recently_activated', array() );
   184 			$recently_activated = get_site_option( 'recently_activated', array() );
   166 		} else {
   185 		} else {
   167 			$recently_activated = get_option( 'recently_activated', array() );
   186 			$recently_activated = get_option( 'recently_activated', array() );
   168 		}
   187 		}
   182 		$plugin_info = get_site_transient( 'update_plugins' );
   201 		$plugin_info = get_site_transient( 'update_plugins' );
   183 
   202 
   184 		foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
   203 		foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
   185 			// Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide.
   204 			// Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide.
   186 			if ( isset( $plugin_info->response[ $plugin_file ] ) ) {
   205 			if ( isset( $plugin_info->response[ $plugin_file ] ) ) {
   187 				$plugin_data                    = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data );
   206 				$plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
   188 				$plugins['all'][ $plugin_file ] = $plugin_data;
       
   189 				// Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade
       
   190 				if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) {
       
   191 					$plugins['upgrade'][ $plugin_file ] = $plugin_data;
       
   192 				}
       
   193 			} elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) {
   207 			} elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) {
   194 				$plugin_data                    = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data );
   208 				$plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], array( 'update-supported' => true ), $plugin_data );
   195 				$plugins['all'][ $plugin_file ] = $plugin_data;
   209 			} elseif ( empty( $plugin_data['update-supported'] ) ) {
   196 				// Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade
   210 				$plugin_data['update-supported'] = false;
   197 				if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) {
   211 			}
   198 					$plugins['upgrade'][ $plugin_file ] = $plugin_data;
   212 
   199 				}
   213 			/*
   200 			}
   214 			 * Create the payload that's used for the auto_update_plugin filter.
   201 
   215 			 * This is the same data contained within $plugin_info->(response|no_update) however
   202 			// Filter into individual sections
   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.
   203 			if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
   249 			if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
   204 				if ( $show_network_active ) {
   250 				if ( $show_network_active ) {
   205 					// On the non-network screen, show inactive network-only plugins if allowed
   251 					// On the non-network screen, show inactive network-only plugins if allowed.
   206 					$plugins['inactive'][ $plugin_file ] = $plugin_data;
   252 					$plugins['inactive'][ $plugin_file ] = $plugin_data;
   207 				} else {
   253 				} else {
   208 					// On the non-network screen, filter out network-only plugins as long as they're not individually active
   254 					// On the non-network screen, filter out network-only plugins as long as they're not individually active.
   209 					unset( $plugins['all'][ $plugin_file ] );
   255 					unset( $plugins['all'][ $plugin_file ] );
   210 				}
   256 				}
   211 			} elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
   257 			} elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
   212 				if ( $show_network_active ) {
   258 				if ( $show_network_active ) {
   213 					// On the non-network screen, show network-active plugins if allowed
   259 					// On the non-network screen, show network-active plugins if allowed.
   214 					$plugins['active'][ $plugin_file ] = $plugin_data;
   260 					$plugins['active'][ $plugin_file ] = $plugin_data;
   215 				} else {
   261 				} else {
   216 					// On the non-network screen, filter out network-active plugins
   262 					// On the non-network screen, filter out network-active plugins.
   217 					unset( $plugins['all'][ $plugin_file ] );
   263 					unset( $plugins['all'][ $plugin_file ] );
   218 				}
   264 				}
   219 			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
   265 			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
   220 				|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
   266 				|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
   221 				// On the non-network screen, populate the active list with plugins that are individually activated
   267 				// On the non-network screen, populate the active list with plugins that are individually activated.
   222 				// On the network-admin screen, populate the active list with plugins that are network activated
   268 				// On the network admin screen, populate the active list with plugins that are network-activated.
   223 				$plugins['active'][ $plugin_file ] = $plugin_data;
   269 				$plugins['active'][ $plugin_file ] = $plugin_data;
   224 
   270 
   225 				if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) {
   271 				if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) {
   226 					$plugins['paused'][ $plugin_file ] = $plugin_data;
   272 					$plugins['paused'][ $plugin_file ] = $plugin_data;
   227 				}
   273 				}
   228 			} else {
   274 			} else {
   229 				if ( isset( $recently_activated[ $plugin_file ] ) ) {
   275 				if ( isset( $recently_activated[ $plugin_file ] ) ) {
   230 					// Populate the recently activated list with plugins that have been recently activated
   276 					// Populate the recently activated list with plugins that have been recently activated.
   231 					$plugins['recently_activated'][ $plugin_file ] = $plugin_data;
   277 					$plugins['recently_activated'][ $plugin_file ] = $plugin_data;
   232 				}
   278 				}
   233 				// Populate the inactive list with plugins that aren't activated
   279 				// Populate the inactive list with plugins that aren't activated.
   234 				$plugins['inactive'][ $plugin_file ] = $plugin_data;
   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 				}
   235 			}
   294 			}
   236 		}
   295 		}
   237 
   296 
   238 		if ( strlen( $s ) ) {
   297 		if ( strlen( $s ) ) {
   239 			$status            = 'search';
   298 			$status            = 'search';
   243 		$totals = array();
   302 		$totals = array();
   244 		foreach ( $plugins as $type => $list ) {
   303 		foreach ( $plugins as $type => $list ) {
   245 			$totals[ $type ] = count( $list );
   304 			$totals[ $type ] = count( $list );
   246 		}
   305 		}
   247 
   306 
   248 		if ( empty( $plugins[ $status ] ) && ! in_array( $status, array( 'all', 'search' ) ) ) {
   307 		if ( empty( $plugins[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) {
   249 			$status = 'all';
   308 			$status = 'all';
   250 		}
   309 		}
   251 
   310 
   252 		$this->items = array();
   311 		$this->items = array();
   253 		foreach ( $plugins[ $status ] as $plugin_file => $plugin_data ) {
   312 		foreach ( $plugins[ $status ] as $plugin_file => $plugin_data ) {
   254 			// Translate, Don't Apply Markup, Sanitize HTML
   313 			// Translate, don't apply markup, sanitize HTML.
   255 			$this->items[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true );
   314 			$this->items[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true );
   256 		}
   315 		}
   257 
   316 
   258 		$total_this_page = $totals[ $status ];
   317 		$total_this_page = $totals[ $status ];
   259 
   318 
   346 		global $plugins;
   405 		global $plugins;
   347 
   406 
   348 		if ( ! empty( $_REQUEST['s'] ) ) {
   407 		if ( ! empty( $_REQUEST['s'] ) ) {
   349 			$s = esc_html( wp_unslash( $_REQUEST['s'] ) );
   408 			$s = esc_html( wp_unslash( $_REQUEST['s'] ) );
   350 
   409 
   351 			/* translators: %s: plugin search term */
   410 			/* translators: %s: Plugin search term. */
   352 			printf( __( 'No plugins found for “%s”.' ), $s );
   411 			printf( __( 'No plugins found for “%s”.' ), $s );
   353 
   412 
   354 			// We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link.
   413 			// We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link.
   355 			if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) {
   414 			if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) {
   356 				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 				echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?tab=search&s=' . urlencode( $s ) ) ) . '">' . __( 'Search for plugins in the WordPress Plugin Directory.' ) . '</a>';
   357 			}
   416 			}
   358 		} elseif ( ! empty( $plugins['all'] ) ) {
   417 		} elseif ( ! empty( $plugins['all'] ) ) {
   359 			_e( 'No plugins found.' );
   418 			_e( 'No plugins found.' );
   360 		} else {
   419 		} else {
   361 			_e( 'You do not appear to have any plugins available at this time.' );
   420 			_e( 'No plugins are currently available.' );
   362 		}
   421 		}
   363 	}
   422 	}
   364 
   423 
   365 	/**
   424 	/**
   366 	 * Displays the search box.
   425 	 * Displays the search box.
   397 	 * @return array
   456 	 * @return array
   398 	 */
   457 	 */
   399 	public function get_columns() {
   458 	public function get_columns() {
   400 		global $status;
   459 		global $status;
   401 
   460 
   402 		return array(
   461 		$columns = array(
   403 			'cb'          => ! in_array( $status, array( 'mustuse', 'dropins' ) ) ? '<input type="checkbox" />' : '',
   462 			'cb'          => ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ? '<input type="checkbox" />' : '',
   404 			'name'        => __( 'Plugin' ),
   463 			'name'        => __( 'Plugin' ),
   405 			'description' => __( 'Description' ),
   464 			'description' => __( 'Description' ),
   406 		);
   465 		);
       
   466 
       
   467 		if ( $this->show_autoupdates ) {
       
   468 			$columns['auto-updates'] = __( 'Automatic Updates' );
       
   469 		}
       
   470 
       
   471 		return $columns;
   407 	}
   472 	}
   408 
   473 
   409 	/**
   474 	/**
   410 	 * @return array
   475 	 * @return array
   411 	 */
   476 	 */
   427 				continue;
   492 				continue;
   428 			}
   493 			}
   429 
   494 
   430 			switch ( $type ) {
   495 			switch ( $type ) {
   431 				case 'all':
   496 				case 'all':
   432 					/* translators: %s: plugin count */
   497 					/* translators: %s: Number of plugins. */
   433 					$text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'plugins' );
   498 					$text = _nx(
       
   499 						'All <span class="count">(%s)</span>',
       
   500 						'All <span class="count">(%s)</span>',
       
   501 						$count,
       
   502 						'plugins'
       
   503 					);
   434 					break;
   504 					break;
   435 				case 'active':
   505 				case 'active':
   436 					/* translators: %s: plugin count */
   506 					/* translators: %s: Number of plugins. */
   437 					$text = _n( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', $count );
   507 					$text = _n(
       
   508 						'Active <span class="count">(%s)</span>',
       
   509 						'Active <span class="count">(%s)</span>',
       
   510 						$count
       
   511 					);
   438 					break;
   512 					break;
   439 				case 'recently_activated':
   513 				case 'recently_activated':
   440 					/* translators: %s: plugin count */
   514 					/* translators: %s: Number of plugins. */
   441 					$text = _n( 'Recently Active <span class="count">(%s)</span>', 'Recently Active <span class="count">(%s)</span>', $count );
   515 					$text = _n(
       
   516 						'Recently Active <span class="count">(%s)</span>',
       
   517 						'Recently Active <span class="count">(%s)</span>',
       
   518 						$count
       
   519 					);
   442 					break;
   520 					break;
   443 				case 'inactive':
   521 				case 'inactive':
   444 					/* translators: %s: plugin count */
   522 					/* translators: %s: Number of plugins. */
   445 					$text = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count );
   523 					$text = _n(
       
   524 						'Inactive <span class="count">(%s)</span>',
       
   525 						'Inactive <span class="count">(%s)</span>',
       
   526 						$count
       
   527 					);
   446 					break;
   528 					break;
   447 				case 'mustuse':
   529 				case 'mustuse':
   448 					/* translators: %s: plugin count */
   530 					/* translators: %s: Number of plugins. */
   449 					$text = _n( 'Must-Use <span class="count">(%s)</span>', 'Must-Use <span class="count">(%s)</span>', $count );
   531 					$text = _n(
       
   532 						'Must-Use <span class="count">(%s)</span>',
       
   533 						'Must-Use <span class="count">(%s)</span>',
       
   534 						$count
       
   535 					);
   450 					break;
   536 					break;
   451 				case 'dropins':
   537 				case 'dropins':
   452 					/* translators: %s: plugin count */
   538 					/* translators: %s: Number of plugins. */
   453 					$text = _n( 'Drop-in <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count );
   539 					$text = _n(
       
   540 						'Drop-in <span class="count">(%s)</span>',
       
   541 						'Drop-ins <span class="count">(%s)</span>',
       
   542 						$count
       
   543 					);
   454 					break;
   544 					break;
   455 				case 'paused':
   545 				case 'paused':
   456 					/* translators: %s: plugin count */
   546 					/* translators: %s: Number of plugins. */
   457 					$text = _n( 'Paused <span class="count">(%s)</span>', 'Paused <span class="count">(%s)</span>', $count );
   547 					$text = _n(
       
   548 						'Paused <span class="count">(%s)</span>',
       
   549 						'Paused <span class="count">(%s)</span>',
       
   550 						$count
       
   551 					);
   458 					break;
   552 					break;
   459 				case 'upgrade':
   553 				case 'upgrade':
   460 					/* translators: %s: plugin count */
   554 					/* translators: %s: Number of plugins. */
   461 					$text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
   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 					);
   462 					break;
   576 					break;
   463 			}
   577 			}
   464 
   578 
   465 			if ( 'search' !== $type ) {
   579 			if ( 'search' !== $type ) {
   466 				$status_links[ $type ] = sprintf(
   580 				$status_links[ $type ] = sprintf(
   482 	protected function get_bulk_actions() {
   596 	protected function get_bulk_actions() {
   483 		global $status;
   597 		global $status;
   484 
   598 
   485 		$actions = array();
   599 		$actions = array();
   486 
   600 
   487 		if ( 'active' != $status ) {
   601 		if ( 'active' !== $status ) {
   488 			$actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' );
   602 			$actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' );
   489 		}
   603 		}
   490 
   604 
   491 		if ( 'inactive' != $status && 'recent' != $status ) {
   605 		if ( 'inactive' !== $status && 'recent' !== $status ) {
   492 			$actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' );
   606 			$actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' );
   493 		}
   607 		}
   494 
   608 
   495 		if ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) {
   609 		if ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) {
   496 			if ( current_user_can( 'update_plugins' ) ) {
   610 			if ( current_user_can( 'update_plugins' ) ) {
   497 				$actions['update-selected'] = __( 'Update' );
   611 				$actions['update-selected'] = __( 'Update' );
   498 			}
   612 			}
   499 			if ( current_user_can( 'delete_plugins' ) && ( 'active' != $status ) ) {
   613 
       
   614 			if ( current_user_can( 'delete_plugins' ) && ( 'active' !== $status ) ) {
   500 				$actions['delete-selected'] = __( 'Delete' );
   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 				}
   501 			}
   625 			}
   502 		}
   626 		}
   503 
   627 
   504 		return $actions;
   628 		return $actions;
   505 	}
   629 	}
   509 	 * @param string $which
   633 	 * @param string $which
   510 	 */
   634 	 */
   511 	public function bulk_actions( $which = '' ) {
   635 	public function bulk_actions( $which = '' ) {
   512 		global $status;
   636 		global $status;
   513 
   637 
   514 		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) ) {
   638 		if ( in_array( $status, array( 'mustuse', 'dropins' ), true ) ) {
   515 			return;
   639 			return;
   516 		}
   640 		}
   517 
   641 
   518 		parent::bulk_actions( $which );
   642 		parent::bulk_actions( $which );
   519 	}
   643 	}
   523 	 * @param string $which
   647 	 * @param string $which
   524 	 */
   648 	 */
   525 	protected function extra_tablenav( $which ) {
   649 	protected function extra_tablenav( $which ) {
   526 		global $status;
   650 		global $status;
   527 
   651 
   528 		if ( ! in_array( $status, array( 'recently_activated', 'mustuse', 'dropins' ) ) ) {
   652 		if ( ! in_array( $status, array( 'recently_activated', 'mustuse', 'dropins' ), true ) ) {
   529 			return;
   653 			return;
   530 		}
   654 		}
   531 
   655 
   532 		echo '<div class="alignleft actions">';
   656 		echo '<div class="alignleft actions">';
   533 
   657 
   534 		if ( 'recently_activated' == $status ) {
   658 		if ( 'recently_activated' === $status ) {
   535 			submit_button( __( 'Clear List' ), '', 'clear-recent-list', false );
   659 			submit_button( __( 'Clear List' ), '', 'clear-recent-list', false );
   536 		} elseif ( 'top' === $which && 'mustuse' === $status ) {
   660 		} elseif ( 'top' === $which && 'mustuse' === $status ) {
   537 			echo '<p>' . sprintf(
   661 			echo '<p>' . sprintf(
   538 				/* translators: %s: mu-plugins directory name */
   662 				/* translators: %s: mu-plugins directory name. */
   539 				__( 'Files in the %s directory are executed automatically.' ),
   663 				__( 'Files in the %s directory are executed automatically.' ),
   540 				'<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>'
   664 				'<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>'
   541 			) . '</p>';
   665 			) . '</p>';
   542 		} elseif ( 'top' === $which && 'dropins' === $status ) {
   666 		} elseif ( 'top' === $which && 'dropins' === $status ) {
   543 			echo '<p>' . sprintf(
   667 			echo '<p>' . sprintf(
   544 				/* translators: %s: wp-content directory name */
   668 				/* translators: %s: wp-content directory name. */
   545 				__( 'Drop-ins are advanced plugins in the %s directory that replace WordPress functionality when present.' ),
   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.' ),
   546 				'<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
   670 				'<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
   547 			) . '</p>';
   671 			) . '</p>';
   548 		}
   672 		}
   549 		echo '</div>';
   673 		echo '</div>';
   550 	}
   674 	}
   564 	 * @global string $status
   688 	 * @global string $status
   565 	 */
   689 	 */
   566 	public function display_rows() {
   690 	public function display_rows() {
   567 		global $status;
   691 		global $status;
   568 
   692 
   569 		if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ) ) ) {
   693 		if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ), true ) ) {
   570 			return;
   694 			return;
   571 		}
   695 		}
   572 
   696 
   573 		foreach ( $this->items as $plugin_file => $plugin_data ) {
   697 		foreach ( $this->items as $plugin_file => $plugin_data ) {
   574 			$this->single_row( array( $plugin_file, $plugin_data ) );
   698 			$this->single_row( array( $plugin_file, $plugin_data ) );
   583 	 *
   707 	 *
   584 	 * @param array $item
   708 	 * @param array $item
   585 	 */
   709 	 */
   586 	public function single_row( $item ) {
   710 	public function single_row( $item ) {
   587 		global $status, $page, $s, $totals;
   711 		global $status, $page, $s, $totals;
       
   712 		static $plugin_id_attrs = array();
   588 
   713 
   589 		list( $plugin_file, $plugin_data ) = $item;
   714 		list( $plugin_file, $plugin_data ) = $item;
   590 		$context                           = $status;
   715 
   591 		$screen                            = $this->screen;
   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;
   592 
   730 
   593 		// Pre-order.
   731 		// Pre-order.
   594 		$actions = array(
   732 		$actions = array(
   595 			'deactivate' => '',
   733 			'deactivate' => '',
   596 			'activate'   => '',
   734 			'activate'   => '',
   597 			'details'    => '',
   735 			'details'    => '',
   598 			'delete'     => '',
   736 			'delete'     => '',
   599 		);
   737 		);
   600 
   738 
   601 		// Do not restrict by default
   739 		// Do not restrict by default.
   602 		$restrict_network_active = false;
   740 		$restrict_network_active = false;
   603 		$restrict_network_only   = false;
   741 		$restrict_network_only   = false;
   604 
   742 
   605 		if ( 'mustuse' === $context ) {
   743 		if ( 'mustuse' === $context ) {
   606 			$is_active = true;
   744 			$is_active = true;
   608 			$dropins     = _get_dropins();
   746 			$dropins     = _get_dropins();
   609 			$plugin_name = $plugin_file;
   747 			$plugin_name = $plugin_file;
   610 			if ( $plugin_file != $plugin_data['Name'] ) {
   748 			if ( $plugin_file != $plugin_data['Name'] ) {
   611 				$plugin_name .= '<br/>' . $plugin_data['Name'];
   749 				$plugin_name .= '<br/>' . $plugin_data['Name'];
   612 			}
   750 			}
   613 			if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant
   751 			if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant.
   614 				$is_active   = true;
   752 				$is_active   = true;
   615 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   753 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   616 			} elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true
   754 			} elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true.
   617 				$is_active   = true;
   755 				$is_active   = true;
   618 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   756 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   619 			} else {
   757 			} else {
   620 				$is_active   = false;
   758 				$is_active   = false;
   621 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="error-message">' . __( 'Inactive:' ) . '</span></strong> ' .
   759 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="error-message">' . __( 'Inactive:' ) . '</span></strong> ' .
   622 					sprintf(
   760 					sprintf(
   623 						/* translators: 1: drop-in constant name, 2: wp-config.php */
   761 						/* translators: 1: Drop-in constant name, 2: wp-config.php */
   624 						__( 'Requires %1$s in %2$s file.' ),
   762 						__( 'Requires %1$s in %2$s file.' ),
   625 						"<code>define('" . $dropins[ $plugin_file ][1] . "', true);</code>",
   763 						"<code>define('" . $dropins[ $plugin_file ][1] . "', true);</code>",
   626 						'<code>wp-config.php</code>'
   764 						'<code>wp-config.php</code>'
   627 					) . '</p>';
   765 					) . '</p>';
   628 			}
   766 			}
   639 			}
   777 			}
   640 
   778 
   641 			if ( $screen->in_admin( 'network' ) ) {
   779 			if ( $screen->in_admin( 'network' ) ) {
   642 				if ( $is_active ) {
   780 				if ( $is_active ) {
   643 					if ( current_user_can( 'manage_network_plugins' ) ) {
   781 					if ( current_user_can( 'manage_network_plugins' ) ) {
   644 						/* translators: %s: plugin name */
   782 						$actions['deactivate'] = sprintf(
   645 						$actions['deactivate'] = '<a href="' . 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 ) . '" aria-label="' . esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Deactivate' ) . '</a>';
   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 						);
   646 					}
   790 					}
   647 				} else {
   791 				} else {
   648 					if ( current_user_can( 'manage_network_plugins' ) ) {
   792 					if ( current_user_can( 'manage_network_plugins' ) ) {
   649 						/* translators: %s: plugin name */
   793 						$actions['activate'] = sprintf(
   650 						$actions['activate'] = '<a href="' . 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 ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Activate' ) . '</a>';
   794 							'<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>',
   651 					}
   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 
   652 					if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
   803 					if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
   653 						/* translators: %s: plugin name */
   804 						$actions['delete'] = sprintf(
   654 						$actions['delete'] = '<a href="' . 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' ) . '" class="delete" aria-label="' . esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Delete' ) . '</a>';
   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 						);
   655 					}
   812 					}
   656 				}
   813 				}
   657 			} else {
   814 			} else {
   658 				if ( $restrict_network_active ) {
   815 				if ( $restrict_network_active ) {
   659 					$actions = array(
   816 					$actions = array(
   663 					$actions = array(
   820 					$actions = array(
   664 						'network_only' => __( 'Network Only' ),
   821 						'network_only' => __( 'Network Only' ),
   665 					);
   822 					);
   666 				} elseif ( $is_active ) {
   823 				} elseif ( $is_active ) {
   667 					if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
   824 					if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
   668 						/* translators: %s: plugin name */
   825 						$actions['deactivate'] = sprintf(
   669 						$actions['deactivate'] = '<a href="' . 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 ) . '" aria-label="' . esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Deactivate' ) . '</a>';
   826 							'<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>',
   670 					}
   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 
   671 					if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
   835 					if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
   672 						/* translators: %s: plugin name */
   836 						$actions['resume'] = sprintf(
   673 						$actions['resume'] = '<a class="resume-link" href="' . 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 ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume' ) . '</a>';
   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 						);
   674 					}
   844 					}
   675 				} else {
   845 				} else {
   676 					if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
   846 					if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
   677 						/* translators: %s: plugin name */
   847 						$actions['activate'] = sprintf(
   678 						$actions['activate'] = '<a href="' . 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 ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Activate' ) . '</a>';
   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 						);
   679 					}
   855 					}
   680 
   856 
   681 					if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
   857 					if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
   682 						/* translators: %s: plugin name */
   858 						$actions['delete'] = sprintf(
   683 						$actions['delete'] = '<a href="' . 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' ) . '" class="delete" aria-label="' . esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Delete' ) . '</a>';
   859 							'<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>',
   684 					}
   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' ),
   685 				} // end if $is_active
   861 							esc_attr( $plugin_id_attr ),
   686 			} // end if $screen->in_admin( 'network' )
   862 							/* translators: %s: Plugin name. */
   687 		} // end if $context
   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.
   688 
   870 
   689 		$actions = array_filter( $actions );
   871 		$actions = array_filter( $actions );
   690 
   872 
   691 		if ( $screen->in_admin( 'network' ) ) {
   873 		if ( $screen->in_admin( 'network' ) ) {
   692 
   874 
   763 
   945 
   764 		$requires_php   = isset( $plugin_data['requires_php'] ) ? $plugin_data['requires_php'] : null;
   946 		$requires_php   = isset( $plugin_data['requires_php'] ) ? $plugin_data['requires_php'] : null;
   765 		$compatible_php = is_php_version_compatible( $requires_php );
   947 		$compatible_php = is_php_version_compatible( $requires_php );
   766 		$class          = $is_active ? 'active' : 'inactive';
   948 		$class          = $is_active ? 'active' : 'inactive';
   767 		$checkbox_id    = 'checkbox_' . md5( $plugin_data['Name'] );
   949 		$checkbox_id    = 'checkbox_' . md5( $plugin_data['Name'] );
   768 		if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ) ) || ! $compatible_php ) {
   950 		if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ), true ) || ! $compatible_php ) {
   769 			$checkbox = '';
   951 			$checkbox = '';
   770 		} else {
   952 		} else {
   771 			/* translators: %s: plugin name */
   953 			$checkbox = sprintf(
   772 			$checkbox = "<label class='screen-reader-text' for='" . $checkbox_id . "' >" . sprintf( __( 'Select %s' ), $plugin_data['Name'] ) . '</label>'
   954 				'<label class="screen-reader-text" for="%1$s">%2$s</label>' .
   773 				. "<input type='checkbox' name='checked[]' value='" . esc_attr( $plugin_file ) . "' id='" . $checkbox_id . "' />";
   955 				'<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" />',
   774 		}
   956 				$checkbox_id,
   775 		if ( 'dropins' != $context ) {
   957 				/* translators: %s: Plugin name. */
       
   958 				sprintf( __( 'Select %s' ), $plugin_data['Name'] ),
       
   959 				esc_attr( $plugin_file )
       
   960 			);
       
   961 		}
       
   962 		if ( 'dropins' !== $context ) {
   776 			$description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : '&nbsp;' ) . '</p>';
   963 			$description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : '&nbsp;' ) . '</p>';
   777 			$plugin_name = $plugin_data['Name'];
   964 			$plugin_name = $plugin_data['Name'];
   778 		}
   965 		}
   779 
   966 
   780 		if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) ) {
   967 		if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) ) {
   785 
   972 
   786 		if ( $paused ) {
   973 		if ( $paused ) {
   787 			$class .= ' paused';
   974 			$class .= ' paused';
   788 		}
   975 		}
   789 
   976 
   790 		$plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
   977 		if ( is_uninstallable_plugin( $plugin_file ) ) {
       
   978 			$class .= ' is-uninstallable';
       
   979 		}
       
   980 
   791 		printf(
   981 		printf(
   792 			'<tr class="%s" data-slug="%s" data-plugin="%s">',
   982 			'<tr class="%s" data-slug="%s" data-plugin="%s">',
   793 			esc_attr( $class ),
   983 			esc_attr( $class ),
   794 			esc_attr( $plugin_slug ),
   984 			esc_attr( $plugin_slug ),
   795 			esc_attr( $plugin_file )
   985 			esc_attr( $plugin_file )
   796 		);
   986 		);
   797 
   987 
   798 		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
   988 		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
   799 
   989 
       
   990 		$auto_updates      = (array) get_site_option( 'auto_update_plugins', array() );
       
   991 		$available_updates = get_site_transient( 'update_plugins' );
       
   992 
   800 		foreach ( $columns as $column_name => $column_display_name ) {
   993 		foreach ( $columns as $column_name => $column_display_name ) {
   801 			$extra_classes = '';
   994 			$extra_classes = '';
   802 			if ( in_array( $column_name, $hidden ) ) {
   995 			if ( in_array( $column_name, $hidden, true ) ) {
   803 				$extra_classes = ' hidden';
   996 				$extra_classes = ' hidden';
   804 			}
   997 			}
   805 
   998 
   806 			switch ( $column_name ) {
   999 			switch ( $column_name ) {
   807 				case 'cb':
  1000 				case 'cb':
   819 						<div class='plugin-description'>$description</div>
  1012 						<div class='plugin-description'>$description</div>
   820 						<div class='$class second plugin-version-author-uri'>";
  1013 						<div class='$class second plugin-version-author-uri'>";
   821 
  1014 
   822 					$plugin_meta = array();
  1015 					$plugin_meta = array();
   823 					if ( ! empty( $plugin_data['Version'] ) ) {
  1016 					if ( ! empty( $plugin_data['Version'] ) ) {
   824 						/* translators: %s: plugin version number */
  1017 						/* translators: %s: Plugin version number. */
   825 						$plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
  1018 						$plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
   826 					}
  1019 					}
   827 					if ( ! empty( $plugin_data['Author'] ) ) {
  1020 					if ( ! empty( $plugin_data['Author'] ) ) {
   828 						$author = $plugin_data['Author'];
  1021 						$author = $plugin_data['Author'];
   829 						if ( ! empty( $plugin_data['AuthorURI'] ) ) {
  1022 						if ( ! empty( $plugin_data['AuthorURI'] ) ) {
   830 							$author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
  1023 							$author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>';
   831 						}
  1024 						}
   832 						/* translators: %s: plugin version number */
  1025 						/* translators: %s: Plugin author name. */
   833 						$plugin_meta[] = sprintf( __( 'By %s' ), $author );
  1026 						$plugin_meta[] = sprintf( __( 'By %s' ), $author );
   834 					}
  1027 					}
   835 
  1028 
   836 					// Details link using API info, if available
  1029 					// Details link using API info, if available.
   837 					if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) {
  1030 					if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) {
   838 						$plugin_meta[] = sprintf(
  1031 						$plugin_meta[] = sprintf(
   839 							'<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
  1032 							'<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
   840 							esc_url(
  1033 							esc_url(
   841 								network_admin_url(
  1034 								network_admin_url(
   842 									'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
  1035 									'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
   843 									'&TB_iframe=true&width=600&height=550'
  1036 									'&TB_iframe=true&width=600&height=550'
   844 								)
  1037 								)
   845 							),
  1038 							),
   846 							/* translators: %s: plugin name */
  1039 							/* translators: %s: Plugin name. */
   847 							esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ),
  1040 							esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ),
   848 							esc_attr( $plugin_name ),
  1041 							esc_attr( $plugin_name ),
   849 							__( 'View details' )
  1042 							__( 'View details' )
   850 						);
  1043 						);
   851 					} elseif ( ! empty( $plugin_data['PluginURI'] ) ) {
  1044 					} elseif ( ! empty( $plugin_data['PluginURI'] ) ) {
   859 					/**
  1052 					/**
   860 					 * Filters the array of row meta for each plugin in the Plugins list table.
  1053 					 * Filters the array of row meta for each plugin in the Plugins list table.
   861 					 *
  1054 					 *
   862 					 * @since 2.8.0
  1055 					 * @since 2.8.0
   863 					 *
  1056 					 *
   864 					 * @param string[] $plugin_meta An array of the plugin's metadata,
  1057 					 * @param string[] $plugin_meta An array of the plugin's metadata, including
   865 					 *                              including the version, author,
  1058 					 *                              the version, author, author URI, and plugin URI.
   866 					 *                              author URI, and plugin URI.
       
   867 					 * @param string   $plugin_file Path to the plugin file relative to the plugins directory.
  1059 					 * @param string   $plugin_file Path to the plugin file relative to the plugins directory.
   868 					 * @param array    $plugin_data An array of plugin data.
  1060 					 * @param array    $plugin_data An array of plugin data.
   869 					 * @param string   $status      Status of the plugin. Defaults are 'All', 'Active',
  1061 					 * @param string   $status      Status filter currently applied to the plugin list. Possible
   870 					 *                              'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
  1062 					 *                              values are: 'all', 'active', 'inactive', 'recently_activated',
   871 					 *                              'Drop-ins', 'Search', 'Paused'.
  1063 					 *                              'upgrade', 'mustuse', 'dropins', 'search', 'paused',
       
  1064 					 *                              'auto-update-enabled', 'auto-update-disabled'.
   872 					 */
  1065 					 */
   873 					$plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
  1066 					$plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
       
  1067 
   874 					echo implode( ' | ', $plugin_meta );
  1068 					echo implode( ' | ', $plugin_meta );
   875 
  1069 
   876 					echo '</div>';
  1070 					echo '</div>';
   877 
  1071 
   878 					if ( $paused ) {
  1072 					if ( $paused ) {
   886 							printf( '<div class="error-display"><p>%s</p></div>', wp_get_extension_error_description( $error ) );
  1080 							printf( '<div class="error-display"><p>%s</p></div>', wp_get_extension_error_description( $error ) );
   887 						}
  1081 						}
   888 					}
  1082 					}
   889 
  1083 
   890 					echo '</td>';
  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 ( empty( $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 
   891 					break;
  1166 					break;
   892 				default:
  1167 				default:
   893 					$classes = "$column_name column-$column_name $class";
  1168 					$classes = "$column_name column-$column_name $class";
   894 
  1169 
   895 					echo "<td class='$classes{$extra_classes}'>";
  1170 					echo "<td class='$classes{$extra_classes}'>";
   913 
  1188 
   914 		/**
  1189 		/**
   915 		 * Fires after each row in the Plugins list table.
  1190 		 * Fires after each row in the Plugins list table.
   916 		 *
  1191 		 *
   917 		 * @since 2.3.0
  1192 		 * @since 2.3.0
       
  1193 		 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' to possible values for `$status`.
   918 		 *
  1194 		 *
   919 		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
  1195 		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
   920 		 * @param array  $plugin_data An array of plugin data.
  1196 		 * @param array  $plugin_data An array of plugin data.
   921 		 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
  1197 		 * @param string $status      Status filter currently applied to the plugin list. Possible
   922 		 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
  1198 		 *                            values are: 'all', 'active', 'inactive', 'recently_activated',
   923 		 *                            'Drop-ins', 'Search', 'Paused'.
  1199 		 *                            'upgrade', 'mustuse', 'dropins', 'search', 'paused',
       
  1200 		 *                            'auto-update-enabled', 'auto-update-disabled'.
   924 		 */
  1201 		 */
   925 		do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
  1202 		do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
   926 
  1203 
   927 		/**
  1204 		/**
   928 		 * Fires after each specific row in the Plugins list table.
  1205 		 * Fires after each specific row in the Plugins list table.
   929 		 *
  1206 		 *
   930 		 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
  1207 		 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
   931 		 * to the plugin file, relative to the plugins directory.
  1208 		 * to the plugin file, relative to the plugins directory.
   932 		 *
  1209 		 *
   933 		 * @since 2.7.0
  1210 		 * @since 2.7.0
       
  1211 		 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' to possible values for `$status`.
   934 		 *
  1212 		 *
   935 		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
  1213 		 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
   936 		 * @param array  $plugin_data An array of plugin data.
  1214 		 * @param array  $plugin_data An array of plugin data.
   937 		 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
  1215 		 * @param string $status      Status filter currently applied to the plugin list. Possible
   938 		 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
  1216 		 *                            values are: 'all', 'active', 'inactive', 'recently_activated',
   939 		 *                            'Drop-ins', 'Search', 'Paused'.
  1217 		 *                            'upgrade', 'mustuse', 'dropins', 'search', 'paused',
       
  1218 		 *                            'auto-update-enabled', 'auto-update-disabled'.
   940 		 */
  1219 		 */
   941 		do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
  1220 		do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
   942 	}
  1221 	}
   943 
  1222 
   944 	/**
  1223 	/**