class-wp-plugins-list-table.php 48 KB

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