/app/helper/class-ms-helper-listtable.php

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