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

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