class-wp-themes-list-table.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. /**
  3. * List Table API: WP_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 installed themes in a list table.
  11. *
  12. * @since 3.1.0
  13. *
  14. * @see WP_List_Table
  15. */
  16. class WP_Themes_List_Table extends WP_List_Table {
  17. protected $search_terms = array();
  18. public $features = array();
  19. /**
  20. * Constructor.
  21. *
  22. * @since 3.1.0
  23. *
  24. * @see WP_List_Table::__construct() for more information on default arguments.
  25. *
  26. * @param array $args An associative array of arguments.
  27. */
  28. public function __construct( $args = array() ) {
  29. parent::__construct(
  30. array(
  31. 'ajax' => true,
  32. 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
  33. )
  34. );
  35. }
  36. /**
  37. * @return bool
  38. */
  39. public function ajax_user_can() {
  40. // Do not check edit_theme_options here. Ajax calls for available themes require switch_themes.
  41. return current_user_can( 'switch_themes' );
  42. }
  43. /**
  44. */
  45. public function prepare_items() {
  46. $themes = wp_get_themes( array( 'allowed' => true ) );
  47. if ( ! empty( $_REQUEST['s'] ) ) {
  48. $this->search_terms = array_unique( array_filter( array_map( 'trim', explode( ',', strtolower( wp_unslash( $_REQUEST['s'] ) ) ) ) ) );
  49. }
  50. if ( ! empty( $_REQUEST['features'] ) ) {
  51. $this->features = $_REQUEST['features'];
  52. }
  53. if ( $this->search_terms || $this->features ) {
  54. foreach ( $themes as $key => $theme ) {
  55. if ( ! $this->search_theme( $theme ) ) {
  56. unset( $themes[ $key ] );
  57. }
  58. }
  59. }
  60. unset( $themes[ get_option( 'stylesheet' ) ] );
  61. WP_Theme::sort_by_name( $themes );
  62. $per_page = 36;
  63. $page = $this->get_pagenum();
  64. $start = ( $page - 1 ) * $per_page;
  65. $this->items = array_slice( $themes, $start, $per_page, true );
  66. $this->set_pagination_args(
  67. array(
  68. 'total_items' => count( $themes ),
  69. 'per_page' => $per_page,
  70. 'infinite_scroll' => true,
  71. )
  72. );
  73. }
  74. /**
  75. */
  76. public function no_items() {
  77. if ( $this->search_terms || $this->features ) {
  78. _e( 'No items found.' );
  79. return;
  80. }
  81. $blog_id = get_current_blog_id();
  82. if ( is_multisite() ) {
  83. if ( current_user_can( 'install_themes' ) && current_user_can( 'manage_network_themes' ) ) {
  84. printf(
  85. /* translators: 1: URL to Themes tab on Edit Site screen, 2: URL to Add Themes screen. */
  86. __( 'You only have one theme enabled for this site right now. Visit the Network Admin to <a href="%1$s">enable</a> or <a href="%2$s">install</a> more themes.' ),
  87. network_admin_url( 'site-themes.php?id=' . $blog_id ),
  88. network_admin_url( 'theme-install.php' )
  89. );
  90. return;
  91. } elseif ( current_user_can( 'manage_network_themes' ) ) {
  92. printf(
  93. /* translators: %s: URL to Themes tab on Edit Site screen. */
  94. __( 'You only have one theme enabled for this site right now. Visit the Network Admin to <a href="%s">enable</a> more themes.' ),
  95. network_admin_url( 'site-themes.php?id=' . $blog_id )
  96. );
  97. return;
  98. }
  99. // Else, fallthrough. install_themes doesn't help if you can't enable it.
  100. } else {
  101. if ( current_user_can( 'install_themes' ) ) {
  102. printf(
  103. /* translators: %s: URL to Add Themes screen. */
  104. __( 'You only have one theme installed right now. Live a little! You can choose from over 1,000 free themes in the WordPress Theme Directory at any time: just click on the <a href="%s">Install Themes</a> tab above.' ),
  105. admin_url( 'theme-install.php' )
  106. );
  107. return;
  108. }
  109. }
  110. // Fallthrough.
  111. printf(
  112. /* translators: %s: Network title. */
  113. __( 'Only the active theme is available to you. Contact the %s administrator for information about accessing additional themes.' ),
  114. get_site_option( 'site_name' )
  115. );
  116. }
  117. /**
  118. * @param string $which
  119. */
  120. public function tablenav( $which = 'top' ) {
  121. if ( $this->get_pagination_arg( 'total_pages' ) <= 1 ) {
  122. return;
  123. }
  124. ?>
  125. <div class="tablenav themes <?php echo $which; ?>">
  126. <?php $this->pagination( $which ); ?>
  127. <span class="spinner"></span>
  128. <br class="clear" />
  129. </div>
  130. <?php
  131. }
  132. /**
  133. * Displays the themes table.
  134. *
  135. * Overrides the parent display() method to provide a different container.
  136. *
  137. * @since 3.1.0
  138. */
  139. public function display() {
  140. wp_nonce_field( 'fetch-list-' . get_class( $this ), '_ajax_fetch_list_nonce' );
  141. ?>
  142. <?php $this->tablenav( 'top' ); ?>
  143. <div id="availablethemes">
  144. <?php $this->display_rows_or_placeholder(); ?>
  145. </div>
  146. <?php $this->tablenav( 'bottom' ); ?>
  147. <?php
  148. }
  149. /**
  150. * @return array
  151. */
  152. public function get_columns() {
  153. return array();
  154. }
  155. /**
  156. */
  157. public function display_rows_or_placeholder() {
  158. if ( $this->has_items() ) {
  159. $this->display_rows();
  160. } else {
  161. echo '<div class="no-items">';
  162. $this->no_items();
  163. echo '</div>';
  164. }
  165. }
  166. /**
  167. */
  168. public function display_rows() {
  169. $themes = $this->items;
  170. foreach ( $themes as $theme ) :
  171. ?>
  172. <div class="available-theme">
  173. <?php
  174. $template = $theme->get_template();
  175. $stylesheet = $theme->get_stylesheet();
  176. $title = $theme->display( 'Name' );
  177. $version = $theme->display( 'Version' );
  178. $author = $theme->display( 'Author' );
  179. $activate_link = wp_nonce_url( 'themes.php?action=activate&amp;template=' . urlencode( $template ) . '&amp;stylesheet=' . urlencode( $stylesheet ), 'switch-theme_' . $stylesheet );
  180. $actions = array();
  181. $actions['activate'] = sprintf(
  182. '<a href="%s" class="activatelink" title="%s">%s</a>',
  183. $activate_link,
  184. /* translators: %s: Theme name. */
  185. esc_attr( sprintf( _x( 'Activate &#8220;%s&#8221;', 'theme' ), $title ) ),
  186. __( 'Activate' )
  187. );
  188. if ( current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
  189. $actions['preview'] .= sprintf(
  190. '<a href="%s" class="load-customize hide-if-no-customize">%s</a>',
  191. wp_customize_url( $stylesheet ),
  192. __( 'Live Preview' )
  193. );
  194. }
  195. if ( ! is_multisite() && current_user_can( 'delete_themes' ) ) {
  196. $actions['delete'] = sprintf(
  197. '<a class="submitdelete deletion" href="%s" onclick="return confirm( \'%s\' );">%s</a>',
  198. wp_nonce_url( 'themes.php?action=delete&amp;stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet ),
  199. /* translators: %s: Theme name. */
  200. esc_js( sprintf( __( "You are about to delete this theme '%s'\n 'Cancel' to stop, 'OK' to delete." ), $title ) ),
  201. __( 'Delete' )
  202. );
  203. }
  204. /** This filter is documented in wp-admin/includes/class-wp-ms-themes-list-table.php */
  205. $actions = apply_filters( 'theme_action_links', $actions, $theme, 'all' );
  206. /** This filter is documented in wp-admin/includes/class-wp-ms-themes-list-table.php */
  207. $actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, 'all' );
  208. $delete_action = isset( $actions['delete'] ) ? '<div class="delete-theme">' . $actions['delete'] . '</div>' : '';
  209. unset( $actions['delete'] );
  210. $screenshot = $theme->get_screenshot();
  211. ?>
  212. <span class="screenshot hide-if-customize">
  213. <?php if ( $screenshot ) : ?>
  214. <img src="<?php echo esc_url( $screenshot . '?ver=' . $theme->version ); ?>" alt="" />
  215. <?php endif; ?>
  216. </span>
  217. <a href="<?php echo wp_customize_url( $stylesheet ); ?>" class="screenshot load-customize hide-if-no-customize">
  218. <?php if ( $screenshot ) : ?>
  219. <img src="<?php echo esc_url( $screenshot . '?ver=' . $theme->version ); ?>" alt="" />
  220. <?php endif; ?>
  221. </a>
  222. <h3><?php echo $title; ?></h3>
  223. <div class="theme-author">
  224. <?php
  225. /* translators: %s: Theme author. */
  226. printf( __( 'By %s' ), $author );
  227. ?>
  228. </div>
  229. <div class="action-links">
  230. <ul>
  231. <?php foreach ( $actions as $action ) : ?>
  232. <li><?php echo $action; ?></li>
  233. <?php endforeach; ?>
  234. <li class="hide-if-no-js"><a href="#" class="theme-detail"><?php _e( 'Details' ); ?></a></li>
  235. </ul>
  236. <?php echo $delete_action; ?>
  237. <?php theme_update_available( $theme ); ?>
  238. </div>
  239. <div class="themedetaildiv hide-if-js">
  240. <p><strong><?php _e( 'Version:' ); ?></strong> <?php echo $version; ?></p>
  241. <p><?php echo $theme->display( 'Description' ); ?></p>
  242. <?php
  243. if ( $theme->parent() ) {
  244. printf(
  245. /* translators: 1: Link to documentation on child themes, 2: Name of parent theme. */
  246. ' <p class="howto">' . __( 'This <a href="%1$s">child theme</a> requires its parent theme, %2$s.' ) . '</p>',
  247. __( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
  248. $theme->parent()->display( 'Name' )
  249. );
  250. }
  251. ?>
  252. </div>
  253. </div>
  254. <?php
  255. endforeach;
  256. }
  257. /**
  258. * @param WP_Theme $theme
  259. * @return bool
  260. */
  261. public function search_theme( $theme ) {
  262. // Search the features.
  263. foreach ( $this->features as $word ) {
  264. if ( ! in_array( $word, $theme->get( 'Tags' ), true ) ) {
  265. return false;
  266. }
  267. }
  268. // Match all phrases.
  269. foreach ( $this->search_terms as $word ) {
  270. if ( in_array( $word, $theme->get( 'Tags' ), true ) ) {
  271. continue;
  272. }
  273. foreach ( array( 'Name', 'Description', 'Author', 'AuthorURI' ) as $header ) {
  274. // Don't mark up; Do translate.
  275. if ( false !== stripos( strip_tags( $theme->display( $header, false, true ) ), $word ) ) {
  276. continue 2;
  277. }
  278. }
  279. if ( false !== stripos( $theme->get_stylesheet(), $word ) ) {
  280. continue;
  281. }
  282. if ( false !== stripos( $theme->get_template(), $word ) ) {
  283. continue;
  284. }
  285. return false;
  286. }
  287. return true;
  288. }
  289. /**
  290. * Send required variables to JavaScript land
  291. *
  292. * @since 3.4.0
  293. *
  294. * @param array $extra_args
  295. */
  296. public function _js_vars( $extra_args = array() ) {
  297. $search_string = isset( $_REQUEST['s'] ) ? esc_attr( wp_unslash( $_REQUEST['s'] ) ) : '';
  298. $args = array(
  299. 'search' => $search_string,
  300. 'features' => $this->features,
  301. 'paged' => $this->get_pagenum(),
  302. 'total_pages' => ! empty( $this->_pagination_args['total_pages'] ) ? $this->_pagination_args['total_pages'] : 1,
  303. );
  304. if ( is_array( $extra_args ) ) {
  305. $args = array_merge( $args, $extra_args );
  306. }
  307. printf( "<script type='text/javascript'>var theme_list_args = %s;</script>\n", wp_json_encode( $args ) );
  308. parent::_js_vars();
  309. }
  310. }