class-wp-theme-install-list-table.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <?php
  2. /**
  3. * List Table API: WP_Theme_Install_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 to install in a list table.
  11. *
  12. * @since 3.1.0
  13. *
  14. * @see WP_Themes_List_Table
  15. */
  16. class WP_Theme_Install_List_Table extends WP_Themes_List_Table {
  17. public $features = array();
  18. /**
  19. * @return bool
  20. */
  21. public function ajax_user_can() {
  22. return current_user_can( 'install_themes' );
  23. }
  24. /**
  25. * @global array $tabs
  26. * @global string $tab
  27. * @global int $paged
  28. * @global string $type
  29. * @global array $theme_field_defaults
  30. */
  31. public function prepare_items() {
  32. require ABSPATH . 'wp-admin/includes/theme-install.php';
  33. global $tabs, $tab, $paged, $type, $theme_field_defaults;
  34. wp_reset_vars( array( 'tab' ) );
  35. $search_terms = array();
  36. $search_string = '';
  37. if ( ! empty( $_REQUEST['s'] ) ) {
  38. $search_string = strtolower( wp_unslash( $_REQUEST['s'] ) );
  39. $search_terms = array_unique( array_filter( array_map( 'trim', explode( ',', $search_string ) ) ) );
  40. }
  41. if ( ! empty( $_REQUEST['features'] ) ) {
  42. $this->features = $_REQUEST['features'];
  43. }
  44. $paged = $this->get_pagenum();
  45. $per_page = 36;
  46. // These are the tabs which are shown on the page,
  47. $tabs = array();
  48. $tabs['dashboard'] = __( 'Search' );
  49. if ( 'search' === $tab ) {
  50. $tabs['search'] = __( 'Search Results' );
  51. }
  52. $tabs['upload'] = __( 'Upload' );
  53. $tabs['featured'] = _x( 'Featured', 'themes' );
  54. //$tabs['popular'] = _x( 'Popular', 'themes' );
  55. $tabs['new'] = _x( 'Latest', 'themes' );
  56. $tabs['updated'] = _x( 'Recently Updated', 'themes' );
  57. $nonmenu_tabs = array( 'theme-information' ); // Valid actions to perform which do not have a Menu item.
  58. /** This filter is documented in wp-admin/theme-install.php */
  59. $tabs = apply_filters( 'install_themes_tabs', $tabs );
  60. /**
  61. * Filters tabs not associated with a menu item on the Install Themes screen.
  62. *
  63. * @since 2.8.0
  64. *
  65. * @param string[] $nonmenu_tabs The tabs that don't have a menu item on
  66. * the Install Themes screen.
  67. */
  68. $nonmenu_tabs = apply_filters( 'install_themes_nonmenu_tabs', $nonmenu_tabs );
  69. // If a non-valid menu tab has been selected, And it's not a non-menu action.
  70. if ( empty( $tab ) || ( ! isset( $tabs[ $tab ] ) && ! in_array( $tab, (array) $nonmenu_tabs, true ) ) ) {
  71. $tab = key( $tabs );
  72. }
  73. $args = array(
  74. 'page' => $paged,
  75. 'per_page' => $per_page,
  76. 'fields' => $theme_field_defaults,
  77. );
  78. switch ( $tab ) {
  79. case 'search':
  80. $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term';
  81. switch ( $type ) {
  82. case 'tag':
  83. $args['tag'] = array_map( 'sanitize_key', $search_terms );
  84. break;
  85. case 'term':
  86. $args['search'] = $search_string;
  87. break;
  88. case 'author':
  89. $args['author'] = $search_string;
  90. break;
  91. }
  92. if ( ! empty( $this->features ) ) {
  93. $args['tag'] = $this->features;
  94. $_REQUEST['s'] = implode( ',', $this->features );
  95. $_REQUEST['type'] = 'tag';
  96. }
  97. add_action( 'install_themes_table_header', 'install_theme_search_form', 10, 0 );
  98. break;
  99. case 'featured':
  100. // case 'popular':
  101. case 'new':
  102. case 'updated':
  103. $args['browse'] = $tab;
  104. break;
  105. default:
  106. $args = false;
  107. break;
  108. }
  109. /**
  110. * Filters API request arguments for each Install Themes screen tab.
  111. *
  112. * The dynamic portion of the hook name, `$tab`, refers to the theme install
  113. * tab.
  114. *
  115. * Possible hook names include:
  116. *
  117. * - `install_themes_table_api_args_dashboard`
  118. * - `install_themes_table_api_args_featured`
  119. * - `install_themes_table_api_args_new`
  120. * - `install_themes_table_api_args_search`
  121. * - `install_themes_table_api_args_updated`
  122. * - `install_themes_table_api_args_upload`
  123. *
  124. * @since 3.7.0
  125. *
  126. * @param array|false $args Theme install API arguments.
  127. */
  128. $args = apply_filters( "install_themes_table_api_args_{$tab}", $args );
  129. if ( ! $args ) {
  130. return;
  131. }
  132. $api = themes_api( 'query_themes', $args );
  133. if ( is_wp_error( $api ) ) {
  134. wp_die( '<p>' . $api->get_error_message() . '</p> <p><a href="#" onclick="document.location.reload(); return false;">' . __( 'Try Again' ) . '</a></p>' );
  135. }
  136. $this->items = $api->themes;
  137. $this->set_pagination_args(
  138. array(
  139. 'total_items' => $api->info['results'],
  140. 'per_page' => $args['per_page'],
  141. 'infinite_scroll' => true,
  142. )
  143. );
  144. }
  145. /**
  146. */
  147. public function no_items() {
  148. _e( 'No themes match your request.' );
  149. }
  150. /**
  151. * @global array $tabs
  152. * @global string $tab
  153. * @return array
  154. */
  155. protected function get_views() {
  156. global $tabs, $tab;
  157. $display_tabs = array();
  158. foreach ( (array) $tabs as $action => $text ) {
  159. $display_tabs[ 'theme-install-' . $action ] = array(
  160. 'url' => self_admin_url( 'theme-install.php?tab=' . $action ),
  161. 'label' => $text,
  162. 'current' => $action === $tab,
  163. );
  164. }
  165. return $this->get_views_links( $display_tabs );
  166. }
  167. /**
  168. * Displays the theme install table.
  169. *
  170. * Overrides the parent display() method to provide a different container.
  171. *
  172. * @since 3.1.0
  173. */
  174. public function display() {
  175. wp_nonce_field( 'fetch-list-' . get_class( $this ), '_ajax_fetch_list_nonce' );
  176. ?>
  177. <div class="tablenav top themes">
  178. <div class="alignleft actions">
  179. <?php
  180. /**
  181. * Fires in the Install Themes list table header.
  182. *
  183. * @since 2.8.0
  184. */
  185. do_action( 'install_themes_table_header' );
  186. ?>
  187. </div>
  188. <?php $this->pagination( 'top' ); ?>
  189. <br class="clear" />
  190. </div>
  191. <div id="availablethemes">
  192. <?php $this->display_rows_or_placeholder(); ?>
  193. </div>
  194. <?php
  195. $this->tablenav( 'bottom' );
  196. }
  197. /**
  198. */
  199. public function display_rows() {
  200. $themes = $this->items;
  201. foreach ( $themes as $theme ) {
  202. ?>
  203. <div class="available-theme installable-theme">
  204. <?php
  205. $this->single_row( $theme );
  206. ?>
  207. </div>
  208. <?php
  209. } // End foreach $theme_names.
  210. $this->theme_installer();
  211. }
  212. /**
  213. * Prints a theme from the WordPress.org API.
  214. *
  215. * @since 3.1.0
  216. *
  217. * @global array $themes_allowedtags
  218. *
  219. * @param stdClass $theme {
  220. * An object that contains theme data returned by the WordPress.org API.
  221. *
  222. * @type string $name Theme name, e.g. 'Twenty Twenty-One'.
  223. * @type string $slug Theme slug, e.g. 'twentytwentyone'.
  224. * @type string $version Theme version, e.g. '1.1'.
  225. * @type string $author Theme author username, e.g. 'melchoyce'.
  226. * @type string $preview_url Preview URL, e.g. 'https://2021.wordpress.net/'.
  227. * @type string $screenshot_url Screenshot URL, e.g. 'https://wordpress.org/themes/twentytwentyone/'.
  228. * @type float $rating Rating score.
  229. * @type int $num_ratings The number of ratings.
  230. * @type string $homepage Theme homepage, e.g. 'https://wordpress.org/themes/twentytwentyone/'.
  231. * @type string $description Theme description.
  232. * @type string $download_link Theme ZIP download URL.
  233. * }
  234. */
  235. public function single_row( $theme ) {
  236. global $themes_allowedtags;
  237. if ( empty( $theme ) ) {
  238. return;
  239. }
  240. $name = wp_kses( $theme->name, $themes_allowedtags );
  241. $author = wp_kses( $theme->author, $themes_allowedtags );
  242. /* translators: %s: Theme name. */
  243. $preview_title = sprintf( __( 'Preview &#8220;%s&#8221;' ), $name );
  244. $preview_url = add_query_arg(
  245. array(
  246. 'tab' => 'theme-information',
  247. 'theme' => $theme->slug,
  248. ),
  249. self_admin_url( 'theme-install.php' )
  250. );
  251. $actions = array();
  252. $install_url = add_query_arg(
  253. array(
  254. 'action' => 'install-theme',
  255. 'theme' => $theme->slug,
  256. ),
  257. self_admin_url( 'update.php' )
  258. );
  259. $update_url = add_query_arg(
  260. array(
  261. 'action' => 'upgrade-theme',
  262. 'theme' => $theme->slug,
  263. ),
  264. self_admin_url( 'update.php' )
  265. );
  266. $status = $this->_get_theme_status( $theme );
  267. switch ( $status ) {
  268. case 'update_available':
  269. $actions[] = sprintf(
  270. '<a class="install-now" href="%s" title="%s">%s</a>',
  271. esc_url( wp_nonce_url( $update_url, 'upgrade-theme_' . $theme->slug ) ),
  272. /* translators: %s: Theme version. */
  273. esc_attr( sprintf( __( 'Update to version %s' ), $theme->version ) ),
  274. __( 'Update' )
  275. );
  276. break;
  277. case 'newer_installed':
  278. case 'latest_installed':
  279. $actions[] = sprintf(
  280. '<span class="install-now" title="%s">%s</span>',
  281. esc_attr__( 'This theme is already installed and is up to date' ),
  282. _x( 'Installed', 'theme' )
  283. );
  284. break;
  285. case 'install':
  286. default:
  287. $actions[] = sprintf(
  288. '<a class="install-now" href="%s" title="%s">%s</a>',
  289. esc_url( wp_nonce_url( $install_url, 'install-theme_' . $theme->slug ) ),
  290. /* translators: %s: Theme name. */
  291. esc_attr( sprintf( _x( 'Install %s', 'theme' ), $name ) ),
  292. __( 'Install Now' )
  293. );
  294. break;
  295. }
  296. $actions[] = sprintf(
  297. '<a class="install-theme-preview" href="%s" title="%s">%s</a>',
  298. esc_url( $preview_url ),
  299. /* translators: %s: Theme name. */
  300. esc_attr( sprintf( __( 'Preview %s' ), $name ) ),
  301. __( 'Preview' )
  302. );
  303. /**
  304. * Filters the install action links for a theme in the Install Themes list table.
  305. *
  306. * @since 3.4.0
  307. *
  308. * @param string[] $actions An array of theme action links. Defaults are
  309. * links to Install Now, Preview, and Details.
  310. * @param stdClass $theme An object that contains theme data returned by the
  311. * WordPress.org API.
  312. */
  313. $actions = apply_filters( 'theme_install_actions', $actions, $theme );
  314. ?>
  315. <a class="screenshot install-theme-preview" href="<?php echo esc_url( $preview_url ); ?>" title="<?php echo esc_attr( $preview_title ); ?>">
  316. <img src="<?php echo esc_url( $theme->screenshot_url . '?ver=' . $theme->version ); ?>" width="150" alt="" />
  317. </a>
  318. <h3><?php echo $name; ?></h3>
  319. <div class="theme-author">
  320. <?php
  321. /* translators: %s: Theme author. */
  322. printf( __( 'By %s' ), $author );
  323. ?>
  324. </div>
  325. <div class="action-links">
  326. <ul>
  327. <?php foreach ( $actions as $action ) : ?>
  328. <li><?php echo $action; ?></li>
  329. <?php endforeach; ?>
  330. <li class="hide-if-no-js"><a href="#" class="theme-detail"><?php _e( 'Details' ); ?></a></li>
  331. </ul>
  332. </div>
  333. <?php
  334. $this->install_theme_info( $theme );
  335. }
  336. /**
  337. * Prints the wrapper for the theme installer.
  338. */
  339. public function theme_installer() {
  340. ?>
  341. <div id="theme-installer" class="wp-full-overlay expanded">
  342. <div class="wp-full-overlay-sidebar">
  343. <div class="wp-full-overlay-header">
  344. <a href="#" class="close-full-overlay button"><?php _e( 'Close' ); ?></a>
  345. <span class="theme-install"></span>
  346. </div>
  347. <div class="wp-full-overlay-sidebar-content">
  348. <div class="install-theme-info"></div>
  349. </div>
  350. <div class="wp-full-overlay-footer">
  351. <button type="button" class="collapse-sidebar button" aria-expanded="true" aria-label="<?php esc_attr_e( 'Collapse Sidebar' ); ?>">
  352. <span class="collapse-sidebar-arrow"></span>
  353. <span class="collapse-sidebar-label"><?php _e( 'Collapse' ); ?></span>
  354. </button>
  355. </div>
  356. </div>
  357. <div class="wp-full-overlay-main"></div>
  358. </div>
  359. <?php
  360. }
  361. /**
  362. * Prints the wrapper for the theme installer with a provided theme's data.
  363. * Used to make the theme installer work for no-js.
  364. *
  365. * @param stdClass $theme A WordPress.org Theme API object.
  366. */
  367. public function theme_installer_single( $theme ) {
  368. ?>
  369. <div id="theme-installer" class="wp-full-overlay single-theme">
  370. <div class="wp-full-overlay-sidebar">
  371. <?php $this->install_theme_info( $theme ); ?>
  372. </div>
  373. <div class="wp-full-overlay-main">
  374. <iframe src="<?php echo esc_url( $theme->preview_url ); ?>"></iframe>
  375. </div>
  376. </div>
  377. <?php
  378. }
  379. /**
  380. * Prints the info for a theme (to be used in the theme installer modal).
  381. *
  382. * @global array $themes_allowedtags
  383. *
  384. * @param stdClass $theme A WordPress.org Theme API object.
  385. */
  386. public function install_theme_info( $theme ) {
  387. global $themes_allowedtags;
  388. if ( empty( $theme ) ) {
  389. return;
  390. }
  391. $name = wp_kses( $theme->name, $themes_allowedtags );
  392. $author = wp_kses( $theme->author, $themes_allowedtags );
  393. $install_url = add_query_arg(
  394. array(
  395. 'action' => 'install-theme',
  396. 'theme' => $theme->slug,
  397. ),
  398. self_admin_url( 'update.php' )
  399. );
  400. $update_url = add_query_arg(
  401. array(
  402. 'action' => 'upgrade-theme',
  403. 'theme' => $theme->slug,
  404. ),
  405. self_admin_url( 'update.php' )
  406. );
  407. $status = $this->_get_theme_status( $theme );
  408. ?>
  409. <div class="install-theme-info">
  410. <?php
  411. switch ( $status ) {
  412. case 'update_available':
  413. printf(
  414. '<a class="theme-install button button-primary" href="%s" title="%s">%s</a>',
  415. esc_url( wp_nonce_url( $update_url, 'upgrade-theme_' . $theme->slug ) ),
  416. /* translators: %s: Theme version. */
  417. esc_attr( sprintf( __( 'Update to version %s' ), $theme->version ) ),
  418. __( 'Update' )
  419. );
  420. break;
  421. case 'newer_installed':
  422. case 'latest_installed':
  423. printf(
  424. '<span class="theme-install" title="%s">%s</span>',
  425. esc_attr__( 'This theme is already installed and is up to date' ),
  426. _x( 'Installed', 'theme' )
  427. );
  428. break;
  429. case 'install':
  430. default:
  431. printf(
  432. '<a class="theme-install button button-primary" href="%s">%s</a>',
  433. esc_url( wp_nonce_url( $install_url, 'install-theme_' . $theme->slug ) ),
  434. __( 'Install' )
  435. );
  436. break;
  437. }
  438. ?>
  439. <h3 class="theme-name"><?php echo $name; ?></h3>
  440. <span class="theme-by">
  441. <?php
  442. /* translators: %s: Theme author. */
  443. printf( __( 'By %s' ), $author );
  444. ?>
  445. </span>
  446. <?php if ( isset( $theme->screenshot_url ) ) : ?>
  447. <img class="theme-screenshot" src="<?php echo esc_url( $theme->screenshot_url . '?ver=' . $theme->version ); ?>" alt="" />
  448. <?php endif; ?>
  449. <div class="theme-details">
  450. <?php
  451. wp_star_rating(
  452. array(
  453. 'rating' => $theme->rating,
  454. 'type' => 'percent',
  455. 'number' => $theme->num_ratings,
  456. )
  457. );
  458. ?>
  459. <div class="theme-version">
  460. <strong><?php _e( 'Version:' ); ?> </strong>
  461. <?php echo wp_kses( $theme->version, $themes_allowedtags ); ?>
  462. </div>
  463. <div class="theme-description">
  464. <?php echo wp_kses( $theme->description, $themes_allowedtags ); ?>
  465. </div>
  466. </div>
  467. <input class="theme-preview-url" type="hidden" value="<?php echo esc_url( $theme->preview_url ); ?>" />
  468. </div>
  469. <?php
  470. }
  471. /**
  472. * Send required variables to JavaScript land
  473. *
  474. * @since 3.4.0
  475. *
  476. * @global string $tab Current tab within Themes->Install screen
  477. * @global string $type Type of search.
  478. *
  479. * @param array $extra_args Unused.
  480. */
  481. public function _js_vars( $extra_args = array() ) {
  482. global $tab, $type;
  483. parent::_js_vars( compact( 'tab', 'type' ) );
  484. }
  485. /**
  486. * Check to see if the theme is already installed.
  487. *
  488. * @since 3.4.0
  489. *
  490. * @param stdClass $theme A WordPress.org Theme API object.
  491. * @return string Theme status.
  492. */
  493. private function _get_theme_status( $theme ) {
  494. $status = 'install';
  495. $installed_theme = wp_get_theme( $theme->slug );
  496. if ( $installed_theme->exists() ) {
  497. if ( version_compare( $installed_theme->get( 'Version' ), $theme->version, '=' ) ) {
  498. $status = 'latest_installed';
  499. } elseif ( version_compare( $installed_theme->get( 'Version' ), $theme->version, '>' ) ) {
  500. $status = 'newer_installed';
  501. } else {
  502. $status = 'update_available';
  503. }
  504. }
  505. return $status;
  506. }
  507. }