MS_Helper_ListTable

Base class for displaying a list of items in an ajaxified HTML table.

Defined (1)

The class is defined in the following location(s).

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