PageRenderTime 39ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-admin/includes/class-wp-ms-themes-list-table.php

http://github.com/wordpress/wordpress
PHP | 797 lines | 452 code | 108 blank | 237 comment | 75 complexity | 0ede4e40fffed95b16c494ed0601684d MD5 | raw file
Possible License(s): 0BSD
  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. * Core class used to implement displaying themes in a list table for the network admin.
  11. *
  12. * @since 3.1.0
  13. * @access private
  14. *
  15. * @see WP_List_Table
  16. */
  17. class WP_MS_Themes_List_Table extends WP_List_Table {
  18. public $site_id;
  19. public $is_site_themes;
  20. private $has_items;
  21. /**
  22. * Constructor.
  23. *
  24. * @since 3.1.0
  25. *
  26. * @see WP_List_Table::__construct() for more information on default arguments.
  27. *
  28. * @global string $status
  29. * @global int $page
  30. *
  31. * @param array $args An associative array of arguments.
  32. */
  33. public function __construct( $args = array() ) {
  34. global $status, $page;
  35. parent::__construct(
  36. array(
  37. 'plural' => 'themes',
  38. 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
  39. )
  40. );
  41. $status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all';
  42. if ( ! in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken' ), true ) ) {
  43. $status = 'all';
  44. }
  45. $page = $this->get_pagenum();
  46. $this->is_site_themes = ( 'site-themes-network' === $this->screen->id ) ? true : false;
  47. if ( $this->is_site_themes ) {
  48. $this->site_id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0;
  49. }
  50. }
  51. /**
  52. * @return array
  53. */
  54. protected function get_table_classes() {
  55. // @todo Remove and add CSS for .themes.
  56. return array( 'widefat', 'plugins' );
  57. }
  58. /**
  59. * @return bool
  60. */
  61. public function ajax_user_can() {
  62. if ( $this->is_site_themes ) {
  63. return current_user_can( 'manage_sites' );
  64. } else {
  65. return current_user_can( 'manage_network_themes' );
  66. }
  67. }
  68. /**
  69. * @global string $status
  70. * @global array $totals
  71. * @global int $page
  72. * @global string $orderby
  73. * @global string $order
  74. * @global string $s
  75. */
  76. public function prepare_items() {
  77. global $status, $totals, $page, $orderby, $order, $s;
  78. wp_reset_vars( array( 'orderby', 'order', 's' ) );
  79. $themes = array(
  80. /**
  81. * Filters the full array of WP_Theme objects to list in the Multisite
  82. * themes list table.
  83. *
  84. * @since 3.1.0
  85. *
  86. * @param WP_Theme[] $all Array of WP_Theme objects to display in the list table.
  87. */
  88. 'all' => apply_filters( 'all_themes', wp_get_themes() ),
  89. 'search' => array(),
  90. 'enabled' => array(),
  91. 'disabled' => array(),
  92. 'upgrade' => array(),
  93. 'broken' => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ),
  94. );
  95. if ( $this->is_site_themes ) {
  96. $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' );
  97. $allowed_where = 'site';
  98. } else {
  99. $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' );
  100. $allowed_where = 'network';
  101. }
  102. $current = get_site_transient( 'update_themes' );
  103. $maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current;
  104. foreach ( (array) $themes['all'] as $key => $theme ) {
  105. if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) {
  106. unset( $themes['all'][ $key ] );
  107. continue;
  108. }
  109. if ( $maybe_update && isset( $current->response[ $key ] ) ) {
  110. $themes['all'][ $key ]->update = true;
  111. $themes['upgrade'][ $key ] = $themes['all'][ $key ];
  112. }
  113. $filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled';
  114. $themes[ $filter ][ $key ] = $themes['all'][ $key ];
  115. }
  116. if ( $s ) {
  117. $status = 'search';
  118. $themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) );
  119. }
  120. $totals = array();
  121. foreach ( $themes as $type => $list ) {
  122. $totals[ $type ] = count( $list );
  123. }
  124. if ( empty( $themes[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) {
  125. $status = 'all';
  126. }
  127. $this->items = $themes[ $status ];
  128. WP_Theme::sort_by_name( $this->items );
  129. $this->has_items = ! empty( $themes['all'] );
  130. $total_this_page = $totals[ $status ];
  131. wp_localize_script(
  132. 'updates',
  133. '_wpUpdatesItemCounts',
  134. array(
  135. 'themes' => $totals,
  136. 'totals' => wp_get_update_data(),
  137. )
  138. );
  139. if ( $orderby ) {
  140. $orderby = ucfirst( $orderby );
  141. $order = strtoupper( $order );
  142. if ( 'Name' === $orderby ) {
  143. if ( 'ASC' === $order ) {
  144. $this->items = array_reverse( $this->items );
  145. }
  146. } else {
  147. uasort( $this->items, array( $this, '_order_callback' ) );
  148. }
  149. }
  150. $start = ( $page - 1 ) * $themes_per_page;
  151. if ( $total_this_page > $themes_per_page ) {
  152. $this->items = array_slice( $this->items, $start, $themes_per_page, true );
  153. }
  154. $this->set_pagination_args(
  155. array(
  156. 'total_items' => $total_this_page,
  157. 'per_page' => $themes_per_page,
  158. )
  159. );
  160. }
  161. /**
  162. * @staticvar string $term
  163. * @param WP_Theme $theme
  164. * @return bool
  165. */
  166. public function _search_callback( $theme ) {
  167. static $term = null;
  168. if ( is_null( $term ) ) {
  169. $term = wp_unslash( $_REQUEST['s'] );
  170. }
  171. foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) {
  172. // Don't mark up; Do translate.
  173. if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) {
  174. return true;
  175. }
  176. }
  177. if ( false !== stripos( $theme->get_stylesheet(), $term ) ) {
  178. return true;
  179. }
  180. if ( false !== stripos( $theme->get_template(), $term ) ) {
  181. return true;
  182. }
  183. return false;
  184. }
  185. // Not used by any core columns.
  186. /**
  187. * @global string $orderby
  188. * @global string $order
  189. * @param array $theme_a
  190. * @param array $theme_b
  191. * @return int
  192. */
  193. public function _order_callback( $theme_a, $theme_b ) {
  194. global $orderby, $order;
  195. $a = $theme_a[ $orderby ];
  196. $b = $theme_b[ $orderby ];
  197. if ( $a == $b ) {
  198. return 0;
  199. }
  200. if ( 'DESC' === $order ) {
  201. return ( $a < $b ) ? 1 : -1;
  202. } else {
  203. return ( $a < $b ) ? -1 : 1;
  204. }
  205. }
  206. /**
  207. */
  208. public function no_items() {
  209. if ( $this->has_items ) {
  210. _e( 'No themes found.' );
  211. } else {
  212. _e( 'You do not appear to have any themes available at this time.' );
  213. }
  214. }
  215. /**
  216. * @return array
  217. */
  218. public function get_columns() {
  219. return array(
  220. 'cb' => '<input type="checkbox" />',
  221. 'name' => __( 'Theme' ),
  222. 'description' => __( 'Description' ),
  223. );
  224. }
  225. /**
  226. * @return array
  227. */
  228. protected function get_sortable_columns() {
  229. return array(
  230. 'name' => 'name',
  231. );
  232. }
  233. /**
  234. * Gets the name of the primary column.
  235. *
  236. * @since 4.3.0
  237. *
  238. * @return string Unalterable name of the primary column name, in this case, 'name'.
  239. */
  240. protected function get_primary_column_name() {
  241. return 'name';
  242. }
  243. /**
  244. * @global array $totals
  245. * @global string $status
  246. * @return array
  247. */
  248. protected function get_views() {
  249. global $totals, $status;
  250. $status_links = array();
  251. foreach ( $totals as $type => $count ) {
  252. if ( ! $count ) {
  253. continue;
  254. }
  255. switch ( $type ) {
  256. case 'all':
  257. /* translators: %s: Number of themes. */
  258. $text = _nx(
  259. 'All <span class="count">(%s)</span>',
  260. 'All <span class="count">(%s)</span>',
  261. $count,
  262. 'themes'
  263. );
  264. break;
  265. case 'enabled':
  266. /* translators: %s: Number of themes. */
  267. $text = _nx(
  268. 'Enabled <span class="count">(%s)</span>',
  269. 'Enabled <span class="count">(%s)</span>',
  270. $count,
  271. 'themes'
  272. );
  273. break;
  274. case 'disabled':
  275. /* translators: %s: Number of themes. */
  276. $text = _nx(
  277. 'Disabled <span class="count">(%s)</span>',
  278. 'Disabled <span class="count">(%s)</span>',
  279. $count,
  280. 'themes'
  281. );
  282. break;
  283. case 'upgrade':
  284. /* translators: %s: Number of themes. */
  285. $text = _nx(
  286. 'Update Available <span class="count">(%s)</span>',
  287. 'Update Available <span class="count">(%s)</span>',
  288. $count,
  289. 'themes'
  290. );
  291. break;
  292. case 'broken':
  293. /* translators: %s: Number of themes. */
  294. $text = _nx(
  295. 'Broken <span class="count">(%s)</span>',
  296. 'Broken <span class="count">(%s)</span>',
  297. $count,
  298. 'themes'
  299. );
  300. break;
  301. }
  302. if ( $this->is_site_themes ) {
  303. $url = 'site-themes.php?id=' . $this->site_id;
  304. } else {
  305. $url = 'themes.php';
  306. }
  307. if ( 'search' != $type ) {
  308. $status_links[ $type ] = sprintf(
  309. "<a href='%s'%s>%s</a>",
  310. esc_url( add_query_arg( 'theme_status', $type, $url ) ),
  311. ( $type === $status ) ? ' class="current" aria-current="page"' : '',
  312. sprintf( $text, number_format_i18n( $count ) )
  313. );
  314. }
  315. }
  316. return $status_links;
  317. }
  318. /**
  319. * @global string $status
  320. *
  321. * @return array
  322. */
  323. protected function get_bulk_actions() {
  324. global $status;
  325. $actions = array();
  326. if ( 'enabled' != $status ) {
  327. $actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' );
  328. }
  329. if ( 'disabled' != $status ) {
  330. $actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' );
  331. }
  332. if ( ! $this->is_site_themes ) {
  333. if ( current_user_can( 'update_themes' ) ) {
  334. $actions['update-selected'] = __( 'Update' );
  335. }
  336. if ( current_user_can( 'delete_themes' ) ) {
  337. $actions['delete-selected'] = __( 'Delete' );
  338. }
  339. }
  340. return $actions;
  341. }
  342. /**
  343. */
  344. public function display_rows() {
  345. foreach ( $this->items as $theme ) {
  346. $this->single_row( $theme );
  347. }
  348. }
  349. /**
  350. * Handles the checkbox column output.
  351. *
  352. * @since 4.3.0
  353. *
  354. * @param WP_Theme $theme The current WP_Theme object.
  355. */
  356. public function column_cb( $theme ) {
  357. $checkbox_id = 'checkbox_' . md5( $theme->get( 'Name' ) );
  358. ?>
  359. <input type="checkbox" name="checked[]" value="<?php echo esc_attr( $theme->get_stylesheet() ); ?>" id="<?php echo $checkbox_id; ?>" />
  360. <label class="screen-reader-text" for="<?php echo $checkbox_id; ?>" ><?php _e( 'Select' ); ?> <?php echo $theme->display( 'Name' ); ?></label>
  361. <?php
  362. }
  363. /**
  364. * Handles the name column output.
  365. *
  366. * @since 4.3.0
  367. *
  368. * @global string $status
  369. * @global int $page
  370. * @global string $s
  371. *
  372. * @param WP_Theme $theme The current WP_Theme object.
  373. */
  374. public function column_name( $theme ) {
  375. global $status, $page, $s;
  376. $context = $status;
  377. if ( $this->is_site_themes ) {
  378. $url = "site-themes.php?id={$this->site_id}&amp;";
  379. $allowed = $theme->is_allowed( 'site', $this->site_id );
  380. } else {
  381. $url = 'themes.php?';
  382. $allowed = $theme->is_allowed( 'network' );
  383. }
  384. // Pre-order.
  385. $actions = array(
  386. 'enable' => '',
  387. 'disable' => '',
  388. 'delete' => '',
  389. );
  390. $stylesheet = $theme->get_stylesheet();
  391. $theme_key = urlencode( $stylesheet );
  392. if ( ! $allowed ) {
  393. if ( ! $theme->errors() ) {
  394. $url = add_query_arg(
  395. array(
  396. 'action' => 'enable',
  397. 'theme' => $theme_key,
  398. 'paged' => $page,
  399. 's' => $s,
  400. ),
  401. $url
  402. );
  403. if ( $this->is_site_themes ) {
  404. /* translators: %s: Theme name. */
  405. $aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) );
  406. } else {
  407. /* translators: %s: Theme name. */
  408. $aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) );
  409. }
  410. $actions['enable'] = sprintf(
  411. '<a href="%s" class="edit" aria-label="%s">%s</a>',
  412. esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ),
  413. esc_attr( $aria_label ),
  414. ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) )
  415. );
  416. }
  417. } else {
  418. $url = add_query_arg(
  419. array(
  420. 'action' => 'disable',
  421. 'theme' => $theme_key,
  422. 'paged' => $page,
  423. 's' => $s,
  424. ),
  425. $url
  426. );
  427. if ( $this->is_site_themes ) {
  428. /* translators: %s: Theme name. */
  429. $aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) );
  430. } else {
  431. /* translators: %s: Theme name. */
  432. $aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) );
  433. }
  434. $actions['disable'] = sprintf(
  435. '<a href="%s" aria-label="%s">%s</a>',
  436. esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ),
  437. esc_attr( $aria_label ),
  438. ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) )
  439. );
  440. }
  441. if ( ! $allowed && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && get_option( 'stylesheet' ) !== $stylesheet && get_option( 'template' ) !== $stylesheet ) {
  442. $url = add_query_arg(
  443. array(
  444. 'action' => 'delete-selected',
  445. 'checked[]' => $theme_key,
  446. 'theme_status' => $context,
  447. 'paged' => $page,
  448. 's' => $s,
  449. ),
  450. 'themes.php'
  451. );
  452. /* translators: %s: Theme name. */
  453. $aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) );
  454. $actions['delete'] = sprintf(
  455. '<a href="%s" class="delete" aria-label="%s">%s</a>',
  456. esc_url( wp_nonce_url( $url, 'bulk-themes' ) ),
  457. esc_attr( $aria_label ),
  458. __( 'Delete' )
  459. );
  460. }
  461. /**
  462. * Filters the action links displayed for each theme in the Multisite
  463. * themes list table.
  464. *
  465. * The action links displayed are determined by the theme's status, and
  466. * which Multisite themes list table is being displayed - the Network
  467. * themes list table (themes.php), which displays all installed themes,
  468. * or the Site themes list table (site-themes.php), which displays the
  469. * non-network enabled themes when editing a site in the Network admin.
  470. *
  471. * The default action links for the Network themes list table include
  472. * 'Network Enable', 'Network Disable', and 'Delete'.
  473. *
  474. * The default action links for the Site themes list table include
  475. * 'Enable', and 'Disable'.
  476. *
  477. * @since 2.8.0
  478. *
  479. * @param string[] $actions An array of action links.
  480. * @param WP_Theme $theme The current WP_Theme object.
  481. * @param string $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
  482. */
  483. $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context );
  484. /**
  485. * Filters the action links of a specific theme in the Multisite themes
  486. * list table.
  487. *
  488. * The dynamic portion of the hook name, `$stylesheet`, refers to the
  489. * directory name of the theme, which in most cases is synonymous
  490. * with the template name.
  491. *
  492. * @since 3.1.0
  493. *
  494. * @param string[] $actions An array of action links.
  495. * @param WP_Theme $theme The current WP_Theme object.
  496. * @param string $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
  497. */
  498. $actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, $context );
  499. echo $this->row_actions( $actions, true );
  500. }
  501. /**
  502. * Handles the description column output.
  503. *
  504. * @since 4.3.0
  505. *
  506. * @global string $status
  507. * @global array $totals
  508. *
  509. * @param WP_Theme $theme The current WP_Theme object.
  510. */
  511. public function column_description( $theme ) {
  512. global $status, $totals;
  513. if ( $theme->errors() ) {
  514. $pre = 'broken' === $status ? __( 'Broken Theme:' ) . ' ' : '';
  515. echo '<p><strong class="error-message">' . $pre . $theme->errors()->get_error_message() . '</strong></p>';
  516. }
  517. if ( $this->is_site_themes ) {
  518. $allowed = $theme->is_allowed( 'site', $this->site_id );
  519. } else {
  520. $allowed = $theme->is_allowed( 'network' );
  521. }
  522. $class = ! $allowed ? 'inactive' : 'active';
  523. if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
  524. $class .= ' update';
  525. }
  526. echo "<div class='theme-description'><p>" . $theme->display( 'Description' ) . "</p></div>
  527. <div class='$class second theme-version-author-uri'>";
  528. $stylesheet = $theme->get_stylesheet();
  529. $theme_meta = array();
  530. if ( $theme->get( 'Version' ) ) {
  531. /* translators: %s: Theme version. */
  532. $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display( 'Version' ) );
  533. }
  534. /* translators: %s: Theme author. */
  535. $theme_meta[] = sprintf( __( 'By %s' ), $theme->display( 'Author' ) );
  536. if ( $theme->get( 'ThemeURI' ) ) {
  537. /* translators: %s: Theme name. */
  538. $aria_label = sprintf( __( 'Visit %s homepage' ), $theme->display( 'Name' ) );
  539. $theme_meta[] = sprintf(
  540. '<a href="%s" aria-label="%s">%s</a>',
  541. $theme->display( 'ThemeURI' ),
  542. esc_attr( $aria_label ),
  543. __( 'Visit Theme Site' )
  544. );
  545. }
  546. /**
  547. * Filters the array of row meta for each theme in the Multisite themes
  548. * list table.
  549. *
  550. * @since 3.1.0
  551. *
  552. * @param string[] $theme_meta An array of the theme's metadata,
  553. * including the version, author, and
  554. * theme URI.
  555. * @param string $stylesheet Directory name of the theme.
  556. * @param WP_Theme $theme WP_Theme object.
  557. * @param string $status Status of the theme.
  558. */
  559. $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $stylesheet, $theme, $status );
  560. echo implode( ' | ', $theme_meta );
  561. echo '</div>';
  562. }
  563. /**
  564. * Handles default column output.
  565. *
  566. * @since 4.3.0
  567. *
  568. * @param WP_Theme $theme The current WP_Theme object.
  569. * @param string $column_name The current column name.
  570. */
  571. public function column_default( $theme, $column_name ) {
  572. $stylesheet = $theme->get_stylesheet();
  573. /**
  574. * Fires inside each custom column of the Multisite themes list table.
  575. *
  576. * @since 3.1.0
  577. *
  578. * @param string $column_name Name of the column.
  579. * @param string $stylesheet Directory name of the theme.
  580. * @param WP_Theme $theme Current WP_Theme object.
  581. */
  582. do_action( 'manage_themes_custom_column', $column_name, $stylesheet, $theme );
  583. }
  584. /**
  585. * Handles the output for a single table row.
  586. *
  587. * @since 4.3.0
  588. *
  589. * @param WP_Theme $item The current WP_Theme object.
  590. */
  591. public function single_row_columns( $item ) {
  592. list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
  593. foreach ( $columns as $column_name => $column_display_name ) {
  594. $extra_classes = '';
  595. if ( in_array( $column_name, $hidden, true ) ) {
  596. $extra_classes .= ' hidden';
  597. }
  598. switch ( $column_name ) {
  599. case 'cb':
  600. echo '<th scope="row" class="check-column">';
  601. $this->column_cb( $item );
  602. echo '</th>';
  603. break;
  604. case 'name':
  605. $active_theme_label = '';
  606. /* 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 */
  607. if ( ! empty( $this->site_id ) ) {
  608. $stylesheet = get_blog_option( $this->site_id, 'stylesheet' );
  609. $template = get_blog_option( $this->site_id, 'template' );
  610. /* Add a label for the active template */
  611. if ( $item->get_template() === $template ) {
  612. $active_theme_label = ' &mdash; ' . __( 'Active Theme' );
  613. }
  614. /* In case this is a child theme, label it properly */
  615. if ( $stylesheet !== $template && $item->get_stylesheet() === $stylesheet ) {
  616. $active_theme_label = ' &mdash; ' . __( 'Active Child Theme' );
  617. }
  618. }
  619. echo "<td class='theme-title column-primary{$extra_classes}'><strong>" . $item->display( 'Name' ) . $active_theme_label . '</strong>';
  620. $this->column_name( $item );
  621. echo '</td>';
  622. break;
  623. case 'description':
  624. echo "<td class='column-description desc{$extra_classes}'>";
  625. $this->column_description( $item );
  626. echo '</td>';
  627. break;
  628. default:
  629. echo "<td class='$column_name column-$column_name{$extra_classes}'>";
  630. $this->column_default( $item, $column_name );
  631. echo '</td>';
  632. break;
  633. }
  634. }
  635. }
  636. /**
  637. * @global string $status
  638. * @global array $totals
  639. *
  640. * @param WP_Theme $theme
  641. */
  642. public function single_row( $theme ) {
  643. global $status, $totals;
  644. if ( $this->is_site_themes ) {
  645. $allowed = $theme->is_allowed( 'site', $this->site_id );
  646. } else {
  647. $allowed = $theme->is_allowed( 'network' );
  648. }
  649. $stylesheet = $theme->get_stylesheet();
  650. $class = ! $allowed ? 'inactive' : 'active';
  651. if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
  652. $class .= ' update';
  653. }
  654. printf(
  655. '<tr class="%s" data-slug="%s">',
  656. esc_attr( $class ),
  657. esc_attr( $stylesheet )
  658. );
  659. $this->single_row_columns( $theme );
  660. echo '</tr>';
  661. if ( $this->is_site_themes ) {
  662. remove_action( "after_theme_row_$stylesheet", 'wp_theme_update_row' );
  663. }
  664. /**
  665. * Fires after each row in the Multisite themes list table.
  666. *
  667. * @since 3.1.0
  668. *
  669. * @param string $stylesheet Directory name of the theme.
  670. * @param WP_Theme $theme Current WP_Theme object.
  671. * @param string $status Status of the theme.
  672. */
  673. do_action( 'after_theme_row', $stylesheet, $theme, $status );
  674. /**
  675. * Fires after each specific row in the Multisite themes list table.
  676. *
  677. * The dynamic portion of the hook name, `$stylesheet`, refers to the
  678. * directory name of the theme, most often synonymous with the template
  679. * name of the theme.
  680. *
  681. * @since 3.5.0
  682. *
  683. * @param string $stylesheet Directory name of the theme.
  684. * @param WP_Theme $theme Current WP_Theme object.
  685. * @param string $status Status of the theme.
  686. */
  687. do_action( "after_theme_row_{$stylesheet}", $stylesheet, $theme, $status );
  688. }
  689. }