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

  1. <?php 
  2. /** 
  3. * Administration API: WP_List_Table class 
  4. * 
  5. * @package WordPress 
  6. * @subpackage List_Table 
  7. * @since 3.1.0 
  8. */ 
  9.  
  10. /** 
  11. * Base class for displaying a list of items in an ajaxified HTML table. 
  12. * 
  13. * @since 3.1.0 
  14. * @access private 
  15. */ 
  16. class WP_List_Table { 
  17.  
  18. /** 
  19. * The current list of items. 
  20. * 
  21. * @since 3.1.0 
  22. * @access public 
  23. * @var array 
  24. */ 
  25. public $items; 
  26.  
  27. /** 
  28. * Various information about the current table. 
  29. * 
  30. * @since 3.1.0 
  31. * @access protected 
  32. * @var array 
  33. */ 
  34. protected $_args; 
  35.  
  36. /** 
  37. * Various information needed for displaying the pagination. 
  38. * 
  39. * @since 3.1.0 
  40. * @access protected 
  41. * @var array 
  42. */ 
  43. protected $_pagination_args = array(); 
  44.  
  45. /** 
  46. * The current screen. 
  47. * 
  48. * @since 3.1.0 
  49. * @access protected 
  50. * @var object 
  51. */ 
  52. protected $screen; 
  53.  
  54. /** 
  55. * Cached bulk actions. 
  56. * 
  57. * @since 3.1.0 
  58. * @access private 
  59. * @var array 
  60. */ 
  61. private $_actions; 
  62.  
  63. /** 
  64. * Cached pagination output. 
  65. * 
  66. * @since 3.1.0 
  67. * @access private 
  68. * @var string 
  69. */ 
  70. private $_pagination; 
  71.  
  72. /** 
  73. * The view switcher modes. 
  74. * 
  75. * @since 4.1.0 
  76. * @access protected 
  77. * @var array 
  78. */ 
  79. protected $modes = array(); 
  80.  
  81. /** 
  82. * Stores the value returned by ->get_column_info(). 
  83. * 
  84. * @since 4.1.0 
  85. * @access protected 
  86. * @var array 
  87. */ 
  88. protected $_column_headers; 
  89.  
  90. /** 
  91. * {@internal Missing Summary} 
  92. * 
  93. * @access protected 
  94. * @var array 
  95. */ 
  96. protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' ); 
  97.  
  98. /** 
  99. * {@internal Missing Summary} 
  100. * 
  101. * @access protected 
  102. * @var array 
  103. */ 
  104. protected $compat_methods = array( 'set_pagination_args', 'get_views', 'get_bulk_actions', 'bulk_actions',  
  105. 'row_actions', 'months_dropdown', 'view_switcher', 'comments_bubble', 'get_items_per_page', 'pagination',  
  106. 'get_sortable_columns', 'get_column_info', 'get_table_classes', 'display_tablenav', 'extra_tablenav',  
  107. 'single_row_columns' ); 
  108.  
  109. /** 
  110. * Constructor. 
  111. * 
  112. * The child class should call this constructor from its own constructor to override 
  113. * the default $args. 
  114. * 
  115. * @since 3.1.0 
  116. * @access public 
  117. * 
  118. * @param array|string $args { 
  119. * Array or string of arguments. 
  120. * 
  121. * @type string $plural Plural value used for labels and the objects being listed. 
  122. * This affects things such as CSS class-names and nonces used 
  123. * in the list table, e.g. 'posts'. Default empty. 
  124. * @type string $singular Singular label for an object being listed, e.g. 'post'. 
  125. * Default empty 
  126. * @type bool $ajax Whether the list table supports Ajax. This includes loading 
  127. * and sorting data, for example. If true, the class will call 
  128. * the _js_vars() method in the footer to provide variables 
  129. * to any scripts handling Ajax events. Default false. 
  130. * @type string $screen String containing the hook name used to determine the current 
  131. * screen. If left null, the current screen will be automatically set. 
  132. * Default null. 
  133. * } 
  134. */ 
  135. public function __construct( $args = array() ) { 
  136. $args = wp_parse_args( $args, array( 
  137. 'plural' => '',  
  138. 'singular' => '',  
  139. 'ajax' => false,  
  140. 'screen' => null,  
  141. ) ); 
  142.  
  143. $this->screen = convert_to_screen( $args['screen'] ); 
  144.  
  145. add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 ); 
  146.  
  147. if ( !$args['plural'] ) 
  148. $args['plural'] = $this->screen->base; 
  149.  
  150. $args['plural'] = sanitize_key( $args['plural'] ); 
  151. $args['singular'] = sanitize_key( $args['singular'] ); 
  152.  
  153. $this->_args = $args; 
  154.  
  155. if ( $args['ajax'] ) { 
  156. // wp_enqueue_script( 'list-table' ); 
  157. add_action( 'admin_footer', array( $this, '_js_vars' ) ); 
  158.  
  159. if ( empty( $this->modes ) ) { 
  160. $this->modes = array( 
  161. 'list' => __( 'List View' ),  
  162. 'excerpt' => __( 'Excerpt View' ) 
  163. ); 
  164.  
  165. /** 
  166. * Make private properties readable for backward compatibility. 
  167. * 
  168. * @since 4.0.0 
  169. * @access public 
  170. * 
  171. * @param string $name Property to get. 
  172. * @return mixed Property. 
  173. */ 
  174. public function __get( $name ) { 
  175. if ( in_array( $name, $this->compat_fields ) ) { 
  176. return $this->$name; 
  177.  
  178. /** 
  179. * Make private properties settable for backward compatibility. 
  180. * 
  181. * @since 4.0.0 
  182. * @access public 
  183. * 
  184. * @param string $name Property to check if set. 
  185. * @param mixed $value Property value. 
  186. * @return mixed Newly-set property. 
  187. */ 
  188. public function __set( $name, $value ) { 
  189. if ( in_array( $name, $this->compat_fields ) ) { 
  190. return $this->$name = $value; 
  191.  
  192. /** 
  193. * Make private properties checkable for backward compatibility. 
  194. * 
  195. * @since 4.0.0 
  196. * @access public 
  197. * 
  198. * @param string $name Property to check if set. 
  199. * @return bool Whether the property is set. 
  200. */ 
  201. public function __isset( $name ) { 
  202. if ( in_array( $name, $this->compat_fields ) ) { 
  203. return isset( $this->$name ); 
  204.  
  205. /** 
  206. * Make private properties un-settable for backward compatibility. 
  207. * 
  208. * @since 4.0.0 
  209. * @access public 
  210. * 
  211. * @param string $name Property to unset. 
  212. */ 
  213. public function __unset( $name ) { 
  214. if ( in_array( $name, $this->compat_fields ) ) { 
  215. unset( $this->$name ); 
  216.  
  217. /** 
  218. * Make private/protected methods readable for backward compatibility. 
  219. * 
  220. * @since 4.0.0 
  221. * @access public 
  222. * 
  223. * @param callable $name Method to call. 
  224. * @param array $arguments Arguments to pass when calling. 
  225. * @return mixed|bool Return value of the callback, false otherwise. 
  226. */ 
  227. public function __call( $name, $arguments ) { 
  228. if ( in_array( $name, $this->compat_methods ) ) { 
  229. return call_user_func_array( array( $this, $name ), $arguments ); 
  230. return false; 
  231.  
  232. /** 
  233. * Checks the current user's permissions 
  234. * 
  235. * @since 3.1.0 
  236. * @access public 
  237. * @abstract 
  238. */ 
  239. public function ajax_user_can() { 
  240. die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' ); 
  241.  
  242. /** 
  243. * Prepares the list of items for displaying. 
  244. * @uses WP_List_Table::set_pagination_args() 
  245. * 
  246. * @since 3.1.0 
  247. * @access public 
  248. * @abstract 
  249. */ 
  250. public function prepare_items() { 
  251. die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' ); 
  252.  
  253. /** 
  254. * An internal method that sets all the necessary pagination arguments 
  255. * 
  256. * @since 3.1.0 
  257. * @access protected 
  258. * 
  259. * @param array|string $args Array or string of arguments with information about the pagination. 
  260. */ 
  261. protected function set_pagination_args( $args ) { 
  262. $args = wp_parse_args( $args, array( 
  263. 'total_items' => 0,  
  264. 'total_pages' => 0,  
  265. 'per_page' => 0,  
  266. ) ); 
  267.  
  268. if ( !$args['total_pages'] && $args['per_page'] > 0 ) 
  269. $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] ); 
  270.  
  271. // Redirect if page number is invalid and headers are not already sent. 
  272. if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) { 
  273. wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) ); 
  274. exit; 
  275.  
  276. $this->_pagination_args = $args; 
  277.  
  278. /** 
  279. * Access the pagination args. 
  280. * 
  281. * @since 3.1.0 
  282. * @access public 
  283. * 
  284. * @param string $key Pagination argument to retrieve. Common values include 'total_items',  
  285. * 'total_pages', 'per_page', or 'infinite_scroll'. 
  286. * @return int Number of items that correspond to the given pagination argument. 
  287. */ 
  288. public function get_pagination_arg( $key ) { 
  289. if ( 'page' === $key ) { 
  290. return $this->get_pagenum(); 
  291.  
  292. if ( isset( $this->_pagination_args[$key] ) ) { 
  293. return $this->_pagination_args[$key]; 
  294.  
  295. /** 
  296. * Whether the table has items to display or not 
  297. * 
  298. * @since 3.1.0 
  299. * @access public 
  300. * 
  301. * @return bool 
  302. */ 
  303. public function has_items() { 
  304. return !empty( $this->items ); 
  305.  
  306. /** 
  307. * Message to be displayed when there are no items 
  308. * 
  309. * @since 3.1.0 
  310. * @access public 
  311. */ 
  312. public function no_items() { 
  313. _e( 'No items found.' ); 
  314.  
  315. /** 
  316. * Displays the search box. 
  317. * 
  318. * @since 3.1.0 
  319. * @access public 
  320. * 
  321. * @param string $text The 'submit' button label. 
  322. * @param string $input_id ID attribute value for the search input field. 
  323. */ 
  324. public function search_box( $text, $input_id ) { 
  325. if ( empty( $_REQUEST['s'] ) && !$this->has_items() ) 
  326. return; 
  327.  
  328. $input_id = $input_id . '-search-input'; 
  329.  
  330. if ( ! empty( $_REQUEST['orderby'] ) ) 
  331. echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />'; 
  332. if ( ! empty( $_REQUEST['order'] ) ) 
  333. echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />'; 
  334. if ( ! empty( $_REQUEST['post_mime_type'] ) ) 
  335. echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />'; 
  336. if ( ! empty( $_REQUEST['detached'] ) ) 
  337. echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />'; 
  338. ?> 
  339. <p class="search-box"> 
  340. <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label> 
  341. <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" /> 
  342. <?php submit_button( $text, 'button', '', false, array( 'id' => 'search-submit' ) ); ?> 
  343. </p> 
  344. <?php 
  345.  
  346. /** 
  347. * Get an associative array ( id => link ) with the list 
  348. * of views available on this table. 
  349. * 
  350. * @since 3.1.0 
  351. * @access protected 
  352. * 
  353. * @return array 
  354. */ 
  355. protected function get_views() { 
  356. return array(); 
  357.  
  358. /** 
  359. * Display the list of views available on this table. 
  360. * 
  361. * @since 3.1.0 
  362. * @access public 
  363. */ 
  364. public function views() { 
  365. $views = $this->get_views(); 
  366. /** 
  367. * Filters the list of available list table views. 
  368. * 
  369. * The dynamic portion of the hook name, `$this->screen->id`, refers 
  370. * to the ID of the current screen, usually a string. 
  371. * 
  372. * @since 3.5.0 
  373. * 
  374. * @param array $views An array of available list table views. 
  375. */ 
  376. $views = apply_filters( "views_{$this->screen->id}", $views ); 
  377.  
  378. if ( empty( $views ) ) 
  379. return; 
  380.  
  381. $this->screen->render_screen_reader_content( 'heading_views' ); 
  382.  
  383. echo "<ul class='subsubsub'>\n"; 
  384. foreach ( $views as $class => $view ) { 
  385. $views[ $class ] = "\t<li class='$class'>$view"; 
  386. echo implode( " |</li>\n", $views ) . "</li>\n"; 
  387. echo "</ul>"; 
  388.  
  389. /** 
  390. * Get an associative array ( option_name => option_title ) with the list 
  391. * of bulk actions available on this table. 
  392. * 
  393. * @since 3.1.0 
  394. * @access protected 
  395. * 
  396. * @return array 
  397. */ 
  398. protected function get_bulk_actions() { 
  399. return array(); 
  400.  
  401. /** 
  402. * Display the bulk actions dropdown. 
  403. * 
  404. * @since 3.1.0 
  405. * @access protected 
  406. * 
  407. * @param string $which The location of the bulk actions: 'top' or 'bottom'. 
  408. * This is designated as optional for backward compatibility. 
  409. */ 
  410. protected function bulk_actions( $which = '' ) { 
  411. if ( is_null( $this->_actions ) ) { 
  412. $no_new_actions = $this->_actions = $this->get_bulk_actions(); 
  413. /** 
  414. * Filters the list table Bulk Actions drop-down. 
  415. * 
  416. * The dynamic portion of the hook name, `$this->screen->id`, refers 
  417. * to the ID of the current screen, usually a string. 
  418. * 
  419. * This filter can currently only be used to remove bulk actions. 
  420. * 
  421. * @since 3.5.0 
  422. * 
  423. * @param array $actions An array of the available bulk actions. 
  424. */ 
  425. $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions ); 
  426. $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions ); 
  427. $two = ''; 
  428. } else { 
  429. $two = '2'; 
  430.  
  431. if ( empty( $this->_actions ) ) 
  432. return; 
  433.  
  434. echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' . __( 'Select bulk action' ) . '</label>'; 
  435. echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n"; 
  436. echo '<option value="-1">' . __( 'Bulk Actions' ) . "</option>\n"; 
  437.  
  438. foreach ( $this->_actions as $name => $title ) { 
  439. $class = 'edit' === $name ? ' class="hide-if-no-js"' : ''; 
  440.  
  441. echo "\t" . '<option value="' . $name . '"' . $class . '>' . $title . "</option>\n"; 
  442.  
  443. echo "</select>\n"; 
  444.  
  445. submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) ); 
  446. echo "\n"; 
  447.  
  448. /** 
  449. * Get the current action selected from the bulk actions dropdown. 
  450. * 
  451. * @since 3.1.0 
  452. * @access public 
  453. * 
  454. * @return string|false The action name or False if no action was selected 
  455. */ 
  456. public function current_action() { 
  457. if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) 
  458. return false; 
  459.  
  460. if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) 
  461. return $_REQUEST['action']; 
  462.  
  463. if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] ) 
  464. return $_REQUEST['action2']; 
  465.  
  466. return false; 
  467.  
  468. /** 
  469. * Generate row actions div 
  470. * 
  471. * @since 3.1.0 
  472. * @access protected 
  473. * 
  474. * @param array $actions The list of actions 
  475. * @param bool $always_visible Whether the actions should be always visible 
  476. * @return string 
  477. */ 
  478. protected function row_actions( $actions, $always_visible = false ) { 
  479. $action_count = count( $actions ); 
  480. $i = 0; 
  481.  
  482. if ( !$action_count ) 
  483. return ''; 
  484.  
  485. $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">'; 
  486. foreach ( $actions as $action => $link ) { 
  487. ++$i; 
  488. ( $i == $action_count ) ? $sep = '' : $sep = ' | '; 
  489. $out .= "<span class='$action'>$link$sep</span>"; 
  490. $out .= '</div>'; 
  491.  
  492. $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>'; 
  493.  
  494. return $out; 
  495.  
  496. /** 
  497. * Display a monthly dropdown for filtering items 
  498. * 
  499. * @since 3.1.0 
  500. * @access protected 
  501. * 
  502. * @global wpdb $wpdb 
  503. * @global WP_Locale $wp_locale 
  504. * 
  505. * @param string $post_type 
  506. */ 
  507. protected function months_dropdown( $post_type ) { 
  508. global $wpdb, $wp_locale; 
  509.  
  510. /** 
  511. * Filters whether to remove the 'Months' drop-down from the post list table. 
  512. * 
  513. * @since 4.2.0 
  514. * 
  515. * @param bool $disable Whether to disable the drop-down. Default false. 
  516. * @param string $post_type The post type. 
  517. */ 
  518. if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) { 
  519. return; 
  520.  
  521. $extra_checks = "AND post_status != 'auto-draft'"; 
  522. if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) { 
  523. $extra_checks .= " AND post_status != 'trash'"; 
  524. } elseif ( isset( $_GET['post_status'] ) ) { 
  525. $extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] ); 
  526.  
  527. $months = $wpdb->get_results( $wpdb->prepare( " 
  528. SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month 
  529. FROM $wpdb->posts 
  530. WHERE post_type = %s 
  531. $extra_checks 
  532. ORDER BY post_date DESC 
  533. ", $post_type ) ); 
  534.  
  535. /** 
  536. * Filters the 'Months' drop-down results. 
  537. * 
  538. * @since 3.7.0 
  539. * 
  540. * @param object $months The months drop-down query results. 
  541. * @param string $post_type The post type. 
  542. */ 
  543. $months = apply_filters( 'months_dropdown_results', $months, $post_type ); 
  544.  
  545. $month_count = count( $months ); 
  546.  
  547. if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) 
  548. return; 
  549.  
  550. $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0; 
  551. ?> 
  552. <label for="filter-by-date" class="screen-reader-text"><?php _e( 'Filter by date' ); ?></label> 
  553. <select name="m" id="filter-by-date"> 
  554. <option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option> 
  555. <?php 
  556. foreach ( $months as $arc_row ) { 
  557. if ( 0 == $arc_row->year ) 
  558. continue; 
  559.  
  560. $month = zeroise( $arc_row->month, 2 ); 
  561. $year = $arc_row->year; 
  562.  
  563. printf( "<option %s value='%s'>%s</option>\n",  
  564. selected( $m, $year . $month, false ),  
  565. esc_attr( $arc_row->year . $month ),  
  566. /** translators: 1: month name, 2: 4-digit year */ 
  567. sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year ) 
  568. ); 
  569. ?> 
  570. </select> 
  571. <?php 
  572.  
  573. /** 
  574. * Display a view switcher 
  575. * 
  576. * @since 3.1.0 
  577. * @access protected 
  578. * 
  579. * @param string $current_mode 
  580. */ 
  581. protected function view_switcher( $current_mode ) { 
  582. ?> 
  583. <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" /> 
  584. <div class="view-switch"> 
  585. <?php 
  586. foreach ( $this->modes as $mode => $title ) { 
  587. $classes = array( 'view-' . $mode ); 
  588. if ( $current_mode === $mode ) 
  589. $classes[] = 'current'; 
  590. printf( 
  591. "<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",  
  592. esc_url( add_query_arg( 'mode', $mode ) ),  
  593. implode( ' ', $classes ),  
  594. $title 
  595. ); 
  596. ?> 
  597. </div> 
  598. <?php 
  599.  
  600. /** 
  601. * Display a comment count bubble 
  602. * 
  603. * @since 3.1.0 
  604. * @access protected 
  605. * 
  606. * @param int $post_id The post ID. 
  607. * @param int $pending_comments Number of pending comments. 
  608. */ 
  609. protected function comments_bubble( $post_id, $pending_comments ) { 
  610. $approved_comments = get_comments_number(); 
  611.  
  612. $approved_comments_number = number_format_i18n( $approved_comments ); 
  613. $pending_comments_number = number_format_i18n( $pending_comments ); 
  614.  
  615. $approved_only_phrase = sprintf( _n( '%s comment', '%s comments', $approved_comments ), $approved_comments_number ); 
  616. $approved_phrase = sprintf( _n( '%s approved comment', '%s approved comments', $approved_comments ), $approved_comments_number ); 
  617. $pending_phrase = sprintf( _n( '%s pending comment', '%s pending comments', $pending_comments ), $pending_comments_number ); 
  618.  
  619. // No comments at all. 
  620. if ( ! $approved_comments && ! $pending_comments ) { 
  621. printf( '<span aria-hidden="true">*</span><span class="screen-reader-text">%s</span>',  
  622. __( 'No comments' ) 
  623. ); 
  624. // Approved comments have different display depending on some conditions. 
  625. } elseif ( $approved_comments ) { 
  626. printf( '<a href="%s" class="post-com-count post-com-count-approved"><span class="comment-count-approved" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',  
  627. esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'approved' ), admin_url( 'edit-comments.php' ) ) ),  
  628. $approved_comments_number,  
  629. $pending_comments ? $approved_phrase : $approved_only_phrase 
  630. ); 
  631. } else { 
  632. printf( '<span class="post-com-count post-com-count-no-comments"><span class="comment-count comment-count-no-comments" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',  
  633. $approved_comments_number,  
  634. $pending_comments ? __( 'No approved comments' ) : __( 'No comments' ) 
  635. ); 
  636.  
  637. if ( $pending_comments ) { 
  638. printf( '<a href="%s" class="post-com-count post-com-count-pending"><span class="comment-count-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',  
  639. esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'moderated' ), admin_url( 'edit-comments.php' ) ) ),  
  640. $pending_comments_number,  
  641. $pending_phrase 
  642. ); 
  643. } else { 
  644. printf( '<span class="post-com-count post-com-count-pending post-com-count-no-pending"><span class="comment-count comment-count-no-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',  
  645. $pending_comments_number,  
  646. $approved_comments ? __( 'No pending comments' ) : __( 'No comments' ) 
  647. ); 
  648.  
  649. /** 
  650. * Get the current page number 
  651. * 
  652. * @since 3.1.0 
  653. * @access public 
  654. * 
  655. * @return int 
  656. */ 
  657. public function get_pagenum() { 
  658. $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0; 
  659.  
  660. if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) 
  661. $pagenum = $this->_pagination_args['total_pages']; 
  662.  
  663. return max( 1, $pagenum ); 
  664.  
  665. /** 
  666. * Get number of items to display on a single page 
  667. * 
  668. * @since 3.1.0 
  669. * @access protected 
  670. * 
  671. * @param string $option 
  672. * @param int $default 
  673. * @return int 
  674. */ 
  675. protected function get_items_per_page( $option, $default = 20 ) { 
  676. $per_page = (int) get_user_option( $option ); 
  677. if ( empty( $per_page ) || $per_page < 1 ) 
  678. $per_page = $default; 
  679.  
  680. /** 
  681. * Filters the number of items to be displayed on each page of the list table. 
  682. * 
  683. * The dynamic hook name, $option, refers to the `per_page` option depending 
  684. * on the type of list table in use. Possible values include: 'edit_comments_per_page',  
  685. * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',  
  686. * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',  
  687. * 'edit_{$post_type}_per_page', etc. 
  688. * 
  689. * @since 2.9.0 
  690. * 
  691. * @param int $per_page Number of items to be displayed. Default 20. 
  692. */ 
  693. return (int) apply_filters( $option, $per_page ); 
  694.  
  695. /** 
  696. * Display the pagination. 
  697. * 
  698. * @since 3.1.0 
  699. * @access protected 
  700. * 
  701. * @param string $which 
  702. */ 
  703. protected function pagination( $which ) { 
  704. if ( empty( $this->_pagination_args ) ) { 
  705. return; 
  706.  
  707. $total_items = $this->_pagination_args['total_items']; 
  708. $total_pages = $this->_pagination_args['total_pages']; 
  709. $infinite_scroll = false; 
  710. if ( isset( $this->_pagination_args['infinite_scroll'] ) ) { 
  711. $infinite_scroll = $this->_pagination_args['infinite_scroll']; 
  712.  
  713. if ( 'top' === $which && $total_pages > 1 ) { 
  714. $this->screen->render_screen_reader_content( 'heading_pagination' ); 
  715.  
  716. $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>'; 
  717.  
  718. $current = $this->get_pagenum(); 
  719. $removable_query_args = wp_removable_query_args(); 
  720.  
  721. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 
  722.  
  723. $current_url = remove_query_arg( $removable_query_args, $current_url ); 
  724.  
  725. $page_links = array(); 
  726.  
  727. $total_pages_before = '<span class="paging-input">'; 
  728. $total_pages_after = '</span></span>'; 
  729.  
  730. $disable_first = $disable_last = $disable_prev = $disable_next = false; 
  731.  
  732. if ( $current == 1 ) { 
  733. $disable_first = true; 
  734. $disable_prev = true; 
  735. if ( $current == 2 ) { 
  736. $disable_first = true; 
  737. if ( $current == $total_pages ) { 
  738. $disable_last = true; 
  739. $disable_next = true; 
  740. if ( $current == $total_pages - 1 ) { 
  741. $disable_last = true; 
  742.  
  743. if ( $disable_first ) { 
  744. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">«</span>'; 
  745. } else { 
  746. $page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  747. esc_url( remove_query_arg( 'paged', $current_url ) ),  
  748. __( 'First page' ),  
  749. '«' 
  750. ); 
  751.  
  752. if ( $disable_prev ) { 
  753. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">‹</span>'; 
  754. } else { 
  755. $page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  756. esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),  
  757. __( 'Previous page' ),  
  758. '‹' 
  759. ); 
  760.  
  761. if ( 'bottom' === $which ) { 
  762. $html_current_page = $current; 
  763. $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">'; 
  764. } else { 
  765. $html_current_page = sprintf( "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",  
  766. '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',  
  767. $current,  
  768. strlen( $total_pages ) 
  769. ); 
  770. $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) ); 
  771. $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after; 
  772.  
  773. if ( $disable_next ) { 
  774. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">›</span>'; 
  775. } else { 
  776. $page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  777. esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),  
  778. __( 'Next page' ),  
  779. '›' 
  780. ); 
  781.  
  782. if ( $disable_last ) { 
  783. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">»</span>'; 
  784. } else { 
  785. $page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  786. esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),  
  787. __( 'Last page' ),  
  788. '»' 
  789. ); 
  790.  
  791. $pagination_links_class = 'pagination-links'; 
  792. if ( ! empty( $infinite_scroll ) ) { 
  793. $pagination_links_class = ' hide-if-js'; 
  794. $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>'; 
  795.  
  796. if ( $total_pages ) { 
  797. $page_class = $total_pages < 2 ? ' one-page' : ''; 
  798. } else { 
  799. $page_class = ' no-pages'; 
  800. $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>"; 
  801.  
  802. echo $this->_pagination; 
  803.  
  804. /** 
  805. * Get a list of columns. The format is: 
  806. * 'internal-name' => 'Title' 
  807. * 
  808. * @since 3.1.0 
  809. * @access public 
  810. * @abstract 
  811. * 
  812. * @return array 
  813. */ 
  814. public function get_columns() { 
  815. die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' ); 
  816.  
  817. /** 
  818. * Get a list of sortable columns. The format is: 
  819. * 'internal-name' => 'orderby' 
  820. * or 
  821. * 'internal-name' => array( 'orderby', true ) 
  822. * 
  823. * The second format will make the initial sorting order be descending 
  824. * 
  825. * @since 3.1.0 
  826. * @access protected 
  827. * 
  828. * @return array 
  829. */ 
  830. protected function get_sortable_columns() { 
  831. return array(); 
  832.  
  833. /** 
  834. * Gets the name of the default primary column. 
  835. * 
  836. * @since 4.3.0 
  837. * @access protected 
  838. * 
  839. * @return string Name of the default primary column, in this case, an empty string. 
  840. */ 
  841. protected function get_default_primary_column_name() { 
  842. $columns = $this->get_columns(); 
  843. $column = ''; 
  844.  
  845. if ( empty( $columns ) ) { 
  846. return $column; 
  847.  
  848. // We need a primary defined so responsive views show something,  
  849. // so let's fall back to the first non-checkbox column. 
  850. foreach ( $columns as $col => $column_name ) { 
  851. if ( 'cb' === $col ) { 
  852. continue; 
  853.  
  854. $column = $col; 
  855. break; 
  856.  
  857. return $column; 
  858.  
  859. /** 
  860. * Public wrapper for WP_List_Table::get_default_primary_column_name(). 
  861. * 
  862. * @since 4.4.0 
  863. * @access public 
  864. * 
  865. * @return string Name of the default primary column. 
  866. */ 
  867. public function get_primary_column() { 
  868. return $this->get_primary_column_name(); 
  869.  
  870. /** 
  871. * Gets the name of the primary column. 
  872. * 
  873. * @since 4.3.0 
  874. * @access protected 
  875. * 
  876. * @return string The name of the primary column. 
  877. */ 
  878. protected function get_primary_column_name() { 
  879. $columns = get_column_headers( $this->screen ); 
  880. $default = $this->get_default_primary_column_name(); 
  881.  
  882. // If the primary column doesn't exist fall back to the 
  883. // first non-checkbox column. 
  884. if ( ! isset( $columns[ $default ] ) ) { 
  885. $default = WP_List_Table::get_default_primary_column_name(); 
  886.  
  887. /** 
  888. * Filters the name of the primary column for the current list table. 
  889. * 
  890. * @since 4.3.0 
  891. * 
  892. * @param string $default Column name default for the specific list table, e.g. 'name'. 
  893. * @param string $context Screen ID for specific list table, e.g. 'plugins'. 
  894. */ 
  895. $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id ); 
  896.  
  897. if ( empty( $column ) || ! isset( $columns[ $column ] ) ) { 
  898. $column = $default; 
  899.  
  900. return $column; 
  901.  
  902. /** 
  903. * Get a list of all, hidden and sortable columns, with filter applied 
  904. * 
  905. * @since 3.1.0 
  906. * @access protected 
  907. * 
  908. * @return array 
  909. */ 
  910. protected function get_column_info() { 
  911. // $_column_headers is already set / cached 
  912. if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) { 
  913. // Back-compat for list tables that have been manually setting $_column_headers for horse reasons. 
  914. // In 4.3, we added a fourth argument for primary column. 
  915. $column_headers = array( array(), array(), array(), $this->get_primary_column_name() ); 
  916. foreach ( $this->_column_headers as $key => $value ) { 
  917. $column_headers[ $key ] = $value; 
  918.  
  919. return $column_headers; 
  920.  
  921. $columns = get_column_headers( $this->screen ); 
  922. $hidden = get_hidden_columns( $this->screen ); 
  923.  
  924. $sortable_columns = $this->get_sortable_columns(); 
  925. /** 
  926. * Filters the list table sortable columns for a specific screen. 
  927. * 
  928. * The dynamic portion of the hook name, `$this->screen->id`, refers 
  929. * to the ID of the current screen, usually a string. 
  930. * 
  931. * @since 3.5.0 
  932. * 
  933. * @param array $sortable_columns An array of sortable columns. 
  934. */ 
  935. $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns ); 
  936.  
  937. $sortable = array(); 
  938. foreach ( $_sortable as $id => $data ) { 
  939. if ( empty( $data ) ) 
  940. continue; 
  941.  
  942. $data = (array) $data; 
  943. if ( !isset( $data[1] ) ) 
  944. $data[1] = false; 
  945.  
  946. $sortable[$id] = $data; 
  947.  
  948. $primary = $this->get_primary_column_name(); 
  949. $this->_column_headers = array( $columns, $hidden, $sortable, $primary ); 
  950.  
  951. return $this->_column_headers; 
  952.  
  953. /** 
  954. * Return number of visible columns 
  955. * 
  956. * @since 3.1.0 
  957. * @access public 
  958. * 
  959. * @return int 
  960. */ 
  961. public function get_column_count() { 
  962. list ( $columns, $hidden ) = $this->get_column_info(); 
  963. $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) ); 
  964. return count( $columns ) - count( $hidden ); 
  965.  
  966. /** 
  967. * Print column headers, accounting for hidden and sortable columns. 
  968. * 
  969. * @since 3.1.0 
  970. * @access public 
  971. * 
  972. * @staticvar int $cb_counter 
  973. * 
  974. * @param bool $with_id Whether to set the id attribute or not 
  975. */ 
  976. public function print_column_headers( $with_id = true ) { 
  977. list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 
  978.  
  979. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 
  980. $current_url = remove_query_arg( 'paged', $current_url ); 
  981.  
  982. if ( isset( $_GET['orderby'] ) ) { 
  983. $current_orderby = $_GET['orderby']; 
  984. } else { 
  985. $current_orderby = ''; 
  986.  
  987. if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) { 
  988. $current_order = 'desc'; 
  989. } else { 
  990. $current_order = 'asc'; 
  991.  
  992. if ( ! empty( $columns['cb'] ) ) { 
  993. static $cb_counter = 1; 
  994. $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>' 
  995. . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />'; 
  996. $cb_counter++; 
  997.  
  998. foreach ( $columns as $column_key => $column_display_name ) { 
  999. $class = array( 'manage-column', "column-$column_key" ); 
  1000.  
  1001. if ( in_array( $column_key, $hidden ) ) { 
  1002. $class[] = 'hidden'; 
  1003.  
  1004. if ( 'cb' === $column_key ) 
  1005. $class[] = 'check-column'; 
  1006. elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) 
  1007. $class[] = 'num'; 
  1008.  
  1009. if ( $column_key === $primary ) { 
  1010. $class[] = 'column-primary'; 
  1011.  
  1012. if ( isset( $sortable[$column_key] ) ) { 
  1013. list( $orderby, $desc_first ) = $sortable[$column_key]; 
  1014.  
  1015. if ( $current_orderby === $orderby ) { 
  1016. $order = 'asc' === $current_order ? 'desc' : 'asc'; 
  1017. $class[] = 'sorted'; 
  1018. $class[] = $current_order; 
  1019. } else { 
  1020. $order = $desc_first ? 'desc' : 'asc'; 
  1021. $class[] = 'sortable'; 
  1022. $class[] = $desc_first ? 'asc' : 'desc'; 
  1023.  
  1024. $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>'; 
  1025.  
  1026. $tag = ( 'cb' === $column_key ) ? 'td' : 'th'; 
  1027. $scope = ( 'th' === $tag ) ? 'scope="col"' : ''; 
  1028. $id = $with_id ? "id='$column_key'" : ''; 
  1029.  
  1030. if ( !empty( $class ) ) 
  1031. $class = "class='" . join( ' ', $class ) . "'"; 
  1032.  
  1033. echo "<$tag $scope $id $class>$column_display_name</$tag>"; 
  1034.  
  1035. /** 
  1036. * Display the table 
  1037. * 
  1038. * @since 3.1.0 
  1039. * @access public 
  1040. */ 
  1041. public function display() { 
  1042. $singular = $this->_args['singular']; 
  1043.  
  1044. $this->display_tablenav( 'top' ); 
  1045.  
  1046. $this->screen->render_screen_reader_content( 'heading_list' ); 
  1047. ?> 
  1048. <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>"> 
  1049. <thead> 
  1050. <tr> 
  1051. <?php $this->print_column_headers(); ?> 
  1052. </tr> 
  1053. </thead> 
  1054.  
  1055. <tbody id="the-list"<?php 
  1056. if ( $singular ) { 
  1057. echo " data-wp-lists='list:$singular'"; 
  1058. } ?>> 
  1059. <?php $this->display_rows_or_placeholder(); ?> 
  1060. </tbody> 
  1061.  
  1062. <tfoot> 
  1063. <tr> 
  1064. <?php $this->print_column_headers( false ); ?> 
  1065. </tr> 
  1066. </tfoot> 
  1067.  
  1068. </table> 
  1069. <?php 
  1070. $this->display_tablenav( 'bottom' ); 
  1071.  
  1072. /** 
  1073. * Get a list of CSS classes for the WP_List_Table table tag. 
  1074. * 
  1075. * @since 3.1.0 
  1076. * @access protected 
  1077. * 
  1078. * @return array List of CSS classes for the table tag. 
  1079. */ 
  1080. protected function get_table_classes() { 
  1081. return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] ); 
  1082.  
  1083. /** 
  1084. * Generate the table navigation above or below the table 
  1085. * 
  1086. * @since 3.1.0 
  1087. * @access protected 
  1088. * @param string $which 
  1089. */ 
  1090. protected function display_tablenav( $which ) { 
  1091. if ( 'top' === $which ) { 
  1092. wp_nonce_field( 'bulk-' . $this->_args['plural'] ); 
  1093. ?> 
  1094. <div class="tablenav <?php echo esc_attr( $which ); ?>"> 
  1095.  
  1096. <?php if ( $this->has_items() ): ?> 
  1097. <div class="alignleft actions bulkactions"> 
  1098. <?php $this->bulk_actions( $which ); ?> 
  1099. </div> 
  1100. <?php endif; 
  1101. $this->extra_tablenav( $which ); 
  1102. $this->pagination( $which ); 
  1103. ?> 
  1104.  
  1105. <br class="clear" /> 
  1106. </div> 
  1107. <?php 
  1108.  
  1109. /** 
  1110. * Extra controls to be displayed between bulk actions and pagination 
  1111. * 
  1112. * @since 3.1.0 
  1113. * @access protected 
  1114. * 
  1115. * @param string $which 
  1116. */ 
  1117. protected function extra_tablenav( $which ) {} 
  1118.  
  1119. /** 
  1120. * Generate the tbody element for the list table. 
  1121. * 
  1122. * @since 3.1.0 
  1123. * @access public 
  1124. */ 
  1125. public function display_rows_or_placeholder() { 
  1126. if ( $this->has_items() ) { 
  1127. $this->display_rows(); 
  1128. } else { 
  1129. echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">'; 
  1130. $this->no_items(); 
  1131. echo '</td></tr>'; 
  1132.  
  1133. /** 
  1134. * Generate the table rows 
  1135. * 
  1136. * @since 3.1.0 
  1137. * @access public 
  1138. */ 
  1139. public function display_rows() { 
  1140. foreach ( $this->items as $item ) 
  1141. $this->single_row( $item ); 
  1142.  
  1143. /** 
  1144. * Generates content for a single row of the table 
  1145. * 
  1146. * @since 3.1.0 
  1147. * @access public 
  1148. * 
  1149. * @param object $item The current item 
  1150. */ 
  1151. public function single_row( $item ) { 
  1152. echo '<tr>'; 
  1153. $this->single_row_columns( $item ); 
  1154. echo '</tr>'; 
  1155.  
  1156. /** 
  1157. * 
  1158. * @param object $item 
  1159. * @param string $column_name 
  1160. */ 
  1161. protected function column_default( $item, $column_name ) {} 
  1162.  
  1163. /** 
  1164. * 
  1165. * @param object $item 
  1166. */ 
  1167. protected function column_cb( $item ) {} 
  1168.  
  1169. /** 
  1170. * Generates the columns for a single row of the table 
  1171. * 
  1172. * @since 3.1.0 
  1173. * @access protected 
  1174. * 
  1175. * @param object $item The current item 
  1176. */ 
  1177. protected function single_row_columns( $item ) { 
  1178. list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 
  1179.  
  1180. foreach ( $columns as $column_name => $column_display_name ) { 
  1181. $classes = "$column_name column-$column_name"; 
  1182. if ( $primary === $column_name ) { 
  1183. $classes .= ' has-row-actions column-primary'; 
  1184.  
  1185. if ( in_array( $column_name, $hidden ) ) { 
  1186. $classes .= ' hidden'; 
  1187.  
  1188. // Comments column uses HTML in the display name with screen reader text. 
  1189. // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string. 
  1190. $data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"'; 
  1191.  
  1192. $attributes = "class='$classes' $data"; 
  1193.  
  1194. if ( 'cb' === $column_name ) { 
  1195. echo '<th scope="row" class="check-column">'; 
  1196. echo $this->column_cb( $item ); 
  1197. echo '</th>'; 
  1198. } elseif ( method_exists( $this, '_column_' . $column_name ) ) { 
  1199. echo call_user_func( 
  1200. array( $this, '_column_' . $column_name ),  
  1201. $item,  
  1202. $classes,  
  1203. $data,  
  1204. $primary 
  1205. ); 
  1206. } elseif ( method_exists( $this, 'column_' . $column_name ) ) { 
  1207. echo "<td $attributes>"; 
  1208. echo call_user_func( array( $this, 'column_' . $column_name ), $item ); 
  1209. echo $this->handle_row_actions( $item, $column_name, $primary ); 
  1210. echo "</td>"; 
  1211. } else { 
  1212. echo "<td $attributes>"; 
  1213. echo $this->column_default( $item, $column_name ); 
  1214. echo $this->handle_row_actions( $item, $column_name, $primary ); 
  1215. echo "</td>"; 
  1216.  
  1217. /** 
  1218. * Generates and display row actions links for the list table. 
  1219. * 
  1220. * @since 4.3.0 
  1221. * @access protected 
  1222. * 
  1223. * @param object $item The item being acted upon. 
  1224. * @param string $column_name Current column name. 
  1225. * @param string $primary Primary column name. 
  1226. * @return string The row actions HTML, or an empty string if the current column is the primary column. 
  1227. */ 
  1228. protected function handle_row_actions( $item, $column_name, $primary ) { 
  1229. return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : ''; 
  1230.  
  1231. /** 
  1232. * Handle an incoming ajax request (called from admin-ajax.php) 
  1233. * 
  1234. * @since 3.1.0 
  1235. * @access public 
  1236. */ 
  1237. public function ajax_response() { 
  1238. $this->prepare_items(); 
  1239.  
  1240. ob_start(); 
  1241. if ( ! empty( $_REQUEST['no_placeholder'] ) ) { 
  1242. $this->display_rows(); 
  1243. } else { 
  1244. $this->display_rows_or_placeholder(); 
  1245.  
  1246. $rows = ob_get_clean(); 
  1247.  
  1248. $response = array( 'rows' => $rows ); 
  1249.  
  1250. if ( isset( $this->_pagination_args['total_items'] ) ) { 
  1251. $response['total_items_i18n'] = sprintf( 
  1252. _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),  
  1253. number_format_i18n( $this->_pagination_args['total_items'] ) 
  1254. ); 
  1255. if ( isset( $this->_pagination_args['total_pages'] ) ) { 
  1256. $response['total_pages'] = $this->_pagination_args['total_pages']; 
  1257. $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] ); 
  1258.  
  1259. die( wp_json_encode( $response ) ); 
  1260.  
  1261. /** 
  1262. * Send required variables to JavaScript land 
  1263. * 
  1264. * @access public 
  1265. */ 
  1266. public function _js_vars() { 
  1267. $args = array( 
  1268. 'class' => get_class( $this ),  
  1269. 'screen' => array( 
  1270. 'id' => $this->screen->id,  
  1271. 'base' => $this->screen->base,  
  1272. ); 
  1273.  
  1274. printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) ); 
.