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