[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * List Table API: WP_MS_Themes_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 themes in a list table for the network admin.
  12   *
  13   * @since 3.1.0
  14   *
  15   * @see WP_List_Table
  16   */
  17  class WP_MS_Themes_List_Table extends WP_List_Table {
  18  
  19      public $site_id;
  20      public $is_site_themes;
  21  
  22      private $has_items;
  23  
  24      /**
  25       * Whether to show the auto-updates UI.
  26       *
  27       * @since 5.5.0
  28       *
  29       * @var bool True if auto-updates UI is to be shown, false otherwise.
  30       */
  31      protected $show_autoupdates = true;
  32  
  33      /**
  34       * Constructor.
  35       *
  36       * @since 3.1.0
  37       *
  38       * @see WP_List_Table::__construct() for more information on default arguments.
  39       *
  40       * @global string $status The current theme status.
  41       * @global int    $page   The current page number.
  42       *
  43       * @param array $args An associative array of arguments.
  44       */
  45  	public function __construct( $args = array() ) {
  46          global $status, $page;
  47  
  48          parent::__construct(
  49              array(
  50                  'plural' => 'themes',
  51                  'screen' => $args['screen'] ?? null,
  52              )
  53          );
  54  
  55          $status = $_REQUEST['theme_status'] ?? 'all';
  56          if ( ! in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken', 'auto-update-enabled', 'auto-update-disabled' ), true ) ) {
  57              $status = 'all';
  58          }
  59  
  60          $page = $this->get_pagenum();
  61  
  62          $this->is_site_themes = 'site-themes-network' === $this->screen->id;
  63  
  64          if ( $this->is_site_themes ) {
  65              $this->site_id = isset( $_REQUEST['id'] ) ? (int) $_REQUEST['id'] : 0;
  66          }
  67  
  68          $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'theme' ) &&
  69              ! $this->is_site_themes && current_user_can( 'update_themes' );
  70      }
  71  
  72      /**
  73       * Gets the list of CSS classes for the table tag.
  74       *
  75       * @return string[] The list of CSS classes.
  76       *
  77       * @phpstan-return non-empty-list<string>
  78       */
  79  	protected function get_table_classes() {
  80          // @todo Remove and add CSS for .themes.
  81          return array( 'widefat', 'plugins' );
  82      }
  83  
  84      /**
  85       * Checks if the current user has permissions to perform AJAX actions.
  86       *
  87       * @return bool True if the current user has permissions, false otherwise.
  88       */
  89  	public function ajax_user_can() {
  90          if ( $this->is_site_themes ) {
  91              return current_user_can( 'manage_sites' );
  92          } else {
  93              return current_user_can( 'manage_network_themes' );
  94          }
  95      }
  96  
  97      /**
  98       * Prepares the themes list for display.
  99       *
 100       * @global string             $status  The current theme status.
 101       * @global array<string, int> $totals  An array of theme counts for each status.
 102       * @global int                $page    The current page number.
 103       * @global string             $orderby The column to order the themes list by.
 104       * @global string             $order   The order of the themes list (ASC or DESC).
 105       * @global string             $s       The search string.
 106       */
 107  	public function prepare_items() {
 108          global $status, $totals, $page, $orderby, $order, $s;
 109  
 110          $orderby = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : '';
 111          $order   = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : '';
 112          $s       = ! empty( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '';
 113  
 114          $themes = array(
 115              /**
 116               * Filters the full array of WP_Theme objects to list in the Multisite
 117               * themes list table.
 118               *
 119               * @since 3.1.0
 120               *
 121               * @param WP_Theme[] $all Array of WP_Theme objects to display in the list table.
 122               */
 123              'all'      => apply_filters( 'all_themes', wp_get_themes() ),
 124              'search'   => array(),
 125              'enabled'  => array(),
 126              'disabled' => array(),
 127              'upgrade'  => array(),
 128              'broken'   => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ),
 129          );
 130  
 131          if ( $this->show_autoupdates ) {
 132              $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
 133  
 134              $themes['auto-update-enabled']  = array();
 135              $themes['auto-update-disabled'] = array();
 136          }
 137  
 138          if ( $this->is_site_themes ) {
 139              $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' );
 140              $allowed_where   = 'site';
 141          } else {
 142              $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' );
 143              $allowed_where   = 'network';
 144          }
 145  
 146          $current      = get_site_transient( 'update_themes' );
 147          $maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current;
 148  
 149          foreach ( (array) $themes['all'] as $key => $theme ) {
 150              if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) {
 151                  unset( $themes['all'][ $key ] );
 152                  continue;
 153              }
 154  
 155              if ( $maybe_update && isset( $current->response[ $key ] ) ) {
 156                  $themes['all'][ $key ]->update = true;
 157                  $themes['upgrade'][ $key ]     = $themes['all'][ $key ];
 158              }
 159  
 160              $filter                    = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled';
 161              $themes[ $filter ][ $key ] = $themes['all'][ $key ];
 162  
 163              $theme_data = array(
 164                  'update_supported' => $theme->update_supported ?? true,
 165              );
 166  
 167              // Extra info if known. array_merge() ensures $theme_data has precedence if keys collide.
 168              if ( isset( $current->response[ $key ] ) ) {
 169                  $theme_data = array_merge( (array) $current->response[ $key ], $theme_data );
 170              } elseif ( isset( $current->no_update[ $key ] ) ) {
 171                  $theme_data = array_merge( (array) $current->no_update[ $key ], $theme_data );
 172              } else {
 173                  $theme_data['update_supported'] = false;
 174              }
 175  
 176              $theme->update_supported = $theme_data['update_supported'];
 177  
 178              /*
 179               * Create the expected payload for the auto_update_theme filter, this is the same data
 180               * as contained within $updates or $no_updates but used when the Theme is not known.
 181               */
 182              $filter_payload = array(
 183                  'theme'        => $key,
 184                  'new_version'  => '',
 185                  'url'          => '',
 186                  'package'      => '',
 187                  'requires'     => '',
 188                  'requires_php' => '',
 189              );
 190  
 191              $filter_payload = (object) array_merge( $filter_payload, array_intersect_key( $theme_data, $filter_payload ) );
 192  
 193              $auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, $filter_payload );
 194  
 195              if ( ! is_null( $auto_update_forced ) ) {
 196                  $theme->auto_update_forced = $auto_update_forced;
 197              }
 198  
 199              if ( $this->show_autoupdates ) {
 200                  $enabled = in_array( $key, $auto_updates, true ) && $theme->update_supported;
 201                  if ( isset( $theme->auto_update_forced ) ) {
 202                      $enabled = (bool) $theme->auto_update_forced;
 203                  }
 204  
 205                  if ( $enabled ) {
 206                      $themes['auto-update-enabled'][ $key ] = $theme;
 207                  } else {
 208                      $themes['auto-update-disabled'][ $key ] = $theme;
 209                  }
 210              }
 211          }
 212  
 213          if ( $s ) {
 214              $status           = 'search';
 215              $themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) );
 216          }
 217  
 218          $totals    = array();
 219          $js_themes = array();
 220          foreach ( $themes as $type => $list ) {
 221              $totals[ $type ]    = count( $list );
 222              $js_themes[ $type ] = array_keys( $list );
 223          }
 224  
 225          if ( empty( $themes[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) {
 226              $status = 'all';
 227          }
 228  
 229          $this->items = $themes[ $status ];
 230          WP_Theme::sort_by_name( $this->items );
 231  
 232          $this->has_items = ! empty( $themes['all'] );
 233          $total_this_page = $totals[ $status ];
 234  
 235          wp_localize_script(
 236              'updates',
 237              '_wpUpdatesItemCounts',
 238              array(
 239                  'themes' => $js_themes,
 240                  'totals' => wp_get_update_data(),
 241              )
 242          );
 243  
 244          if ( $orderby ) {
 245              $orderby = ucfirst( $orderby );
 246              $order   = strtoupper( $order );
 247  
 248              if ( 'Name' === $orderby ) {
 249                  if ( 'ASC' === $order ) {
 250                      $this->items = array_reverse( $this->items );
 251                  }
 252              } else {
 253                  uasort( $this->items, array( $this, '_order_callback' ) );
 254              }
 255          }
 256  
 257          $start = ( $page - 1 ) * $themes_per_page;
 258  
 259          if ( $total_this_page > $themes_per_page ) {
 260              $this->items = array_slice( $this->items, $start, $themes_per_page, true );
 261          }
 262  
 263          $this->set_pagination_args(
 264              array(
 265                  'total_items' => $total_this_page,
 266                  'per_page'    => $themes_per_page,
 267              )
 268          );
 269      }
 270  
 271      /**
 272       * Filters a theme by the search term.
 273       *
 274       * @param WP_Theme $theme The WP_Theme object to check.
 275       * @return bool True if the theme matches the search term, false otherwise.
 276       */
 277  	public function _search_callback( $theme ) {
 278          static $term = null;
 279          if ( is_null( $term ) ) {
 280              $term = wp_unslash( $_REQUEST['s'] );
 281          }
 282  
 283          foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) {
 284              // Don't mark up; Do translate.
 285              if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) {
 286                  return true;
 287              }
 288          }
 289  
 290          if ( false !== stripos( $theme->get_stylesheet(), $term ) ) {
 291              return true;
 292          }
 293  
 294          if ( false !== stripos( $theme->get_template(), $term ) ) {
 295              return true;
 296          }
 297  
 298          return false;
 299      }
 300  
 301      // Not used by any core columns.
 302      /**
 303       * Compares the order of two themes by a specific field.
 304       *
 305       * @global string $orderby The column to order the themes list by.
 306       * @global string $order   The order of the themes list (ASC or DESC).
 307       *
 308       * @param WP_Theme $theme_a The first theme to compare.
 309       * @param WP_Theme $theme_b The second theme to compare.
 310       * @return int 0 if equal, -1 if the first is less than the second, 1 if more.
 311       */
 312  	public function _order_callback( $theme_a, $theme_b ) {
 313          global $orderby, $order;
 314  
 315          $a = $theme_a[ $orderby ];
 316          $b = $theme_b[ $orderby ];
 317  
 318          return 'DESC' === $order ?
 319              $b <=> $a :
 320              $a <=> $b;
 321      }
 322  
 323      /**
 324       * Displays the message when there are no items to list.
 325       */
 326  	public function no_items() {
 327          if ( $this->has_items ) {
 328              _e( 'No themes found.' );
 329          } else {
 330              _e( 'No themes are currently available.' );
 331          }
 332      }
 333  
 334      /**
 335       * Gets the list of columns for the list table.
 336       *
 337       * @return array<string, string> Array of column titles keyed by their column name.
 338       */
 339  	public function get_columns() {
 340          $columns = array(
 341              'cb'          => '<input type="checkbox" />',
 342              'name'        => __( 'Theme' ),
 343              'description' => __( 'Description' ),
 344          );
 345  
 346          if ( $this->show_autoupdates ) {
 347              $columns['auto-updates'] = __( 'Automatic Updates' );
 348          }
 349  
 350          return $columns;
 351      }
 352  
 353      /**
 354       * Gets the list of sortable columns for the list table.
 355       *
 356       * @return array<string, array<int, mixed>> An array of sortable columns.
 357       */
 358  	protected function get_sortable_columns() {
 359          return array(
 360              'name' => array( 'name', false, __( 'Theme' ), __( 'Table ordered by Theme Name.' ), 'asc' ),
 361          );
 362      }
 363  
 364      /**
 365       * Gets the name of the primary column.
 366       *
 367       * @since 4.3.0
 368       *
 369       * @return string Unalterable name of the primary column name, in this case, 'name'.
 370       */
 371  	protected function get_primary_column_name() {
 372          return 'name';
 373      }
 374  
 375      /**
 376       * Gets the list of views (statuses) for the list table.
 377       *
 378       * @global array<string, int> $totals An array of theme counts for each status.
 379       * @global string             $status The current theme status.
 380       *
 381       * @return array<string, string> The list of views.
 382       */
 383  	protected function get_views() {
 384          global $totals, $status;
 385  
 386          $status_links = array();
 387          foreach ( $totals as $type => $count ) {
 388              if ( ! $count ) {
 389                  continue;
 390              }
 391  
 392              switch ( $type ) {
 393                  case 'all':
 394                      /* translators: %s: Number of themes. */
 395                      $text = _nx(
 396                          'All <span class="count">(%s)</span>',
 397                          'All <span class="count">(%s)</span>',
 398                          $count,
 399                          'themes'
 400                      );
 401                      break;
 402                  case 'enabled':
 403                      /* translators: %s: Number of themes. */
 404                      $text = _nx(
 405                          'Enabled <span class="count">(%s)</span>',
 406                          'Enabled <span class="count">(%s)</span>',
 407                          $count,
 408                          'themes'
 409                      );
 410                      break;
 411                  case 'disabled':
 412                      /* translators: %s: Number of themes. */
 413                      $text = _nx(
 414                          'Disabled <span class="count">(%s)</span>',
 415                          'Disabled <span class="count">(%s)</span>',
 416                          $count,
 417                          'themes'
 418                      );
 419                      break;
 420                  case 'upgrade':
 421                      /* translators: %s: Number of themes. */
 422                      $text = _nx(
 423                          'Update Available <span class="count">(%s)</span>',
 424                          'Update Available <span class="count">(%s)</span>',
 425                          $count,
 426                          'themes'
 427                      );
 428                      break;
 429                  case 'broken':
 430                      /* translators: %s: Number of themes. */
 431                      $text = _nx(
 432                          'Broken <span class="count">(%s)</span>',
 433                          'Broken <span class="count">(%s)</span>',
 434                          $count,
 435                          'themes'
 436                      );
 437                      break;
 438                  case 'auto-update-enabled':
 439                      /* translators: %s: Number of themes. */
 440                      $text = _n(
 441                          'Auto-updates Enabled <span class="count">(%s)</span>',
 442                          'Auto-updates Enabled <span class="count">(%s)</span>',
 443                          $count
 444                      );
 445                      break;
 446                  case 'auto-update-disabled':
 447                      /* translators: %s: Number of themes. */
 448                      $text = _n(
 449                          'Auto-updates Disabled <span class="count">(%s)</span>',
 450                          'Auto-updates Disabled <span class="count">(%s)</span>',
 451                          $count
 452                      );
 453                      break;
 454              }
 455  
 456              if ( $this->is_site_themes ) {
 457                  $url = 'site-themes.php?id=' . $this->site_id;
 458              } else {
 459                  $url = 'themes.php';
 460              }
 461  
 462              if ( 'search' !== $type ) {
 463                  $status_links[ $type ] = array(
 464                      'url'     => esc_url( add_query_arg( 'theme_status', $type, $url ) ),
 465                      'label'   => sprintf( $text, number_format_i18n( $count ) ),
 466                      'current' => $type === $status,
 467                  );
 468              }
 469          }
 470  
 471          return $this->get_views_links( $status_links );
 472      }
 473  
 474      /**
 475       * Gets the list of bulk actions for the list table.
 476       *
 477       * @global string $status The current theme status.
 478       *
 479       * @return array<string, string> The list of bulk actions.
 480       */
 481  	protected function get_bulk_actions() {
 482          global $status;
 483  
 484          $actions = array();
 485          if ( 'enabled' !== $status ) {
 486              $actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' );
 487          }
 488          if ( 'disabled' !== $status ) {
 489              $actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' );
 490          }
 491          if ( ! $this->is_site_themes ) {
 492              if ( current_user_can( 'update_themes' ) ) {
 493                  $actions['update-selected'] = __( 'Update' );
 494              }
 495              if ( current_user_can( 'delete_themes' ) ) {
 496                  $actions['delete-selected'] = __( 'Delete' );
 497              }
 498          }
 499  
 500          if ( $this->show_autoupdates ) {
 501              if ( 'auto-update-enabled' !== $status ) {
 502                  $actions['enable-auto-update-selected'] = __( 'Enable Auto-updates' );
 503              }
 504  
 505              if ( 'auto-update-disabled' !== $status ) {
 506                  $actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' );
 507              }
 508          }
 509  
 510          return $actions;
 511      }
 512  
 513      /**
 514       * Generates the list table rows.
 515       *
 516       * @since 3.1.0
 517       */
 518  	public function display_rows() {
 519          foreach ( $this->items as $theme ) {
 520              $this->single_row( $theme );
 521          }
 522      }
 523  
 524      /**
 525       * Handles the checkbox column output.
 526       *
 527       * @since 4.3.0
 528       * @since 5.9.0 Renamed `$theme` to `$item` to match parent class for PHP 8 named parameter support.
 529       *
 530       * @param WP_Theme $item The current WP_Theme object.
 531       */
 532  	public function column_cb( $item ) {
 533          // Restores the more descriptive, specific name for use within this method.
 534          $theme = $item;
 535  
 536          $checkbox_id = 'checkbox_' . md5( $theme->get( 'Name' ) );
 537          ?>
 538          <input type="checkbox" name="checked[]" value="<?php echo esc_attr( $theme->get_stylesheet() ); ?>" id="<?php echo $checkbox_id; ?>" />
 539          <label for="<?php echo $checkbox_id; ?>" >
 540              <span class="screen-reader-text">
 541              <?php
 542              printf(
 543                  /* translators: Hidden accessibility text. %s: Theme name */
 544                  __( 'Select %s' ),
 545                  $theme->display( 'Name' )
 546              );
 547              ?>
 548              </span>
 549          </label>
 550          <?php
 551      }
 552  
 553      /**
 554       * Handles the name column output.
 555       *
 556       * @since 4.3.0
 557       *
 558       * @global string $status The current theme status.
 559       * @global int    $page   The current page number.
 560       * @global string $s      The search string.
 561       *
 562       * @param WP_Theme $theme The current WP_Theme object.
 563       */
 564  	public function column_name( $theme ) {
 565          global $status, $page, $s;
 566  
 567          $context = $status;
 568  
 569          if ( $this->is_site_themes ) {
 570              $url     = "site-themes.php?id={$this->site_id}&amp;";
 571              $allowed = $theme->is_allowed( 'site', $this->site_id );
 572          } else {
 573              $url     = 'themes.php?';
 574              $allowed = $theme->is_allowed( 'network' );
 575          }
 576  
 577          // Pre-order.
 578          $actions = array(
 579              'enable'  => '',
 580              'disable' => '',
 581              'delete'  => '',
 582          );
 583  
 584          $stylesheet = $theme->get_stylesheet();
 585          $theme_key  = urlencode( $stylesheet );
 586  
 587          if ( ! $allowed ) {
 588              if ( ! $theme->errors() ) {
 589                  $url = add_query_arg(
 590                      array(
 591                          'action' => 'enable',
 592                          'theme'  => $theme_key,
 593                          'paged'  => $page,
 594                          's'      => $s,
 595                      ),
 596                      $url
 597                  );
 598  
 599                  if ( $this->is_site_themes ) {
 600                      /* translators: %s: Theme name. */
 601                      $aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) );
 602                  } else {
 603                      /* translators: %s: Theme name. */
 604                      $aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) );
 605                  }
 606  
 607                  $actions['enable'] = sprintf(
 608                      '<a href="%s" class="edit" aria-label="%s">%s</a>',
 609                      esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ),
 610                      esc_attr( $aria_label ),
 611                      ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) )
 612                  );
 613              }
 614          } else {
 615              $url = add_query_arg(
 616                  array(
 617                      'action' => 'disable',
 618                      'theme'  => $theme_key,
 619                      'paged'  => $page,
 620                      's'      => $s,
 621                  ),
 622                  $url
 623              );
 624  
 625              if ( $this->is_site_themes ) {
 626                  /* translators: %s: Theme name. */
 627                  $aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) );
 628              } else {
 629                  /* translators: %s: Theme name. */
 630                  $aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) );
 631              }
 632  
 633              $actions['disable'] = sprintf(
 634                  '<a href="%s" aria-label="%s">%s</a>',
 635                  esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ),
 636                  esc_attr( $aria_label ),
 637                  ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) )
 638              );
 639          }
 640  
 641          if ( ! $allowed && ! $this->is_site_themes
 642              && current_user_can( 'delete_themes' )
 643              && get_option( 'stylesheet' ) !== $stylesheet
 644              && get_option( 'template' ) !== $stylesheet
 645          ) {
 646              $url = add_query_arg(
 647                  array(
 648                      'action'       => 'delete-selected',
 649                      'checked[]'    => $theme_key,
 650                      'theme_status' => $context,
 651                      'paged'        => $page,
 652                      's'            => $s,
 653                  ),
 654                  'themes.php'
 655              );
 656  
 657              /* translators: %s: Theme name. */
 658              $aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) );
 659  
 660              $actions['delete'] = sprintf(
 661                  '<a href="%s" class="delete" aria-label="%s">%s</a>',
 662                  esc_url( wp_nonce_url( $url, 'bulk-themes' ) ),
 663                  esc_attr( $aria_label ),
 664                  __( 'Delete' )
 665              );
 666          }
 667          /**
 668           * Filters the action links displayed for each theme in the Multisite
 669           * themes list table.
 670           *
 671           * The action links displayed are determined by the theme's status, and
 672           * which Multisite themes list table is being displayed - the Network
 673           * themes list table (themes.php), which displays all installed themes,
 674           * or the Site themes list table (site-themes.php), which displays the
 675           * non-network enabled themes when editing a site in the Network admin.
 676           *
 677           * The default action links for the Network themes list table include
 678           * 'Network Enable', 'Network Disable', and 'Delete'.
 679           *
 680           * The default action links for the Site themes list table include
 681           * 'Enable', and 'Disable'.
 682           *
 683           * @since 2.8.0
 684           *
 685           * @param string[] $actions An array of action links.
 686           * @param WP_Theme $theme   The current WP_Theme object.
 687           * @param string   $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
 688           */
 689          $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context );
 690  
 691          /**
 692           * Filters the action links of a specific theme in the Multisite themes
 693           * list table.
 694           *
 695           * The dynamic portion of the hook name, `$stylesheet`, refers to the
 696           * directory name of the theme, which in most cases is synonymous
 697           * with the template name.
 698           *
 699           * @since 3.1.0
 700           *
 701           * @param string[] $actions An array of action links.
 702           * @param WP_Theme $theme   The current WP_Theme object.
 703           * @param string   $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
 704           */
 705          $actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, $context );
 706  
 707          echo $this->row_actions( $actions, true );
 708      }
 709  
 710      /**
 711       * Handles the description column output.
 712       *
 713       * @since 4.3.0
 714       *
 715       * @global string             $status The current theme status.
 716       * @global array<string, int> $totals An array of theme counts for each status.
 717       *
 718       * @param WP_Theme $theme The current WP_Theme object.
 719       */
 720  	public function column_description( $theme ) {
 721          global $status, $totals;
 722  
 723          if ( $theme->errors() ) {
 724              $pre = 'broken' === $status ? '<strong class="error-message">' . __( 'Broken Theme:' ) . '</strong> ' : '';
 725              wp_admin_notice(
 726                  $pre . $theme->errors()->get_error_message(),
 727                  array(
 728                      'type'               => 'error',
 729                      'additional_classes' => array( 'inline' ),
 730                  )
 731              );
 732          }
 733  
 734          if ( $this->is_site_themes ) {
 735              $allowed = $theme->is_allowed( 'site', $this->site_id );
 736          } else {
 737              $allowed = $theme->is_allowed( 'network' );
 738          }
 739  
 740          $class = ! $allowed ? 'inactive' : 'active';
 741          if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
 742              $class .= ' update';
 743          }
 744  
 745          echo "<div class='theme-description'><p>" . $theme->display( 'Description' ) . "</p></div>
 746              <div class='$class second theme-version-author-uri'>";
 747  
 748          $stylesheet = $theme->get_stylesheet();
 749          $theme_meta = array();
 750  
 751          if ( $theme->get( 'Version' ) ) {
 752              /* translators: %s: Theme version. */
 753              $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display( 'Version' ) );
 754          }
 755  
 756          /* translators: %s: Theme author. */
 757          $theme_meta[] = sprintf( __( 'By %s' ), $theme->display( 'Author' ) );
 758  
 759          if ( $theme->get( 'ThemeURI' ) ) {
 760              /* translators: %s: Theme name. */
 761              $aria_label = sprintf( __( 'Visit theme site for %s' ), $theme->display( 'Name' ) );
 762  
 763              $theme_meta[] = sprintf(
 764                  '<a href="%s" aria-label="%s">%s</a>',
 765                  $theme->display( 'ThemeURI' ),
 766                  esc_attr( $aria_label ),
 767                  __( 'Visit Theme Site' )
 768              );
 769          }
 770  
 771          if ( $theme->parent() ) {
 772              $theme_meta[] = sprintf(
 773                  /* translators: %s: Theme name. */
 774                  __( 'Child theme of %s' ),
 775                  '<strong>' . $theme->parent()->display( 'Name' ) . '</strong>'
 776              );
 777          }
 778  
 779          /**
 780           * Filters the array of row meta for each theme in the Multisite themes
 781           * list table.
 782           *
 783           * @since 3.1.0
 784           *
 785           * @param string[] $theme_meta An array of the theme's metadata, including
 786           *                             the version, author, and theme URI.
 787           * @param string   $stylesheet Directory name of the theme.
 788           * @param WP_Theme $theme      WP_Theme object.
 789           * @param string   $status     Status of the theme.
 790           */
 791          $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $stylesheet, $theme, $status );
 792  
 793          echo implode( ' | ', $theme_meta );
 794  
 795          echo '</div>';
 796      }
 797  
 798      /**
 799       * Handles the auto-updates column output.
 800       *
 801       * @since 5.5.0
 802       *
 803       * @global string $status The current theme status.
 804       * @global int    $page   The current page number.
 805       *
 806       * @param WP_Theme $theme The current WP_Theme object.
 807       */
 808  	public function column_autoupdates( $theme ) {
 809          global $status, $page;
 810  
 811          static $auto_updates, $available_updates;
 812  
 813          if ( ! $auto_updates ) {
 814              $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
 815          }
 816          if ( ! $available_updates ) {
 817              $available_updates = get_site_transient( 'update_themes' );
 818          }
 819  
 820          $stylesheet = $theme->get_stylesheet();
 821  
 822          if ( isset( $theme->auto_update_forced ) ) {
 823              if ( $theme->auto_update_forced ) {
 824                  // Forced on.
 825                  $text = __( 'Auto-updates enabled' );
 826              } else {
 827                  $text = __( 'Auto-updates disabled' );
 828              }
 829              $action     = 'unavailable';
 830              $time_class = ' hidden';
 831          } elseif ( empty( $theme->update_supported ) ) {
 832              $text       = '';
 833              $action     = 'unavailable';
 834              $time_class = ' hidden';
 835          } elseif ( in_array( $stylesheet, $auto_updates, true ) ) {
 836              $text       = __( 'Disable auto-updates' );
 837              $action     = 'disable';
 838              $time_class = '';
 839          } else {
 840              $text       = __( 'Enable auto-updates' );
 841              $action     = 'enable';
 842              $time_class = ' hidden';
 843          }
 844  
 845          $query_args = array(
 846              'action'       => "{$action}-auto-update",
 847              'theme'        => $stylesheet,
 848              'paged'        => $page,
 849              'theme_status' => $status,
 850          );
 851  
 852          $url = add_query_arg( $query_args, 'themes.php' );
 853  
 854          if ( 'unavailable' === $action ) {
 855              $html[] = '<span class="label">' . $text . '</span>';
 856          } else {
 857              $html[] = sprintf(
 858                  '<a href="%s" class="toggle-auto-update aria-button-if-js" data-wp-action="%s">',
 859                  wp_nonce_url( $url, 'updates' ),
 860                  $action
 861              );
 862  
 863              $html[] = '<span class="dashicons dashicons-update spin hidden" aria-hidden="true"></span>';
 864              $html[] = '<span class="label">' . $text . '</span>';
 865              $html[] = '</a>';
 866  
 867          }
 868  
 869          if ( isset( $available_updates->response[ $stylesheet ] ) ) {
 870              $html[] = sprintf(
 871                  '<div class="auto-update-time%s">%s</div>',
 872                  $time_class,
 873                  wp_get_auto_update_message()
 874              );
 875          }
 876  
 877          $html = implode( '', $html );
 878  
 879          /**
 880           * Filters the HTML of the auto-updates setting for each theme in the Themes list table.
 881           *
 882           * @since 5.5.0
 883           *
 884           * @param string   $html       The HTML for theme's auto-update setting, including
 885           *                             toggle auto-update action link and time to next update.
 886           * @param string   $stylesheet Directory name of the theme.
 887           * @param WP_Theme $theme      WP_Theme object.
 888           */
 889          echo apply_filters( 'theme_auto_update_setting_html', $html, $stylesheet, $theme );
 890  
 891          wp_admin_notice(
 892              '',
 893              array(
 894                  'type'               => 'error',
 895                  'additional_classes' => array( 'notice-alt', 'inline', 'hidden' ),
 896              )
 897          );
 898      }
 899  
 900      /**
 901       * Handles default column output.
 902       *
 903       * @since 4.3.0
 904       * @since 5.9.0 Renamed `$theme` to `$item` to match parent class for PHP 8 named parameter support.
 905       *
 906       * @param WP_Theme $item        The current WP_Theme object.
 907       * @param string   $column_name The current column name.
 908       */
 909  	public function column_default( $item, $column_name ) {
 910          // Restores the more descriptive, specific name for use within this method.
 911          $theme = $item;
 912  
 913          $stylesheet = $theme->get_stylesheet();
 914  
 915          /**
 916           * Fires inside each custom column of the Multisite themes list table.
 917           *
 918           * @since 3.1.0
 919           *
 920           * @param string   $column_name Name of the column.
 921           * @param string   $stylesheet  Directory name of the theme.
 922           * @param WP_Theme $theme       Current WP_Theme object.
 923           */
 924          do_action( 'manage_themes_custom_column', $column_name, $stylesheet, $theme );
 925      }
 926  
 927      /**
 928       * Handles the output for a single table row.
 929       *
 930       * @since 4.3.0
 931       *
 932       * @param WP_Theme $item The current WP_Theme object.
 933       */
 934  	public function single_row_columns( $item ) {
 935          list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
 936  
 937          foreach ( $columns as $column_name => $column_display_name ) {
 938              $extra_classes = '';
 939              if ( in_array( $column_name, $hidden, true ) ) {
 940                  $extra_classes .= ' hidden';
 941              }
 942  
 943              switch ( $column_name ) {
 944                  case 'cb':
 945                      echo '<td class="check-column">';
 946  
 947                      $this->column_cb( $item );
 948  
 949                      echo '</td>';
 950                      break;
 951  
 952                  case 'name':
 953                      $active_theme_label = '';
 954  
 955                      /* The presence of the site_id property means that this is a subsite view and a label for the active theme needs to be added */
 956                      if ( ! empty( $this->site_id ) ) {
 957                          $stylesheet = get_blog_option( $this->site_id, 'stylesheet' );
 958                          $template   = get_blog_option( $this->site_id, 'template' );
 959  
 960                          /* Add a label for the active template */
 961                          if ( $item->get_template() === $template ) {
 962                              $active_theme_label = ' &mdash; ' . __( 'Active Theme' );
 963                          }
 964  
 965                          /* In case this is a child theme, label it properly */
 966                          if ( $stylesheet !== $template && $item->get_stylesheet() === $stylesheet ) {
 967                              $active_theme_label = ' &mdash; ' . __( 'Active Child Theme' );
 968                          }
 969                      }
 970  
 971                      echo "<th scope='row' class='theme-title column-primary{$extra_classes}' aria-label='" . esc_attr( $item->display( 'Name' ) ) . "'><strong>" . $item->display( 'Name' ) . $active_theme_label . '</strong>';
 972  
 973                      $this->column_name( $item );
 974  
 975                      echo '</th>';
 976                      break;
 977  
 978                  case 'description':
 979                      echo "<td class='column-description desc{$extra_classes}'>";
 980  
 981                      $this->column_description( $item );
 982  
 983                      echo '</td>';
 984                      break;
 985  
 986                  case 'auto-updates':
 987                      echo "<td class='column-auto-updates{$extra_classes}'>";
 988  
 989                      $this->column_autoupdates( $item );
 990  
 991                      echo '</td>';
 992                      break;
 993                  default:
 994                      echo "<td class='$column_name column-$column_name{$extra_classes}'>";
 995  
 996                      $this->column_default( $item, $column_name );
 997  
 998                      echo '</td>';
 999                      break;
1000              }
1001          }
1002      }
1003  
1004      /**
1005       * Handles the output for a single table row.
1006       *
1007       * @global string             $status The current theme status.
1008       * @global array<string, int> $totals An array of theme counts for each status.
1009       *
1010       * @param WP_Theme $theme The current WP_Theme object.
1011       */
1012  	public function single_row( $theme ) {
1013          global $status, $totals;
1014  
1015          if ( $this->is_site_themes ) {
1016              $allowed = $theme->is_allowed( 'site', $this->site_id );
1017          } else {
1018              $allowed = $theme->is_allowed( 'network' );
1019          }
1020  
1021          $stylesheet = $theme->get_stylesheet();
1022  
1023          $class = ! $allowed ? 'inactive' : 'active';
1024          if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
1025              $class .= ' update';
1026          }
1027  
1028          printf(
1029              '<tr class="%s" data-slug="%s">',
1030              esc_attr( $class ),
1031              esc_attr( $stylesheet )
1032          );
1033  
1034          $this->single_row_columns( $theme );
1035  
1036          echo '</tr>';
1037  
1038          if ( $this->is_site_themes ) {
1039              remove_action( "after_theme_row_$stylesheet", 'wp_theme_update_row' );
1040          }
1041  
1042          /**
1043           * Fires after each row in the Multisite themes list table.
1044           *
1045           * @since 3.1.0
1046           *
1047           * @param string   $stylesheet Directory name of the theme.
1048           * @param WP_Theme $theme      Current WP_Theme object.
1049           * @param string   $status     Status of the theme.
1050           */
1051          do_action( 'after_theme_row', $stylesheet, $theme, $status );
1052  
1053          /**
1054           * Fires after each specific row in the Multisite themes list table.
1055           *
1056           * The dynamic portion of the hook name, `$stylesheet`, refers to the
1057           * directory name of the theme, most often synonymous with the template
1058           * name of the theme.
1059           *
1060           * @since 3.5.0
1061           *
1062           * @param string   $stylesheet Directory name of the theme.
1063           * @param WP_Theme $theme      Current WP_Theme object.
1064           * @param string   $status     Status of the theme.
1065           */
1066          do_action( "after_theme_row_{$stylesheet}", $stylesheet, $theme, $status );
1067      }
1068  }


Generated : Fri Oct 9 08:20:33 2026 Cross-referenced by PHPXref