wp/wp-admin/includes/class-wp-plugins-list-table.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     1 <?php
     1 <?php
     2 /**
     2 /**
     3  * Plugins List Table class.
     3  * List Table API: WP_Plugins_List_Table class
     4  *
     4  *
     5  * @package WordPress
     5  * @package WordPress
     6  * @subpackage List_Table
     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  *
     7  * @since 3.1.0
    13  * @since 3.1.0
     8  * @access private
    14  * @access private
       
    15  *
       
    16  * @see WP_List_Table
     9  */
    17  */
    10 class WP_Plugins_List_Table extends WP_List_Table {
    18 class WP_Plugins_List_Table extends WP_List_Table {
    11 
    19 
    12 	/**
    20 	/**
    13 	 * Constructor.
    21 	 * Constructor.
    14 	 *
    22 	 *
    15 	 * @since 3.1.0
    23 	 * @since 3.1.0
    16 	 * @access public
       
    17 	 *
    24 	 *
    18 	 * @see WP_List_Table::__construct() for more information on default arguments.
    25 	 * @see WP_List_Table::__construct() for more information on default arguments.
       
    26 	 *
       
    27 	 * @global string $status
       
    28 	 * @global int    $page
    19 	 *
    29 	 *
    20 	 * @param array $args An associative array of arguments.
    30 	 * @param array $args An associative array of arguments.
    21 	 */
    31 	 */
    22 	public function __construct( $args = array() ) {
    32 	public function __construct( $args = array() ) {
    23 		global $status, $page;
    33 		global $status, $page;
    35 			$_SERVER['REQUEST_URI'] = add_query_arg('s', wp_unslash($_REQUEST['s']) );
    45 			$_SERVER['REQUEST_URI'] = add_query_arg('s', wp_unslash($_REQUEST['s']) );
    36 
    46 
    37 		$page = $this->get_pagenum();
    47 		$page = $this->get_pagenum();
    38 	}
    48 	}
    39 
    49 
       
    50 	/**
       
    51 	 * @return array
       
    52 	 */
    40 	protected function get_table_classes() {
    53 	protected function get_table_classes() {
    41 		return array( 'widefat', $this->_args['plural'] );
    54 		return array( 'widefat', $this->_args['plural'] );
    42 	}
    55 	}
    43 
    56 
       
    57 	/**
       
    58 	 * @return bool
       
    59 	 */
    44 	public function ajax_user_can() {
    60 	public function ajax_user_can() {
    45 		return current_user_can('activate_plugins');
    61 		return current_user_can('activate_plugins');
    46 	}
    62 	}
    47 
    63 
       
    64 	/**
       
    65 	 *
       
    66 	 * @global string $status
       
    67 	 * @global array  $plugins
       
    68 	 * @global array  $totals
       
    69 	 * @global int    $page
       
    70 	 * @global string $orderby
       
    71 	 * @global string $order
       
    72 	 * @global string $s
       
    73 	 */
    48 	public function prepare_items() {
    74 	public function prepare_items() {
    49 		global $status, $plugins, $totals, $page, $orderby, $order, $s;
    75 		global $status, $plugins, $totals, $page, $orderby, $order, $s;
    50 
    76 
    51 		wp_reset_vars( array( 'orderby', 'order', 's' ) );
    77 		wp_reset_vars( array( 'orderby', 'order' ) );
    52 
    78 
    53 		/**
    79 		/**
    54 		 * Filter the full array of plugins to list in the Plugins list table.
    80 		 * Filters the full array of plugins to list in the Plugins list table.
    55 		 *
    81 		 *
    56 		 * @since 3.0.0
    82 		 * @since 3.0.0
    57 		 *
    83 		 *
    58 		 * @see get_plugins()
    84 		 * @see get_plugins()
    59 		 *
    85 		 *
    60 		 * @param array $plugins An array of plugins to display in the list table.
    86 		 * @param array $all_plugins An array of plugins to display in the list table.
    61 		 */
    87 		 */
       
    88 		$all_plugins = apply_filters( 'all_plugins', get_plugins() );
       
    89 
    62 		$plugins = array(
    90 		$plugins = array(
    63 			'all' => apply_filters( 'all_plugins', get_plugins() ),
    91 			'all'                => $all_plugins,
    64 			'search' => array(),
    92 			'search'             => array(),
    65 			'active' => array(),
    93 			'active'             => array(),
    66 			'inactive' => array(),
    94 			'inactive'           => array(),
    67 			'recently_activated' => array(),
    95 			'recently_activated' => array(),
    68 			'upgrade' => array(),
    96 			'upgrade'            => array(),
    69 			'mustuse' => array(),
    97 			'mustuse'            => array(),
    70 			'dropins' => array()
    98 			'dropins'            => array(),
    71 		);
    99 		);
    72 
   100 
    73 		$screen = $this->screen;
   101 		$screen = $this->screen;
    74 
   102 
    75 		if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) {
   103 		if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) {
    76 
   104 
    77 			/**
   105 			/**
    78 			 * Filter whether to display the advanced plugins list table.
   106 			 * Filters whether to display the advanced plugins list table.
    79 			 *
   107 			 *
    80 			 * There are two types of advanced plugins - must-use and drop-ins -
   108 			 * There are two types of advanced plugins - must-use and drop-ins -
    81 			 * which can be used in a single site or Multisite network.
   109 			 * which can be used in a single site or Multisite network.
    82 			 *
   110 			 *
    83 			 * The $type parameter allows you to differentiate between the type of advanced
   111 			 * The $type parameter allows you to differentiate between the type of advanced
   106 					}
   134 					}
   107 				}
   135 				}
   108 			}
   136 			}
   109 		}
   137 		}
   110 
   138 
       
   139 		if ( ! $screen->in_admin( 'network' ) ) {
       
   140 			$show = current_user_can( 'manage_network_plugins' );
       
   141 			/**
       
   142 			 * Filters whether to display network-active plugins alongside plugins active for the current site.
       
   143 			 *
       
   144 			 * This also controls the display of inactive network-only plugins (plugins with
       
   145 			 * "Network: true" in the plugin header).
       
   146 			 *
       
   147 			 * Plugins cannot be network-activated or network-deactivated from this screen.
       
   148 			 *
       
   149 			 * @since 4.4.0
       
   150 			 *
       
   151 			 * @param bool $show Whether to show network-active plugins. Default is whether the current
       
   152 			 *                   user can manage network plugins (ie. a Super Admin).
       
   153 			 */
       
   154 			$show_network_active = apply_filters( 'show_network_active_plugins', $show );
       
   155 		}
       
   156 
   111 		set_transient( 'plugin_slugs', array_keys( $plugins['all'] ), DAY_IN_SECONDS );
   157 		set_transient( 'plugin_slugs', array_keys( $plugins['all'] ), DAY_IN_SECONDS );
   112 
   158 
   113 		if ( ! $screen->in_admin( 'network' ) ) {
   159 		if ( $screen->in_admin( 'network' ) ) {
       
   160 			$recently_activated = get_site_option( 'recently_activated', array() );
       
   161 		} else {
   114 			$recently_activated = get_option( 'recently_activated', array() );
   162 			$recently_activated = get_option( 'recently_activated', array() );
   115 
   163 		}
   116 			foreach ( $recently_activated as $key => $time )
   164 
   117 				if ( $time + WEEK_IN_SECONDS < time() )
   165 		foreach ( $recently_activated as $key => $time ) {
   118 					unset( $recently_activated[$key] );
   166 			if ( $time + WEEK_IN_SECONDS < time() ) {
       
   167 				unset( $recently_activated[$key] );
       
   168 			}
       
   169 		}
       
   170 
       
   171 		if ( $screen->in_admin( 'network' ) ) {
       
   172 			update_site_option( 'recently_activated', $recently_activated );
       
   173 		} else {
   119 			update_option( 'recently_activated', $recently_activated );
   174 			update_option( 'recently_activated', $recently_activated );
   120 		}
   175 		}
   121 
   176 
   122 		$plugin_info = get_site_transient( 'update_plugins' );
   177 		$plugin_info = get_site_transient( 'update_plugins' );
   123 
   178 
   138 				}
   193 				}
   139 			}
   194 			}
   140 
   195 
   141 			// Filter into individual sections
   196 			// Filter into individual sections
   142 			if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
   197 			if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) {
   143 				// On the non-network screen, filter out network-only plugins as long as they're not individually activated
   198 				if ( $show_network_active ) {
   144 				unset( $plugins['all'][ $plugin_file ] );
   199 					// On the non-network screen, show inactive network-only plugins if allowed
       
   200 					$plugins['inactive'][ $plugin_file ] = $plugin_data;
       
   201 				} else {
       
   202 					// On the non-network screen, filter out network-only plugins as long as they're not individually active
       
   203 					unset( $plugins['all'][ $plugin_file ] );
       
   204 				}
   145 			} elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
   205 			} elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
   146 				// On the non-network screen, filter out network activated plugins
   206 				if ( $show_network_active ) {
   147 				unset( $plugins['all'][ $plugin_file ] );
   207 					// On the non-network screen, show network-active plugins if allowed
       
   208 					$plugins['active'][ $plugin_file ] = $plugin_data;
       
   209 				} else {
       
   210 					// On the non-network screen, filter out network-active plugins
       
   211 					unset( $plugins['all'][ $plugin_file ] );
       
   212 				}
   148 			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
   213 			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
   149 				|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
   214 				|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
   150 				// On the non-network screen, populate the active list with plugins that are individually activated
   215 				// On the non-network screen, populate the active list with plugins that are individually activated
   151 				// On the network-admin screen, populate the active list with plugins that are network activated
   216 				// On the network-admin screen, populate the active list with plugins that are network activated
   152 				$plugins['active'][ $plugin_file ] = $plugin_data;
   217 				$plugins['active'][ $plugin_file ] = $plugin_data;
   153 			} else {
   218 			} else {
   154 				if ( ! $screen->in_admin( 'network' ) && isset( $recently_activated[ $plugin_file ] ) ) {
   219 				if ( isset( $recently_activated[ $plugin_file ] ) ) {
   155 					// On the non-network screen, populate the recently activated list with plugins that have been recently activated
   220 					// Populate the recently activated list with plugins that have been recently activated
   156 					$plugins['recently_activated'][ $plugin_file ] = $plugin_data;
   221 					$plugins['recently_activated'][ $plugin_file ] = $plugin_data;
   157 				}
   222 				}
   158 				// Populate the inactive list with plugins that aren't activated
   223 				// Populate the inactive list with plugins that aren't activated
   159 				$plugins['inactive'][ $plugin_file ] = $plugin_data;
   224 				$plugins['inactive'][ $plugin_file ] = $plugin_data;
   160 			}
   225 			}
   161 		}
   226 		}
   162 
   227 
   163 		if ( $s ) {
   228 		if ( strlen( $s ) ) {
   164 			$status = 'search';
   229 			$status = 'search';
   165 			$plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) );
   230 			$plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) );
   166 		}
   231 		}
   167 
   232 
   168 		$totals = array();
   233 		$totals = array();
   178 			$this->items[$plugin_file] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true );
   243 			$this->items[$plugin_file] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true );
   179 		}
   244 		}
   180 
   245 
   181 		$total_this_page = $totals[ $status ];
   246 		$total_this_page = $totals[ $status ];
   182 
   247 
   183 		if ( $orderby ) {
   248 		$js_plugins = array();
       
   249 		foreach ( $plugins as $key => $list ) {
       
   250 			$js_plugins[ $key ] = array_keys( (array) $list );
       
   251 		}
       
   252 
       
   253 		wp_localize_script( 'updates', '_wpUpdatesItemCounts', array(
       
   254 			'plugins' => $js_plugins,
       
   255 			'totals'  => wp_get_update_data(),
       
   256 		) );
       
   257 
       
   258 		if ( ! $orderby ) {
       
   259 			$orderby = 'Name';
       
   260 		} else {
   184 			$orderby = ucfirst( $orderby );
   261 			$orderby = ucfirst( $orderby );
   185 			$order = strtoupper( $order );
   262 		}
   186 
   263 
   187 			uasort( $this->items, array( $this, '_order_callback' ) );
   264 		$order = strtoupper( $order );
   188 		}
   265 
       
   266 		uasort( $this->items, array( $this, '_order_callback' ) );
   189 
   267 
   190 		$plugins_per_page = $this->get_items_per_page( str_replace( '-', '_', $screen->id . '_per_page' ), 999 );
   268 		$plugins_per_page = $this->get_items_per_page( str_replace( '-', '_', $screen->id . '_per_page' ), 999 );
   191 
   269 
   192 		$start = ( $page - 1 ) * $plugins_per_page;
   270 		$start = ( $page - 1 ) * $plugins_per_page;
   193 
   271 
   199 			'per_page' => $plugins_per_page,
   277 			'per_page' => $plugins_per_page,
   200 		) );
   278 		) );
   201 	}
   279 	}
   202 
   280 
   203 	/**
   281 	/**
   204 	 * @staticvar string $term
   282 	 * @global string $s URL encoded search term.
       
   283 	 *
   205 	 * @param array $plugin
   284 	 * @param array $plugin
   206 	 * @return boolean
   285 	 * @return bool
   207 	 */
   286 	 */
   208 	public function _search_callback( $plugin ) {
   287 	public function _search_callback( $plugin ) {
   209 		static $term;
   288 		global $s;
   210 		if ( is_null( $term ) )
       
   211 			$term = wp_unslash( $_REQUEST['s'] );
       
   212 
   289 
   213 		foreach ( $plugin as $value ) {
   290 		foreach ( $plugin as $value ) {
   214 			if ( false !== stripos( strip_tags( $value ), $term ) ) {
   291 			if ( is_string( $value ) && false !== stripos( strip_tags( $value ), urldecode( $s ) ) ) {
   215 				return true;
   292 				return true;
   216 			}
   293 			}
   217 		}
   294 		}
   218 
   295 
   219 		return false;
   296 		return false;
   233 		$b = $plugin_b[$orderby];
   310 		$b = $plugin_b[$orderby];
   234 
   311 
   235 		if ( $a == $b )
   312 		if ( $a == $b )
   236 			return 0;
   313 			return 0;
   237 
   314 
   238 		if ( 'DESC' == $order )
   315 		if ( 'DESC' === $order ) {
   239 			return ( $a < $b ) ? 1 : -1;
   316 			return strcasecmp( $b, $a );
   240 		else
   317 		} else {
   241 			return ( $a < $b ) ? -1 : 1;
   318 			return strcasecmp( $a, $b );
   242 	}
   319 		}
   243 
   320 	}
       
   321 
       
   322 	/**
       
   323 	 *
       
   324 	 * @global array $plugins
       
   325 	 */
   244 	public function no_items() {
   326 	public function no_items() {
   245 		global $plugins;
   327 		global $plugins;
   246 
   328 
   247 		if ( !empty( $plugins['all'] ) )
   329 		if ( ! empty( $_REQUEST['s'] ) ) {
       
   330 			$s = esc_html( wp_unslash( $_REQUEST['s'] ) );
       
   331 
       
   332 			printf( __( 'No plugins found for &#8220;%s&#8221;.' ), $s );
       
   333 
       
   334 			// We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link.
       
   335 			if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) {
       
   336 				echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?tab=search&s=' . urlencode( $s ) ) ) . '">' . __( 'Search for plugins in the WordPress Plugin Directory.' ) . '</a>';
       
   337 			}
       
   338 		} elseif ( ! empty( $plugins['all'] ) )
   248 			_e( 'No plugins found.' );
   339 			_e( 'No plugins found.' );
   249 		else
   340 		else
   250 			_e( 'You do not appear to have any plugins available at this time.' );
   341 			_e( 'You do not appear to have any plugins available at this time.' );
   251 	}
   342 	}
   252 
   343 
       
   344 	/**
       
   345 	 * Displays the search box.
       
   346 	 *
       
   347 	 * @since 4.6.0
       
   348 	 *
       
   349 	 * @param string $text     The 'submit' button label.
       
   350 	 * @param string $input_id ID attribute value for the search input field.
       
   351 	 */
       
   352 	public function search_box( $text, $input_id ) {
       
   353 		if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
       
   354 			return;
       
   355 		}
       
   356 
       
   357 		$input_id = $input_id . '-search-input';
       
   358 
       
   359 		if ( ! empty( $_REQUEST['orderby'] ) ) {
       
   360 			echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
       
   361 		}
       
   362 		if ( ! empty( $_REQUEST['order'] ) ) {
       
   363 			echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
       
   364 		}
       
   365 		?>
       
   366 		<p class="search-box">
       
   367 			<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
       
   368 			<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...' ); ?>"/>
       
   369 			<?php submit_button( $text, 'hide-if-js', '', false, array( 'id' => 'search-submit' ) ); ?>
       
   370 		</p>
       
   371 		<?php
       
   372 	}
       
   373 
       
   374 	/**
       
   375 	 *
       
   376 	 * @global string $status
       
   377 	 * @return array
       
   378 	 */
   253 	public function get_columns() {
   379 	public function get_columns() {
   254 		global $status;
   380 		global $status;
   255 
   381 
   256 		return array(
   382 		return array(
   257 			'cb'          => !in_array( $status, array( 'mustuse', 'dropins' ) ) ? '<input type="checkbox" />' : '',
   383 			'cb'          => !in_array( $status, array( 'mustuse', 'dropins' ) ) ? '<input type="checkbox" />' : '',
   258 			'name'        => __( 'Plugin' ),
   384 			'name'        => __( 'Plugin' ),
   259 			'description' => __( 'Description' ),
   385 			'description' => __( 'Description' ),
   260 		);
   386 		);
   261 	}
   387 	}
   262 
   388 
       
   389 	/**
       
   390 	 * @return array
       
   391 	 */
   263 	protected function get_sortable_columns() {
   392 	protected function get_sortable_columns() {
   264 		return array();
   393 		return array();
   265 	}
   394 	}
   266 
   395 
       
   396 	/**
       
   397 	 *
       
   398 	 * @global array $totals
       
   399 	 * @global string $status
       
   400 	 * @return array
       
   401 	 */
   267 	protected function get_views() {
   402 	protected function get_views() {
   268 		global $totals, $status;
   403 		global $totals, $status;
   269 
   404 
   270 		$status_links = array();
   405 		$status_links = array();
   271 		foreach ( $totals as $type => $count ) {
   406 		foreach ( $totals as $type => $count ) {
   294 				case 'upgrade':
   429 				case 'upgrade':
   295 					$text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
   430 					$text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
   296 					break;
   431 					break;
   297 			}
   432 			}
   298 
   433 
   299 			if ( 'search' != $type ) {
   434 			if ( 'search' !== $type ) {
   300 				$status_links[$type] = sprintf( "<a href='%s' %s>%s</a>",
   435 				$status_links[$type] = sprintf( "<a href='%s'%s>%s</a>",
   301 					add_query_arg('plugin_status', $type, 'plugins.php'),
   436 					add_query_arg('plugin_status', $type, 'plugins.php'),
   302 					( $type == $status ) ? ' class="current"' : '',
   437 					( $type === $status ) ? ' class="current" aria-current="page"' : '',
   303 					sprintf( $text, number_format_i18n( $count ) )
   438 					sprintf( $text, number_format_i18n( $count ) )
   304 					);
   439 					);
   305 			}
   440 			}
   306 		}
   441 		}
   307 
   442 
   308 		return $status_links;
   443 		return $status_links;
   309 	}
   444 	}
   310 
   445 
       
   446 	/**
       
   447 	 *
       
   448 	 * @global string $status
       
   449 	 * @return array
       
   450 	 */
   311 	protected function get_bulk_actions() {
   451 	protected function get_bulk_actions() {
   312 		global $status;
   452 		global $status;
   313 
   453 
   314 		$actions = array();
   454 		$actions = array();
   315 
   455 
   330 	}
   470 	}
   331 
   471 
   332 	/**
   472 	/**
   333 	 * @global string $status
   473 	 * @global string $status
   334 	 * @param string $which
   474 	 * @param string $which
   335 	 * @return null
       
   336 	 */
   475 	 */
   337 	public function bulk_actions( $which = '' ) {
   476 	public function bulk_actions( $which = '' ) {
   338 		global $status;
   477 		global $status;
   339 
   478 
   340 		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) )
   479 		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) )
   344 	}
   483 	}
   345 
   484 
   346 	/**
   485 	/**
   347 	 * @global string $status
   486 	 * @global string $status
   348 	 * @param string $which
   487 	 * @param string $which
   349 	 * @return null
       
   350 	 */
   488 	 */
   351 	protected function extra_tablenav( $which ) {
   489 	protected function extra_tablenav( $which ) {
   352 		global $status;
   490 		global $status;
   353 
   491 
   354 		if ( ! in_array($status, array('recently_activated', 'mustuse', 'dropins') ) )
   492 		if ( ! in_array($status, array('recently_activated', 'mustuse', 'dropins') ) )
   355 			return;
   493 			return;
   356 
   494 
   357 		echo '<div class="alignleft actions">';
   495 		echo '<div class="alignleft actions">';
   358 
   496 
   359 		if ( ! $this->screen->in_admin( 'network' ) && 'recently_activated' == $status )
   497 		if ( 'recently_activated' == $status ) {
   360 			submit_button( __( 'Clear List' ), 'button', 'clear-recent-list', false );
   498 			submit_button( __( 'Clear List' ), '', 'clear-recent-list', false );
   361 		elseif ( 'top' == $which && 'mustuse' == $status )
   499 		} elseif ( 'top' === $which && 'mustuse' === $status ) {
   362 			echo '<p>' . sprintf( __( 'Files in the <code>%s</code> directory are executed automatically.' ), str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) ) . '</p>';
   500 			/* translators: %s: mu-plugins directory name */
   363 		elseif ( 'top' == $which && 'dropins' == $status )
   501 			echo '<p>' . sprintf( __( 'Files in the %s directory are executed automatically.' ),
   364 			echo '<p>' . sprintf( __( 'Drop-ins are advanced plugins in the <code>%s</code> directory that replace WordPress functionality when present.' ), str_replace( ABSPATH, '', WP_CONTENT_DIR ) ) . '</p>';
   502 				'<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>'
   365 
   503 			) . '</p>';
       
   504 		} elseif ( 'top' === $which && 'dropins' === $status ) {
       
   505 			/* translators: %s: wp-content directory name */
       
   506 			echo '<p>' . sprintf( __( 'Drop-ins are advanced plugins in the %s directory that replace WordPress functionality when present.' ),
       
   507 				'<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
       
   508 			) . '</p>';
       
   509 		}
   366 		echo '</div>';
   510 		echo '</div>';
   367 	}
   511 	}
   368 
   512 
       
   513 	/**
       
   514 	 * @return string
       
   515 	 */
   369 	public function current_action() {
   516 	public function current_action() {
   370 		if ( isset($_POST['clear-recent-list']) )
   517 		if ( isset($_POST['clear-recent-list']) )
   371 			return 'clear-recent-list';
   518 			return 'clear-recent-list';
   372 
   519 
   373 		return parent::current_action();
   520 		return parent::current_action();
   374 	}
   521 	}
   375 
   522 
       
   523 	/**
       
   524 	 *
       
   525 	 * @global string $status
       
   526 	 */
   376 	public function display_rows() {
   527 	public function display_rows() {
   377 		global $status;
   528 		global $status;
   378 
   529 
   379 		if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ) ) )
   530 		if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ) ) )
   380 			return;
   531 			return;
   386 	/**
   537 	/**
   387 	 * @global string $status
   538 	 * @global string $status
   388 	 * @global int $page
   539 	 * @global int $page
   389 	 * @global string $s
   540 	 * @global string $s
   390 	 * @global array $totals
   541 	 * @global array $totals
       
   542 	 *
   391 	 * @param array $item
   543 	 * @param array $item
   392 	 */
   544 	 */
   393 	public function single_row( $item ) {
   545 	public function single_row( $item ) {
   394 		global $status, $page, $s, $totals;
   546 		global $status, $page, $s, $totals;
   395 
   547 
   400 		// Pre-order.
   552 		// Pre-order.
   401 		$actions = array(
   553 		$actions = array(
   402 			'deactivate' => '',
   554 			'deactivate' => '',
   403 			'activate' => '',
   555 			'activate' => '',
   404 			'details' => '',
   556 			'details' => '',
   405 			'edit' => '',
       
   406 			'delete' => '',
   557 			'delete' => '',
   407 		);
   558 		);
   408 
   559 
   409 		if ( 'mustuse' == $context ) {
   560 		// Do not restrict by default
       
   561 		$restrict_network_active = false;
       
   562 		$restrict_network_only = false;
       
   563 
       
   564 		if ( 'mustuse' === $context ) {
   410 			$is_active = true;
   565 			$is_active = true;
   411 		} elseif ( 'dropins' == $context ) {
   566 		} elseif ( 'dropins' === $context ) {
   412 			$dropins = _get_dropins();
   567 			$dropins = _get_dropins();
   413 			$plugin_name = $plugin_file;
   568 			$plugin_name = $plugin_file;
   414 			if ( $plugin_file != $plugin_data['Name'] )
   569 			if ( $plugin_file != $plugin_data['Name'] )
   415 				$plugin_name .= '<br/>' . $plugin_data['Name'];
   570 				$plugin_name .= '<br/>' . $plugin_data['Name'];
   416 			if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant
   571 			if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant
   419 			} elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true
   574 			} elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true
   420 				$is_active = true;
   575 				$is_active = true;
   421 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   576 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>';
   422 			} else {
   577 			} else {
   423 				$is_active = false;
   578 				$is_active = false;
   424 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="attention">' . __('Inactive:') . '</span></strong> ' . sprintf( __( 'Requires <code>%s</code> in <code>wp-config.php</code>.' ), "define('" . $dropins[ $plugin_file ][1] . "', true);" ) . '</p>';
   579 				$description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="error-message">' . __( 'Inactive:' ) . '</span></strong> ' .
       
   580 					/* translators: 1: drop-in constant name, 2: wp-config.php */
       
   581 					sprintf( __( 'Requires %1$s in %2$s file.' ),
       
   582 						"<code>define('" . $dropins[ $plugin_file ][1] . "', true);</code>",
       
   583 						'<code>wp-config.php</code>'
       
   584 					) . '</p>';
   425 			}
   585 			}
   426 			if ( $plugin_data['Description'] )
   586 			if ( $plugin_data['Description'] )
   427 				$description .= '<p>' . $plugin_data['Description'] . '</p>';
   587 				$description .= '<p>' . $plugin_data['Description'] . '</p>';
   428 		} else {
   588 		} else {
   429 			if ( $screen->in_admin( 'network' ) )
   589 			if ( $screen->in_admin( 'network' ) ) {
   430 				$is_active = is_plugin_active_for_network( $plugin_file );
   590 				$is_active = is_plugin_active_for_network( $plugin_file );
   431 			else
   591 			} else {
   432 				$is_active = is_plugin_active( $plugin_file );
   592 				$is_active = is_plugin_active( $plugin_file );
       
   593 				$restrict_network_active = ( is_multisite() && is_plugin_active_for_network( $plugin_file ) );
       
   594 				$restrict_network_only = ( is_multisite() && is_network_only_plugin( $plugin_file ) && ! $is_active );
       
   595 			}
   433 
   596 
   434 			if ( $screen->in_admin( 'network' ) ) {
   597 			if ( $screen->in_admin( 'network' ) ) {
   435 				if ( $is_active ) {
   598 				if ( $is_active ) {
   436 					if ( current_user_can( 'manage_network_plugins' ) )
   599 					if ( current_user_can( 'manage_network_plugins' ) ) {
   437 						$actions['deactivate'] = '<a href="' . wp_nonce_url('plugins.php?action=deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Deactivate this plugin') . '">' . __('Network Deactivate') . '</a>';
   600 						/* translators: %s: plugin name */
       
   601 						$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>';
       
   602 						}
   438 				} else {
   603 				} else {
   439 					if ( current_user_can( 'manage_network_plugins' ) )
   604 					if ( current_user_can( 'manage_network_plugins' ) ) {
   440 						$actions['activate'] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Activate this plugin for all sites in this network') . '" class="edit">' . __('Network Activate') . '</a>';
   605 						/* translators: %s: plugin name */
   441 					if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) )
   606 						$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>';
   442 						$actions['delete'] = '<a href="' . wp_nonce_url('plugins.php?action=delete-selected&amp;checked[]=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins') . '" title="' . esc_attr__('Delete this plugin') . '" class="delete">' . __('Delete') . '</a>';
   607 					}
       
   608 					if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
       
   609 						/* translators: %s: plugin name */
       
   610 						$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>';
       
   611 					}
   443 				}
   612 				}
   444 			} else {
   613 			} else {
   445 				if ( $is_active ) {
   614 				if ( $restrict_network_active ) {
   446 					$actions['deactivate'] = '<a href="' . wp_nonce_url('plugins.php?action=deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Deactivate this plugin') . '">' . __('Deactivate') . '</a>';
   615 					$actions = array(
       
   616 						'network_active' => __( 'Network Active' ),
       
   617 					);
       
   618 				} elseif ( $restrict_network_only ) {
       
   619 					$actions = array(
       
   620 						'network_only' => __( 'Network Only' ),
       
   621 					);
       
   622 				} elseif ( $is_active ) {
       
   623 					if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) {
       
   624 						/* translators: %s: plugin name */
       
   625 						$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>';
       
   626 					}
   447 				} else {
   627 				} else {
   448 					$actions['activate'] = '<a href="' . wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file) . '" title="' . esc_attr__('Activate this plugin') . '" class="edit">' . __('Activate') . '</a>';
   628 					if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
   449 
   629 						/* translators: %s: plugin name */
   450 					if ( ! is_multisite() && current_user_can('delete_plugins') )
   630 						$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>';
   451 						$actions['delete'] = '<a href="' . wp_nonce_url('plugins.php?action=delete-selected&amp;checked[]=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins') . '" title="' . esc_attr__('Delete this plugin') . '" class="delete">' . __('Delete') . '</a>';
   631 					}
       
   632 
       
   633 					if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) {
       
   634 						/* translators: %s: plugin name */
       
   635 						$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>';
       
   636 					}
   452 				} // end if $is_active
   637 				} // end if $is_active
   453 
   638 
   454 			 } // end if $screen->in_admin( 'network' )
   639 			 } // end if $screen->in_admin( 'network' )
   455 
   640 
   456 			if ( ( ! is_multisite() || $screen->in_admin( 'network' ) ) && current_user_can('edit_plugins') && is_writable(WP_PLUGIN_DIR . '/' . $plugin_file) )
       
   457 				$actions['edit'] = '<a href="plugin-editor.php?file=' . $plugin_file . '" title="' . esc_attr__('Open this file in the Plugin Editor') . '" class="edit">' . __('Edit') . '</a>';
       
   458 		} // end if $context
   641 		} // end if $context
   459 
   642 
   460 		$prefix = $screen->in_admin( 'network' ) ? 'network_admin_' : '';
   643 		$actions = array_filter( $actions );
   461 
   644 
   462 		/**
   645 		if ( $screen->in_admin( 'network' ) ) {
   463 		 * Filter the action links displayed for each plugin in the Plugins list table.
   646 
   464 		 *
   647 			/**
   465 		 * The dynamic portion of the hook name, `$prefix`, refers to the context the
   648 			 * Filters the action links displayed for each plugin in the Network Admin Plugins list table.
   466 		 * action links are displayed in. The 'network_admin_' prefix is used if the
   649 			 *
   467 		 * current screen is the Network plugins list table. The prefix is empty ('')
   650 			 * @since 3.1.0
   468 		 * if the current screen is the site plugins list table.
   651 			 *
   469 		 *
   652 			 * @param array  $actions     An array of plugin action links. By default this can include 'activate',
   470 		 * The default action links for the Network plugins list table include
   653 			 *                            'deactivate', and 'delete'.
   471 		 * 'Network Activate', 'Network Deactivate', 'Edit', and 'Delete'.
   654 			 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
   472 		 *
   655 			 * @param array  $plugin_data An array of plugin data. See `get_plugin_data()`.
   473 		 * The default action links for the site plugins list table include
   656 			 * @param string $context     The plugin context. By default this can include 'all', 'active', 'inactive',
   474 		 * 'Activate', 'Deactivate', and 'Edit', for a network site, and
   657 			 *                            'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
   475 		 * 'Activate', 'Deactivate', 'Edit', and 'Delete' for a single site.
   658 			 */
   476 		 *
   659 			$actions = apply_filters( 'network_admin_plugin_action_links', $actions, $plugin_file, $plugin_data, $context );
   477 		 * @since 2.5.0
   660 
   478 		 *
   661 			/**
   479 		 * @param array  $actions     An array of plugin action links.
   662 			 * Filters the list of action links displayed for a specific plugin in the Network Admin Plugins list table.
   480 		 * @param string $plugin_file Path to the plugin file.
   663 			 *
   481 		 * @param array  $plugin_data An array of plugin data.
   664 			 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
   482 		 * @param string $context     The plugin context. Defaults are 'All', 'Active',
   665 			 * to the plugin file, relative to the plugins directory.
   483 		 *                            'Inactive', 'Recently Activated', 'Upgrade',
   666 			 *
   484 		 *                            'Must-Use', 'Drop-ins', 'Search'.
   667 			 * @since 3.1.0
   485 		 */
   668 			 *
   486 		$actions = apply_filters( $prefix . 'plugin_action_links', array_filter( $actions ), $plugin_file, $plugin_data, $context );
   669 			 * @param array  $actions     An array of plugin action links. By default this can include 'activate',
   487 
   670 			 *                            'deactivate', and 'delete'.
   488 		/**
   671 			 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
   489 		 * Filter the list of action links displayed for a specific plugin.
   672 			 * @param array  $plugin_data An array of plugin data. See `get_plugin_data()`.
   490 		 *
   673 			 * @param string $context     The plugin context. By default this can include 'all', 'active', 'inactive',
   491 		 * The first dynamic portion of the hook name, $prefix, refers to the context
   674 			 *                            'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
   492 		 * the action links are displayed in. The 'network_admin_' prefix is used if the
   675 			 */
   493 		 * current screen is the Network plugins list table. The prefix is empty ('')
   676 			$actions = apply_filters( "network_admin_plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context );
   494 		 * if the current screen is the site plugins list table.
   677 
   495 		 *
   678 		} else {
   496 		 * The second dynamic portion of the hook name, $plugin_file, refers to the path
   679 
   497 		 * to the plugin file, relative to the plugins directory.
   680 			/**
   498 		 *
   681 			 * Filters the action links displayed for each plugin in the Plugins list table.
   499 		 * @since 2.7.0
   682 			 *
   500 		 *
   683 			 * @since 2.5.0
   501 		 * @param array  $actions     An array of plugin action links.
   684 			 * @since 2.6.0 The `$context` parameter was added.
   502 		 * @param string $plugin_file Path to the plugin file.
   685 			 * @since 4.9.0 The 'Edit' link was removed from the list of action links.
   503 		 * @param array  $plugin_data An array of plugin data.
   686 			 *
   504 		 * @param string $context     The plugin context. Defaults are 'All', 'Active',
   687 			 * @param array  $actions     An array of plugin action links. By default this can include 'activate',
   505 		 *                            'Inactive', 'Recently Activated', 'Upgrade',
   688 			 *                            'deactivate', and 'delete'. With Multisite active this can also include
   506 		 *                            'Must-Use', 'Drop-ins', 'Search'.
   689 			 *                            'network_active' and 'network_only' items.
   507 		 */
   690 			 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
   508 		$actions = apply_filters( $prefix . "plugin_action_links_$plugin_file", $actions, $plugin_file, $plugin_data, $context );
   691 			 * @param array  $plugin_data An array of plugin data. See `get_plugin_data()`.
       
   692 			 * @param string $context     The plugin context. By default this can include 'all', 'active', 'inactive',
       
   693 			 *                            'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
       
   694 			 */
       
   695 			$actions = apply_filters( 'plugin_action_links', $actions, $plugin_file, $plugin_data, $context );
       
   696 
       
   697 			/**
       
   698 			 * Filters the list of action links displayed for a specific plugin in the Plugins list table.
       
   699 			 *
       
   700 			 * The dynamic portion of the hook name, `$plugin_file`, refers to the path
       
   701 			 * to the plugin file, relative to the plugins directory.
       
   702 			 *
       
   703 			 * @since 2.7.0
       
   704 			 * @since 4.9.0 The 'Edit' link was removed from the list of action links.
       
   705 			 *
       
   706 			 * @param array  $actions     An array of plugin action links. By default this can include 'activate',
       
   707 			 *                            'deactivate', and 'delete'. With Multisite active this can also include
       
   708 			 *                            'network_active' and 'network_only' items.
       
   709 			 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
       
   710 			 * @param array  $plugin_data An array of plugin data. See `get_plugin_data()`.
       
   711 			 * @param string $context     The plugin context. By default this can include 'all', 'active', 'inactive',
       
   712 			 *                            'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'.
       
   713 			 */
       
   714 			$actions = apply_filters( "plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context );
       
   715 
       
   716 		}
   509 
   717 
   510 		$class = $is_active ? 'active' : 'inactive';
   718 		$class = $is_active ? 'active' : 'inactive';
   511 		$checkbox_id =  "checkbox_" . md5($plugin_data['Name']);
   719 		$checkbox_id =  "checkbox_" . md5($plugin_data['Name']);
   512 		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) ) {
   720 		if ( $restrict_network_active || $restrict_network_only || in_array( $status, array( 'mustuse', 'dropins' ) ) ) {
   513 			$checkbox = '';
   721 			$checkbox = '';
   514 		} else {
   722 		} else {
   515 			$checkbox = "<label class='screen-reader-text' for='" . $checkbox_id . "' >" . sprintf( __( 'Select %s' ), $plugin_data['Name'] ) . "</label>"
   723 			$checkbox = "<label class='screen-reader-text' for='" . $checkbox_id . "' >" . sprintf( __( 'Select %s' ), $plugin_data['Name'] ) . "</label>"
   516 				. "<input type='checkbox' name='checked[]' value='" . esc_attr( $plugin_file ) . "' id='" . $checkbox_id . "' />";
   724 				. "<input type='checkbox' name='checked[]' value='" . esc_attr( $plugin_file ) . "' id='" . $checkbox_id . "' />";
   517 		}
   725 		}
   518 		if ( 'dropins' != $context ) {
   726 		if ( 'dropins' != $context ) {
   519 			$description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : '&nbsp;' ) . '</p>';
   727 			$description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : '&nbsp;' ) . '</p>';
   520 			$plugin_name = $plugin_data['Name'];
   728 			$plugin_name = $plugin_data['Name'];
   521 		}
   729 		}
   522 
   730 
   523 		$id = sanitize_title( $plugin_name );
       
   524 		if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) )
   731 		if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) )
   525 			$class .= ' update';
   732 			$class .= ' update';
   526 
   733 
   527 		$plugin_slug = ( isset( $plugin_data['slug'] ) ) ? $plugin_data['slug'] : '';
   734 		$plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
   528 		printf( "<tr id='%s' class='%s' data-slug='%s'>",
   735 		printf( '<tr class="%s" data-slug="%s" data-plugin="%s">',
   529 			$id,
   736 			esc_attr( $class ),
   530 			$class,
   737 			esc_attr( $plugin_slug ),
   531 			$plugin_slug
   738 			esc_attr( $plugin_file )
   532 		);
   739 		);
   533 
   740 
   534 		list( $columns, $hidden ) = $this->get_column_info();
   741 		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
   535 
   742 
   536 		foreach ( $columns as $column_name => $column_display_name ) {
   743 		foreach ( $columns as $column_name => $column_display_name ) {
   537 			$style = '';
   744 			$extra_classes = '';
   538 			if ( in_array( $column_name, $hidden ) )
   745 			if ( in_array( $column_name, $hidden ) ) {
   539 				$style = ' style="display:none;"';
   746 				$extra_classes = ' hidden';
       
   747 			}
   540 
   748 
   541 			switch ( $column_name ) {
   749 			switch ( $column_name ) {
   542 				case 'cb':
   750 				case 'cb':
   543 					echo "<th scope='row' class='check-column'>$checkbox</th>";
   751 					echo "<th scope='row' class='check-column'>$checkbox</th>";
   544 					break;
   752 					break;
   545 				case 'name':
   753 				case 'name':
   546 					echo "<td class='plugin-title'$style><strong>$plugin_name</strong>";
   754 					echo "<td class='plugin-title column-primary'><strong>$plugin_name</strong>";
   547 					echo $this->row_actions( $actions, true );
   755 					echo $this->row_actions( $actions, true );
   548 					echo "</td>";
   756 					echo "</td>";
   549 					break;
   757 					break;
   550 				case 'description':
   758 				case 'description':
   551 					echo "<td class='column-description desc'$style>
   759 					$classes = 'column-description desc';
       
   760 
       
   761 					echo "<td class='$classes{$extra_classes}'>
   552 						<div class='plugin-description'>$description</div>
   762 						<div class='plugin-description'>$description</div>
   553 						<div class='$class second plugin-version-author-uri'>";
   763 						<div class='$class second plugin-version-author-uri'>";
   554 
   764 
   555 					$plugin_meta = array();
   765 					$plugin_meta = array();
   556 					if ( !empty( $plugin_data['Version'] ) )
   766 					if ( !empty( $plugin_data['Version'] ) )
   562 						$plugin_meta[] = sprintf( __( 'By %s' ), $author );
   772 						$plugin_meta[] = sprintf( __( 'By %s' ), $author );
   563 					}
   773 					}
   564 
   774 
   565 					// Details link using API info, if available
   775 					// Details link using API info, if available
   566 					if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) {
   776 					if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) {
   567 						$plugin_meta[] = sprintf( '<a href="%s" class="thickbox" aria-label="%s" data-title="%s">%s</a>',
   777 						$plugin_meta[] = sprintf( '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
   568 							esc_url( network_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
   778 							esc_url( network_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] .
   569 								'&TB_iframe=true&width=600&height=550' ) ),
   779 								'&TB_iframe=true&width=600&height=550' ) ),
   570 							esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ),
   780 							esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ),
   571 							esc_attr( $plugin_name ),
   781 							esc_attr( $plugin_name ),
   572 							__( 'View details' )
   782 							__( 'View details' )
   577 							__( 'Visit plugin site' )
   787 							__( 'Visit plugin site' )
   578 						);
   788 						);
   579 					}
   789 					}
   580 
   790 
   581 					/**
   791 					/**
   582 					 * Filter the array of row meta for each plugin in the Plugins list table.
   792 					 * Filters the array of row meta for each plugin in the Plugins list table.
   583 					 *
   793 					 *
   584 					 * @since 2.8.0
   794 					 * @since 2.8.0
   585 					 *
   795 					 *
   586 					 * @param array  $plugin_meta An array of the plugin's metadata,
   796 					 * @param array  $plugin_meta An array of the plugin's metadata,
   587 					 *                            including the version, author,
   797 					 *                            including the version, author,
   596 					echo implode( ' | ', $plugin_meta );
   806 					echo implode( ' | ', $plugin_meta );
   597 
   807 
   598 					echo "</div></td>";
   808 					echo "</div></td>";
   599 					break;
   809 					break;
   600 				default:
   810 				default:
   601 					echo "<td class='$column_name column-$column_name'$style>";
   811 					$classes = "$column_name column-$column_name $class";
       
   812 
       
   813 					echo "<td class='$classes{$extra_classes}'>";
   602 
   814 
   603 					/**
   815 					/**
   604 					 * Fires inside each custom column of the Plugins list table.
   816 					 * Fires inside each custom column of the Plugins list table.
   605 					 *
   817 					 *
   606 					 * @since 3.1.0
   818 					 * @since 3.1.0
   608 					 * @param string $column_name Name of the column.
   820 					 * @param string $column_name Name of the column.
   609 					 * @param string $plugin_file Path to the plugin file.
   821 					 * @param string $plugin_file Path to the plugin file.
   610 					 * @param array  $plugin_data An array of plugin data.
   822 					 * @param array  $plugin_data An array of plugin data.
   611 					 */
   823 					 */
   612 					do_action( 'manage_plugins_custom_column', $column_name, $plugin_file, $plugin_data );
   824 					do_action( 'manage_plugins_custom_column', $column_name, $plugin_file, $plugin_data );
       
   825 
   613 					echo "</td>";
   826 					echo "</td>";
   614 			}
   827 			}
   615 		}
   828 		}
   616 
   829 
   617 		echo "</tr>";
   830 		echo "</tr>";
   641 		 * @param array  $plugin_data An array of plugin data.
   854 		 * @param array  $plugin_data An array of plugin data.
   642 		 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
   855 		 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
   643 		 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
   856 		 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
   644 		 *                            'Drop-ins', 'Search'.
   857 		 *                            'Drop-ins', 'Search'.
   645 		 */
   858 		 */
   646 		do_action( "after_plugin_row_$plugin_file", $plugin_file, $plugin_data, $status );
   859 		do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
       
   860 	}
       
   861 
       
   862 	/**
       
   863 	 * Gets the name of the primary column for this specific list table.
       
   864 	 *
       
   865 	 * @since 4.3.0
       
   866 	 *
       
   867 	 * @return string Unalterable name for the primary column, in this case, 'name'.
       
   868 	 */
       
   869 	protected function get_primary_column_name() {
       
   870 		return 'name';
   647 	}
   871 	}
   648 }
   872 }