PageRenderTime 72ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://gitlab.com/math4youbyusgroupillinois/WordPress
PHP | 1110 lines | 508 code | 140 blank | 462 comment | 92 complexity | 5d6877c23d56258b8d0002f63f955d0b MD5 | raw file
  1. <?php
  2. /**
  3. * Base class for displaying a list of items in an ajaxified HTML table.
  4. *
  5. * @since 3.1.0
  6. * @access private
  7. *
  8. * @package WordPress
  9. * @subpackage List_Table
  10. */
  11. class WP_List_Table {
  12. /**
  13. * The current list of items
  14. *
  15. * @since 3.1.0
  16. * @var array
  17. * @access public
  18. */
  19. public $items;
  20. /**
  21. * Various information about the current table
  22. *
  23. * @since 3.1.0
  24. * @var array
  25. * @access protected
  26. */
  27. protected $_args;
  28. /**
  29. * Various information needed for displaying the pagination
  30. *
  31. * @since 3.1.0
  32. * @var array
  33. * @access private
  34. */
  35. private $_pagination_args = array();
  36. /**
  37. * The current screen
  38. *
  39. * @since 3.1.0
  40. * @var object
  41. * @access protected
  42. */
  43. protected $screen;
  44. /**
  45. * Cached bulk actions
  46. *
  47. * @since 3.1.0
  48. * @var array
  49. * @access private
  50. */
  51. private $_actions;
  52. /**
  53. * Cached pagination output
  54. *
  55. * @since 3.1.0
  56. * @var string
  57. * @access private
  58. */
  59. private $_pagination;
  60. /**
  61. * The view switcher modes.
  62. *
  63. * @since 4.1.0
  64. * @var array
  65. * @access protected
  66. */
  67. protected $modes = array();
  68. /**
  69. * Stores the value returned by ->get_column_info()
  70. *
  71. * @var array
  72. */
  73. protected $_column_headers;
  74. /**
  75. * Constructor.
  76. *
  77. * The child class should call this constructor from its own constructor to override
  78. * the default $args.
  79. *
  80. * @since 3.1.0
  81. * @access public
  82. *
  83. * @param array|string $args {
  84. * Array or string of arguments.
  85. *
  86. * @type string $plural Plural value used for labels and the objects being listed.
  87. * This affects things such as CSS class-names and nonces used
  88. * in the list table, e.g. 'posts'. Default empty.
  89. * @type string $singular Singular label for an object being listed, e.g. 'post'.
  90. * Default empty
  91. * @type bool $ajax Whether the list table supports AJAX. This includes loading
  92. * and sorting data, for example. If true, the class will call
  93. * the {@see _js_vars()} method in the footer to provide variables
  94. * to any scripts handling AJAX events. Default false.
  95. * @type string $screen String containing the hook name used to determine the current
  96. * screen. If left null, the current screen will be automatically set.
  97. * Default null.
  98. * }
  99. */
  100. public function __construct( $args = array() ) {
  101. $args = wp_parse_args( $args, array(
  102. 'plural' => '',
  103. 'singular' => '',
  104. 'ajax' => false,
  105. 'screen' => null,
  106. ) );
  107. $this->screen = convert_to_screen( $args['screen'] );
  108. add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
  109. if ( !$args['plural'] )
  110. $args['plural'] = $this->screen->base;
  111. $args['plural'] = sanitize_key( $args['plural'] );
  112. $args['singular'] = sanitize_key( $args['singular'] );
  113. $this->_args = $args;
  114. if ( $args['ajax'] ) {
  115. // wp_enqueue_script( 'list-table' );
  116. add_action( 'admin_footer', array( $this, '_js_vars' ) );
  117. }
  118. if ( empty( $this->modes ) ) {
  119. $this->modes = array(
  120. 'list' => __( 'List View' ),
  121. 'excerpt' => __( 'Excerpt View' )
  122. );
  123. }
  124. }
  125. /**
  126. * Make private properties readable for backwards compatibility.
  127. *
  128. * @since 4.0.0
  129. * @access public
  130. *
  131. * @param string $name Property to get.
  132. * @return mixed Property.
  133. */
  134. public function __get( $name ) {
  135. return $this->$name;
  136. }
  137. /**
  138. * Make private properties settable for backwards compatibility.
  139. *
  140. * @since 4.0.0
  141. * @access public
  142. *
  143. * @param string $name Property to set.
  144. * @param mixed $value Property value.
  145. * @return mixed Newly-set property.
  146. */
  147. public function __set( $name, $value ) {
  148. return $this->$name = $value;
  149. }
  150. /**
  151. * Make private properties checkable for backwards compatibility.
  152. *
  153. * @since 4.0.0
  154. * @access public
  155. *
  156. * @param string $name Property to check if set.
  157. * @return bool Whether the property is set.
  158. */
  159. public function __isset( $name ) {
  160. return isset( $this->$name );
  161. }
  162. /**
  163. * Make private properties un-settable for backwards compatibility.
  164. *
  165. * @since 4.0.0
  166. * @access public
  167. *
  168. * @param string $name Property to unset.
  169. */
  170. public function __unset( $name ) {
  171. unset( $this->$name );
  172. }
  173. /**
  174. * Make private/protected methods readable for backwards compatibility.
  175. *
  176. * @since 4.0.0
  177. * @access public
  178. *
  179. * @param callable $name Method to call.
  180. * @param array $arguments Arguments to pass when calling.
  181. * @return mixed|bool Return value of the callback, false otherwise.
  182. */
  183. public function __call( $name, $arguments ) {
  184. return call_user_func_array( array( $this, $name ), $arguments );
  185. }
  186. /**
  187. * Checks the current user's permissions
  188. *
  189. * @since 3.1.0
  190. * @access public
  191. * @abstract
  192. */
  193. public function ajax_user_can() {
  194. die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
  195. }
  196. /**
  197. * Prepares the list of items for displaying.
  198. * @uses WP_List_Table::set_pagination_args()
  199. *
  200. * @since 3.1.0
  201. * @access public
  202. * @abstract
  203. */
  204. public function prepare_items() {
  205. die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
  206. }
  207. /**
  208. * An internal method that sets all the necessary pagination arguments
  209. *
  210. * @param array $args An associative array with information about the pagination
  211. * @access protected
  212. */
  213. protected function set_pagination_args( $args ) {
  214. $args = wp_parse_args( $args, array(
  215. 'total_items' => 0,
  216. 'total_pages' => 0,
  217. 'per_page' => 0,
  218. ) );
  219. if ( !$args['total_pages'] && $args['per_page'] > 0 )
  220. $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
  221. // Redirect if page number is invalid and headers are not already sent.
  222. if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
  223. wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
  224. exit;
  225. }
  226. $this->_pagination_args = $args;
  227. }
  228. /**
  229. * Access the pagination args.
  230. *
  231. * @since 3.1.0
  232. * @access public
  233. *
  234. * @param string $key Pagination argument to retrieve. Common values include 'total_items',
  235. * 'total_pages', 'per_page', or 'infinite_scroll'.
  236. * @return int Number of items that correspond to the given pagination argument.
  237. */
  238. public function get_pagination_arg( $key ) {
  239. if ( 'page' == $key )
  240. return $this->get_pagenum();
  241. if ( isset( $this->_pagination_args[$key] ) )
  242. return $this->_pagination_args[$key];
  243. }
  244. /**
  245. * Whether the table has items to display or not
  246. *
  247. * @since 3.1.0
  248. * @access public
  249. *
  250. * @return bool
  251. */
  252. public function has_items() {
  253. return !empty( $this->items );
  254. }
  255. /**
  256. * Message to be displayed when there are no items
  257. *
  258. * @since 3.1.0
  259. * @access public
  260. */
  261. public function no_items() {
  262. _e( 'No items found.' );
  263. }
  264. /**
  265. * Display the search box.
  266. *
  267. * @since 3.1.0
  268. * @access public
  269. *
  270. * @param string $text The search button text
  271. * @param string $input_id The search input id
  272. */
  273. public function search_box( $text, $input_id ) {
  274. if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
  275. return;
  276. $input_id = $input_id . '-search-input';
  277. if ( ! empty( $_REQUEST['orderby'] ) )
  278. echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
  279. if ( ! empty( $_REQUEST['order'] ) )
  280. echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
  281. if ( ! empty( $_REQUEST['post_mime_type'] ) )
  282. echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
  283. if ( ! empty( $_REQUEST['detached'] ) )
  284. echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
  285. ?>
  286. <p class="search-box">
  287. <label class="screen-reader-text" for="<?php echo $input_id ?>"><?php echo $text; ?>:</label>
  288. <input type="search" id="<?php echo $input_id ?>" name="s" value="<?php _admin_search_query(); ?>" />
  289. <?php submit_button( $text, 'button', false, false, array('id' => 'search-submit') ); ?>
  290. </p>
  291. <?php
  292. }
  293. /**
  294. * Get an associative array ( id => link ) with the list
  295. * of views available on this table.
  296. *
  297. * @since 3.1.0
  298. * @access protected
  299. *
  300. * @return array
  301. */
  302. protected function get_views() {
  303. return array();
  304. }
  305. /**
  306. * Display the list of views available on this table.
  307. *
  308. * @since 3.1.0
  309. * @access public
  310. */
  311. public function views() {
  312. $views = $this->get_views();
  313. /**
  314. * Filter the list of available list table views.
  315. *
  316. * The dynamic portion of the hook name, `$this->screen->id`, refers
  317. * to the ID of the current screen, usually a string.
  318. *
  319. * @since 3.5.0
  320. *
  321. * @param array $views An array of available list table views.
  322. */
  323. $views = apply_filters( "views_{$this->screen->id}", $views );
  324. if ( empty( $views ) )
  325. return;
  326. echo "<ul class='subsubsub'>\n";
  327. foreach ( $views as $class => $view ) {
  328. $views[ $class ] = "\t<li class='$class'>$view";
  329. }
  330. echo implode( " |</li>\n", $views ) . "</li>\n";
  331. echo "</ul>";
  332. }
  333. /**
  334. * Get an associative array ( option_name => option_title ) with the list
  335. * of bulk actions available on this table.
  336. *
  337. * @since 3.1.0
  338. * @access protected
  339. *
  340. * @return array
  341. */
  342. protected function get_bulk_actions() {
  343. return array();
  344. }
  345. /**
  346. * Display the bulk actions dropdown.
  347. *
  348. * @since 3.1.0
  349. * @access protected
  350. *
  351. * @param string $which The location of the bulk actions: 'top' or 'bottom'.
  352. * This is designated as optional for backwards-compatibility.
  353. */
  354. protected function bulk_actions( $which = '' ) {
  355. if ( is_null( $this->_actions ) ) {
  356. $no_new_actions = $this->_actions = $this->get_bulk_actions();
  357. /**
  358. * Filter the list table Bulk Actions drop-down.
  359. *
  360. * The dynamic portion of the hook name, `$this->screen->id`, refers
  361. * to the ID of the current screen, usually a string.
  362. *
  363. * This filter can currently only be used to remove bulk actions.
  364. *
  365. * @since 3.5.0
  366. *
  367. * @param array $actions An array of the available bulk actions.
  368. */
  369. $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
  370. $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
  371. $two = '';
  372. } else {
  373. $two = '2';
  374. }
  375. if ( empty( $this->_actions ) )
  376. return;
  377. echo "<label for='bulk-action-selector-" . esc_attr( $which ) . "' class='screen-reader-text'>" . __( 'Select bulk action' ) . "</label>";
  378. echo "<select name='action$two' id='bulk-action-selector-" . esc_attr( $which ) . "'>\n";
  379. echo "<option value='-1' selected='selected'>" . __( 'Bulk Actions' ) . "</option>\n";
  380. foreach ( $this->_actions as $name => $title ) {
  381. $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
  382. echo "\t<option value='$name'$class>$title</option>\n";
  383. }
  384. echo "</select>\n";
  385. submit_button( __( 'Apply' ), 'action', false, false, array( 'id' => "doaction$two" ) );
  386. echo "\n";
  387. }
  388. /**
  389. * Get the current action selected from the bulk actions dropdown.
  390. *
  391. * @since 3.1.0
  392. * @access public
  393. *
  394. * @return string|bool The action name or False if no action was selected
  395. */
  396. public function current_action() {
  397. if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) )
  398. return false;
  399. if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
  400. return $_REQUEST['action'];
  401. if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
  402. return $_REQUEST['action2'];
  403. return false;
  404. }
  405. /**
  406. * Generate row actions div
  407. *
  408. * @since 3.1.0
  409. * @access protected
  410. *
  411. * @param array $actions The list of actions
  412. * @param bool $always_visible Whether the actions should be always visible
  413. * @return string
  414. */
  415. protected function row_actions( $actions, $always_visible = false ) {
  416. $action_count = count( $actions );
  417. $i = 0;
  418. if ( !$action_count )
  419. return '';
  420. $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
  421. foreach ( $actions as $action => $link ) {
  422. ++$i;
  423. ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
  424. $out .= "<span class='$action'>$link$sep</span>";
  425. }
  426. $out .= '</div>';
  427. return $out;
  428. }
  429. /**
  430. * Display a monthly dropdown for filtering items
  431. *
  432. * @since 3.1.0
  433. * @access protected
  434. *
  435. * @param string $post_type
  436. */
  437. protected function months_dropdown( $post_type ) {
  438. global $wpdb, $wp_locale;
  439. $months = $wpdb->get_results( $wpdb->prepare( "
  440. SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
  441. FROM $wpdb->posts
  442. WHERE post_type = %s
  443. ORDER BY post_date DESC
  444. ", $post_type ) );
  445. /**
  446. * Filter the 'Months' drop-down results.
  447. *
  448. * @since 3.7.0
  449. *
  450. * @param object $months The months drop-down query results.
  451. * @param string $post_type The post type.
  452. */
  453. $months = apply_filters( 'months_dropdown_results', $months, $post_type );
  454. $month_count = count( $months );
  455. if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
  456. return;
  457. $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
  458. ?>
  459. <label for="filter-by-date" class="screen-reader-text"><?php _e( 'Filter by date' ); ?></label>
  460. <select name="m" id="filter-by-date">
  461. <option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
  462. <?php
  463. foreach ( $months as $arc_row ) {
  464. if ( 0 == $arc_row->year )
  465. continue;
  466. $month = zeroise( $arc_row->month, 2 );
  467. $year = $arc_row->year;
  468. printf( "<option %s value='%s'>%s</option>\n",
  469. selected( $m, $year . $month, false ),
  470. esc_attr( $arc_row->year . $month ),
  471. /* translators: 1: month name, 2: 4-digit year */
  472. sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
  473. );
  474. }
  475. ?>
  476. </select>
  477. <?php
  478. }
  479. /**
  480. * Display a view switcher
  481. *
  482. * @since 3.1.0
  483. * @access protected
  484. *
  485. * @param string $current_mode
  486. */
  487. protected function view_switcher( $current_mode ) {
  488. ?>
  489. <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
  490. <div class="view-switch">
  491. <?php
  492. foreach ( $this->modes as $mode => $title ) {
  493. $classes = array( 'view-' . $mode );
  494. if ( $current_mode == $mode )
  495. $classes[] = 'current';
  496. printf(
  497. "<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
  498. esc_url( add_query_arg( 'mode', $mode ) ),
  499. implode( ' ', $classes ),
  500. $title
  501. );
  502. }
  503. ?>
  504. </div>
  505. <?php
  506. }
  507. /**
  508. * Display a comment count bubble
  509. *
  510. * @since 3.1.0
  511. * @access protected
  512. *
  513. * @param int $post_id The post ID.
  514. * @param int $pending_comments Number of pending comments.
  515. */
  516. protected function comments_bubble( $post_id, $pending_comments ) {
  517. $pending_phrase = sprintf( __( '%s pending' ), number_format( $pending_comments ) );
  518. if ( $pending_comments )
  519. echo '<strong>';
  520. echo "<a href='" . esc_url( add_query_arg( 'p', $post_id, admin_url( 'edit-comments.php' ) ) ) . "' title='" . esc_attr( $pending_phrase ) . "' class='post-com-count'><span class='comment-count'>" . number_format_i18n( get_comments_number() ) . "</span></a>";
  521. if ( $pending_comments )
  522. echo '</strong>';
  523. }
  524. /**
  525. * Get the current page number
  526. *
  527. * @since 3.1.0
  528. * @access public
  529. *
  530. * @return int
  531. */
  532. public function get_pagenum() {
  533. $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
  534. if( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
  535. $pagenum = $this->_pagination_args['total_pages'];
  536. return max( 1, $pagenum );
  537. }
  538. /**
  539. * Get number of items to display on a single page
  540. *
  541. * @since 3.1.0
  542. * @access protected
  543. *
  544. * @param string $option
  545. * @param int $default
  546. * @return int
  547. */
  548. protected function get_items_per_page( $option, $default = 20 ) {
  549. $per_page = (int) get_user_option( $option );
  550. if ( empty( $per_page ) || $per_page < 1 )
  551. $per_page = $default;
  552. /**
  553. * Filter the number of items to be displayed on each page of the list table.
  554. *
  555. * The dynamic hook name, $option, refers to the `per_page` option depending
  556. * on the type of list table in use. Possible values include: 'edit_comments_per_page',
  557. * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
  558. * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
  559. * 'edit_{$post_type}_per_page', etc.
  560. *
  561. * @since 2.9.0
  562. *
  563. * @param int $per_page Number of items to be displayed. Default 20.
  564. */
  565. return (int) apply_filters( $option, $per_page );
  566. }
  567. /**
  568. * Display the pagination.
  569. *
  570. * @since 3.1.0
  571. * @access protected
  572. *
  573. * @param string $which
  574. */
  575. protected function pagination( $which ) {
  576. if ( empty( $this->_pagination_args ) ) {
  577. return;
  578. }
  579. $total_items = $this->_pagination_args['total_items'];
  580. $total_pages = $this->_pagination_args['total_pages'];
  581. $infinite_scroll = false;
  582. if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
  583. $infinite_scroll = $this->_pagination_args['infinite_scroll'];
  584. }
  585. $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
  586. $current = $this->get_pagenum();
  587. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  588. $current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
  589. $page_links = array();
  590. $disable_first = $disable_last = '';
  591. if ( $current == 1 ) {
  592. $disable_first = ' disabled';
  593. }
  594. if ( $current == $total_pages ) {
  595. $disable_last = ' disabled';
  596. }
  597. $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
  598. 'first-page' . $disable_first,
  599. esc_attr__( 'Go to the first page' ),
  600. esc_url( remove_query_arg( 'paged', $current_url ) ),
  601. '&laquo;'
  602. );
  603. $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
  604. 'prev-page' . $disable_first,
  605. esc_attr__( 'Go to the previous page' ),
  606. esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
  607. '&lsaquo;'
  608. );
  609. if ( 'bottom' == $which ) {
  610. $html_current_page = $current;
  611. } else {
  612. $html_current_page = sprintf( "%s<input class='current-page' id='current-page-selector' title='%s' type='text' name='paged' value='%s' size='%d' />",
  613. '<label for="current-page-selector" class="screen-reader-text">' . __( 'Select Page' ) . '</label>',
  614. esc_attr__( 'Current page' ),
  615. $current,
  616. strlen( $total_pages )
  617. );
  618. }
  619. $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
  620. $page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . '</span>';
  621. $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
  622. 'next-page' . $disable_last,
  623. esc_attr__( 'Go to the next page' ),
  624. esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
  625. '&rsaquo;'
  626. );
  627. $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
  628. 'last-page' . $disable_last,
  629. esc_attr__( 'Go to the last page' ),
  630. esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
  631. '&raquo;'
  632. );
  633. $pagination_links_class = 'pagination-links';
  634. if ( ! empty( $infinite_scroll ) ) {
  635. $pagination_links_class = ' hide-if-js';
  636. }
  637. $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
  638. if ( $total_pages ) {
  639. $page_class = $total_pages < 2 ? ' one-page' : '';
  640. } else {
  641. $page_class = ' no-pages';
  642. }
  643. $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
  644. echo $this->_pagination;
  645. }
  646. /**
  647. * Get a list of columns. The format is:
  648. * 'internal-name' => 'Title'
  649. *
  650. * @since 3.1.0
  651. * @access public
  652. * @abstract
  653. *
  654. * @return array
  655. */
  656. public function get_columns() {
  657. die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
  658. }
  659. /**
  660. * Get a list of sortable columns. The format is:
  661. * 'internal-name' => 'orderby'
  662. * or
  663. * 'internal-name' => array( 'orderby', true )
  664. *
  665. * The second format will make the initial sorting order be descending
  666. *
  667. * @since 3.1.0
  668. * @access protected
  669. *
  670. * @return array
  671. */
  672. protected function get_sortable_columns() {
  673. return array();
  674. }
  675. /**
  676. * Get a list of all, hidden and sortable columns, with filter applied
  677. *
  678. * @since 3.1.0
  679. * @access protected
  680. *
  681. * @return array
  682. */
  683. protected function get_column_info() {
  684. if ( isset( $this->_column_headers ) )
  685. return $this->_column_headers;
  686. $columns = get_column_headers( $this->screen );
  687. $hidden = get_hidden_columns( $this->screen );
  688. $sortable_columns = $this->get_sortable_columns();
  689. /**
  690. * Filter the list table sortable columns for a specific screen.
  691. *
  692. * The dynamic portion of the hook name, `$this->screen->id`, refers
  693. * to the ID of the current screen, usually a string.
  694. *
  695. * @since 3.5.0
  696. *
  697. * @param array $sortable_columns An array of sortable columns.
  698. */
  699. $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
  700. $sortable = array();
  701. foreach ( $_sortable as $id => $data ) {
  702. if ( empty( $data ) )
  703. continue;
  704. $data = (array) $data;
  705. if ( !isset( $data[1] ) )
  706. $data[1] = false;
  707. $sortable[$id] = $data;
  708. }
  709. $this->_column_headers = array( $columns, $hidden, $sortable );
  710. return $this->_column_headers;
  711. }
  712. /**
  713. * Return number of visible columns
  714. *
  715. * @since 3.1.0
  716. * @access public
  717. *
  718. * @return int
  719. */
  720. public function get_column_count() {
  721. list ( $columns, $hidden ) = $this->get_column_info();
  722. $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
  723. return count( $columns ) - count( $hidden );
  724. }
  725. /**
  726. * Print column headers, accounting for hidden and sortable columns.
  727. *
  728. * @since 3.1.0
  729. * @access public
  730. *
  731. * @param bool $with_id Whether to set the id attribute or not
  732. */
  733. public function print_column_headers( $with_id = true ) {
  734. list( $columns, $hidden, $sortable ) = $this->get_column_info();
  735. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  736. $current_url = remove_query_arg( 'paged', $current_url );
  737. if ( isset( $_GET['orderby'] ) )
  738. $current_orderby = $_GET['orderby'];
  739. else
  740. $current_orderby = '';
  741. if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
  742. $current_order = 'desc';
  743. else
  744. $current_order = 'asc';
  745. if ( ! empty( $columns['cb'] ) ) {
  746. static $cb_counter = 1;
  747. $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
  748. . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
  749. $cb_counter++;
  750. }
  751. foreach ( $columns as $column_key => $column_display_name ) {
  752. $class = array( 'manage-column', "column-$column_key" );
  753. $style = '';
  754. if ( in_array( $column_key, $hidden ) )
  755. $style = 'display:none;';
  756. $style = ' style="' . $style . '"';
  757. if ( 'cb' == $column_key )
  758. $class[] = 'check-column';
  759. elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
  760. $class[] = 'num';
  761. if ( isset( $sortable[$column_key] ) ) {
  762. list( $orderby, $desc_first ) = $sortable[$column_key];
  763. if ( $current_orderby == $orderby ) {
  764. $order = 'asc' == $current_order ? 'desc' : 'asc';
  765. $class[] = 'sorted';
  766. $class[] = $current_order;
  767. } else {
  768. $order = $desc_first ? 'desc' : 'asc';
  769. $class[] = 'sortable';
  770. $class[] = $desc_first ? 'asc' : 'desc';
  771. }
  772. $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
  773. }
  774. $id = $with_id ? "id='$column_key'" : '';
  775. if ( !empty( $class ) )
  776. $class = "class='" . join( ' ', $class ) . "'";
  777. echo "<th scope='col' $id $class $style>$column_display_name</th>";
  778. }
  779. }
  780. /**
  781. * Display the table
  782. *
  783. * @since 3.1.0
  784. * @access public
  785. */
  786. public function display() {
  787. $singular = $this->_args['singular'];
  788. $this->display_tablenav( 'top' );
  789. ?>
  790. <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
  791. <thead>
  792. <tr>
  793. <?php $this->print_column_headers(); ?>
  794. </tr>
  795. </thead>
  796. <tfoot>
  797. <tr>
  798. <?php $this->print_column_headers( false ); ?>
  799. </tr>
  800. </tfoot>
  801. <tbody id="the-list"<?php
  802. if ( $singular ) {
  803. echo " data-wp-lists='list:$singular'";
  804. } ?>>
  805. <?php $this->display_rows_or_placeholder(); ?>
  806. </tbody>
  807. </table>
  808. <?php
  809. $this->display_tablenav( 'bottom' );
  810. }
  811. /**
  812. * Get a list of CSS classes for the list table table tag.
  813. *
  814. * @since 3.1.0
  815. * @access protected
  816. *
  817. * @return array List of CSS classes for the table tag.
  818. */
  819. protected function get_table_classes() {
  820. return array( 'widefat', 'fixed', $this->_args['plural'] );
  821. }
  822. /**
  823. * Generate the table navigation above or below the table
  824. *
  825. * @since 3.1.0
  826. * @access protected
  827. * @param string $which
  828. */
  829. protected function display_tablenav( $which ) {
  830. if ( 'top' == $which )
  831. wp_nonce_field( 'bulk-' . $this->_args['plural'] );
  832. ?>
  833. <div class="tablenav <?php echo esc_attr( $which ); ?>">
  834. <div class="alignleft actions bulkactions">
  835. <?php $this->bulk_actions( $which ); ?>
  836. </div>
  837. <?php
  838. $this->extra_tablenav( $which );
  839. $this->pagination( $which );
  840. ?>
  841. <br class="clear" />
  842. </div>
  843. <?php
  844. }
  845. /**
  846. * Extra controls to be displayed between bulk actions and pagination
  847. *
  848. * @since 3.1.0
  849. * @access protected
  850. *
  851. * @param string $which
  852. */
  853. protected function extra_tablenav( $which ) {}
  854. /**
  855. * Generate the tbody element for the list table.
  856. *
  857. * @since 3.1.0
  858. * @access public
  859. */
  860. public function display_rows_or_placeholder() {
  861. if ( $this->has_items() ) {
  862. $this->display_rows();
  863. } else {
  864. echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
  865. $this->no_items();
  866. echo '</td></tr>';
  867. }
  868. }
  869. /**
  870. * Generate the table rows
  871. *
  872. * @since 3.1.0
  873. * @access public
  874. */
  875. public function display_rows() {
  876. foreach ( $this->items as $item )
  877. $this->single_row( $item );
  878. }
  879. /**
  880. * Generates content for a single row of the table
  881. *
  882. * @since 3.1.0
  883. * @access public
  884. *
  885. * @param object $item The current item
  886. */
  887. public function single_row( $item ) {
  888. static $row_class = '';
  889. $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
  890. echo '<tr' . $row_class . '>';
  891. $this->single_row_columns( $item );
  892. echo '</tr>';
  893. }
  894. /**
  895. * Generates the columns for a single row of the table
  896. *
  897. * @since 3.1.0
  898. * @access protected
  899. *
  900. * @param object $item The current item
  901. */
  902. protected function single_row_columns( $item ) {
  903. list( $columns, $hidden ) = $this->get_column_info();
  904. foreach ( $columns as $column_name => $column_display_name ) {
  905. $class = "class='$column_name column-$column_name'";
  906. $style = '';
  907. if ( in_array( $column_name, $hidden ) )
  908. $style = ' style="display:none;"';
  909. $attributes = "$class$style";
  910. if ( 'cb' == $column_name ) {
  911. echo '<th scope="row" class="check-column">';
  912. echo $this->column_cb( $item );
  913. echo '</th>';
  914. }
  915. elseif ( method_exists( $this, 'column_' . $column_name ) ) {
  916. echo "<td $attributes>";
  917. echo call_user_func( array( $this, 'column_' . $column_name ), $item );
  918. echo "</td>";
  919. }
  920. else {
  921. echo "<td $attributes>";
  922. echo $this->column_default( $item, $column_name );
  923. echo "</td>";
  924. }
  925. }
  926. }
  927. /**
  928. * Handle an incoming ajax request (called from admin-ajax.php)
  929. *
  930. * @since 3.1.0
  931. * @access public
  932. */
  933. public function ajax_response() {
  934. $this->prepare_items();
  935. ob_start();
  936. if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
  937. $this->display_rows();
  938. } else {
  939. $this->display_rows_or_placeholder();
  940. }
  941. $rows = ob_get_clean();
  942. $response = array( 'rows' => $rows );
  943. if ( isset( $this->_pagination_args['total_items'] ) ) {
  944. $response['total_items_i18n'] = sprintf(
  945. _n( '1 item', '%s items', $this->_pagination_args['total_items'] ),
  946. number_format_i18n( $this->_pagination_args['total_items'] )
  947. );
  948. }
  949. if ( isset( $this->_pagination_args['total_pages'] ) ) {
  950. $response['total_pages'] = $this->_pagination_args['total_pages'];
  951. $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
  952. }
  953. die( wp_json_encode( $response ) );
  954. }
  955. /**
  956. * Send required variables to JavaScript land
  957. *
  958. * @access public
  959. */
  960. public function _js_vars() {
  961. $args = array(
  962. 'class' => get_class( $this ),
  963. 'screen' => array(
  964. 'id' => $this->screen->id,
  965. 'base' => $this->screen->base,
  966. )
  967. );
  968. printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
  969. }
  970. }