説明なし

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

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