PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.47
Code Manager v1.0.47
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 6 days ago Code_Manager_Dashboard.php 6 days ago Code_Manager_Export.php 6 days ago Code_Manager_Form.php 6 days ago Code_Manager_Import.php 6 days ago Code_Manager_Import_File.php 6 days ago Code_Manager_List.php 6 days ago Code_Manager_List_View.php 6 days ago Code_Manager_Model.php 6 days ago Code_Manager_Preview.php 6 days ago Code_Manager_Settings.php 6 days ago Code_Manager_Tabs.php 6 days ago Message_Box.php 6 days ago WP_List_Table.php 6 days ago
WP_List_Table.php
1638 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 */
18 #[AllowDynamicProperties]
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 WP_Screen
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' => __( 'Compact view' ),
173 'excerpt' => __( 'Extended 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, true ) ) {
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, true ) ) {
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 a back-compat property and it is set.
214 */
215 public function __isset( $name ) {
216 if ( in_array( $name, $this->compat_fields, true ) ) {
217 return isset( $this->$name );
218 }
219
220 return false;
221 }
222
223 /**
224 * Make private properties un-settable for backward compatibility.
225 *
226 * @since 4.0.0
227 *
228 * @param string $name Property to unset.
229 */
230 public function __unset( $name ) {
231 if ( in_array( $name, $this->compat_fields, true ) ) {
232 unset( $this->$name );
233 }
234 }
235
236 /**
237 * Make private/protected methods readable for backward compatibility.
238 *
239 * @since 4.0.0
240 *
241 * @param string $name Method to call.
242 * @param array $arguments Arguments to pass when calling.
243 * @return mixed|bool Return value of the callback, false otherwise.
244 */
245 public function __call( $name, $arguments ) {
246 if ( in_array( $name, $this->compat_methods, true ) ) {
247 return $this->$name( ...$arguments );
248 }
249 return false;
250 }
251
252 /**
253 * Checks the current user's permissions
254 *
255 * @since 3.1.0
256 * @abstract
257 */
258 public function ajax_user_can() {
259 die( 'function WP_List_Table::ajax_user_can() must be overridden in a subclass.' );
260 }
261
262 /**
263 * Prepares the list of items for displaying.
264 *
265 * @uses WP_List_Table::set_pagination_args()
266 *
267 * @since 3.1.0
268 * @abstract
269 */
270 public function prepare_items() {
271 die( 'function WP_List_Table::prepare_items() must be overridden in a subclass.' );
272 }
273
274 /**
275 * An internal method that sets all the necessary pagination arguments
276 *
277 * @since 3.1.0
278 *
279 * @param array|string $args Array or string of arguments with information about the pagination.
280 */
281 protected function set_pagination_args( $args ) {
282 $args = wp_parse_args(
283 $args,
284 array(
285 'total_items' => 0,
286 'total_pages' => 0,
287 'per_page' => 0,
288 )
289 );
290
291 if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
292 $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
293 }
294
295 // Redirect if page number is invalid and headers are not already sent.
296 if ( ! headers_sent() && ! wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
297 wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
298 exit;
299 }
300
301 $this->_pagination_args = $args;
302 }
303
304 /**
305 * Access the pagination args.
306 *
307 * @since 3.1.0
308 *
309 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
310 * 'total_pages', 'per_page', or 'infinite_scroll'.
311 * @return int Number of items that correspond to the given pagination argument.
312 */
313 public function get_pagination_arg( $key ) {
314 if ( 'page' === $key ) {
315 return $this->get_pagenum();
316 }
317
318 if ( isset( $this->_pagination_args[ $key ] ) ) {
319 return $this->_pagination_args[ $key ];
320 }
321
322 return 0;
323 }
324
325 /**
326 * Whether the table has items to display or not
327 *
328 * @since 3.1.0
329 *
330 * @return bool
331 */
332 public function has_items() {
333 return ! empty( $this->items );
334 }
335
336 /**
337 * Message to be displayed when there are no items
338 *
339 * @since 3.1.0
340 */
341 public function no_items() {
342 _e( 'No items found.' );
343 }
344
345 /**
346 * Displays the search box.
347 *
348 * @since 3.1.0
349 *
350 * @param string $text The 'submit' button label.
351 * @param string $input_id ID attribute value for the search input field.
352 */
353 public function search_box( $text, $input_id ) {
354 if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
355 return;
356 }
357
358 $input_id = $input_id . '-search-input';
359
360 if ( ! empty( $_REQUEST['orderby'] ) ) {
361 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
362 }
363 if ( ! empty( $_REQUEST['order'] ) ) {
364 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
365 }
366 if ( ! empty( $_REQUEST['post_mime_type'] ) ) {
367 echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
368 }
369 if ( ! empty( $_REQUEST['detached'] ) ) {
370 echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
371 }
372 ?>
373 <p class="search-box">
374 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
375 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
376 <?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
377 </p>
378 <?php
379 }
380
381 /**
382 * Generates views links.
383 *
384 * @since 6.1.0
385 *
386 * @param array $link_data {
387 * An array of link data.
388 *
389 * @type string $url The link URL.
390 * @type string $label The link label.
391 * @type bool $current Optional. Whether this is the currently selected view.
392 * }
393 * @return string[] An array of link markup. Keys match the `$link_data` input array.
394 */
395 protected function get_views_links( $link_data = array() ) {
396 if ( ! is_array( $link_data ) ) {
397 _doing_it_wrong(
398 __METHOD__,
399 sprintf(
400 /* translators: %s: The $link_data argument. */
401 __( 'The %s argument must be an array.' ),
402 '<code>$link_data</code>'
403 ),
404 '6.1.0'
405 );
406
407 return array( '' );
408 }
409
410 $views_links = array();
411
412 foreach ( $link_data as $view => $link ) {
413 if ( empty( $link['url'] ) || ! is_string( $link['url'] ) || '' === trim( $link['url'] ) ) {
414 _doing_it_wrong(
415 __METHOD__,
416 sprintf(
417 /* translators: %1$s: The argument name. %2$s: The view name. */
418 __( 'The %1$s argument must be a non-empty string for %2$s.' ),
419 '<code>url</code>',
420 '<code>' . esc_html( $view ) . '</code>'
421 ),
422 '6.1.0'
423 );
424
425 continue;
426 }
427
428 if ( empty( $link['label'] ) || ! is_string( $link['label'] ) || '' === trim( $link['label'] ) ) {
429 _doing_it_wrong(
430 __METHOD__,
431 sprintf(
432 /* translators: %1$s: The argument name. %2$s: The view name. */
433 __( 'The %1$s argument must be a non-empty string for %2$s.' ),
434 '<code>label</code>',
435 '<code>' . esc_html( $view ) . '</code>'
436 ),
437 '6.1.0'
438 );
439
440 continue;
441 }
442
443 $views_links[ $view ] = sprintf(
444 '<a href="%s"%s>%s</a>',
445 esc_url( $link['url'] ),
446 isset( $link['current'] ) && true === $link['current'] ? ' class="current" aria-current="page"' : '',
447 $link['label']
448 );
449 }
450
451 return $views_links;
452 }
453
454 /**
455 * Gets the list of views available on this table.
456 *
457 * The format is an associative array:
458 * - `'id' => 'link'`
459 *
460 * @since 3.1.0
461 *
462 * @return array
463 */
464 protected function get_views() {
465 return array();
466 }
467
468 /**
469 * Displays the list of views available on this table.
470 *
471 * @since 3.1.0
472 */
473 public function views() {
474 $views = $this->get_views();
475 /**
476 * Filters the list of available list table views.
477 *
478 * The dynamic portion of the hook name, `$this->screen->id`, refers
479 * to the ID of the current screen.
480 *
481 * @since 3.1.0
482 *
483 * @param string[] $views An array of available list table views.
484 */
485 $views = apply_filters( "views_{$this->screen->id}", $views );
486
487 if ( empty( $views ) ) {
488 return;
489 }
490
491 $this->screen->render_screen_reader_content( 'heading_views' );
492
493 echo "<ul class='subsubsub'>\n";
494 foreach ( $views as $class => $view ) {
495 $views[ $class ] = "\t<li class='$class'>$view";
496 }
497 echo implode( " |</li>\n", $views ) . "</li>\n";
498 echo '</ul>';
499 }
500
501 /**
502 * Retrieves the list of bulk actions available for this table.
503 *
504 * The format is an associative array where each element represents either a top level option value and label, or
505 * an array representing an optgroup and its options.
506 *
507 * For a standard option, the array element key is the field value and the array element value is the field label.
508 *
509 * For an optgroup, the array element key is the label and the array element value is an associative array of
510 * options as above.
511 *
512 * Example:
513 *
514 * [
515 * 'edit' => 'Edit',
516 * 'delete' => 'Delete',
517 * 'Change State' => [
518 * 'feature' => 'Featured',
519 * 'sale' => 'On Sale',
520 * ]
521 * ]
522 *
523 * @since 3.1.0
524 * @since 5.6.0 A bulk action can now contain an array of options in order to create an optgroup.
525 *
526 * @return array
527 */
528 protected function get_bulk_actions() {
529 return array();
530 }
531
532 /**
533 * Displays the bulk actions dropdown.
534 *
535 * @since 3.1.0
536 *
537 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
538 * This is designated as optional for backward compatibility.
539 */
540 protected function bulk_actions( $which = '' ) {
541 if ( is_null( $this->_actions ) ) {
542 $this->_actions = $this->get_bulk_actions();
543
544 /**
545 * Filters the items in the bulk actions menu of the list table.
546 *
547 * The dynamic portion of the hook name, `$this->screen->id`, refers
548 * to the ID of the current screen.
549 *
550 * @since 3.1.0
551 * @since 5.6.0 A bulk action can now contain an array of options in order to create an optgroup.
552 *
553 * @param array $actions An array of the available bulk actions.
554 */
555 $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
556
557 $two = '';
558 } else {
559 $two = '2';
560 }
561
562 if ( empty( $this->_actions ) ) {
563 return;
564 }
565
566 echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' .
567 /* translators: Hidden accessibility text. */
568 __( 'Select bulk action' ) .
569 '</label>';
570 echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
571 echo '<option value="-1">' . __( 'Bulk actions' ) . "</option>\n";
572
573 foreach ( $this->_actions as $key => $value ) {
574 if ( is_array( $value ) ) {
575 echo "\t" . '<optgroup label="' . esc_attr( $key ) . '">' . "\n";
576
577 foreach ( $value as $name => $title ) {
578 $class = ( 'edit' === $name ) ? ' class="hide-if-no-js"' : '';
579
580 echo "\t\t" . '<option value="' . esc_attr( $name ) . '"' . $class . '>' . $title . "</option>\n";
581 }
582 echo "\t" . "</optgroup>\n";
583 } else {
584 $class = ( 'edit' === $key ) ? ' class="hide-if-no-js"' : '';
585
586 echo "\t" . '<option value="' . esc_attr( $key ) . '"' . $class . '>' . $value . "</option>\n";
587 }
588 }
589
590 echo "</select>\n";
591
592 submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
593 echo "\n";
594 }
595
596 /**
597 * Gets the current action selected from the bulk actions dropdown.
598 *
599 * @since 3.1.0
600 *
601 * @return string|false The action name. False if no action was selected.
602 */
603 public function current_action() {
604 if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
605 return false;
606 }
607
608 if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) {
609 return $_REQUEST['action'];
610 }
611
612 return false;
613 }
614
615 /**
616 * Generates the required HTML for a list of row action links.
617 *
618 * @since 3.1.0
619 *
620 * @param string[] $actions An array of action links.
621 * @param bool $always_visible Whether the actions should be always visible.
622 * @return string The HTML for the row actions.
623 */
624 protected function row_actions( $actions, $always_visible = false ) {
625 $action_count = count( $actions );
626
627 if ( ! $action_count ) {
628 return '';
629 }
630
631 $mode = get_user_setting( 'posts_list_mode', 'list' );
632
633 if ( 'excerpt' === $mode ) {
634 $always_visible = true;
635 }
636
637 $output = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
638
639 $i = 0;
640
641 foreach ( $actions as $action => $link ) {
642 ++$i;
643
644 $separator = ( $i < $action_count ) ? ' | ' : '';
645
646 $output .= "<span class='$action'>{$link}{$separator}</span>";
647 }
648
649 $output .= '</div>';
650
651 $output .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
652 /* translators: Hidden accessibility text. */
653 __( 'Show more details' ) .
654 '</span></button>';
655
656 return $output;
657 }
658
659 /**
660 * Displays a dropdown for filtering items in the list table by month.
661 *
662 * @since 3.1.0
663 *
664 * @global wpdb $wpdb WordPress database abstraction object.
665 * @global WP_Locale $wp_locale WordPress date and time locale object.
666 *
667 * @param string $post_type The post type.
668 */
669 protected function months_dropdown( $post_type ) {
670 global $wpdb, $wp_locale;
671
672 /**
673 * Filters whether to remove the 'Months' drop-down from the post list table.
674 *
675 * @since 4.2.0
676 *
677 * @param bool $disable Whether to disable the drop-down. Default false.
678 * @param string $post_type The post type.
679 */
680 if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
681 return;
682 }
683
684 /**
685 * Filters whether to short-circuit performing the months dropdown query.
686 *
687 * @since 5.7.0
688 *
689 * @param object[]|false $months 'Months' drop-down results. Default false.
690 * @param string $post_type The post type.
691 */
692 $months = apply_filters( 'pre_months_dropdown_query', false, $post_type );
693
694 if ( ! is_array( $months ) ) {
695 $extra_checks = "AND post_status != 'auto-draft'";
696 if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) {
697 $extra_checks .= " AND post_status != 'trash'";
698 } elseif ( isset( $_GET['post_status'] ) ) {
699 $extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
700 }
701
702 $months = $wpdb->get_results(
703 $wpdb->prepare(
704 "
705 SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
706 FROM $wpdb->posts
707 WHERE post_type = %s
708 $extra_checks
709 ORDER BY post_date DESC
710 ",
711 $post_type
712 )
713 );
714 }
715
716 /**
717 * Filters the 'Months' drop-down results.
718 *
719 * @since 3.7.0
720 *
721 * @param object[] $months Array of the months drop-down query results.
722 * @param string $post_type The post type.
723 */
724 $months = apply_filters( 'months_dropdown_results', $months, $post_type );
725
726 $month_count = count( $months );
727
728 if ( ! $month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) {
729 return;
730 }
731
732 $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
733 ?>
734 <label for="filter-by-date" class="screen-reader-text"><?php echo get_post_type_object( $post_type )->labels->filter_by_date; ?></label>
735 <select name="m" id="filter-by-date">
736 <option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
737 <?php
738 foreach ( $months as $arc_row ) {
739 if ( 0 == $arc_row->year ) {
740 continue;
741 }
742
743 $month = zeroise( $arc_row->month, 2 );
744 $year = $arc_row->year;
745
746 printf(
747 "<option %s value='%s'>%s</option>\n",
748 selected( $m, $year . $month, false ),
749 esc_attr( $arc_row->year . $month ),
750 /* translators: 1: Month name, 2: 4-digit year. */
751 sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
752 );
753 }
754 ?>
755 </select>
756 <?php
757 }
758
759 /**
760 * Displays a view switcher.
761 *
762 * @since 3.1.0
763 *
764 * @param string $current_mode
765 */
766 protected function view_switcher( $current_mode ) {
767 ?>
768 <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
769 <div class="view-switch">
770 <?php
771 foreach ( $this->modes as $mode => $title ) {
772 $classes = array( 'view-' . $mode );
773 $aria_current = '';
774
775 if ( $current_mode === $mode ) {
776 $classes[] = 'current';
777 $aria_current = ' aria-current="page"';
778 }
779
780 printf(
781 "<a href='%s' class='%s' id='view-switch-$mode'$aria_current><span class='screen-reader-text'>%s</span></a>\n",
782 esc_url( remove_query_arg( 'attachment-filter', add_query_arg( 'mode', $mode ) ) ),
783 implode( ' ', $classes ),
784 $title
785 );
786 }
787 ?>
788 </div>
789 <?php
790 }
791
792 /**
793 * Displays a comment count bubble.
794 *
795 * @since 3.1.0
796 *
797 * @param int $post_id The post ID.
798 * @param int $pending_comments Number of pending comments.
799 */
800 protected function comments_bubble( $post_id, $pending_comments ) {
801 $approved_comments = get_comments_number();
802
803 $approved_comments_number = number_format_i18n( $approved_comments );
804 $pending_comments_number = number_format_i18n( $pending_comments );
805
806 $approved_only_phrase = sprintf(
807 /* translators: %s: Number of comments. */
808 _n( '%s comment', '%s comments', $approved_comments ),
809 $approved_comments_number
810 );
811
812 $approved_phrase = sprintf(
813 /* translators: %s: Number of comments. */
814 _n( '%s approved comment', '%s approved comments', $approved_comments ),
815 $approved_comments_number
816 );
817
818 $pending_phrase = sprintf(
819 /* translators: %s: Number of comments. */
820 _n( '%s pending comment', '%s pending comments', $pending_comments ),
821 $pending_comments_number
822 );
823
824 if ( ! $approved_comments && ! $pending_comments ) {
825 // No comments at all.
826 printf(
827 '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">%s</span>',
828 __( 'No comments' )
829 );
830 } elseif ( $approved_comments && 'trash' === get_post_status( $post_id ) ) {
831 // Don't link the comment bubble for a trashed post.
832 printf(
833 '<span 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></span>',
834 $approved_comments_number,
835 $pending_comments ? $approved_phrase : $approved_only_phrase
836 );
837 } elseif ( $approved_comments ) {
838 // Link the comment bubble to approved comments.
839 printf(
840 '<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>',
841 esc_url(
842 add_query_arg(
843 array(
844 'p' => $post_id,
845 'comment_status' => 'approved',
846 ),
847 admin_url( 'edit-comments.php' )
848 )
849 ),
850 $approved_comments_number,
851 $pending_comments ? $approved_phrase : $approved_only_phrase
852 );
853 } else {
854 // Don't link the comment bubble when there are no approved comments.
855 printf(
856 '<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>',
857 $approved_comments_number,
858 $pending_comments ?
859 /* translators: Hidden accessibility text. */
860 __( 'No approved comments' ) :
861 /* translators: Hidden accessibility text. */
862 __( 'No comments' )
863 );
864 }
865
866 if ( $pending_comments ) {
867 printf(
868 '<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>',
869 esc_url(
870 add_query_arg(
871 array(
872 'p' => $post_id,
873 'comment_status' => 'moderated',
874 ),
875 admin_url( 'edit-comments.php' )
876 )
877 ),
878 $pending_comments_number,
879 $pending_phrase
880 );
881 } else {
882 printf(
883 '<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>',
884 $pending_comments_number,
885 $approved_comments ?
886 /* translators: Hidden accessibility text. */
887 __( 'No pending comments' ) :
888 /* translators: Hidden accessibility text. */
889 __( 'No comments' )
890 );
891 }
892 }
893
894 /**
895 * Gets the current page number.
896 *
897 * @since 3.1.0
898 *
899 * @return int
900 */
901 public function get_pagenum() {
902 $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
903
904 if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
905 $pagenum = $this->_pagination_args['total_pages'];
906 }
907
908 return max( 1, $pagenum );
909 }
910
911 /**
912 * Gets the number of items to display on a single page.
913 *
914 * @since 3.1.0
915 *
916 * @param string $option User option name.
917 * @param int $default_value Optional. The number of items to display. Default 20.
918 * @return int
919 */
920 protected function get_items_per_page( $option, $default_value = 20 ) {
921 $per_page = (int) get_user_option( $option );
922 if ( empty( $per_page ) || $per_page < 1 ) {
923 $per_page = $default_value;
924 }
925
926 /**
927 * Filters the number of items to be displayed on each page of the list table.
928 *
929 * The dynamic hook name, `$option`, refers to the `per_page` option depending
930 * on the type of list table in use. Possible filter names include:
931 *
932 * - `edit_comments_per_page`
933 * - `sites_network_per_page`
934 * - `site_themes_network_per_page`
935 * - `themes_network_per_page'`
936 * - `users_network_per_page`
937 * - `edit_post_per_page`
938 * - `edit_page_per_page'`
939 * - `edit_{$post_type}_per_page`
940 * - `edit_post_tag_per_page`
941 * - `edit_category_per_page`
942 * - `edit_{$taxonomy}_per_page`
943 * - `site_users_network_per_page`
944 * - `users_per_page`
945 *
946 * @since 2.9.0
947 *
948 * @param int $per_page Number of items to be displayed. Default 20.
949 */
950 return (int) apply_filters( "{$option}", $per_page );
951 }
952
953 /**
954 * Displays the pagination.
955 *
956 * @since 3.1.0
957 *
958 * @param string $which
959 */
960 protected function pagination( $which ) {
961 if ( empty( $this->_pagination_args ) ) {
962 return;
963 }
964
965 $total_items = $this->_pagination_args['total_items'];
966 $total_pages = $this->_pagination_args['total_pages'];
967 $infinite_scroll = false;
968 if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
969 $infinite_scroll = $this->_pagination_args['infinite_scroll'];
970 }
971
972 if ( 'top' === $which && $total_pages > 1 ) {
973 $this->screen->render_screen_reader_content( 'heading_pagination' );
974 }
975
976 $output = '<span class="displaying-num">' . sprintf(
977 /* translators: %s: Number of items. */
978 _n( '%s item', '%s items', $total_items ),
979 number_format_i18n( $total_items )
980 ) . '</span>';
981
982 $current = $this->get_pagenum();
983 $removable_query_args = wp_removable_query_args();
984
985 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
986
987 $current_url = remove_query_arg( $removable_query_args, $current_url );
988
989 $page_links = array();
990
991 $total_pages_before = '<span class="paging-input">';
992 $total_pages_after = '</span></span>';
993
994 $disable_first = false;
995 $disable_last = false;
996 $disable_prev = false;
997 $disable_next = false;
998
999 if ( 1 == $current ) {
1000 $disable_first = true;
1001 $disable_prev = true;
1002 }
1003 if ( $total_pages == $current ) {
1004 $disable_last = true;
1005 $disable_next = true;
1006 }
1007
1008 if ( $disable_first ) {
1009 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
1010 } else {
1011 $page_links[] = sprintf(
1012 "<a class='first-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
1013 esc_url( remove_query_arg( 'paged', $current_url ) ),
1014 /* translators: Hidden accessibility text. */
1015 __( 'First page' ),
1016 '&laquo;'
1017 );
1018 }
1019
1020 if ( $disable_prev ) {
1021 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
1022 } else {
1023 $page_links[] = sprintf(
1024 "<a class='prev-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
1025 esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
1026 /* translators: Hidden accessibility text. */
1027 __( 'Previous page' ),
1028 '&lsaquo;'
1029 );
1030 }
1031
1032 if ( 'bottom' === $which ) {
1033 $html_current_page = $current;
1034 $total_pages_before = '<span class="screen-reader-text">' .
1035 /* translators: Hidden accessibility text. */
1036 __( 'Current Page' ) .
1037 '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
1038 } else {
1039 $html_current_page = sprintf(
1040 "%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'>",
1041 '<label for="current-page-selector" class="screen-reader-text">' .
1042 /* translators: Hidden accessibility text. */
1043 __( 'Current Page' ) .
1044 '</label>',
1045 $current,
1046 strlen( $total_pages )
1047 );
1048 }
1049 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
1050 $page_links[] = $total_pages_before . sprintf(
1051 /* translators: 1: Current page, 2: Total pages. */
1052 _x( '%1$s of %2$s', 'paging' ),
1053 $html_current_page,
1054 $html_total_pages
1055 ) . $total_pages_after;
1056
1057 if ( $disable_next ) {
1058 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
1059 } else {
1060 $page_links[] = sprintf(
1061 "<a class='next-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
1062 esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
1063 /* translators: Hidden accessibility text. */
1064 __( 'Next page' ),
1065 '&rsaquo;'
1066 );
1067 }
1068
1069 if ( $disable_last ) {
1070 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
1071 } else {
1072 $page_links[] = sprintf(
1073 "<a class='last-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
1074 esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
1075 /* translators: Hidden accessibility text. */
1076 __( 'Last page' ),
1077 '&raquo;'
1078 );
1079 }
1080
1081 $pagination_links_class = 'pagination-links';
1082 if ( ! empty( $infinite_scroll ) ) {
1083 $pagination_links_class .= ' hide-if-js';
1084 }
1085 $output .= "\n<span class='$pagination_links_class'>" . implode( "\n", $page_links ) . '</span>';
1086
1087 if ( $total_pages ) {
1088 $page_class = $total_pages < 2 ? ' one-page' : '';
1089 } else {
1090 $page_class = ' no-pages';
1091 }
1092 $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
1093
1094 echo $this->_pagination;
1095 }
1096
1097 /**
1098 * Gets a list of columns.
1099 *
1100 * The format is:
1101 * - `'internal-name' => 'Title'`
1102 *
1103 * @since 3.1.0
1104 * @abstract
1105 *
1106 * @return array
1107 */
1108 public function get_columns() {
1109 die( 'function WP_List_Table::get_columns() must be overridden in a subclass.' );
1110 }
1111
1112 /**
1113 * Gets a list of sortable columns.
1114 *
1115 * The format is:
1116 * - `'internal-name' => 'orderby'`
1117 * - `'internal-name' => array( 'orderby', 'asc' )` - The second element sets the initial sorting order.
1118 * - `'internal-name' => array( 'orderby', true )` - The second element makes the initial order descending.
1119 *
1120 * @since 3.1.0
1121 *
1122 * @return array
1123 */
1124 protected function get_sortable_columns() {
1125 return array();
1126 }
1127
1128 /**
1129 * Gets the name of the default primary column.
1130 *
1131 * @since 4.3.0
1132 *
1133 * @return string Name of the default primary column, in this case, an empty string.
1134 */
1135 protected function get_default_primary_column_name() {
1136 $columns = $this->get_columns();
1137 $column = '';
1138
1139 if ( empty( $columns ) ) {
1140 return $column;
1141 }
1142
1143 // We need a primary defined so responsive views show something,
1144 // so let's fall back to the first non-checkbox column.
1145 foreach ( $columns as $col => $column_name ) {
1146 if ( 'cb' === $col ) {
1147 continue;
1148 }
1149
1150 $column = $col;
1151 break;
1152 }
1153
1154 return $column;
1155 }
1156
1157 /**
1158 * Public wrapper for WP_List_Table::get_default_primary_column_name().
1159 *
1160 * @since 4.4.0
1161 *
1162 * @return string Name of the default primary column.
1163 */
1164 public function get_primary_column() {
1165 return $this->get_primary_column_name();
1166 }
1167
1168 /**
1169 * Gets the name of the primary column.
1170 *
1171 * @since 4.3.0
1172 *
1173 * @return string The name of the primary column.
1174 */
1175 protected function get_primary_column_name() {
1176 $columns = get_column_headers( $this->screen );
1177 $default = $this->get_default_primary_column_name();
1178
1179 // If the primary column doesn't exist,
1180 // fall back to the first non-checkbox column.
1181 if ( ! isset( $columns[ $default ] ) ) {
1182 $default = self::get_default_primary_column_name();
1183 }
1184
1185 /**
1186 * Filters the name of the primary column for the current list table.
1187 *
1188 * @since 4.3.0
1189 *
1190 * @param string $default Column name default for the specific list table, e.g. 'name'.
1191 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
1192 */
1193 $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
1194
1195 if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
1196 $column = $default;
1197 }
1198
1199 return $column;
1200 }
1201
1202 /**
1203 * Gets a list of all, hidden, and sortable columns, with filter applied.
1204 *
1205 * @since 3.1.0
1206 *
1207 * @return array
1208 */
1209 protected function get_column_info() {
1210 // $_column_headers is already set / cached.
1211 if (
1212 isset( $this->_column_headers ) &&
1213 is_array( $this->_column_headers )
1214 ) {
1215 /*
1216 * Backward compatibility for `$_column_headers` format prior to WordPress 4.3.
1217 *
1218 * In WordPress 4.3 the primary column name was added as a fourth item in the
1219 * column headers property. This ensures the primary column name is included
1220 * in plugins setting the property directly in the three item format.
1221 */
1222 if ( 4 === count( $this->_column_headers ) ) {
1223 return $this->_column_headers;
1224 }
1225
1226 $column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
1227 foreach ( $this->_column_headers as $key => $value ) {
1228 $column_headers[ $key ] = $value;
1229 }
1230
1231 $this->_column_headers = $column_headers;
1232
1233 return $this->_column_headers;
1234 }
1235
1236 $columns = get_column_headers( $this->screen );
1237 $hidden = get_hidden_columns( $this->screen );
1238
1239 $sortable_columns = $this->get_sortable_columns();
1240 /**
1241 * Filters the list table sortable columns for a specific screen.
1242 *
1243 * The dynamic portion of the hook name, `$this->screen->id`, refers
1244 * to the ID of the current screen.
1245 *
1246 * @since 3.1.0
1247 *
1248 * @param array $sortable_columns An array of sortable columns.
1249 */
1250 $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
1251
1252 $sortable = array();
1253 foreach ( $_sortable as $id => $data ) {
1254 if ( empty( $data ) ) {
1255 continue;
1256 }
1257
1258 $data = (array) $data;
1259 if ( ! isset( $data[1] ) ) {
1260 $data[1] = false;
1261 }
1262
1263 $sortable[ $id ] = $data;
1264 }
1265
1266 $primary = $this->get_primary_column_name();
1267 $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
1268
1269 return $this->_column_headers;
1270 }
1271
1272 /**
1273 * Returns the number of visible columns.
1274 *
1275 * @since 3.1.0
1276 *
1277 * @return int
1278 */
1279 public function get_column_count() {
1280 list ( $columns, $hidden ) = $this->get_column_info();
1281 $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
1282 return count( $columns ) - count( $hidden );
1283 }
1284
1285 /**
1286 * Prints column headers, accounting for hidden and sortable columns.
1287 *
1288 * @since 3.1.0
1289 *
1290 * @param bool $with_id Whether to set the ID attribute or not
1291 */
1292 public function print_column_headers( $with_id = true ) {
1293 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1294
1295 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1296 $current_url = remove_query_arg( 'paged', $current_url );
1297
1298 if ( isset( $_GET['orderby'] ) ) {
1299 $current_orderby = $_GET['orderby'];
1300 } else {
1301 $current_orderby = '';
1302 }
1303
1304 if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
1305 $current_order = 'desc';
1306 } else {
1307 $current_order = 'asc';
1308 }
1309
1310 if ( ! empty( $columns['cb'] ) ) {
1311 static $cb_counter = 1;
1312 $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' .
1313 /* translators: Hidden accessibility text. */
1314 __( 'Select All' ) .
1315 '</label>' .
1316 '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
1317 $cb_counter++;
1318 }
1319
1320 foreach ( $columns as $column_key => $column_display_name ) {
1321 $class = array( 'manage-column', "column-$column_key" );
1322
1323 if ( in_array( $column_key, $hidden, true ) ) {
1324 $class[] = 'hidden';
1325 }
1326
1327 if ( 'cb' === $column_key ) {
1328 $class[] = 'check-column';
1329 } elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ), true ) ) {
1330 $class[] = 'num';
1331 }
1332
1333 if ( $column_key === $primary ) {
1334 $class[] = 'column-primary';
1335 }
1336
1337 if ( isset( $sortable[ $column_key ] ) ) {
1338 list( $orderby, $desc_first ) = $sortable[ $column_key ];
1339
1340 if ( $current_orderby === $orderby ) {
1341 $order = 'asc' === $current_order ? 'desc' : 'asc';
1342
1343 $class[] = 'sorted';
1344 $class[] = $current_order;
1345 } else {
1346 $order = strtolower( $desc_first );
1347
1348 if ( ! in_array( $order, array( 'desc', 'asc' ), true ) ) {
1349 $order = $desc_first ? 'desc' : 'asc';
1350 }
1351
1352 $class[] = 'sortable';
1353 $class[] = 'desc' === $order ? 'asc' : 'desc';
1354 }
1355
1356 $column_display_name = sprintf(
1357 '<a href="%s"><span>%s</span><span class="sorting-indicator"></span></a>',
1358 esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ),
1359 $column_display_name
1360 );
1361 }
1362
1363 $tag = ( 'cb' === $column_key ) ? 'td' : 'th';
1364 $scope = ( 'th' === $tag ) ? 'scope="col"' : '';
1365 $id = $with_id ? "id='$column_key'" : '';
1366
1367 if ( ! empty( $class ) ) {
1368 $class = "class='" . implode( ' ', $class ) . "'";
1369 }
1370
1371 echo "<$tag $scope $id $class>$column_display_name</$tag>";
1372 }
1373 }
1374
1375 /**
1376 * Displays the table.
1377 *
1378 * @since 3.1.0
1379 */
1380 public function display() {
1381 $singular = $this->_args['singular'];
1382
1383 $this->display_tablenav( 'top' );
1384
1385 $this->screen->render_screen_reader_content( 'heading_list' );
1386 ?>
1387 <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
1388 <thead>
1389 <tr>
1390 <?php $this->print_column_headers(); ?>
1391 </tr>
1392 </thead>
1393
1394 <tbody id="the-list"
1395 <?php
1396 if ( $singular ) {
1397 echo " data-wp-lists='list:$singular'";
1398 }
1399 ?>
1400 >
1401 <?php $this->display_rows_or_placeholder(); ?>
1402 </tbody>
1403
1404 <tfoot>
1405 <tr>
1406 <?php $this->print_column_headers( false ); ?>
1407 </tr>
1408 </tfoot>
1409
1410 </table>
1411 <?php
1412 $this->display_tablenav( 'bottom' );
1413 }
1414
1415 /**
1416 * Gets a list of CSS classes for the WP_List_Table table tag.
1417 *
1418 * @since 3.1.0
1419 *
1420 * @return string[] Array of CSS classes for the table tag.
1421 */
1422 protected function get_table_classes() {
1423 $mode = get_user_setting( 'posts_list_mode', 'list' );
1424
1425 $mode_class = esc_attr( 'table-view-' . $mode );
1426
1427 return array( 'widefat', 'fixed', 'striped', $mode_class, $this->_args['plural'] );
1428 }
1429
1430 /**
1431 * Generates the table navigation above or below the table
1432 *
1433 * @since 3.1.0
1434 * @param string $which
1435 */
1436 protected function display_tablenav( $which ) {
1437 if ( 'top' === $which ) {
1438 wp_nonce_field( 'bulk-' . $this->_args['plural'] );
1439 }
1440 ?>
1441 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1442
1443 <?php if ( $this->has_items() ) : ?>
1444 <div class="alignleft actions bulkactions">
1445 <?php $this->bulk_actions( $which ); ?>
1446 </div>
1447 <?php
1448 endif;
1449 $this->extra_tablenav( $which );
1450 $this->pagination( $which );
1451 ?>
1452
1453 <br class="clear" />
1454 </div>
1455 <?php
1456 }
1457
1458 /**
1459 * Extra controls to be displayed between bulk actions and pagination.
1460 *
1461 * @since 3.1.0
1462 *
1463 * @param string $which
1464 */
1465 protected function extra_tablenav( $which ) {}
1466
1467 /**
1468 * Generates the tbody element for the list table.
1469 *
1470 * @since 3.1.0
1471 */
1472 public function display_rows_or_placeholder() {
1473 if ( $this->has_items() ) {
1474 $this->display_rows();
1475 } else {
1476 echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
1477 $this->no_items();
1478 echo '</td></tr>';
1479 }
1480 }
1481
1482 /**
1483 * Generates the table rows.
1484 *
1485 * @since 3.1.0
1486 */
1487 public function display_rows() {
1488 foreach ( $this->items as $item ) {
1489 $this->single_row( $item );
1490 }
1491 }
1492
1493 /**
1494 * Generates content for a single row of the table.
1495 *
1496 * @since 3.1.0
1497 *
1498 * @param object|array $item The current item
1499 */
1500 public function single_row( $item ) {
1501 echo '<tr>';
1502 $this->single_row_columns( $item );
1503 echo '</tr>';
1504 }
1505
1506 /**
1507 * @param object|array $item
1508 * @param string $column_name
1509 */
1510 protected function column_default( $item, $column_name ) {}
1511
1512 /**
1513 * @param object|array $item
1514 */
1515 protected function column_cb( $item ) {}
1516
1517 /**
1518 * Generates the columns for a single row of the table.
1519 *
1520 * @since 3.1.0
1521 *
1522 * @param object|array $item The current item.
1523 */
1524 protected function single_row_columns( $item ) {
1525 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1526
1527 foreach ( $columns as $column_name => $column_display_name ) {
1528 $classes = "$column_name column-$column_name";
1529 if ( $primary === $column_name ) {
1530 $classes .= ' has-row-actions column-primary';
1531 }
1532
1533 if ( in_array( $column_name, $hidden, true ) ) {
1534 $classes .= ' hidden';
1535 }
1536
1537 // Comments column uses HTML in the display name with screen reader text.
1538 // Strip tags to get closer to a user-friendly string.
1539 $data = 'data-colname="' . esc_attr( wp_strip_all_tags( $column_display_name ) ) . '"';
1540
1541 $attributes = "class='$classes' $data";
1542
1543 if ( 'cb' === $column_name ) {
1544 echo '<th scope="row" class="check-column">';
1545 echo $this->column_cb( $item );
1546 echo '</th>';
1547 } elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1548 echo call_user_func(
1549 array( $this, '_column_' . $column_name ),
1550 $item,
1551 $classes,
1552 $data,
1553 $primary
1554 );
1555 } elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1556 echo "<td $attributes>";
1557 echo call_user_func( array( $this, 'column_' . $column_name ), $item );
1558 echo $this->handle_row_actions( $item, $column_name, $primary );
1559 echo '</td>';
1560 } else {
1561 echo "<td $attributes>";
1562 echo $this->column_default( $item, $column_name );
1563 echo $this->handle_row_actions( $item, $column_name, $primary );
1564 echo '</td>';
1565 }
1566 }
1567 }
1568
1569 /**
1570 * Generates and display row actions links for the list table.
1571 *
1572 * @since 4.3.0
1573 *
1574 * @param object|array $item The item being acted upon.
1575 * @param string $column_name Current column name.
1576 * @param string $primary Primary column name.
1577 * @return string The row actions HTML, or an empty string
1578 * if the current column is not the primary column.
1579 */
1580 protected function handle_row_actions( $item, $column_name, $primary ) {
1581 return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
1582 /* translators: Hidden accessibility text. */
1583 __( 'Show more details' ) .
1584 '</span></button>' : '';
1585 }
1586
1587 /**
1588 * Handles an incoming ajax request (called from admin-ajax.php)
1589 *
1590 * @since 3.1.0
1591 */
1592 public function ajax_response() {
1593 $this->prepare_items();
1594
1595 ob_start();
1596 if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1597 $this->display_rows();
1598 } else {
1599 $this->display_rows_or_placeholder();
1600 }
1601
1602 $rows = ob_get_clean();
1603
1604 $response = array( 'rows' => $rows );
1605
1606 if ( isset( $this->_pagination_args['total_items'] ) ) {
1607 $response['total_items_i18n'] = sprintf(
1608 /* translators: Number of items. */
1609 _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1610 number_format_i18n( $this->_pagination_args['total_items'] )
1611 );
1612 }
1613 if ( isset( $this->_pagination_args['total_pages'] ) ) {
1614 $response['total_pages'] = $this->_pagination_args['total_pages'];
1615 $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1616 }
1617
1618 die( wp_json_encode( $response ) );
1619 }
1620
1621 /**
1622 * Sends required variables to JavaScript land.
1623 *
1624 * @since 3.1.0
1625 */
1626 public function _js_vars() {
1627 $args = array(
1628 'class' => get_class( $this ),
1629 'screen' => array(
1630 'id' => $this->screen->id,
1631 'base' => $this->screen->base,
1632 ),
1633 );
1634
1635 printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1636 }
1637 }
1638