[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * List Table API: WP_Plugin_Install_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 plugins to install in a list table.
  12   *
  13   * @since 3.1.0
  14   *
  15   * @see WP_List_Table
  16   */
  17  class WP_Plugin_Install_List_Table extends WP_List_Table {
  18  
  19      /**
  20       * Sort order of the plugins list: Either 'ASC' or 'DESC'.
  21       *
  22       * @since 4.0.0
  23       *
  24       * @var string
  25       * @phpstan-var 'ASC'|'DESC'
  26       */
  27      public $order = 'ASC';
  28  
  29      /**
  30       * Plugin field to sort the list by, or null to keep the API's order.
  31       *
  32       * Not set by core. Sorting only applies when the plugins are objects, since
  33       * {@see self::order_callback()} reads object properties, whereas the plugins
  34       * returned by the API have been arrays since WordPress 5.1.
  35       *
  36       * @since 4.0.0
  37       *
  38       * @var string|null
  39       */
  40      public $orderby = null;
  41  
  42      /**
  43       * Plugin group names keyed by group slug, as returned by the Plugin Installation API.
  44       *
  45       * @since 4.0.0
  46       *
  47       * @var array<string, string>
  48       */
  49      public $groups = array();
  50  
  51      /**
  52       * Error returned by the Plugin Installation API, if any.
  53       *
  54       * @since 4.0.0
  55       * @since 4.2.0 Declared as a private property.
  56       *
  57       * @var WP_Error|null
  58       */
  59      private $error;
  60  
  61      /**
  62       * Checks if the current user has permissions to perform an Ajax action.
  63       *
  64       * @since 3.1.0
  65       *
  66       * @return bool Whether the current user can perform an Ajax action.
  67       */
  68  	public function ajax_user_can() {
  69          return current_user_can( 'install_plugins' );
  70      }
  71  
  72      /**
  73       * Returns the list of known plugins.
  74       *
  75       * Uses the transient data from the updates API to determine the known
  76       * installed plugins.
  77       *
  78       * @since 4.9.0
  79       *
  80       * @return array
  81       */
  82  	protected function get_installed_plugins() {
  83          $plugins = array();
  84  
  85          $plugin_info = get_site_transient( 'update_plugins' );
  86          if ( isset( $plugin_info->no_update ) ) {
  87              foreach ( $plugin_info->no_update as $plugin ) {
  88                  if ( isset( $plugin->slug ) ) {
  89                      $plugin->upgrade          = false;
  90                      $plugins[ $plugin->slug ] = $plugin;
  91                  }
  92              }
  93          }
  94  
  95          if ( isset( $plugin_info->response ) ) {
  96              foreach ( $plugin_info->response as $plugin ) {
  97                  if ( isset( $plugin->slug ) ) {
  98                      $plugin->upgrade          = true;
  99                      $plugins[ $plugin->slug ] = $plugin;
 100                  }
 101              }
 102          }
 103  
 104          return $plugins;
 105      }
 106  
 107      /**
 108       * Returns a list of slugs of installed plugins, if known.
 109       *
 110       * Uses the transient data from the updates API to determine the slugs of
 111       * known installed plugins. This might be better elsewhere, perhaps even
 112       * within get_plugins().
 113       *
 114       * @since 4.0.0
 115       *
 116       * @return array
 117       */
 118  	protected function get_installed_plugin_slugs() {
 119          return array_keys( $this->get_installed_plugins() );
 120      }
 121  
 122      /**
 123       * Prepares the plugins list for display.
 124       *
 125       * @since 3.1.0
 126       *
 127       * @global array<string, string> $tabs  Labels of the tabs shown on the Add Plugins screen, keyed by tab slug.
 128       * @global string                $tab   The current active tab.
 129       * @global int                   $paged The current page number.
 130       * @global string                $type  The type of search being performed.
 131       * @global string                $term  The search term.
 132       */
 133  	public function prepare_items() {
 134          require_once  ABSPATH . 'wp-admin/includes/plugin-install.php';
 135  
 136          global $tabs, $tab, $paged, $type, $term;
 137  
 138          $tab = ! empty( $_REQUEST['tab'] ) ? sanitize_text_field( $_REQUEST['tab'] ) : '';
 139  
 140          $paged = $this->get_pagenum();
 141  
 142          $per_page = 36;
 143  
 144          // These are the tabs which are shown on the page.
 145          $tabs = array();
 146  
 147          if ( 'search' === $tab ) {
 148              $tabs['search'] = __( 'Search Results' );
 149          }
 150  
 151          if ( 'beta' === $tab || str_contains( get_bloginfo( 'version' ), '-' ) ) {
 152              $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer' );
 153          }
 154  
 155          $tabs['featured']    = _x( 'Featured', 'Plugin Installer' );
 156          $tabs['popular']     = _x( 'Popular', 'Plugin Installer' );
 157          $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer' );
 158          $tabs['favorites']   = _x( 'Favorites', 'Plugin Installer' );
 159  
 160          if ( current_user_can( 'upload_plugins' ) ) {
 161              /*
 162               * No longer a real tab. Here for filter compatibility.
 163               * Gets skipped in get_views().
 164               */
 165              $tabs['upload'] = __( 'Upload Plugin' );
 166          }
 167  
 168          $nonmenu_tabs = array( 'plugin-information' ); // Valid actions to perform which do not have a Menu item.
 169  
 170          /**
 171           * Filters the tabs shown on the Add Plugins screen.
 172           *
 173           * @since 2.7.0
 174           *
 175           * @param array<string, string> $tabs Labels of the tabs shown on the Add Plugins screen, keyed by tab
 176           *                                    slug. Default keys include 'featured', 'popular', 'recommended',
 177           *                                    'favorites', and 'upload'.
 178           */
 179          $tabs = apply_filters( 'install_plugins_tabs', $tabs );
 180  
 181          /**
 182           * Filters tabs not associated with a menu item on the Add Plugins screen.
 183           *
 184           * @since 2.7.0
 185           *
 186           * @param string[] $nonmenu_tabs The tabs that don't have a menu item on the Add Plugins screen.
 187           */
 188          $nonmenu_tabs = apply_filters( 'install_plugins_nonmenu_tabs', $nonmenu_tabs );
 189  
 190          // If a non-valid menu tab has been selected, And it's not a non-menu action.
 191          if ( empty( $tab ) || ( ! isset( $tabs[ $tab ] ) && ! in_array( $tab, (array) $nonmenu_tabs, true ) ) ) {
 192              $tab = key( $tabs );
 193          }
 194  
 195          $installed_plugins = $this->get_installed_plugins();
 196  
 197          $args = array(
 198              'page'     => $paged,
 199              'per_page' => $per_page,
 200              // Send the locale to the API so it can provide context-sensitive results.
 201              'locale'   => get_user_locale(),
 202          );
 203  
 204          switch ( $tab ) {
 205              case 'search':
 206                  $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term';
 207                  $term = isset( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : '';
 208  
 209                  switch ( $type ) {
 210                      case 'tag':
 211                          $args['tag'] = sanitize_title_with_dashes( $term );
 212                          break;
 213                      case 'term':
 214                          $args['search'] = $term;
 215                          break;
 216                      case 'author':
 217                          $args['author'] = $term;
 218                          break;
 219                  }
 220  
 221                  break;
 222  
 223              case 'featured':
 224              case 'popular':
 225              case 'new':
 226              case 'beta':
 227                  $args['browse'] = $tab;
 228                  break;
 229              case 'recommended':
 230                  $args['browse'] = $tab;
 231                  // Include the list of installed plugins so we can get relevant results.
 232                  $args['installed_plugins'] = array_keys( $installed_plugins );
 233                  break;
 234  
 235              case 'favorites':
 236                  $action = 'save_wporg_username_' . get_current_user_id();
 237                  if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), $action ) ) {
 238                      $user = isset( $_GET['user'] ) ? wp_unslash( $_GET['user'] ) : get_user_option( 'wporg_favorites' );
 239  
 240                      // If the save url parameter is passed with a falsey value, don't save the favorite user.
 241                      if ( ! isset( $_GET['save'] ) || $_GET['save'] ) {
 242                          update_user_meta( get_current_user_id(), 'wporg_favorites', $user );
 243                      }
 244                  } else {
 245                      $user = get_user_option( 'wporg_favorites' );
 246                  }
 247                  if ( $user ) {
 248                      $args['user'] = $user;
 249                  } else {
 250                      $args = false;
 251                  }
 252  
 253                  add_action( 'install_plugins_favorites', 'install_plugins_favorites_form', 9, 0 );
 254                  break;
 255  
 256              default:
 257                  $args = false;
 258                  break;
 259          }
 260  
 261          /**
 262           * Filters API request arguments for each Add Plugins screen tab.
 263           *
 264           * The dynamic portion of the hook name, `$tab`, refers to the plugin install tabs.
 265           *
 266           * Possible hook names include:
 267           *
 268           *  - `install_plugins_table_api_args_favorites`
 269           *  - `install_plugins_table_api_args_featured`
 270           *  - `install_plugins_table_api_args_popular`
 271           *  - `install_plugins_table_api_args_recommended`
 272           *  - `install_plugins_table_api_args_upload`
 273           *  - `install_plugins_table_api_args_search`
 274           *  - `install_plugins_table_api_args_beta`
 275           *
 276           * @since 3.7.0
 277           *
 278           * @param array|false $args Plugin install API arguments.
 279           */
 280          $args = apply_filters( "install_plugins_table_api_args_{$tab}", $args );
 281  
 282          if ( ! $args ) {
 283              return;
 284          }
 285  
 286          $api = plugins_api( 'query_plugins', $args );
 287  
 288          if ( is_wp_error( $api ) ) {
 289              $this->error = $api;
 290              return;
 291          }
 292  
 293          $this->items = $api->plugins;
 294  
 295          if ( $this->orderby ) {
 296              uasort( $this->items, array( $this, 'order_callback' ) );
 297          }
 298  
 299          $this->set_pagination_args(
 300              array(
 301                  'total_items' => $api->info['results'],
 302                  'per_page'    => $args['per_page'],
 303              )
 304          );
 305  
 306          if ( isset( $api->info['groups'] ) ) {
 307              $this->groups = $api->info['groups'];
 308          }
 309  
 310          if ( $installed_plugins ) {
 311              $js_plugins = array_fill_keys(
 312                  array( 'all', 'search', 'active', 'inactive', 'recently_activated', 'mustuse', 'dropins' ),
 313                  array()
 314              );
 315  
 316              $js_plugins['all'] = array_values( wp_list_pluck( $installed_plugins, 'plugin' ) );
 317              $upgrade_plugins   = wp_filter_object_list( $installed_plugins, array( 'upgrade' => true ), 'and', 'plugin' );
 318  
 319              if ( $upgrade_plugins ) {
 320                  $js_plugins['upgrade'] = array_values( $upgrade_plugins );
 321              }
 322  
 323              wp_localize_script(
 324                  'updates',
 325                  '_wpUpdatesItemCounts',
 326                  array(
 327                      'plugins' => $js_plugins,
 328                      'totals'  => wp_get_update_data(),
 329                  )
 330              );
 331          }
 332      }
 333  
 334      /**
 335       * Outputs the message when no plugins are found.
 336       *
 337       * @since 3.1.0
 338       */
 339  	public function no_items() {
 340          if ( isset( $this->error ) ) {
 341              $error_message  = '<p>' . $this->error->get_error_message() . '</p>';
 342              $error_message .= '<p class="hide-if-no-js"><button class="button try-again">' . __( 'Try Again' ) . '</button></p>';
 343              wp_admin_notice(
 344                  $error_message,
 345                  array(
 346                      'additional_classes' => array( 'inline', 'error' ),
 347                      'paragraph_wrap'     => false,
 348                  )
 349              );
 350              ?>
 351          <?php } else { ?>
 352              <div class="no-plugin-results"><?php _e( 'No plugins found. Try a different search.' ); ?></div>
 353              <?php
 354          }
 355      }
 356  
 357      /**
 358       * Gets the list of views (tabs) available for the plugins list table.
 359       *
 360       * @since 3.1.0
 361       *
 362       * @global array<string, string> $tabs Labels of the tabs shown on the Add Plugins screen, keyed by tab slug.
 363       * @global string                $tab  The current active tab.
 364       *
 365       * @return array<string, string> View link markup keyed by view ID ('plugin-install-' followed by the tab slug).
 366       */
 367  	protected function get_views() {
 368          global $tabs, $tab;
 369  
 370          $display_tabs = array();
 371          foreach ( (array) $tabs as $action => $text ) {
 372              $display_tabs[ 'plugin-install-' . $action ] = array(
 373                  'url'     => self_admin_url( 'plugin-install.php?tab=' . $action ),
 374                  'label'   => $text,
 375                  'current' => $action === $tab,
 376              );
 377          }
 378          // No longer a real tab.
 379          unset( $display_tabs['plugin-install-upload'] );
 380  
 381          return $this->get_views_links( $display_tabs );
 382      }
 383  
 384      /**
 385       * Overrides parent views so we can use the filter bar display.
 386       *
 387       * @since 4.0.0
 388       *
 389       * @global string $tab The current tab.
 390       */
 391  	public function views() {
 392          global $tab;
 393  
 394          $views = $this->get_views();
 395  
 396          /** This filter is documented in wp-admin/includes/class-wp-list-table.php */
 397          $views = apply_filters( "views_{$this->screen->id}", $views );
 398  
 399          $this->screen->render_screen_reader_content( 'heading_views' );
 400  
 401          printf(
 402              /* translators: %s: https://wordpress.org/plugins/ */
 403              '<p>' . __( 'Plugins extend and expand the functionality of WordPress. You may install plugins from the <a href="%s">WordPress Plugin Directory</a> right on this page, or upload a plugin in .zip format by clicking the button above.' ) . '</p>',
 404              __( 'https://wordpress.org/plugins/' )
 405          );
 406          ?>
 407  <div class="wp-filter">
 408      <ul class="filter-links">
 409          <?php
 410          if ( ! empty( $views ) ) {
 411              foreach ( $views as $class => $view ) {
 412                  $views[ $class ] = "\t<li class='$class'>$view";
 413              }
 414              echo implode( " </li>\n", $views ) . "</li>\n";
 415          }
 416          ?>
 417      </ul>
 418  
 419          <?php
 420          if ( 'favorites' !== $tab ) {
 421              install_search_form();
 422          }
 423          ?>
 424  </div>
 425          <?php
 426      }
 427  
 428      /**
 429       * Displays the plugin install table.
 430       *
 431       * Overrides the parent display() method to provide a different container.
 432       *
 433       * @since 4.0.0
 434       */
 435  	public function display() {
 436          $singular = $this->_args['singular'];
 437  
 438          $data_attr = '';
 439  
 440          if ( $singular ) {
 441              $data_attr = " data-wp-lists='list:$singular'";
 442          }
 443  
 444          $this->display_tablenav( 'top' );
 445  
 446          ?>
 447  <div class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
 448          <?php
 449          $this->screen->render_screen_reader_content( 'heading_list' );
 450          ?>
 451      <div id="the-list"<?php echo $data_attr; ?>>
 452          <?php $this->display_rows_or_placeholder(); ?>
 453      </div>
 454  </div>
 455          <?php
 456          $this->display_tablenav( 'bottom' );
 457      }
 458  
 459      /**
 460       * Generates the table navigation.
 461       *
 462       * @since 3.1.0
 463       *
 464       * @global string $tab The current active tab.
 465       *
 466       * @param string $which The location of the navigation: Either 'top' or 'bottom'.
 467       *
 468       * @phpstan-param 'top'|'bottom' $which
 469       */
 470  	protected function display_tablenav( $which ) {
 471          if ( 'featured' === $GLOBALS['tab'] ) {
 472              return;
 473          }
 474  
 475          if ( 'top' === $which ) {
 476              wp_referer_field();
 477              ?>
 478              <div class="tablenav top">
 479                  <div class="alignleft actions">
 480                      <?php
 481                      /**
 482                       * Fires before the Plugin Install table header pagination is displayed.
 483                       *
 484                       * @since 2.7.0
 485                       */
 486                      do_action( 'install_plugins_table_header' );
 487                      ?>
 488                  </div>
 489                  <?php $this->pagination( $which ); ?>
 490                  <br class="clear" />
 491              </div>
 492          <?php } else { ?>
 493              <div class="tablenav bottom">
 494                  <?php $this->pagination( $which ); ?>
 495                  <br class="clear" />
 496              </div>
 497              <?php
 498          }
 499      }
 500  
 501      /**
 502       * Gets a list of CSS classes for the list table container element.
 503       *
 504       * Unlike in the parent class, these are applied to a `div` element rather than a `table` element.
 505       *
 506       * @since 3.1.0
 507       *
 508       * @return string[] Array of CSS classes for the container element.
 509       *
 510       * @phpstan-return non-empty-list<string>
 511       */
 512  	protected function get_table_classes() {
 513          return array( 'widefat', $this->_args['plural'] );
 514      }
 515  
 516      /**
 517       * Gets the list of columns for the plugins list table.
 518       *
 519       * @since 3.1.0
 520       *
 521       * @return array<string, string> Array of column titles keyed by their column name.
 522       */
 523  	public function get_columns() {
 524          return array();
 525      }
 526  
 527      /**
 528       * Callback for sorting plugins.
 529       *
 530       * @since 4.0.0
 531       *
 532       * @param array<string, mixed>|object $plugin_a The first plugin data.
 533       * @param array<string, mixed>|object $plugin_b The second plugin data.
 534       * @return int Comparison result.
 535       */
 536  	private function order_callback( $plugin_a, $plugin_b ) {
 537          $orderby = $this->orderby;
 538          if ( ! isset( $plugin_a->$orderby, $plugin_b->$orderby ) ) {
 539              return 0;
 540          }
 541  
 542          $a = $plugin_a->$orderby;
 543          $b = $plugin_b->$orderby;
 544  
 545          return 'DESC' === $this->order ?
 546              $b <=> $a :
 547              $a <=> $b;
 548      }
 549  
 550      /**
 551       * Generates the list table rows.
 552       *
 553       * @since 3.1.0
 554       */
 555  	public function display_rows() {
 556          $plugins_allowedtags = array(
 557              'a'       => array(
 558                  'href'   => array(),
 559                  'title'  => array(),
 560                  'target' => array(),
 561              ),
 562              'abbr'    => array( 'title' => array() ),
 563              'acronym' => array( 'title' => array() ),
 564              'code'    => array(),
 565              'pre'     => array(),
 566              'em'      => array(),
 567              'strong'  => array(),
 568              'ul'      => array(),
 569              'ol'      => array(),
 570              'li'      => array(),
 571              'p'       => array(),
 572              'br'      => array(),
 573          );
 574  
 575          $plugins_group_titles = array(
 576              'Performance' => _x( 'Performance', 'Plugin installer group title' ),
 577              'Social'      => _x( 'Social', 'Plugin installer group title' ),
 578              'Tools'       => _x( 'Tools', 'Plugin installer group title' ),
 579          );
 580  
 581          $group = null;
 582  
 583          foreach ( (array) $this->items as $plugin ) {
 584              if ( is_object( $plugin ) ) {
 585                  $plugin = (array) $plugin;
 586              }
 587  
 588              // Display the group heading if there is one.
 589              if ( isset( $plugin['group'] ) && $plugin['group'] !== $group ) {
 590                  if ( isset( $this->groups[ $plugin['group'] ] ) ) {
 591                      $group_name = $this->groups[ $plugin['group'] ];
 592                      if ( isset( $plugins_group_titles[ $group_name ] ) ) {
 593                          $group_name = $plugins_group_titles[ $group_name ];
 594                      }
 595                  } else {
 596                      $group_name = $plugin['group'];
 597                  }
 598  
 599                  // Starting a new group, close off the divs of the last one.
 600                  if ( ! empty( $group ) ) {
 601                      echo '</div></div>';
 602                  }
 603  
 604                  echo '<div class="plugin-group"><h3>' . esc_html( $group_name ) . '</h3>';
 605                  // Needs an extra wrapping div for nth-child selectors to work.
 606                  echo '<div class="plugin-items">';
 607  
 608                  $group = $plugin['group'];
 609              }
 610  
 611              $title = wp_kses( $plugin['name'], $plugins_allowedtags );
 612  
 613              // Remove any HTML from the description.
 614              $description = strip_tags( $plugin['short_description'] );
 615  
 616              /**
 617               * Filters the plugin card description on the Add Plugins screen.
 618               *
 619               * @since 6.0.0
 620               *
 621               * @param string $description Plugin card description.
 622               * @param array  $plugin      An array of plugin data. See {@see plugins_api()}
 623               *                            for the list of possible values.
 624               */
 625              $description = apply_filters( 'plugin_install_description', $description, $plugin );
 626  
 627              $version = wp_kses( $plugin['version'], $plugins_allowedtags );
 628  
 629              $name = strip_tags( $title . ' ' . $version );
 630  
 631              $author = wp_kses( $plugin['author'], $plugins_allowedtags );
 632              if ( ! empty( $author ) ) {
 633                  /* translators: %s: Plugin author. */
 634                  $author = ' <cite>' . sprintf( __( 'By %s' ), $author ) . '</cite>';
 635              }
 636  
 637              $requires_php = $plugin['requires_php'] ?? null;
 638              $requires_wp  = $plugin['requires'] ?? null;
 639  
 640              $compatible_php = is_php_version_compatible( $requires_php );
 641              $compatible_wp  = is_wp_version_compatible( $requires_wp );
 642              $tested_wp      = ( empty( $plugin['tested'] ) || version_compare( get_bloginfo( 'version' ), $plugin['tested'], '<=' ) );
 643  
 644              $action_links = array();
 645  
 646              $action_links[] = wp_get_plugin_action_button( $name, $plugin, $compatible_php, $compatible_wp );
 647  
 648              $details_link = self_admin_url(
 649                  'plugin-install.php?tab=plugin-information&amp;plugin=' . $plugin['slug'] .
 650                  '&amp;TB_iframe=true&amp;width=600&amp;height=550'
 651              );
 652  
 653              $action_links[] = sprintf(
 654                  '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
 655                  esc_url( $details_link ),
 656                  /* translators: %s: Plugin name and version. */
 657                  esc_attr( sprintf( __( 'More information about %s' ), $name ) ),
 658                  esc_attr( $name ),
 659                  __( 'More Details' )
 660              );
 661  
 662              if ( ! empty( $plugin['icons']['svg'] ) ) {
 663                  $plugin_icon_url = $plugin['icons']['svg'];
 664              } elseif ( ! empty( $plugin['icons']['2x'] ) ) {
 665                  $plugin_icon_url = $plugin['icons']['2x'];
 666              } elseif ( ! empty( $plugin['icons']['1x'] ) ) {
 667                  $plugin_icon_url = $plugin['icons']['1x'];
 668              } else {
 669                  $plugin_icon_url = $plugin['icons']['default'];
 670              }
 671  
 672              /**
 673               * Filters the install action links for a plugin.
 674               *
 675               * @since 2.7.0
 676               *
 677               * @param string[] $action_links An array of plugin action links.
 678               *                               Defaults are links to Details and Install Now.
 679               * @param array    $plugin       An array of plugin data. See {@see plugins_api()}
 680               *                               for the list of possible values.
 681               */
 682              $action_links = apply_filters( 'plugin_install_action_links', $action_links, $plugin );
 683  
 684              $last_updated_timestamp = strtotime( $plugin['last_updated'] );
 685              ?>
 686          <div class="plugin-card plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>">
 687              <?php
 688              if ( ! $compatible_php || ! $compatible_wp ) {
 689                  $incompatible_notice_message = '';
 690                  if ( ! $compatible_php && ! $compatible_wp ) {
 691                      $incompatible_notice_message .= __( 'This plugin does not work with your versions of WordPress and PHP.' );
 692                      if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
 693                          $incompatible_notice_message .= sprintf(
 694                              /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */
 695                              ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ),
 696                              self_admin_url( 'update-core.php' ),
 697                              esc_url( wp_get_update_php_url() )
 698                          );
 699                          $incompatible_notice_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false );
 700                      } elseif ( current_user_can( 'update_core' ) ) {
 701                          $incompatible_notice_message .= sprintf(
 702                              /* translators: %s: URL to WordPress Updates screen. */
 703                              ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
 704                              self_admin_url( 'update-core.php' )
 705                          );
 706                      } elseif ( current_user_can( 'update_php' ) ) {
 707                          $incompatible_notice_message .= sprintf(
 708                              /* translators: %s: URL to Update PHP page. */
 709                              ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
 710                              esc_url( wp_get_update_php_url() )
 711                          );
 712                          $incompatible_notice_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false );
 713                      }
 714                  } elseif ( ! $compatible_wp ) {
 715                      $incompatible_notice_message .= __( 'This plugin does not work with your version of WordPress.' );
 716                      if ( current_user_can( 'update_core' ) ) {
 717                          $incompatible_notice_message .= sprintf(
 718                              /* translators: %s: URL to WordPress Updates screen. */
 719                              ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
 720                              self_admin_url( 'update-core.php' )
 721                          );
 722                      }
 723                  } elseif ( ! $compatible_php ) {
 724                      $incompatible_notice_message .= __( 'This plugin does not work with your version of PHP.' );
 725                      if ( current_user_can( 'update_php' ) ) {
 726                          $incompatible_notice_message .= sprintf(
 727                              /* translators: %s: URL to Update PHP page. */
 728                              ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
 729                              esc_url( wp_get_update_php_url() )
 730                          );
 731                          $incompatible_notice_message .= wp_update_php_annotation( '</p><p><em>', '</em>', false );
 732                      }
 733                  }
 734  
 735                  wp_admin_notice(
 736                      $incompatible_notice_message,
 737                      array(
 738                          'type'               => 'error',
 739                          'additional_classes' => array( 'notice-alt', 'inline' ),
 740                      )
 741                  );
 742              }
 743              ?>
 744              <div class="plugin-card-top">
 745                  <div class="name column-name">
 746                      <h3>
 747                          <a href="<?php echo esc_url( $details_link ); ?>" class="thickbox open-plugin-details-modal">
 748                          <?php echo $title; ?>
 749                          <img src="<?php echo esc_url( $plugin_icon_url ); ?>" class="plugin-icon" alt="" />
 750                          </a>
 751                      </h3>
 752                  </div>
 753                  <div class="action-links">
 754                      <?php
 755                      if ( $action_links ) {
 756                          echo '<ul class="plugin-action-buttons"><li>' . implode( '</li><li>', $action_links ) . '</li></ul>';
 757                      }
 758                      ?>
 759                  </div>
 760                  <div class="desc column-description">
 761                      <p><?php echo $description; ?></p>
 762                      <p class="authors"><?php echo $author; ?></p>
 763                  </div>
 764              </div>
 765              <?php
 766              $dependencies_notice = $this->get_dependencies_notice( $plugin );
 767              if ( ! empty( $dependencies_notice ) ) {
 768                  echo $dependencies_notice;
 769              }
 770              ?>
 771              <div class="plugin-card-bottom">
 772                  <div class="vers column-rating">
 773                      <?php
 774                      wp_star_rating(
 775                          array(
 776                              'rating' => $plugin['rating'],
 777                              'type'   => 'percent',
 778                              'number' => $plugin['num_ratings'],
 779                          )
 780                      );
 781                      ?>
 782                      <span class="num-ratings" aria-hidden="true">(<?php echo number_format_i18n( $plugin['num_ratings'] ); ?>)</span>
 783                  </div>
 784                  <div class="column-updated">
 785                      <strong><?php _e( 'Last Updated:' ); ?></strong>
 786                      <?php
 787                          /* translators: %s: Human-readable time difference. */
 788                          printf( __( '%s ago' ), human_time_diff( $last_updated_timestamp ) );
 789                      ?>
 790                  </div>
 791                  <div class="column-downloaded">
 792                      <?php
 793                      if ( $plugin['active_installs'] >= 1000000 ) {
 794                          $active_installs_millions = floor( $plugin['active_installs'] / 1000000 );
 795                          $active_installs_text     = sprintf(
 796                              /* translators: %s: Number of millions. */
 797                              _nx( '%s+ Million', '%s+ Million', $active_installs_millions, 'Active plugin installations' ),
 798                              number_format_i18n( $active_installs_millions )
 799                          );
 800                      } elseif ( 0 === $plugin['active_installs'] ) {
 801                          $active_installs_text = _x( 'Less Than 10', 'Active plugin installations' );
 802                      } else {
 803                          $active_installs_text = number_format_i18n( $plugin['active_installs'] ) . '+';
 804                      }
 805                      /* translators: %s: Number of installations. */
 806                      printf( __( '%s Active Installations' ), $active_installs_text );
 807                      ?>
 808                  </div>
 809                  <div class="column-compatibility">
 810                      <?php
 811                      if ( ! $tested_wp ) {
 812                          echo '<span class="compatibility-untested">' . __( 'Untested with your version of WordPress' ) . '</span>';
 813                      } elseif ( ! $compatible_wp ) {
 814                          echo '<span class="compatibility-incompatible">' . __( '<strong>Incompatible</strong> with your version of WordPress' ) . '</span>';
 815                      } else {
 816                          echo '<span class="compatibility-compatible">' . __( '<strong>Compatible</strong> with your version of WordPress' ) . '</span>';
 817                      }
 818                      ?>
 819                  </div>
 820              </div>
 821          </div>
 822              <?php
 823          }
 824  
 825          // Close off the group divs of the last one.
 826          if ( ! empty( $group ) ) {
 827              echo '</div></div>';
 828          }
 829      }
 830  
 831      /**
 832       * Returns a notice containing a list of dependencies required by the plugin.
 833       *
 834       * @since 6.5.0
 835       *
 836       * @param array  $plugin_data An array of plugin data. See {@see plugins_api()}
 837       *                            for the list of possible values.
 838       * @return string A notice containing a list of dependencies required by the plugin,
 839       *                or an empty string if none is required.
 840       */
 841  	protected function get_dependencies_notice( $plugin_data ) {
 842          if ( empty( $plugin_data['requires_plugins'] ) ) {
 843              return '';
 844          }
 845  
 846          $no_name_markup  = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span></div>';
 847          $has_name_markup = '<div class="plugin-dependency"><span class="plugin-dependency-name">%s</span> %s</div>';
 848  
 849          $dependencies_list = '';
 850          foreach ( $plugin_data['requires_plugins'] as $dependency ) {
 851              $dependency_data = WP_Plugin_Dependencies::get_dependency_data( $dependency );
 852  
 853              if (
 854                  false !== $dependency_data &&
 855                  ! empty( $dependency_data['name'] ) &&
 856                  ! empty( $dependency_data['slug'] ) &&
 857                  ! empty( $dependency_data['version'] )
 858              ) {
 859                  $more_details_link  = $this->get_more_details_link( $dependency_data['name'], $dependency_data['slug'] );
 860                  $dependencies_list .= sprintf( $has_name_markup, esc_html( $dependency_data['name'] ), $more_details_link );
 861                  continue;
 862              }
 863  
 864              $result = plugins_api( 'plugin_information', array( 'slug' => $dependency ) );
 865  
 866              if ( ! empty( $result->name ) ) {
 867                  $more_details_link  = $this->get_more_details_link( $result->name, $result->slug );
 868                  $dependencies_list .= sprintf( $has_name_markup, esc_html( $result->name ), $more_details_link );
 869                  continue;
 870              }
 871  
 872              $dependencies_list .= sprintf( $no_name_markup, esc_html( $dependency ) );
 873          }
 874  
 875          $dependencies_notice = sprintf(
 876              '<div class="plugin-dependencies notice notice-alt notice-info inline"><p class="plugin-dependencies-explainer-text">%s</p> %s</div>',
 877              '<strong>' . __( 'Additional plugins are required' ) . '</strong>',
 878              $dependencies_list
 879          );
 880  
 881          return $dependencies_notice;
 882      }
 883  
 884      /**
 885       * Creates a 'More details' link for the plugin.
 886       *
 887       * @since 6.5.0
 888       *
 889       * @param string $name The plugin's name.
 890       * @param string $slug The plugin's slug.
 891       * @return string The 'More details' link for the plugin.
 892       */
 893  	protected function get_more_details_link( $name, $slug ) {
 894          $url = add_query_arg(
 895              array(
 896                  'tab'       => 'plugin-information',
 897                  'plugin'    => $slug,
 898                  'TB_iframe' => 'true',
 899                  'width'     => '600',
 900                  'height'    => '550',
 901              ),
 902              network_admin_url( 'plugin-install.php' )
 903          );
 904  
 905          $more_details_link = sprintf(
 906              '<a href="%1$s" class="more-details-link thickbox open-plugin-details-modal" aria-label="%2$s" data-title="%3$s">%4$s</a>',
 907              esc_url( $url ),
 908              /* translators: %s: Plugin name. */
 909              sprintf( __( 'More information about %s' ), esc_html( $name ) ),
 910              esc_attr( $name ),
 911              __( 'More Details' )
 912          );
 913  
 914          return $more_details_link;
 915      }
 916  }


Generated : Thu Oct 15 08:20:32 2026 Cross-referenced by PHPXref