/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() && ! wp_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, '', '', 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. $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. $two = ''; 
  427. } else { 
  428. $two = '2'; 
  429.  
  430. if ( empty( $this->_actions ) ) 
  431. return; 
  432.  
  433. echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' . __( 'Select bulk action' ) . '</label>'; 
  434. echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n"; 
  435. echo '<option value="-1">' . __( 'Bulk Actions' ) . "</option>\n"; 
  436.  
  437. foreach ( $this->_actions as $name => $title ) { 
  438. $class = 'edit' === $name ? ' class="hide-if-no-js"' : ''; 
  439.  
  440. echo "\t" . '<option value="' . $name . '"' . $class . '>' . $title . "</option>\n"; 
  441.  
  442. echo "</select>\n"; 
  443.  
  444. submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) ); 
  445. echo "\n"; 
  446.  
  447. /** 
  448. * Get the current action selected from the bulk actions dropdown. 
  449. * 
  450. * @since 3.1.0 
  451. * @access public 
  452. * 
  453. * @return string|false The action name or False if no action was selected 
  454. */ 
  455. public function current_action() { 
  456. if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) 
  457. return false; 
  458.  
  459. if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) 
  460. return $_REQUEST['action']; 
  461.  
  462. if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] ) 
  463. return $_REQUEST['action2']; 
  464.  
  465. return false; 
  466.  
  467. /** 
  468. * Generate row actions div 
  469. * 
  470. * @since 3.1.0 
  471. * @access protected 
  472. * 
  473. * @param array $actions The list of actions 
  474. * @param bool $always_visible Whether the actions should be always visible 
  475. * @return string 
  476. */ 
  477. protected function row_actions( $actions, $always_visible = false ) { 
  478. $action_count = count( $actions ); 
  479. $i = 0; 
  480.  
  481. if ( !$action_count ) 
  482. return ''; 
  483.  
  484. $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">'; 
  485. foreach ( $actions as $action => $link ) { 
  486. ++$i; 
  487. ( $i == $action_count ) ? $sep = '' : $sep = ' | '; 
  488. $out .= "<span class='$action'>$link$sep</span>"; 
  489. $out .= '</div>'; 
  490.  
  491. $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>'; 
  492.  
  493. return $out; 
  494.  
  495. /** 
  496. * Display a monthly dropdown for filtering items 
  497. * 
  498. * @since 3.1.0 
  499. * @access protected 
  500. * 
  501. * @global wpdb $wpdb 
  502. * @global WP_Locale $wp_locale 
  503. * 
  504. * @param string $post_type 
  505. */ 
  506. protected function months_dropdown( $post_type ) { 
  507. global $wpdb, $wp_locale; 
  508.  
  509. /** 
  510. * Filters whether to remove the 'Months' drop-down from the post list table. 
  511. * 
  512. * @since 4.2.0 
  513. * 
  514. * @param bool $disable Whether to disable the drop-down. Default false. 
  515. * @param string $post_type The post type. 
  516. */ 
  517. if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) { 
  518. return; 
  519.  
  520. $extra_checks = "AND post_status != 'auto-draft'"; 
  521. if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) { 
  522. $extra_checks .= " AND post_status != 'trash'"; 
  523. } elseif ( isset( $_GET['post_status'] ) ) { 
  524. $extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] ); 
  525.  
  526. $months = $wpdb->get_results( $wpdb->prepare( " 
  527. SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month 
  528. FROM $wpdb->posts 
  529. WHERE post_type = %s 
  530. $extra_checks 
  531. ORDER BY post_date DESC 
  532. ", $post_type ) ); 
  533.  
  534. /** 
  535. * Filters the 'Months' drop-down results. 
  536. * 
  537. * @since 3.7.0 
  538. * 
  539. * @param object $months The months drop-down query results. 
  540. * @param string $post_type The post type. 
  541. */ 
  542. $months = apply_filters( 'months_dropdown_results', $months, $post_type ); 
  543.  
  544. $month_count = count( $months ); 
  545.  
  546. if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) 
  547. return; 
  548.  
  549. $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0; 
  550. ?> 
  551. <label for="filter-by-date" class="screen-reader-text"><?php _e( 'Filter by date' ); ?></label> 
  552. <select name="m" id="filter-by-date"> 
  553. <option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option> 
  554. <?php 
  555. foreach ( $months as $arc_row ) { 
  556. if ( 0 == $arc_row->year ) 
  557. continue; 
  558.  
  559. $month = zeroise( $arc_row->month, 2 ); 
  560. $year = $arc_row->year; 
  561.  
  562. printf( "<option %s value='%s'>%s</option>\n",  
  563. selected( $m, $year . $month, false ),  
  564. esc_attr( $arc_row->year . $month ),  
  565. /** translators: 1: month name, 2: 4-digit year */ 
  566. sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year ) 
  567. ); 
  568. ?> 
  569. </select> 
  570. <?php 
  571.  
  572. /** 
  573. * Display a view switcher 
  574. * 
  575. * @since 3.1.0 
  576. * @access protected 
  577. * 
  578. * @param string $current_mode 
  579. */ 
  580. protected function view_switcher( $current_mode ) { 
  581. ?> 
  582. <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" /> 
  583. <div class="view-switch"> 
  584. <?php 
  585. foreach ( $this->modes as $mode => $title ) { 
  586. $classes = array( 'view-' . $mode ); 
  587. if ( $current_mode === $mode ) 
  588. $classes[] = 'current'; 
  589. printf( 
  590. "<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",  
  591. esc_url( add_query_arg( 'mode', $mode ) ),  
  592. implode( ' ', $classes ),  
  593. $title 
  594. ); 
  595. ?> 
  596. </div> 
  597. <?php 
  598.  
  599. /** 
  600. * Display a comment count bubble 
  601. * 
  602. * @since 3.1.0 
  603. * @access protected 
  604. * 
  605. * @param int $post_id The post ID. 
  606. * @param int $pending_comments Number of pending comments. 
  607. */ 
  608. protected function comments_bubble( $post_id, $pending_comments ) { 
  609. $approved_comments = get_comments_number(); 
  610.  
  611. $approved_comments_number = number_format_i18n( $approved_comments ); 
  612. $pending_comments_number = number_format_i18n( $pending_comments ); 
  613.  
  614. $approved_only_phrase = sprintf( _n( '%s comment', '%s comments', $approved_comments ), $approved_comments_number ); 
  615. $approved_phrase = sprintf( _n( '%s approved comment', '%s approved comments', $approved_comments ), $approved_comments_number ); 
  616. $pending_phrase = sprintf( _n( '%s pending comment', '%s pending comments', $pending_comments ), $pending_comments_number ); 
  617.  
  618. // No comments at all. 
  619. if ( ! $approved_comments && ! $pending_comments ) { 
  620. printf( '<span aria-hidden="true">*</span><span class="screen-reader-text">%s</span>',  
  621. __( 'No comments' ) 
  622. ); 
  623. // Approved comments have different display depending on some conditions. 
  624. } elseif ( $approved_comments ) { 
  625. 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>',  
  626. esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'approved' ), admin_url( 'edit-comments.php' ) ) ),  
  627. $approved_comments_number,  
  628. $pending_comments ? $approved_phrase : $approved_only_phrase 
  629. ); 
  630. } else { 
  631. 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>',  
  632. $approved_comments_number,  
  633. $pending_comments ? __( 'No approved comments' ) : __( 'No comments' ) 
  634. ); 
  635.  
  636. if ( $pending_comments ) { 
  637. 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>',  
  638. esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'moderated' ), admin_url( 'edit-comments.php' ) ) ),  
  639. $pending_comments_number,  
  640. $pending_phrase 
  641. ); 
  642. } else { 
  643. 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>',  
  644. $pending_comments_number,  
  645. $approved_comments ? __( 'No pending comments' ) : __( 'No comments' ) 
  646. ); 
  647.  
  648. /** 
  649. * Get the current page number 
  650. * 
  651. * @since 3.1.0 
  652. * @access public 
  653. * 
  654. * @return int 
  655. */ 
  656. public function get_pagenum() { 
  657. $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0; 
  658.  
  659. if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) 
  660. $pagenum = $this->_pagination_args['total_pages']; 
  661.  
  662. return max( 1, $pagenum ); 
  663.  
  664. /** 
  665. * Get number of items to display on a single page 
  666. * 
  667. * @since 3.1.0 
  668. * @access protected 
  669. * 
  670. * @param string $option 
  671. * @param int $default 
  672. * @return int 
  673. */ 
  674. protected function get_items_per_page( $option, $default = 20 ) { 
  675. $per_page = (int) get_user_option( $option ); 
  676. if ( empty( $per_page ) || $per_page < 1 ) 
  677. $per_page = $default; 
  678.  
  679. /** 
  680. * Filters the number of items to be displayed on each page of the list table. 
  681. * 
  682. * The dynamic hook name, $option, refers to the `per_page` option depending 
  683. * on the type of list table in use. Possible values include: 'edit_comments_per_page',  
  684. * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',  
  685. * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',  
  686. * 'edit_{$post_type}_per_page', etc. 
  687. * 
  688. * @since 2.9.0 
  689. * 
  690. * @param int $per_page Number of items to be displayed. Default 20. 
  691. */ 
  692. return (int) apply_filters( $option, $per_page ); 
  693.  
  694. /** 
  695. * Display the pagination. 
  696. * 
  697. * @since 3.1.0 
  698. * @access protected 
  699. * 
  700. * @param string $which 
  701. */ 
  702. protected function pagination( $which ) { 
  703. if ( empty( $this->_pagination_args ) ) { 
  704. return; 
  705.  
  706. $total_items = $this->_pagination_args['total_items']; 
  707. $total_pages = $this->_pagination_args['total_pages']; 
  708. $infinite_scroll = false; 
  709. if ( isset( $this->_pagination_args['infinite_scroll'] ) ) { 
  710. $infinite_scroll = $this->_pagination_args['infinite_scroll']; 
  711.  
  712. if ( 'top' === $which && $total_pages > 1 ) { 
  713. $this->screen->render_screen_reader_content( 'heading_pagination' ); 
  714.  
  715. $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>'; 
  716.  
  717. $current = $this->get_pagenum(); 
  718. $removable_query_args = wp_removable_query_args(); 
  719.  
  720. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 
  721.  
  722. $current_url = remove_query_arg( $removable_query_args, $current_url ); 
  723.  
  724. $page_links = array(); 
  725.  
  726. $total_pages_before = '<span class="paging-input">'; 
  727. $total_pages_after = '</span></span>'; 
  728.  
  729. $disable_first = $disable_last = $disable_prev = $disable_next = false; 
  730.  
  731. if ( $current == 1 ) { 
  732. $disable_first = true; 
  733. $disable_prev = true; 
  734. if ( $current == 2 ) { 
  735. $disable_first = true; 
  736. if ( $current == $total_pages ) { 
  737. $disable_last = true; 
  738. $disable_next = true; 
  739. if ( $current == $total_pages - 1 ) { 
  740. $disable_last = true; 
  741.  
  742. if ( $disable_first ) { 
  743. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">«</span>'; 
  744. } else { 
  745. $page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  746. esc_url( remove_query_arg( 'paged', $current_url ) ),  
  747. __( 'First page' ),  
  748. '«' 
  749. ); 
  750.  
  751. if ( $disable_prev ) { 
  752. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">‹</span>'; 
  753. } else { 
  754. $page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  755. esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),  
  756. __( 'Previous page' ),  
  757. '‹' 
  758. ); 
  759.  
  760. if ( 'bottom' === $which ) { 
  761. $html_current_page = $current; 
  762. $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">'; 
  763. } else { 
  764. $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'>",  
  765. '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',  
  766. $current,  
  767. strlen( $total_pages ) 
  768. ); 
  769. $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) ); 
  770. $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after; 
  771.  
  772. if ( $disable_next ) { 
  773. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">›</span>'; 
  774. } else { 
  775. $page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  776. esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),  
  777. __( 'Next page' ),  
  778. '›' 
  779. ); 
  780.  
  781. if ( $disable_last ) { 
  782. $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">»</span>'; 
  783. } else { 
  784. $page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",  
  785. esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),  
  786. __( 'Last page' ),  
  787. '»' 
  788. ); 
  789.  
  790. $pagination_links_class = 'pagination-links'; 
  791. if ( ! empty( $infinite_scroll ) ) { 
  792. $pagination_links_class = ' hide-if-js'; 
  793. $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>'; 
  794.  
  795. if ( $total_pages ) { 
  796. $page_class = $total_pages < 2 ? ' one-page' : ''; 
  797. } else { 
  798. $page_class = ' no-pages'; 
  799. $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>"; 
  800.  
  801. echo $this->_pagination; 
  802.  
  803. /** 
  804. * Get a list of columns. The format is: 
  805. * 'internal-name' => 'Title' 
  806. * 
  807. * @since 3.1.0 
  808. * @access public 
  809. * @abstract 
  810. * 
  811. * @return array 
  812. */ 
  813. public function get_columns() { 
  814. die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' ); 
  815.  
  816. /** 
  817. * Get a list of sortable columns. The format is: 
  818. * 'internal-name' => 'orderby' 
  819. * or 
  820. * 'internal-name' => array( 'orderby', true ) 
  821. * 
  822. * The second format will make the initial sorting order be descending 
  823. * 
  824. * @since 3.1.0 
  825. * @access protected 
  826. * 
  827. * @return array 
  828. */ 
  829. protected function get_sortable_columns() { 
  830. return array(); 
  831.  
  832. /** 
  833. * Gets the name of the default primary column. 
  834. * 
  835. * @since 4.3.0 
  836. * @access protected 
  837. * 
  838. * @return string Name of the default primary column, in this case, an empty string. 
  839. */ 
  840. protected function get_default_primary_column_name() { 
  841. $columns = $this->get_columns(); 
  842. $column = ''; 
  843.  
  844. if ( empty( $columns ) ) { 
  845. return $column; 
  846.  
  847. // We need a primary defined so responsive views show something,  
  848. // so let's fall back to the first non-checkbox column. 
  849. foreach ( $columns as $col => $column_name ) { 
  850. if ( 'cb' === $col ) { 
  851. continue; 
  852.  
  853. $column = $col; 
  854. break; 
  855.  
  856. return $column; 
  857.  
  858. /** 
  859. * Public wrapper for WP_List_Table::get_default_primary_column_name(). 
  860. * 
  861. * @since 4.4.0 
  862. * @access public 
  863. * 
  864. * @return string Name of the default primary column. 
  865. */ 
  866. public function get_primary_column() { 
  867. return $this->get_primary_column_name(); 
  868.  
  869. /** 
  870. * Gets the name of the primary column. 
  871. * 
  872. * @since 4.3.0 
  873. * @access protected 
  874. * 
  875. * @return string The name of the primary column. 
  876. */ 
  877. protected function get_primary_column_name() { 
  878. $columns = get_column_headers( $this->screen ); 
  879. $default = $this->get_default_primary_column_name(); 
  880.  
  881. // If the primary column doesn't exist fall back to the 
  882. // first non-checkbox column. 
  883. if ( ! isset( $columns[ $default ] ) ) { 
  884. $default = WP_List_Table::get_default_primary_column_name(); 
  885.  
  886. /** 
  887. * Filters the name of the primary column for the current list table. 
  888. * 
  889. * @since 4.3.0 
  890. * 
  891. * @param string $default Column name default for the specific list table, e.g. 'name'. 
  892. * @param string $context Screen ID for specific list table, e.g. 'plugins'. 
  893. */ 
  894. $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id ); 
  895.  
  896. if ( empty( $column ) || ! isset( $columns[ $column ] ) ) { 
  897. $column = $default; 
  898.  
  899. return $column; 
  900.  
  901. /** 
  902. * Get a list of all, hidden and sortable columns, with filter applied 
  903. * 
  904. * @since 3.1.0 
  905. * @access protected 
  906. * 
  907. * @return array 
  908. */ 
  909. protected function get_column_info() { 
  910. // $_column_headers is already set / cached 
  911. if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) { 
  912. // Back-compat for list tables that have been manually setting $_column_headers for horse reasons. 
  913. // In 4.3, we added a fourth argument for primary column. 
  914. $column_headers = array( array(), array(), array(), $this->get_primary_column_name() ); 
  915. foreach ( $this->_column_headers as $key => $value ) { 
  916. $column_headers[ $key ] = $value; 
  917.  
  918. return $column_headers; 
  919.  
  920. $columns = get_column_headers( $this->screen ); 
  921. $hidden = get_hidden_columns( $this->screen ); 
  922.  
  923. $sortable_columns = $this->get_sortable_columns(); 
  924. /** 
  925. * Filters the list table sortable columns for a specific screen. 
  926. * 
  927. * The dynamic portion of the hook name, `$this->screen->id`, refers 
  928. * to the ID of the current screen, usually a string. 
  929. * 
  930. * @since 3.5.0 
  931. * 
  932. * @param array $sortable_columns An array of sortable columns. 
  933. */ 
  934. $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns ); 
  935.  
  936. $sortable = array(); 
  937. foreach ( $_sortable as $id => $data ) { 
  938. if ( empty( $data ) ) 
  939. continue; 
  940.  
  941. $data = (array) $data; 
  942. if ( !isset( $data[1] ) ) 
  943. $data[1] = false; 
  944.  
  945. $sortable[$id] = $data; 
  946.  
  947. $primary = $this->get_primary_column_name(); 
  948. $this->_column_headers = array( $columns, $hidden, $sortable, $primary ); 
  949.  
  950. return $this->_column_headers; 
  951.  
  952. /** 
  953. * Return number of visible columns 
  954. * 
  955. * @since 3.1.0 
  956. * @access public 
  957. * 
  958. * @return int 
  959. */ 
  960. public function get_column_count() { 
  961. list ( $columns, $hidden ) = $this->get_column_info(); 
  962. $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) ); 
  963. return count( $columns ) - count( $hidden ); 
  964.  
  965. /** 
  966. * Print column headers, accounting for hidden and sortable columns. 
  967. * 
  968. * @since 3.1.0 
  969. * @access public 
  970. * 
  971. * @staticvar int $cb_counter 
  972. * 
  973. * @param bool $with_id Whether to set the id attribute or not 
  974. */ 
  975. public function print_column_headers( $with_id = true ) { 
  976. list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 
  977.  
  978. $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 
  979. $current_url = remove_query_arg( 'paged', $current_url ); 
  980.  
  981. if ( isset( $_GET['orderby'] ) ) { 
  982. $current_orderby = $_GET['orderby']; 
  983. } else { 
  984. $current_orderby = ''; 
  985.  
  986. if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) { 
  987. $current_order = 'desc'; 
  988. } else { 
  989. $current_order = 'asc'; 
  990.  
  991. if ( ! empty( $columns['cb'] ) ) { 
  992. static $cb_counter = 1; 
  993. $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>' 
  994. . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />'; 
  995. $cb_counter++; 
  996.  
  997. foreach ( $columns as $column_key => $column_display_name ) { 
  998. $class = array( 'manage-column', "column-$column_key" ); 
  999.  
  1000. if ( in_array( $column_key, $hidden ) ) { 
  1001. $class[] = 'hidden'; 
  1002.  
  1003. if ( 'cb' === $column_key ) 
  1004. $class[] = 'check-column'; 
  1005. elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) 
  1006. $class[] = 'num'; 
  1007.  
  1008. if ( $column_key === $primary ) { 
  1009. $class[] = 'column-primary'; 
  1010.  
  1011. if ( isset( $sortable[$column_key] ) ) { 
  1012. list( $orderby, $desc_first ) = $sortable[$column_key]; 
  1013.  
  1014. if ( $current_orderby === $orderby ) { 
  1015. $order = 'asc' === $current_order ? 'desc' : 'asc'; 
  1016. $class[] = 'sorted'; 
  1017. $class[] = $current_order; 
  1018. } else { 
  1019. $order = $desc_first ? 'desc' : 'asc'; 
  1020. $class[] = 'sortable'; 
  1021. $class[] = $desc_first ? 'asc' : 'desc'; 
  1022.  
  1023. $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>'; 
  1024.  
  1025. $tag = ( 'cb' === $column_key ) ? 'td' : 'th'; 
  1026. $scope = ( 'th' === $tag ) ? 'scope="col"' : ''; 
  1027. $id = $with_id ? "id='$column_key'" : ''; 
  1028.  
  1029. if ( !empty( $class ) ) 
  1030. $class = "class='" . join( ' ', $class ) . "'"; 
  1031.  
  1032. echo "<$tag $scope $id $class>$column_display_name</$tag>"; 
  1033.  
  1034. /** 
  1035. * Display the table 
  1036. * 
  1037. * @since 3.1.0 
  1038. * @access public 
  1039. */ 
  1040. public function display() { 
  1041. $singular = $this->_args['singular']; 
  1042.  
  1043. $this->display_tablenav( 'top' ); 
  1044.  
  1045. $this->screen->render_screen_reader_content( 'heading_list' ); 
  1046. ?> 
  1047. <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>"> 
  1048. <thead> 
  1049. <tr> 
  1050. <?php $this->print_column_headers(); ?> 
  1051. </tr> 
  1052. </thead> 
  1053.  
  1054. <tbody id="the-list"<?php 
  1055. if ( $singular ) { 
  1056. echo " data-wp-lists='list:$singular'"; 
  1057. } ?>> 
  1058. <?php $this->display_rows_or_placeholder(); ?> 
  1059. </tbody> 
  1060.  
  1061. <tfoot> 
  1062. <tr> 
  1063. <?php $this->print_column_headers( false ); ?> 
  1064. </tr> 
  1065. </tfoot> 
  1066.  
  1067. </table> 
  1068. <?php 
  1069. $this->display_tablenav( 'bottom' ); 
  1070.  
  1071. /** 
  1072. * Get a list of CSS classes for the WP_List_Table table tag. 
  1073. * 
  1074. * @since 3.1.0 
  1075. * @access protected 
  1076. * 
  1077. * @return array List of CSS classes for the table tag. 
  1078. */ 
  1079. protected function get_table_classes() { 
  1080. return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] ); 
  1081.  
  1082. /** 
  1083. * Generate the table navigation above or below the table 
  1084. * 
  1085. * @since 3.1.0 
  1086. * @access protected 
  1087. * @param string $which 
  1088. */ 
  1089. protected function display_tablenav( $which ) { 
  1090. if ( 'top' === $which ) { 
  1091. wp_nonce_field( 'bulk-' . $this->_args['plural'] ); 
  1092. ?> 
  1093. <div class="tablenav <?php echo esc_attr( $which ); ?>"> 
  1094.  
  1095. <?php if ( $this->has_items() ): ?> 
  1096. <div class="alignleft actions bulkactions"> 
  1097. <?php $this->bulk_actions( $which ); ?> 
  1098. </div> 
  1099. <?php endif; 
  1100. $this->extra_tablenav( $which ); 
  1101. $this->pagination( $which ); 
  1102. ?> 
  1103.  
  1104. <br class="clear" /> 
  1105. </div> 
  1106. <?php 
  1107.  
  1108. /** 
  1109. * Extra controls to be displayed between bulk actions and pagination 
  1110. * 
  1111. * @since 3.1.0 
  1112. * @access protected 
  1113. * 
  1114. * @param string $which 
  1115. */ 
  1116. protected function extra_tablenav( $which ) {} 
  1117.  
  1118. /** 
  1119. * Generate the tbody element for the list table. 
  1120. * 
  1121. * @since 3.1.0 
  1122. * @access public 
  1123. */ 
  1124. public function display_rows_or_placeholder() { 
  1125. if ( $this->has_items() ) { 
  1126. $this->display_rows(); 
  1127. } else { 
  1128. echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">'; 
  1129. $this->no_items(); 
  1130. echo '</td></tr>'; 
  1131.  
  1132. /** 
  1133. * Generate the table rows 
  1134. * 
  1135. * @since 3.1.0 
  1136. * @access public 
  1137. */ 
  1138. public function display_rows() { 
  1139. foreach ( $this->items as $item ) 
  1140. $this->single_row( $item ); 
  1141.  
  1142. /** 
  1143. * Generates content for a single row of the table 
  1144. * 
  1145. * @since 3.1.0 
  1146. * @access public 
  1147. * 
  1148. * @param object $item The current item 
  1149. */ 
  1150. public function single_row( $item ) { 
  1151. echo '<tr>'; 
  1152. $this->single_row_columns( $item ); 
  1153. echo '</tr>'; 
  1154.  
  1155. /** 
  1156. * 
  1157. * @param object $item 
  1158. * @param string $column_name 
  1159. */ 
  1160. protected function column_default( $item, $column_name ) {} 
  1161.  
  1162. /** 
  1163. * 
  1164. * @param object $item 
  1165. */ 
  1166. protected function column_cb( $item ) {} 
  1167.  
  1168. /** 
  1169. * Generates the columns for a single row of the table 
  1170. * 
  1171. * @since 3.1.0 
  1172. * @access protected 
  1173. * 
  1174. * @param object $item The current item 
  1175. */ 
  1176. protected function single_row_columns( $item ) { 
  1177. list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 
  1178.  
  1179. foreach ( $columns as $column_name => $column_display_name ) { 
  1180. $classes = "$column_name column-$column_name"; 
  1181. if ( $primary === $column_name ) { 
  1182. $classes .= ' has-row-actions column-primary'; 
  1183.  
  1184. if ( in_array( $column_name, $hidden ) ) { 
  1185. $classes .= ' hidden'; 
  1186.  
  1187. // Comments column uses HTML in the display name with screen reader text. 
  1188. // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string. 
  1189. $data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"'; 
  1190.  
  1191. $attributes = "class='$classes' $data"; 
  1192.  
  1193. if ( 'cb' === $column_name ) { 
  1194. echo '<th scope="row" class="check-column">'; 
  1195. echo $this->column_cb( $item ); 
  1196. echo '</th>'; 
  1197. } elseif ( method_exists( $this, '_column_' . $column_name ) ) { 
  1198. echo call_user_func( 
  1199. array( $this, '_column_' . $column_name ),  
  1200. $item,  
  1201. $classes,  
  1202. $data,  
  1203. $primary 
  1204. ); 
  1205. } elseif ( method_exists( $this, 'column_' . $column_name ) ) { 
  1206. echo "<td $attributes>"; 
  1207. echo call_user_func( array( $this, 'column_' . $column_name ), $item ); 
  1208. echo $this->handle_row_actions( $item, $column_name, $primary ); 
  1209. echo "</td>"; 
  1210. } else { 
  1211. echo "<td $attributes>"; 
  1212. echo $this->column_default( $item, $column_name ); 
  1213. echo $this->handle_row_actions( $item, $column_name, $primary ); 
  1214. echo "</td>"; 
  1215.  
  1216. /** 
  1217. * Generates and display row actions links for the list table. 
  1218. * 
  1219. * @since 4.3.0 
  1220. * @access protected 
  1221. * 
  1222. * @param object $item The item being acted upon. 
  1223. * @param string $column_name Current column name. 
  1224. * @param string $primary Primary column name. 
  1225. * @return string The row actions HTML, or an empty string if the current column is the primary column. 
  1226. */ 
  1227. protected function handle_row_actions( $item, $column_name, $primary ) { 
  1228. return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : ''; 
  1229.  
  1230. /** 
  1231. * Handle an incoming ajax request (called from admin-ajax.php) 
  1232. * 
  1233. * @since 3.1.0 
  1234. * @access public 
  1235. */ 
  1236. public function ajax_response() { 
  1237. $this->prepare_items(); 
  1238.  
  1239. ob_start(); 
  1240. if ( ! empty( $_REQUEST['no_placeholder'] ) ) { 
  1241. $this->display_rows(); 
  1242. } else { 
  1243. $this->display_rows_or_placeholder(); 
  1244.  
  1245. $rows = ob_get_clean(); 
  1246.  
  1247. $response = array( 'rows' => $rows ); 
  1248.  
  1249. if ( isset( $this->_pagination_args['total_items'] ) ) { 
  1250. $response['total_items_i18n'] = sprintf( 
  1251. _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),  
  1252. number_format_i18n( $this->_pagination_args['total_items'] ) 
  1253. ); 
  1254. if ( isset( $this->_pagination_args['total_pages'] ) ) { 
  1255. $response['total_pages'] = $this->_pagination_args['total_pages']; 
  1256. $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] ); 
  1257.  
  1258. die( wp_json_encode( $response ) ); 
  1259.  
  1260. /** 
  1261. * Send required variables to JavaScript land 
  1262. * 
  1263. * @access public 
  1264. */ 
  1265. public function _js_vars() { 
  1266. $args = array( 
  1267. 'class' => get_class( $this ),  
  1268. 'screen' => array( 
  1269. 'id' => $this->screen->id,  
  1270. 'base' => $this->screen->base,  
  1271. ); 
  1272.  
  1273. printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) ); 
.