[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-wp-plugins-list-table.php (source)

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


Generated : Thu May 7 08:20:02 2026 Cross-referenced by PHPXref