| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 /** 11 * Core class used to implement displaying installed plugins in a list table. 12 * 13 * @since 3.1.0 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 /** 28 * Constructor. 29 * 30 * @since 3.2.0 31 * 32 * @see WP_List_Table::__construct() for more information on default arguments. 33 * 34 * @global string $status 35 * @global int $page 36 * 37 * @param array $args An associative array of arguments. 38 */ 39 public function __construct( $args = array() ) { 40 global $status, $page; 41 42 parent::__construct( 43 array( 44 'plural' => 'plugins', 45 'screen' => $args['screen'] ?? null, 46 ) 47 ); 48 49 $status = 'all'; 50 if ( isset( $_REQUEST['plugin_status'] ) ) { 51 $status = sanitize_key( $_REQUEST['plugin_status'] ); 52 } 53 54 if ( isset( $_REQUEST['s'] ) ) { 55 $_SERVER['REQUEST_URI'] = add_query_arg( 's', wp_unslash( $_REQUEST['s'] ) ); 56 } 57 58 $page = $this->get_pagenum(); 59 60 $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'plugin' ) 61 && current_user_can( 'update_plugins' ) 62 && ( ! is_multisite() || $this->screen->in_admin( 'network' ) ); 63 } 64 65 /** 66 * Gets the CSS classes for the list table element. 67 * 68 * @since 3.1.0 69 * 70 * @return string[] Array of CSS classes for the table tag. 71 */ 72 protected function get_table_classes() { 73 return array( 'widefat', $this->_args['plural'] ); 74 } 75 76 /** 77 * Checks whether the current user can activate plugins for this screen. 78 * 79 * @since 3.1.0 80 * 81 * @return bool Whether the current user can activate plugins. 82 */ 83 public function ajax_user_can() { 84 return current_user_can( 'activate_plugins' ); 85 } 86 87 /** 88 * Prepares the list of items for displaying. 89 * 90 * @since 3.1.0 91 * 92 * @global string $status Current plugin status filter slug. 93 * @global array<string, array<string, array<string, mixed>>> $plugins Array of plugin data arrays grouped by status. 94 * @global array<string, int> $totals Count of plugins for each status group. 95 * @global int $page Current page number. 96 * @global string $orderby Column name to sort by. 97 * @global string $order Sort direction, 'ASC' or 'DESC'. 98 * @global string $s URL-encoded search term. 99 */ 100 public function prepare_items() { 101 global $status, $plugins, $totals, $page, $orderby, $order, $s; 102 103 $orderby = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : ''; 104 $order = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : ''; 105 106 /** 107 * Filters the full array of plugins to list in the Plugins list table. 108 * 109 * @since 3.0.0 110 * 111 * @see get_plugins() 112 * 113 * @param array $all_plugins An array of plugins to display in the list table. 114 */ 115 $all_plugins = apply_filters( 'all_plugins', get_plugins() ); 116 117 $plugins = array( 118 'all' => $all_plugins, 119 'search' => array(), 120 'active' => array(), 121 'inactive' => array(), 122 'recently_activated' => array(), 123 'upgrade' => array(), 124 'mustuse' => array(), 125 'dropins' => array(), 126 'paused' => array(), 127 ); 128 if ( $this->show_autoupdates ) { 129 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() ); 130 131 $plugins['auto-update-enabled'] = array(); 132 $plugins['auto-update-disabled'] = array(); 133 } 134 135 $screen = $this->screen; 136 137 if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) { 138 139 /** 140 * Filters whether to display the advanced plugins list table. 141 * 142 * There are two types of advanced plugins - must-use and drop-ins - 143 * which can be used in a single site or Multisite network. 144 * 145 * The $type parameter allows you to differentiate between the type of advanced 146 * plugins to filter the display of. Contexts include 'mustuse' and 'dropins'. 147 * 148 * @since 3.0.0 149 * 150 * @param bool $show Whether to show the advanced plugins for the specified 151 * plugin type. Default true. 152 * @param string $type The plugin type. Accepts 'mustuse', 'dropins'. 153 */ 154 if ( apply_filters( 'show_advanced_plugins', true, 'mustuse' ) ) { 155 $plugins['mustuse'] = get_mu_plugins(); 156 } 157 158 /** This action is documented in wp-admin/includes/class-wp-plugins-list-table.php */ 159 if ( apply_filters( 'show_advanced_plugins', true, 'dropins' ) ) { 160 $plugins['dropins'] = get_dropins(); 161 } 162 163 if ( current_user_can( 'update_plugins' ) ) { 164 $current = get_site_transient( 'update_plugins' ); 165 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) { 166 if ( isset( $current->response[ $plugin_file ] ) ) { 167 $plugins['all'][ $plugin_file ]['update'] = true; 168 $plugins['upgrade'][ $plugin_file ] = $plugins['all'][ $plugin_file ]; 169 } 170 } 171 } 172 } 173 174 if ( ! $screen->in_admin( 'network' ) ) { 175 $show = current_user_can( 'manage_network_plugins' ); 176 /** 177 * Filters whether to display network-active plugins alongside plugins active for the current site. 178 * 179 * This also controls the display of inactive network-only plugins (plugins with 180 * "Network: true" in the plugin header). 181 * 182 * Plugins cannot be network-activated or network-deactivated from this screen. 183 * 184 * @since 4.4.0 185 * 186 * @param bool $show Whether to show network-active plugins. Default is whether the current 187 * user can manage network plugins (ie. a Super Admin). 188 */ 189 $show_network_active = apply_filters( 'show_network_active_plugins', $show ); 190 } 191 192 if ( $screen->in_admin( 'network' ) ) { 193 $recently_activated = get_site_option( 'recently_activated', array() ); 194 } else { 195 $recently_activated = get_option( 'recently_activated', array() ); 196 } 197 198 foreach ( $recently_activated as $key => $time ) { 199 if ( ! is_int( $time ) || $time + WEEK_IN_SECONDS < time() ) { 200 unset( $recently_activated[ $key ] ); 201 } 202 } 203 204 if ( $screen->in_admin( 'network' ) ) { 205 update_site_option( 'recently_activated', $recently_activated ); 206 } else { 207 update_option( 'recently_activated', $recently_activated, false ); 208 } 209 210 $plugin_info = get_site_transient( 'update_plugins' ); 211 212 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) { 213 // Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide. 214 if ( isset( $plugin_info->response[ $plugin_file ] ) ) { 215 $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], array( 'update-supported' => true ), $plugin_data ); 216 } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) { 217 $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], array( 'update-supported' => true ), $plugin_data ); 218 } elseif ( empty( $plugin_data['update-supported'] ) ) { 219 $plugin_data['update-supported'] = false; 220 } 221 222 /* 223 * Create the payload that's used for the auto_update_plugin filter. 224 * This is the same data contained within $plugin_info->(response|no_update) however 225 * not all plugins will be contained in those keys, this avoids unexpected warnings. 226 */ 227 $filter_payload = array( 228 'id' => $plugin_file, 229 'slug' => '', 230 'plugin' => $plugin_file, 231 'new_version' => '', 232 'url' => '', 233 'package' => '', 234 'icons' => array(), 235 'banners' => array(), 236 'banners_rtl' => array(), 237 'tested' => '', 238 'requires_php' => '', 239 'compatibility' => new stdClass(), 240 ); 241 242 $filter_payload = (object) wp_parse_args( $plugin_data, $filter_payload ); 243 244 $auto_update_forced = wp_is_auto_update_forced_for_item( 'plugin', null, $filter_payload ); 245 246 if ( ! is_null( $auto_update_forced ) ) { 247 $plugin_data['auto-update-forced'] = $auto_update_forced; 248 } 249 250 $plugins['all'][ $plugin_file ] = $plugin_data; 251 // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade. 252 if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) { 253 $plugins['upgrade'][ $plugin_file ] = $plugin_data; 254 } 255 256 // Filter into individual sections. 257 if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) { 258 if ( $show_network_active ) { 259 // On the non-network screen, show inactive network-only plugins if allowed. 260 $plugins['inactive'][ $plugin_file ] = $plugin_data; 261 } else { 262 // On the non-network screen, filter out network-only plugins as long as they're not individually active. 263 unset( $plugins['all'][ $plugin_file ] ); 264 } 265 } elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) { 266 if ( $show_network_active ) { 267 // On the non-network screen, show network-active plugins if allowed. 268 $plugins['active'][ $plugin_file ] = $plugin_data; 269 } else { 270 // On the non-network screen, filter out network-active plugins. 271 unset( $plugins['all'][ $plugin_file ] ); 272 } 273 } elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) ) 274 || ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) { 275 /* 276 * On the non-network screen, populate the active list with plugins that are individually activated. 277 * On the network admin screen, populate the active list with plugins that are network-activated. 278 */ 279 $plugins['active'][ $plugin_file ] = $plugin_data; 280 281 if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) { 282 $plugins['paused'][ $plugin_file ] = $plugin_data; 283 } 284 } else { 285 if ( isset( $recently_activated[ $plugin_file ] ) ) { 286 // Populate the recently activated list with plugins that have been recently activated. 287 $plugins['recently_activated'][ $plugin_file ] = $plugin_data; 288 } 289 // Populate the inactive list with plugins that aren't activated. 290 $plugins['inactive'][ $plugin_file ] = $plugin_data; 291 } 292 293 if ( $this->show_autoupdates ) { 294 $enabled = in_array( $plugin_file, $auto_updates, true ) && $plugin_data['update-supported']; 295 if ( isset( $plugin_data['auto-update-forced'] ) ) { 296 $enabled = (bool) $plugin_data['auto-update-forced']; 297 } 298 299 if ( $enabled ) { 300 $plugins['auto-update-enabled'][ $plugin_file ] = $plugin_data; 301 } else { 302 $plugins['auto-update-disabled'][ $plugin_file ] = $plugin_data; 303 } 304 } 305 } 306 307 if ( strlen( $s ) ) { 308 $status = 'search'; 309 $plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) ); 310 } 311 312 /** 313 * Filters the array of plugins for the list table. 314 * 315 * @since 6.3.0 316 * 317 * @param array[] $plugins An array of arrays of plugin data, keyed by context. 318 */ 319 $plugins = apply_filters( 'plugins_list', $plugins ); 320 321 $totals = array(); 322 foreach ( $plugins as $type => $list ) { 323 $totals[ $type ] = count( $list ); 324 } 325 326 if ( empty( $plugins[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) { 327 $status = 'all'; 328 } 329 330 $this->items = array(); 331 foreach ( $plugins[ $status ] as $plugin_file => $plugin_data ) { 332 // Translate, don't apply markup, sanitize HTML. 333 $this->items[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true ); 334 } 335 336 $total_this_page = $totals[ $status ]; 337 338 $js_plugins = array(); 339 foreach ( $plugins as $key => $list ) { 340 $js_plugins[ $key ] = array_keys( $list ); 341 } 342 343 wp_localize_script( 344 'updates', 345 '_wpUpdatesItemCounts', 346 array( 347 'plugins' => $js_plugins, 348 'totals' => wp_get_update_data(), 349 ) 350 ); 351 352 if ( ! $orderby ) { 353 $orderby = 'Name'; 354 } else { 355 $orderby = ucfirst( $orderby ); 356 } 357 358 $order = strtoupper( $order ); 359 360 uasort( $this->items, array( $this, '_order_callback' ) ); 361 362 $plugins_per_page = $this->get_items_per_page( str_replace( '-', '_', $screen->id . '_per_page' ), 999 ); 363 364 $start = ( $page - 1 ) * $plugins_per_page; 365 366 if ( $total_this_page > $plugins_per_page ) { 367 $this->items = array_slice( $this->items, $start, $plugins_per_page ); 368 } 369 370 $this->set_pagination_args( 371 array( 372 'total_items' => $total_this_page, 373 'per_page' => $plugins_per_page, 374 ) 375 ); 376 } 377 378 /** 379 * Callback to filter plugins by a search term. 380 * 381 * @since 3.1.0 382 * 383 * @global string $s URL encoded search term. 384 * 385 * @param array<string, mixed> $plugin Plugin data array to check against the search term. 386 * @return bool True if the plugin matches the search term, false otherwise. 387 */ 388 public function _search_callback( $plugin ) { 389 global $s; 390 391 foreach ( $plugin as $value ) { 392 if ( is_string( $value ) && false !== stripos( strip_tags( $value ), urldecode( $s ) ) ) { 393 return true; 394 } 395 } 396 397 return false; 398 } 399 400 /** 401 * Callback to sort plugins by a given column. 402 * 403 * @since 3.1.0 404 * 405 * @global string $orderby The column name to sort by. 406 * @global string $order The sort direction ('ASC' or 'DESC'). 407 * 408 * @param array<string, mixed> $plugin_a First plugin data array to compare. 409 * @param array<string, mixed> $plugin_b Second plugin data array to compare. 410 * @return int Negative if $plugin_a sorts before $plugin_b, positive if after, 0 if equal. 411 */ 412 public function _order_callback( $plugin_a, $plugin_b ) { 413 global $orderby, $order; 414 415 $a = $plugin_a[ $orderby ]; 416 $b = $plugin_b[ $orderby ]; 417 418 if ( $a === $b ) { 419 return 0; 420 } 421 422 if ( 'DESC' === $order ) { 423 return strcasecmp( $b, $a ); 424 } else { 425 return strcasecmp( $a, $b ); 426 } 427 } 428 429 /** 430 * Message to be displayed when there are no items. 431 * 432 * @since 3.1.0 433 * 434 * @global array<string, array<string, array<string, mixed>>> $plugins Array of plugin data arrays grouped by status. 435 */ 436 public function no_items() { 437 global $plugins; 438 439 if ( ! empty( $_REQUEST['s'] ) ) { 440 $s = esc_html( urldecode( wp_unslash( $_REQUEST['s'] ) ) ); 441 442 /* translators: %s: Plugin search term. */ 443 printf( __( 'No plugins found for: %s.' ), '<strong>' . $s . '</strong>' ); 444 445 // We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link. 446 if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) { 447 echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?tab=search&s=' . urlencode( $s ) ) ) . '">' . __( 'Search for plugins in the WordPress Plugin Directory.' ) . '</a>'; 448 } 449 } elseif ( ! empty( $plugins['all'] ) ) { 450 _e( 'No plugins found.' ); 451 } else { 452 _e( 'No plugins are currently available.' ); 453 } 454 } 455 456 /** 457 * Displays the search box. 458 * 459 * @since 4.6.0 460 * 461 * @param string $text The 'submit' button label. 462 * @param string $input_id ID attribute value for the search input field. 463 */ 464 public function search_box( $text, $input_id ) { 465 if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { 466 return; 467 } 468 469 $input_id = $input_id . '-search-input'; 470 471 if ( ! empty( $_REQUEST['orderby'] ) ) { 472 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />'; 473 } 474 if ( ! empty( $_REQUEST['order'] ) ) { 475 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />'; 476 } 477 ?> 478 <p class="search-box"> 479 <label for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?></label> 480 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" class="wp-filter-search" name="s" value="<?php _admin_search_query(); ?>" /> 481 <?php submit_button( $text, 'hide-if-js', '', false, array( 'id' => 'search-submit' ) ); ?> 482 </p> 483 <?php 484 } 485 486 /** 487 * Gets the list of columns for this list table. 488 * 489 * @since 3.1.0 490 * 491 * @global string $status Current plugin status filter slug. 492 * 493 * @return array<string, string> An associative array of column titles keyed by their column name. 494 */ 495 public function get_columns() { 496 global $status; 497 498 $columns = array( 499 'cb' => ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ? '<input type="checkbox" />' : '', 500 'name' => __( 'Plugin' ), 501 'description' => __( 'Description' ), 502 ); 503 504 if ( $this->show_autoupdates && ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { 505 $columns['auto-updates'] = __( 'Automatic Updates' ); 506 } 507 508 return $columns; 509 } 510 511 /** 512 * Gets the list of sortable columns for this list table. 513 * 514 * @since 3.1.0 515 * 516 * @return array<string, array<int, string|bool>|string> An associative array of sortable columns. 517 */ 518 protected function get_sortable_columns() { 519 return array(); 520 } 521 522 /** 523 * Gets an associative array of status filter links for the views area. 524 * 525 * @since 3.1.0 526 * 527 * @global array<string, int> $totals Count of plugins for each status group. 528 * @global string $status Current plugin status filter slug. 529 * 530 * @return array<string, string> An associative array of views. 531 */ 532 protected function get_views() { 533 global $totals, $status; 534 535 $status_links = array(); 536 foreach ( $totals as $type => $count ) { 537 if ( ! $count ) { 538 continue; 539 } 540 541 switch ( $type ) { 542 case 'all': 543 /* translators: %s: Number of plugins. */ 544 $text = _nx( 545 'All <span class="count">(%s)</span>', 546 'All <span class="count">(%s)</span>', 547 $count, 548 'plugins' 549 ); 550 break; 551 case 'active': 552 /* translators: %s: Number of plugins. */ 553 $text = _n( 554 'Active <span class="count">(%s)</span>', 555 'Active <span class="count">(%s)</span>', 556 $count 557 ); 558 break; 559 case 'recently_activated': 560 /* translators: %s: Number of plugins. */ 561 $text = _n( 562 'Recently Active <span class="count">(%s)</span>', 563 'Recently Active <span class="count">(%s)</span>', 564 $count 565 ); 566 break; 567 case 'inactive': 568 /* translators: %s: Number of plugins. */ 569 $text = _n( 570 'Inactive <span class="count">(%s)</span>', 571 'Inactive <span class="count">(%s)</span>', 572 $count 573 ); 574 break; 575 case 'mustuse': 576 /* translators: %s: Number of plugins. */ 577 $text = _n( 578 'Must-Use <span class="count">(%s)</span>', 579 'Must-Use <span class="count">(%s)</span>', 580 $count 581 ); 582 break; 583 case 'dropins': 584 /* translators: %s: Number of plugins. */ 585 $text = _n( 586 'Drop-in <span class="count">(%s)</span>', 587 'Drop-ins <span class="count">(%s)</span>', 588 $count 589 ); 590 break; 591 case 'paused': 592 /* translators: %s: Number of plugins. */ 593 $text = _n( 594 'Paused <span class="count">(%s)</span>', 595 'Paused <span class="count">(%s)</span>', 596 $count 597 ); 598 break; 599 case 'upgrade': 600 /* translators: %s: Number of plugins. */ 601 $text = _n( 602 'Update Available <span class="count">(%s)</span>', 603 'Update Available <span class="count">(%s)</span>', 604 $count 605 ); 606 break; 607 case 'auto-update-enabled': 608 /* translators: %s: Number of plugins. */ 609 $text = _n( 610 'Auto-updates Enabled <span class="count">(%s)</span>', 611 'Auto-updates Enabled <span class="count">(%s)</span>', 612 $count 613 ); 614 break; 615 case 'auto-update-disabled': 616 /* translators: %s: Number of plugins. */ 617 $text = _n( 618 'Auto-updates Disabled <span class="count">(%s)</span>', 619 'Auto-updates Disabled <span class="count">(%s)</span>', 620 $count 621 ); 622 break; 623 default: 624 /** 625 * Filters the status text of default switch case in the plugins list table. 626 * 627 * @since 7.0.0 628 * 629 * @param string $text Plugins list status text. Default empty string. 630 * @param int $count Count of the number of plugins. 631 * @param string $type The status slug being filtered. 632 */ 633 $text = apply_filters( 'plugins_list_status_text', '', $count, $type ); 634 if ( empty( $text ) || ! is_string( $text ) ) { 635 $text = $type; 636 } 637 $text = esc_html( $text ) . ' ' . sprintf( 638 '<span class="count">(%s)</span>', 639 number_format_i18n( $count ) 640 ); 641 break; 642 } 643 644 if ( 'search' !== $type ) { 645 $status_links[ $type ] = array( 646 'url' => add_query_arg( 'plugin_status', $type, 'plugins.php' ), 647 'label' => sprintf( $text, number_format_i18n( $count ) ), 648 'current' => $type === $status, 649 ); 650 } 651 } 652 653 return $this->get_views_links( $status_links ); 654 } 655 656 /** 657 * Gets the available bulk actions for the plugins list table. 658 * 659 * @since 3.1.0 660 * 661 * @global string $status Current plugin status filter slug. 662 * 663 * @return array<string, string> An associative array of bulk actions. 664 */ 665 protected function get_bulk_actions() { 666 global $status; 667 668 $actions = array(); 669 670 if ( 'active' !== $status ) { 671 $actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? _x( 'Network Activate', 'plugin' ) : _x( 'Activate', 'plugin' ); 672 } 673 674 if ( 'inactive' !== $status && 'recent' !== $status ) { 675 $actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? _x( 'Network Deactivate', 'plugin' ) : _x( 'Deactivate', 'plugin' ); 676 } 677 678 if ( ! is_multisite() || $this->screen->in_admin( 'network' ) ) { 679 if ( current_user_can( 'update_plugins' ) ) { 680 $actions['update-selected'] = __( 'Update' ); 681 } 682 683 if ( current_user_can( 'delete_plugins' ) && ( 'active' !== $status ) ) { 684 $actions['delete-selected'] = __( 'Delete' ); 685 } 686 687 if ( $this->show_autoupdates ) { 688 if ( 'auto-update-enabled' !== $status ) { 689 $actions['enable-auto-update-selected'] = __( 'Enable Auto-updates' ); 690 } 691 if ( 'auto-update-disabled' !== $status ) { 692 $actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' ); 693 } 694 } 695 } 696 697 return $actions; 698 } 699 700 /** 701 * Displays the bulk actions dropdown. 702 * 703 * @since 3.1.0 704 * 705 * @global string $status Current plugin status filter slug. 706 * 707 * @param string $which The location of the bulk actions: Either 'top' or 'bottom'. 708 * This is designated as optional for backward compatibility. 709 */ 710 public function bulk_actions( $which = '' ) { 711 global $status; 712 713 if ( in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { 714 return; 715 } 716 717 parent::bulk_actions( $which ); 718 } 719 720 /** 721 * Displays extra table navigation for the plugins list table. 722 * 723 * @since 3.1.0 724 * 725 * @global string $status Current plugin status filter slug. 726 * 727 * @param string $which The location: 'top' or 'bottom'. 728 */ 729 protected function extra_tablenav( $which ) { 730 global $status; 731 732 if ( ! in_array( $status, array( 'recently_activated', 'mustuse', 'dropins' ), true ) ) { 733 return; 734 } 735 736 echo '<div class="alignleft actions">'; 737 738 if ( 'recently_activated' === $status ) { 739 submit_button( __( 'Clear List' ), '', 'clear-recent-list', false ); 740 } elseif ( 'top' === $which && 'mustuse' === $status ) { 741 echo '<p>' . sprintf( 742 /* translators: %s: mu-plugins directory name. */ 743 __( 'Files in the %s directory are executed automatically.' ), 744 '<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>' 745 ) . '</p>'; 746 } elseif ( 'top' === $which && 'dropins' === $status ) { 747 echo '<p>' . sprintf( 748 /* translators: %s: wp-content directory name. */ 749 __( '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.' ), 750 '<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>' 751 ) . '</p>'; 752 } 753 echo '</div>'; 754 } 755 756 /** 757 * Gets the current action selected from the bulk actions dropdown. 758 * 759 * Also handles the 'clear-recent-list' action from the Recently Active plugins screen. 760 * 761 * @since 3.1.0 762 * 763 * @return string|false The action name. False if no action was selected. 764 */ 765 public function current_action() { 766 if ( isset( $_POST['clear-recent-list'] ) ) { 767 return 'clear-recent-list'; 768 } 769 770 return parent::current_action(); 771 } 772 773 /** 774 * Generates the list table rows. 775 * 776 * @since 3.1.0 777 * 778 * @global string $status Current plugin status filter slug. 779 */ 780 public function display_rows() { 781 global $status; 782 783 if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { 784 return; 785 } 786 787 foreach ( $this->items as $plugin_file => $plugin_data ) { 788 $this->single_row( array( $plugin_file, $plugin_data ) ); 789 } 790 } 791 792 /** 793 * Generates the markup for a single plugin row. 794 * 795 * @since 3.1.0 796 * 797 * @global string $status Current plugin status filter slug. 798 * @global int $page Current page number. 799 * @global string $s URL-encoded search term. 800 * @global array<string, int> $totals Count of plugins for each status group. 801 * 802 * @param array $item The current item. An array containing the plugin file path and plugin data. 803 * @phpstan-param array{string, array<string, mixed>} $item 804 */ 805 public function single_row( $item ) { 806 global $status, $page, $s, $totals; 807 static $plugin_id_attrs = array(); 808 809 list( $plugin_file, $plugin_data ) = $item; 810 811 $plugin_slug = $plugin_data['slug'] ?? sanitize_title( $plugin_data['Name'] ); 812 $plugin_id_attr = $plugin_slug; 813 814 // Ensure the ID attribute is unique. 815 $suffix = 2; 816 while ( in_array( $plugin_id_attr, $plugin_id_attrs, true ) ) { 817 $plugin_id_attr = "$plugin_slug-$suffix"; 818 ++$suffix; 819 } 820 821 $plugin_id_attrs[] = $plugin_id_attr; 822 823 $context = $status; 824 $screen = $this->screen; 825 826 // Pre-order. 827 $actions = array( 828 'deactivate' => '', 829 'activate' => '', 830 'details' => '', 831 'delete' => '', 832 ); 833 834 // Do not restrict by default. 835 $restrict_network_active = false; 836 $restrict_network_only = false; 837 838 $requires_php = $plugin_data['RequiresPHP'] ?? null; 839 $requires_wp = $plugin_data['RequiresWP'] ?? null; 840 841 $compatible_php = is_php_version_compatible( $requires_php ); 842 $compatible_wp = is_wp_version_compatible( $requires_wp ); 843 844 $has_dependents = WP_Plugin_Dependencies::has_dependents( $plugin_file ); 845 $has_active_dependents = WP_Plugin_Dependencies::has_active_dependents( $plugin_file ); 846 $has_unmet_dependencies = WP_Plugin_Dependencies::has_unmet_dependencies( $plugin_file ); 847 $has_circular_dependency = WP_Plugin_Dependencies::has_circular_dependency( $plugin_file ); 848 849 if ( 'mustuse' === $context ) { 850 $is_active = true; 851 } elseif ( 'dropins' === $context ) { 852 $dropins = _get_dropins(); 853 $plugin_name = $plugin_file; 854 855 if ( $plugin_file !== $plugin_data['Name'] ) { 856 $plugin_name .= '<br />' . $plugin_data['Name']; 857 } 858 859 if ( true === ( $dropins[ $plugin_file ][1] ) ) { // Doesn't require a constant. 860 $is_active = true; 861 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>'; 862 } elseif ( defined( $dropins[ $plugin_file ][1] ) && constant( $dropins[ $plugin_file ][1] ) ) { // Constant is true. 863 $is_active = true; 864 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . '</strong></p>'; 865 } else { 866 $is_active = false; 867 $description = '<p><strong>' . $dropins[ $plugin_file ][0] . ' <span class="error-message">' . __( 'Inactive:' ) . '</span></strong> ' . 868 sprintf( 869 /* translators: 1: Drop-in constant name, 2: wp-config.php */ 870 __( 'Requires %1$s in %2$s file.' ), 871 "<code>define('" . $dropins[ $plugin_file ][1] . "', true);</code>", 872 '<code>wp-config.php</code>' 873 ) . '</p>'; 874 } 875 876 if ( $plugin_data['Description'] ) { 877 $description .= '<p>' . $plugin_data['Description'] . '</p>'; 878 } 879 } else { 880 if ( $screen->in_admin( 'network' ) ) { 881 $is_active = is_plugin_active_for_network( $plugin_file ); 882 } else { 883 $is_active = is_plugin_active( $plugin_file ); 884 $restrict_network_active = ( is_multisite() && is_plugin_active_for_network( $plugin_file ) ); 885 $restrict_network_only = ( is_multisite() && is_network_only_plugin( $plugin_file ) && ! $is_active ); 886 } 887 888 if ( $screen->in_admin( 'network' ) ) { 889 if ( $is_active ) { 890 if ( current_user_can( 'manage_network_plugins' ) ) { 891 if ( $has_active_dependents ) { 892 $actions['deactivate'] = __( 'Network Deactivate' ) . 893 '<span class="screen-reader-text">' . 894 __( 'You cannot deactivate this plugin as other plugins require it.' ) . 895 '</span>'; 896 897 } else { 898 $deactivate_url = 'plugins.php?action=deactivate' . 899 '&plugin=' . urlencode( $plugin_file ) . 900 '&plugin_status=' . $context . 901 '&paged=' . $page . 902 '&s=' . $s; 903 904 $actions['deactivate'] = sprintf( 905 '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>', 906 wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), 907 esc_attr( $plugin_id_attr ), 908 /* translators: %s: Plugin name. */ 909 esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), 910 _x( 'Network Deactivate', 'plugin' ) 911 ); 912 } 913 } 914 } else { 915 if ( current_user_can( 'manage_network_plugins' ) ) { 916 if ( $compatible_php && $compatible_wp ) { 917 if ( $has_unmet_dependencies ) { 918 $actions['activate'] = _x( 'Network Activate', 'plugin' ) . 919 '<span class="screen-reader-text">' . 920 __( 'You cannot activate this plugin as it has unmet requirements.' ) . 921 '</span>'; 922 } else { 923 $activate_url = 'plugins.php?action=activate' . 924 '&plugin=' . urlencode( $plugin_file ) . 925 '&plugin_status=' . $context . 926 '&paged=' . $page . 927 '&s=' . $s; 928 929 $actions['activate'] = sprintf( 930 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>', 931 wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), 932 esc_attr( $plugin_id_attr ), 933 /* translators: %s: Plugin name. */ 934 esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ), 935 _x( 'Network Activate', 'plugin' ) 936 ); 937 } 938 } else { 939 $actions['activate'] = sprintf( 940 '<span>%s</span>', 941 _x( 'Cannot Activate', 'plugin' ) 942 ); 943 } 944 } 945 946 if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) { 947 if ( $has_dependents && ! $has_circular_dependency ) { 948 $actions['delete'] = __( 'Delete' ) . 949 '<span class="screen-reader-text">' . 950 __( 'You cannot delete this plugin as other plugins require it.' ) . 951 '</span>'; 952 } else { 953 $delete_url = 'plugins.php?action=delete-selected' . 954 '&checked[]=' . urlencode( $plugin_file ) . 955 '&plugin_status=' . $context . 956 '&paged=' . $page . 957 '&s=' . $s; 958 959 $actions['delete'] = sprintf( 960 '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>', 961 wp_nonce_url( $delete_url, 'bulk-plugins' ), 962 esc_attr( $plugin_id_attr ), 963 /* translators: %s: Plugin name. */ 964 esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), 965 __( 'Delete' ) 966 ); 967 } 968 } 969 } 970 } else { 971 if ( $restrict_network_active ) { 972 $actions = array( 973 'network_active' => __( 'Network Active' ), 974 ); 975 } elseif ( $restrict_network_only ) { 976 $actions = array( 977 'network_only' => __( 'Network Only' ), 978 ); 979 } elseif ( $is_active ) { 980 if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) { 981 if ( $has_active_dependents ) { 982 $actions['deactivate'] = __( 'Deactivate' ) . 983 '<span class="screen-reader-text">' . 984 __( 'You cannot deactivate this plugin as other plugins depend on it.' ) . 985 '</span>'; 986 } else { 987 $deactivate_url = 'plugins.php?action=deactivate' . 988 '&plugin=' . urlencode( $plugin_file ) . 989 '&plugin_status=' . $context . 990 '&paged=' . $page . 991 '&s=' . $s; 992 993 $actions['deactivate'] = sprintf( 994 '<a href="%s" id="deactivate-%s" aria-label="%s">%s</a>', 995 wp_nonce_url( $deactivate_url, 'deactivate-plugin_' . $plugin_file ), 996 esc_attr( $plugin_id_attr ), 997 /* translators: %s: Plugin name. */ 998 esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ), 999 __( 'Deactivate' ) 1000 ); 1001 } 1002 } 1003 1004 if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) { 1005 $resume_url = 'plugins.php?action=resume' . 1006 '&plugin=' . urlencode( $plugin_file ) . 1007 '&plugin_status=' . $context . 1008 '&paged=' . $page . 1009 '&s=' . $s; 1010 1011 $actions['resume'] = sprintf( 1012 '<a href="%s" id="resume-%s" class="resume-link" aria-label="%s">%s</a>', 1013 wp_nonce_url( $resume_url, 'resume-plugin_' . $plugin_file ), 1014 esc_attr( $plugin_id_attr ), 1015 /* translators: %s: Plugin name. */ 1016 esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ), 1017 __( 'Resume' ) 1018 ); 1019 } 1020 } else { 1021 if ( current_user_can( 'activate_plugin', $plugin_file ) ) { 1022 if ( $compatible_php && $compatible_wp ) { 1023 if ( $has_unmet_dependencies ) { 1024 $actions['activate'] = _x( 'Activate', 'plugin' ) . 1025 '<span class="screen-reader-text">' . 1026 __( 'You cannot activate this plugin as it has unmet requirements.' ) . 1027 '</span>'; 1028 } else { 1029 $activate_url = 'plugins.php?action=activate' . 1030 '&plugin=' . urlencode( $plugin_file ) . 1031 '&plugin_status=' . $context . 1032 '&paged=' . $page . 1033 '&s=' . $s; 1034 1035 $actions['activate'] = sprintf( 1036 '<a href="%s" id="activate-%s" class="edit" aria-label="%s">%s</a>', 1037 wp_nonce_url( $activate_url, 'activate-plugin_' . $plugin_file ), 1038 esc_attr( $plugin_id_attr ), 1039 /* translators: %s: Plugin name. */ 1040 esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ), 1041 _x( 'Activate', 'plugin' ) 1042 ); 1043 } 1044 } else { 1045 $actions['activate'] = sprintf( 1046 '<span>%s</span>', 1047 _x( 'Cannot Activate', 'plugin' ) 1048 ); 1049 } 1050 } 1051 1052 if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { 1053 if ( $has_dependents && ! $has_circular_dependency ) { 1054 $actions['delete'] = __( 'Delete' ) . 1055 '<span class="screen-reader-text">' . 1056 __( 'You cannot delete this plugin as other plugins require it.' ) . 1057 '</span>'; 1058 } else { 1059 $delete_url = 'plugins.php?action=delete-selected' . 1060 '&checked[]=' . urlencode( $plugin_file ) . 1061 '&plugin_status=' . $context . 1062 '&paged=' . $page . 1063 '&s=' . $s; 1064 1065 $actions['delete'] = sprintf( 1066 '<a href="%s" id="delete-%s" class="delete" aria-label="%s">%s</a>', 1067 wp_nonce_url( $delete_url, 'bulk-plugins' ), 1068 esc_attr( $plugin_id_attr ), 1069 /* translators: %s: Plugin name. */ 1070 esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ), 1071 __( 'Delete' ) 1072 ); 1073 } 1074 } 1075 } // End if $is_active. 1076 } // End if $screen->in_admin( 'network' ). 1077 } // End if $context. 1078 1079 $actions = array_filter( $actions ); 1080 1081 if ( $screen->in_admin( 'network' ) ) { 1082 1083 /** 1084 * Filters the action links displayed for each plugin in the Network Admin Plugins list table. 1085 * 1086 * @since 3.1.0 1087 * 1088 * @param string[] $actions An array of plugin action links. By default this can include 1089 * 'activate', 'deactivate', and 'delete'. 1090 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1091 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1092 * and the {@see 'plugin_row_meta'} filter for the list 1093 * of possible values. 1094 * @param string $context The plugin context. By default this can include 'all', 1095 * 'active', 'inactive', 'recently_activated', 'upgrade', 1096 * 'mustuse', 'dropins', and 'search'. 1097 */ 1098 $actions = apply_filters( 'network_admin_plugin_action_links', $actions, $plugin_file, $plugin_data, $context ); 1099 1100 /** 1101 * Filters the list of action links displayed for a specific plugin in the Network Admin Plugins list table. 1102 * 1103 * The dynamic portion of the hook name, `$plugin_file`, refers to the path 1104 * to the plugin file, relative to the plugins directory. 1105 * 1106 * @since 3.1.0 1107 * 1108 * @param string[] $actions An array of plugin action links. By default this can include 1109 * 'activate', 'deactivate', and 'delete'. 1110 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1111 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1112 * and the {@see 'plugin_row_meta'} filter for the list 1113 * of possible values. 1114 * @param string $context The plugin context. By default this can include 'all', 1115 * 'active', 'inactive', 'recently_activated', 'upgrade', 1116 * 'mustuse', 'dropins', and 'search'. 1117 */ 1118 $actions = apply_filters( "network_admin_plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context ); 1119 1120 } else { 1121 1122 /** 1123 * Filters the action links displayed for each plugin in the Plugins list table. 1124 * 1125 * @since 2.5.0 1126 * @since 2.6.0 The `$context` parameter was added. 1127 * @since 4.9.0 The 'Edit' link was removed from the list of action links. 1128 * 1129 * @param string[] $actions An array of plugin action links. By default this can include 1130 * 'activate', 'deactivate', and 'delete'. With Multisite active 1131 * this can also include 'network_active' and 'network_only' items. 1132 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1133 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1134 * and the {@see 'plugin_row_meta'} filter for the list 1135 * of possible values. 1136 * @param string $context The plugin context. By default this can include 'all', 1137 * 'active', 'inactive', 'recently_activated', 'upgrade', 1138 * 'mustuse', 'dropins', and 'search'. 1139 */ 1140 $actions = apply_filters( 'plugin_action_links', $actions, $plugin_file, $plugin_data, $context ); 1141 1142 /** 1143 * Filters the list of action links displayed for a specific plugin in the Plugins list table. 1144 * 1145 * The dynamic portion of the hook name, `$plugin_file`, refers to the path 1146 * to the plugin file, relative to the plugins directory. 1147 * 1148 * @since 2.7.0 1149 * @since 4.9.0 The 'Edit' link was removed from the list of action links. 1150 * 1151 * @param string[] $actions An array of plugin action links. By default this can include 1152 * 'activate', 'deactivate', and 'delete'. With Multisite active 1153 * this can also include 'network_active' and 'network_only' items. 1154 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1155 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1156 * and the {@see 'plugin_row_meta'} filter for the list 1157 * of possible values. 1158 * @param string $context The plugin context. By default this can include 'all', 1159 * 'active', 'inactive', 'recently_activated', 'upgrade', 1160 * 'mustuse', 'dropins', and 'search'. 1161 */ 1162 $actions = apply_filters( "plugin_action_links_{$plugin_file}", $actions, $plugin_file, $plugin_data, $context ); 1163 1164 } 1165 1166 $class = $is_active ? 'active' : 'inactive'; 1167 $checkbox_id = 'checkbox_' . md5( $plugin_file ); 1168 $disabled = ''; 1169 1170 if ( $has_dependents || $has_unmet_dependencies ) { 1171 $disabled = 'disabled'; 1172 } 1173 1174 if ( 1175 $restrict_network_active || 1176 $restrict_network_only || 1177 in_array( $status, array( 'mustuse', 'dropins' ), true ) || 1178 ! $compatible_php 1179 ) { 1180 $checkbox = ''; 1181 } else { 1182 $checkbox = sprintf( 1183 '<label class="label-covers-full-cell" for="%1$s">' . 1184 '<span class="screen-reader-text">%2$s</span></label>' . 1185 '<input type="checkbox" name="checked[]" value="%3$s" id="%1$s" ' . $disabled . '/>', 1186 $checkbox_id, 1187 /* translators: Hidden accessibility text. %s: Plugin name. */ 1188 sprintf( __( 'Select %s' ), $plugin_data['Name'] ), 1189 esc_attr( $plugin_file ) 1190 ); 1191 } 1192 1193 if ( 'dropins' !== $context ) { 1194 $description = '<p>' . ( $plugin_data['Description'] ? $plugin_data['Description'] : ' ' ) . '</p>'; 1195 $plugin_name = $plugin_data['Name']; 1196 } 1197 1198 if ( 1199 ! empty( $totals['upgrade'] ) && 1200 ! empty( $plugin_data['update'] ) || 1201 ! $compatible_php || 1202 ! $compatible_wp 1203 ) { 1204 $class .= ' update'; 1205 } 1206 1207 $paused = ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ); 1208 1209 if ( $paused ) { 1210 $class .= ' paused'; 1211 } 1212 1213 if ( is_uninstallable_plugin( $plugin_file ) ) { 1214 $class .= ' is-uninstallable'; 1215 } 1216 1217 printf( 1218 '<tr class="%s" data-slug="%s" data-plugin="%s">', 1219 esc_attr( $class ), 1220 esc_attr( $plugin_slug ), 1221 esc_attr( $plugin_file ) 1222 ); 1223 1224 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 1225 1226 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() ); 1227 1228 foreach ( $columns as $column_name => $column_display_name ) { 1229 $extra_classes = ''; 1230 if ( in_array( $column_name, $hidden, true ) ) { 1231 $extra_classes = ' hidden'; 1232 } 1233 1234 switch ( $column_name ) { 1235 case 'cb': 1236 echo "<th scope='row' class='check-column'>$checkbox</th>"; 1237 break; 1238 case 'name': 1239 echo "<td class='plugin-title column-primary'><strong>$plugin_name</strong>"; 1240 echo $this->row_actions( $actions, true ); 1241 echo '</td>'; 1242 break; 1243 case 'description': 1244 $classes = 'column-description desc'; 1245 1246 echo "<td class='$classes{$extra_classes}'> 1247 <div class='plugin-description'>$description</div> 1248 <div class='$class second plugin-version-author-uri'>"; 1249 1250 $plugin_meta = array(); 1251 1252 if ( ! empty( $plugin_data['Version'] ) ) { 1253 /* translators: %s: Plugin version number. */ 1254 $plugin_meta[] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); 1255 } 1256 1257 if ( ! empty( $plugin_data['Author'] ) ) { 1258 $author = $plugin_data['Author']; 1259 1260 if ( ! empty( $plugin_data['AuthorURI'] ) ) { 1261 $author = '<a href="' . $plugin_data['AuthorURI'] . '">' . $plugin_data['Author'] . '</a>'; 1262 } 1263 1264 /* translators: %s: Plugin author name. */ 1265 $plugin_meta[] = sprintf( __( 'By %s' ), $author ); 1266 } 1267 1268 // Details link using API info, if available. 1269 if ( isset( $plugin_data['slug'] ) && current_user_can( 'install_plugins' ) ) { 1270 $plugin_meta[] = sprintf( 1271 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>', 1272 esc_url( 1273 network_admin_url( 1274 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_data['slug'] . 1275 '&TB_iframe=true&width=600&height=550' 1276 ) 1277 ), 1278 /* translators: %s: Plugin name. */ 1279 esc_attr( sprintf( __( 'More information about %s' ), $plugin_name ) ), 1280 esc_attr( $plugin_name ), 1281 __( 'View details' ) 1282 ); 1283 } elseif ( ! empty( $plugin_data['PluginURI'] ) ) { 1284 /* translators: %s: Plugin name. */ 1285 $aria_label = sprintf( __( 'Visit plugin site for %s' ), $plugin_name ); 1286 1287 $plugin_meta[] = sprintf( 1288 '<a href="%s" aria-label="%s">%s</a>', 1289 esc_url( $plugin_data['PluginURI'] ), 1290 esc_attr( $aria_label ), 1291 __( 'Visit plugin site' ) 1292 ); 1293 } 1294 1295 /** 1296 * Filters the array of row meta for each plugin in the Plugins list table. 1297 * 1298 * @since 2.8.0 1299 * 1300 * @param string[] $plugin_meta An array of the plugin's metadata, including 1301 * the version, author, author URI, and plugin URI. 1302 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1303 * @param array $plugin_data { 1304 * An array of plugin data. 1305 * 1306 * @type string $id Plugin ID, e.g. `w.org/plugins/[plugin-name]`. 1307 * @type string $slug Plugin slug. 1308 * @type string $plugin Plugin basename. 1309 * @type string $new_version New plugin version. 1310 * @type string $url Plugin URL. 1311 * @type string $package Plugin update package URL. 1312 * @type string[] $icons An array of plugin icon URLs. 1313 * @type string[] $banners An array of plugin banner URLs. 1314 * @type string[] $banners_rtl An array of plugin RTL banner URLs. 1315 * @type string $requires The version of WordPress which the plugin requires. 1316 * @type string $tested The version of WordPress the plugin is tested against. 1317 * @type string $requires_php The version of PHP which the plugin requires. 1318 * @type string $upgrade_notice The upgrade notice for the new plugin version. 1319 * @type bool $update-supported Whether the plugin supports updates. 1320 * @type string $Name The human-readable name of the plugin. 1321 * @type string $PluginURI Plugin URI. 1322 * @type string $Version Plugin version. 1323 * @type string $Description Plugin description. 1324 * @type string $Author Plugin author. 1325 * @type string $AuthorURI Plugin author URI. 1326 * @type string $TextDomain Plugin textdomain. 1327 * @type string $DomainPath Relative path to the plugin's .mo file(s). 1328 * @type bool $Network Whether the plugin can only be activated network-wide. 1329 * @type string $RequiresWP The version of WordPress which the plugin requires. 1330 * @type string $RequiresPHP The version of PHP which the plugin requires. 1331 * @type string $UpdateURI ID of the plugin for update purposes, should be a URI. 1332 * @type string $Title The human-readable title of the plugin. 1333 * @type string $AuthorName Plugin author's name. 1334 * @type bool $update Whether there's an available update. Default null. 1335 * } 1336 * @param string $status Status filter currently applied to the plugin list. Possible 1337 * values are: 'all', 'active', 'inactive', 'recently_activated', 1338 * 'upgrade', 'mustuse', 'dropins', 'search', 'paused', 1339 * 'auto-update-enabled', 'auto-update-disabled'. 1340 */ 1341 $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status ); 1342 1343 echo implode( ' | ', $plugin_meta ); 1344 1345 echo '</div>'; 1346 1347 if ( $has_dependents ) { 1348 $this->add_dependents_to_dependency_plugin_row( $plugin_file ); 1349 } 1350 1351 if ( WP_Plugin_Dependencies::has_dependencies( $plugin_file ) ) { 1352 $this->add_dependencies_to_dependent_plugin_row( $plugin_file ); 1353 } 1354 1355 /** 1356 * Fires after plugin row meta. 1357 * 1358 * @since 6.5.0 1359 * 1360 * @param string $plugin_file Refer to {@see 'plugin_row_meta'} filter. 1361 * @param array $plugin_data Refer to {@see 'plugin_row_meta'} filter. 1362 */ 1363 do_action( 'after_plugin_row_meta', $plugin_file, $plugin_data ); 1364 1365 if ( $paused ) { 1366 $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' ); 1367 1368 printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text ); 1369 1370 $error = wp_get_plugin_error( $plugin_file ); 1371 1372 if ( false !== $error ) { 1373 printf( '<div class="error-display"><p>%s</p></div>', wp_get_extension_error_description( $error ) ); 1374 } 1375 } 1376 1377 echo '</td>'; 1378 break; 1379 case 'auto-updates': 1380 if ( ! $this->show_autoupdates || in_array( $status, array( 'mustuse', 'dropins' ), true ) ) { 1381 break; 1382 } 1383 1384 echo "<td class='column-auto-updates{$extra_classes}'>"; 1385 1386 $html = array(); 1387 1388 if ( isset( $plugin_data['auto-update-forced'] ) ) { 1389 if ( $plugin_data['auto-update-forced'] ) { 1390 // Forced on. 1391 $text = __( 'Auto-updates enabled' ); 1392 } else { 1393 $text = __( 'Auto-updates disabled' ); 1394 } 1395 $action = 'unavailable'; 1396 $time_class = ' hidden'; 1397 } elseif ( empty( $plugin_data['update-supported'] ) ) { 1398 $text = ''; 1399 $action = 'unavailable'; 1400 $time_class = ' hidden'; 1401 } elseif ( in_array( $plugin_file, $auto_updates, true ) ) { 1402 $text = __( 'Disable auto-updates' ); 1403 $action = 'disable'; 1404 $time_class = ''; 1405 } else { 1406 $text = __( 'Enable auto-updates' ); 1407 $action = 'enable'; 1408 $time_class = ' hidden'; 1409 } 1410 1411 $query_args = array( 1412 'action' => "{$action}-auto-update", 1413 'plugin' => $plugin_file, 1414 'paged' => $page, 1415 'plugin_status' => $status, 1416 ); 1417 1418 $url = add_query_arg( $query_args, 'plugins.php' ); 1419 1420 if ( 'unavailable' === $action ) { 1421 $html[] = '<span class="label">' . $text . '</span>'; 1422 } else { 1423 $html[] = sprintf( 1424 '<a href="%s" class="toggle-auto-update aria-button-if-js" data-wp-action="%s">', 1425 wp_nonce_url( $url, 'updates' ), 1426 $action 1427 ); 1428 1429 $html[] = '<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span>'; 1430 $html[] = '<span class="label">' . $text . '</span>'; 1431 $html[] = '</a>'; 1432 } 1433 1434 if ( ! empty( $plugin_data['update'] ) ) { 1435 $html[] = sprintf( 1436 '<div class="auto-update-time%s">%s</div>', 1437 $time_class, 1438 wp_get_auto_update_message() 1439 ); 1440 } 1441 1442 $html = implode( '', $html ); 1443 1444 /** 1445 * Filters the HTML of the auto-updates setting for each plugin in the Plugins list table. 1446 * 1447 * @since 5.5.0 1448 * 1449 * @param string $html The HTML of the plugin's auto-update column content, 1450 * including toggle auto-update action links and 1451 * time to next update. 1452 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1453 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1454 * and the {@see 'plugin_row_meta'} filter for the list 1455 * of possible values. 1456 */ 1457 echo apply_filters( 'plugin_auto_update_setting_html', $html, $plugin_file, $plugin_data ); 1458 1459 wp_admin_notice( 1460 '', 1461 array( 1462 'type' => 'error', 1463 'additional_classes' => array( 'notice-alt', 'inline', 'hidden' ), 1464 ) 1465 ); 1466 1467 echo '</td>'; 1468 1469 break; 1470 default: 1471 $classes = "$column_name column-$column_name $class"; 1472 1473 echo "<td class='$classes{$extra_classes}'>"; 1474 1475 /** 1476 * Fires inside each custom column of the Plugins list table. 1477 * 1478 * @since 3.1.0 1479 * 1480 * @param string $column_name Name of the column. 1481 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1482 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1483 * and the {@see 'plugin_row_meta'} filter for the list 1484 * of possible values. 1485 */ 1486 do_action( 'manage_plugins_custom_column', $column_name, $plugin_file, $plugin_data ); 1487 1488 echo '</td>'; 1489 } 1490 } 1491 1492 echo '</tr>'; 1493 1494 if ( ! $compatible_php || ! $compatible_wp ) { 1495 printf( 1496 '<tr class="plugin-update-tr"><td colspan="%s" class="plugin-update colspanchange">', 1497 esc_attr( $this->get_column_count() ) 1498 ); 1499 1500 $incompatible_message = ''; 1501 if ( ! $compatible_php && ! $compatible_wp ) { 1502 $incompatible_message .= __( 'This plugin does not work with your versions of WordPress and PHP.' ); 1503 if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) { 1504 $incompatible_message .= sprintf( 1505 /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */ 1506 ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ), 1507 self_admin_url( 'update-core.php' ), 1508 esc_url( wp_get_update_php_url() ) 1509 ); 1510 $incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false ); 1511 } elseif ( current_user_can( 'update_core' ) ) { 1512 $incompatible_message .= sprintf( 1513 /* translators: %s: URL to WordPress Updates screen. */ 1514 ' ' . __( '<a href="%s">Please update WordPress</a>.' ), 1515 self_admin_url( 'update-core.php' ) 1516 ); 1517 } elseif ( current_user_can( 'update_php' ) ) { 1518 $incompatible_message .= sprintf( 1519 /* translators: %s: URL to Update PHP page. */ 1520 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), 1521 esc_url( wp_get_update_php_url() ) 1522 ); 1523 $incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false ); 1524 } 1525 } elseif ( ! $compatible_wp ) { 1526 $incompatible_message .= __( 'This plugin does not work with your version of WordPress.' ); 1527 if ( current_user_can( 'update_core' ) ) { 1528 $incompatible_message .= sprintf( 1529 /* translators: %s: URL to WordPress Updates screen. */ 1530 ' ' . __( '<a href="%s">Please update WordPress</a>.' ), 1531 self_admin_url( 'update-core.php' ) 1532 ); 1533 } 1534 } elseif ( ! $compatible_php ) { 1535 $incompatible_message .= __( 'This plugin does not work with your version of PHP.' ); 1536 if ( current_user_can( 'update_php' ) ) { 1537 $incompatible_message .= sprintf( 1538 /* translators: %s: URL to Update PHP page. */ 1539 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ), 1540 esc_url( wp_get_update_php_url() ) 1541 ); 1542 $incompatible_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false ); 1543 } 1544 } 1545 1546 wp_admin_notice( 1547 $incompatible_message, 1548 array( 1549 'type' => 'error', 1550 'additional_classes' => array( 'notice-alt', 'inline', 'update-message' ), 1551 ) 1552 ); 1553 1554 echo '</td></tr>'; 1555 } 1556 1557 /** 1558 * Fires after each row in the Plugins list table. 1559 * 1560 * @since 2.3.0 1561 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' 1562 * to possible values for `$status`. 1563 * 1564 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1565 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1566 * and the {@see 'plugin_row_meta'} filter for the list 1567 * of possible values. 1568 * @param string $status Status filter currently applied to the plugin list. 1569 * Possible values are: 'all', 'active', 'inactive', 1570 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', 1571 * 'search', 'paused', 'auto-update-enabled', 'auto-update-disabled'. 1572 */ 1573 do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status ); 1574 1575 /** 1576 * Fires after each specific row in the Plugins list table. 1577 * 1578 * The dynamic portion of the hook name, `$plugin_file`, refers to the path 1579 * to the plugin file, relative to the plugins directory. 1580 * 1581 * @since 2.7.0 1582 * @since 5.5.0 Added 'auto-update-enabled' and 'auto-update-disabled' 1583 * to possible values for `$status`. 1584 * 1585 * @param string $plugin_file Path to the plugin file relative to the plugins directory. 1586 * @param array $plugin_data An array of plugin data. See get_plugin_data() 1587 * and the {@see 'plugin_row_meta'} filter for the list 1588 * of possible values. 1589 * @param string $status Status filter currently applied to the plugin list. 1590 * Possible values are: 'all', 'active', 'inactive', 1591 * 'recently_activated', 'upgrade', 'mustuse', 'dropins', 1592 * 'search', 'paused', 'auto-update-enabled', 'auto-update-disabled'. 1593 */ 1594 do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status ); 1595 } 1596 1597 /** 1598 * Gets the name of the primary column for this specific list table. 1599 * 1600 * @since 4.3.0 1601 * 1602 * @return string Unalterable name for the primary column, in this case, 'name'. 1603 */ 1604 protected function get_primary_column_name() { 1605 return 'name'; 1606 } 1607 1608 /** 1609 * Prints a list of other plugins that depend on the plugin. 1610 * 1611 * @since 6.5.0 1612 * 1613 * @param string $dependency The dependency's filepath, relative to the plugins directory. 1614 */ 1615 protected function add_dependents_to_dependency_plugin_row( $dependency ) { 1616 $dependent_names = WP_Plugin_Dependencies::get_dependent_names( $dependency ); 1617 1618 if ( empty( $dependent_names ) ) { 1619 return; 1620 } 1621 1622 $dependency_note = __( 'Note: This plugin cannot be deactivated or deleted until the plugins that require it are deactivated or deleted.' ); 1623 1624 $comma = wp_get_list_item_separator(); 1625 $required_by = sprintf( 1626 /* translators: %s: List of dependencies. */ 1627 __( '<strong>Required by:</strong> %s' ), 1628 implode( $comma, $dependent_names ) 1629 ); 1630 1631 printf( 1632 '<div class="required-by"><p>%1$s</p><p>%2$s</p></div>', 1633 $required_by, 1634 $dependency_note 1635 ); 1636 } 1637 1638 /** 1639 * Prints a list of other plugins that the plugin depends on. 1640 * 1641 * @since 6.5.0 1642 * 1643 * @param string $dependent The dependent plugin's filepath, relative to the plugins directory. 1644 */ 1645 protected function add_dependencies_to_dependent_plugin_row( $dependent ) { 1646 $dependency_names = WP_Plugin_Dependencies::get_dependency_names( $dependent ); 1647 1648 if ( array() === $dependency_names ) { 1649 return; 1650 } 1651 1652 $links = array(); 1653 foreach ( $dependency_names as $slug => $name ) { 1654 $links[] = $this->get_dependency_view_details_link( $name, $slug ); 1655 } 1656 1657 $is_active = is_multisite() ? is_plugin_active_for_network( $dependent ) : is_plugin_active( $dependent ); 1658 $comma = wp_get_list_item_separator(); 1659 $requires = sprintf( 1660 /* translators: %s: List of dependency names. */ 1661 __( '<strong>Requires:</strong> %s' ), 1662 implode( $comma, $links ) 1663 ); 1664 1665 $notice = ''; 1666 $error_message = ''; 1667 if ( WP_Plugin_Dependencies::has_unmet_dependencies( $dependent ) ) { 1668 if ( $is_active ) { 1669 $error_message = __( 'This plugin is active but may not function correctly because required plugins are missing or inactive.' ); 1670 } else { 1671 $error_message = __( 'This plugin cannot be activated because required plugins are missing or inactive.' ); 1672 } 1673 $notice = wp_get_admin_notice( 1674 $error_message, 1675 array( 1676 'type' => 'error', 1677 'additional_classes' => array( 'inline', 'notice-alt' ), 1678 ) 1679 ); 1680 } 1681 1682 printf( 1683 '<div class="requires"><p>%1$s</p>%2$s</div>', 1684 $requires, 1685 $notice 1686 ); 1687 } 1688 1689 /** 1690 * Returns a 'View details' like link for a dependency. 1691 * 1692 * @since 6.5.0 1693 * 1694 * @param string $name The dependency's name. 1695 * @param string $slug The dependency's slug. 1696 * @return string A 'View details' link for the dependency. 1697 */ 1698 protected function get_dependency_view_details_link( $name, $slug ) { 1699 $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $slug ); 1700 1701 if ( false === $dependency_data 1702 || $name === $slug 1703 || $name !== $dependency_data['name'] 1704 || empty( $dependency_data['version'] ) 1705 ) { 1706 return $name; 1707 } 1708 1709 return $this->get_view_details_link( $name, $slug ); 1710 } 1711 1712 /** 1713 * Returns a 'View details' link for the plugin. 1714 * 1715 * @since 6.5.0 1716 * 1717 * @param string $name The plugin's name. 1718 * @param string $slug The plugin's slug. 1719 * @return string A 'View details' link for the plugin. 1720 */ 1721 protected function get_view_details_link( $name, $slug ) { 1722 $url = add_query_arg( 1723 array( 1724 'tab' => 'plugin-information', 1725 'plugin' => $slug, 1726 'TB_iframe' => 'true', 1727 'width' => '600', 1728 'height' => '550', 1729 ), 1730 network_admin_url( 'plugin-install.php' ) 1731 ); 1732 1733 $name_attr = esc_attr( $name ); 1734 return sprintf( 1735 "<a href='%s' class='thickbox open-plugin-details-modal' aria-label='%s' data-title='%s'>%s</a>", 1736 esc_url( $url ), 1737 /* translators: %s: Plugin name. */ 1738 sprintf( __( 'More information about %s' ), $name_attr ), 1739 $name_attr, 1740 esc_html( $name ) 1741 ); 1742 } 1743 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Jun 24 08:20:11 2026 | Cross-referenced by PHPXref |