run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄class-wp-list-table.php
1<?php
2/**
3 * Administration API: WP_List_Table class
4 *
5 * @package WordPress
6 * @subpackage List_Table
7 * @since 3.1.0
8 */
9
10/**
11 * Base class for displaying a list of items in an ajaxified HTML table.
12 *
13 * @since 3.1.0
14 */
15#[AllowDynamicProperties]
16class WP_List_Table {
17
18 /**
19 * The current list of items.
20 *
21 * @since 3.1.0
22 * @var array
23 */
24 public $items;
25
26 /**
27 * Various information about the current table.
28 *
29 * @since 3.1.0
30 * @var array
31 */
32 protected $_args;
33
34 /**
35 * Various information needed for displaying the pagination.
36 *
37 * @since 3.1.0
38 * @var array
39 */
40 protected $_pagination_args = array();
41
42 /**
43 * The current screen.
44 *
45 * @since 3.1.0
46 * @var WP_Screen
47 */
48 protected $screen;
49
50 /**
51 * Cached bulk actions.
52 *
53 * @since 3.1.0
54 * @var array
55 */
56 private $_actions;
57
58 /**
59 * Cached pagination output.
60 *
61 * @since 3.1.0
62 * @var string
63 */
64 private $_pagination;
65
66 /**
67 * The view switcher modes.
68 *
69 * @since 4.1.0
70 * @var array
71 */
72 protected $modes = array();
73
74 /**
75 * Stores the value returned by ::get_column_info().
76 *
77 * @since 4.1.0
78 * @var array|null
79 */
80 protected $_column_headers;
81
82 /**
83 * {@internal Missing Summary}
84 *
85 * @var array
86 */
87 protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' );
88
89 /**
90 * {@internal Missing Summary}
91 *
92 * @var array
93 */
94 protected $compat_methods = array(
95 'set_pagination_args',
96 'get_views',
97 'get_bulk_actions',
98 'bulk_actions',
99 'row_actions',
100 'months_dropdown',
101 'view_switcher',
102 'comments_bubble',
103 'get_items_per_page',
104 'pagination',
105 'get_sortable_columns',
106 'get_column_info',
107 'get_table_classes',
108 'display_tablenav',
109 'extra_tablenav',
110 'single_row_columns',
111 );
112
113 /**
114 * Constructor.
115 *
116 * The child class should call this constructor from its own constructor to override
117 * the default $args.
118 *
119 * @since 3.1.0
120 *
121 * @param array|string $args {
122 * Array or string of arguments.
123 *
124 * @type string $plural Plural value used for labels and the objects being listed.
125 * This affects things such as CSS class-names and nonces used
126 * in the list table, e.g. 'posts'. Default empty.
127 * @type string $singular Singular label for an object being listed, e.g. 'post'.
128 * Default empty
129 * @type bool $ajax Whether the list table supports Ajax. This includes loading
130 * and sorting data, for example. If true, the class will call
131 * the _js_vars() method in the footer to provide variables
132 * to any scripts handling Ajax events. Default false.
133 * @type string $screen String containing the hook name used to determine the current
134 * screen. If left null, the current screen will be automatically set.
135 * Default null.
136 * }
137 */
138 public function __construct( $args = array() ) {
139 $args = wp_parse_args(
140 $args,
141 array(
142 'plural' => '',
143 'singular' => '',
144 'ajax' => false,
145 'screen' => null,
146 )
147 );
148
149 $this->screen = convert_to_screen( $args['screen'] );
150
151 add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
152
153 if ( ! $args['plural'] ) {
154 $args['plural'] = $this->screen->base;
155 }
156
157 $args['plural'] = sanitize_key( $args['plural'] );
158 $args['singular'] = sanitize_key( $args['singular'] );
159
160 $this->_args = $args;
161
162 if ( $args['ajax'] ) {
163 // wp_enqueue_script( 'list-table' );
164 add_action( 'admin_footer', array( $this, '_js_vars' ) );
165 }
166
167 if ( empty( $this->modes ) ) {
168 $this->modes = array(
169 'list' => __( 'Compact view' ),
170 'excerpt' => __( 'Extended view' ),
171 );
172 }
173 }
174
175 /**
176 * Makes private properties readable for backward compatibility.
177 *
178 * @since 4.0.0
179 * @since 6.4.0 Getting a dynamic property is deprecated.
180 *
181 * @param string $name Property to get.
182 * @return mixed Property.
183 */
184 public function __get( $name ) {
185 if ( in_array( $name, $this->compat_fields, true ) ) {
186 return $this->$name;
187 }
188
189 wp_trigger_error(
190 __METHOD__,
191 "The property `{$name}` is not declared. Getting a dynamic property is " .
192 'deprecated since version 6.4.0! Instead, declare the property on the class.',
193 E_USER_DEPRECATED
194 );
195 return null;
196 }
197
198 /**
199 * Makes private properties settable for backward compatibility.
200 *
201 * @since 4.0.0
202 * @since 6.4.0 Setting a dynamic property is deprecated.
203 *
204 * @param string $name Property to check if set.
205 * @param mixed $value Property value.
206 */
207 public function __set( $name, $value ) {
208 if ( in_array( $name, $this->compat_fields, true ) ) {
209 $this->$name = $value;
210 return;
211 }
212
213 wp_trigger_error(
214 __METHOD__,
215 "The property `{$name}` is not declared. Setting a dynamic property is " .
216 'deprecated since version 6.4.0! Instead, declare the property on the class.',
217 E_USER_DEPRECATED
218 );
219 }
220
221 /**
222 * Makes private properties checkable for backward compatibility.
223 *
224 * @since 4.0.0
225 * @since 6.4.0 Checking a dynamic property is deprecated.
226 *
227 * @param string $name Property to check if set.
228 * @return bool Whether the property is a back-compat property and it is set.
229 */
230 public function __isset( $name ) {
231 if ( in_array( $name, $this->compat_fields, true ) ) {
232 return isset( $this->$name );
233 }
234
235 wp_trigger_error(
236 __METHOD__,
237 "The property `{$name}` is not declared. Checking `isset()` on a dynamic property " .
238 'is deprecated since version 6.4.0! Instead, declare the property on the class.',
239 E_USER_DEPRECATED
240 );
241 return false;
242 }
243
244 /**
245 * Makes private properties un-settable for backward compatibility.
246 *
247 * @since 4.0.0
248 * @since 6.4.0 Unsetting a dynamic property is deprecated.
249 *
250 * @param string $name Property to unset.
251 */
252 public function __unset( $name ) {
253 if ( in_array( $name, $this->compat_fields, true ) ) {
254 unset( $this->$name );
255 return;
256 }
257
258 wp_trigger_error(
259 __METHOD__,
260 "A property `{$name}` is not declared. Unsetting a dynamic property is " .
261 'deprecated since version 6.4.0! Instead, declare the property on the class.',
262 E_USER_DEPRECATED
263 );
264 }
265
266 /**
267 * Makes private/protected methods readable for backward compatibility.
268 *
269 * @since 4.0.0
270 *
271 * @param string $name Method to call.
272 * @param array $arguments Arguments to pass when calling.
273 * @return mixed|bool Return value of the callback, false otherwise.
274 */
275 public function __call( $name, $arguments ) {
276 if ( in_array( $name, $this->compat_methods, true ) ) {
277 return $this->$name( ...$arguments );
278 }
279 return false;
280 }
281
282 /**
283 * Checks the current user's permissions
284 *
285 * @since 3.1.0
286 * @abstract
287 */
288 public function ajax_user_can() {
289 die( 'function WP_List_Table::ajax_user_can() must be overridden in a subclass.' );
290 }
291
292 /**
293 * Prepares the list of items for displaying.
294 *
295 * @uses WP_List_Table::set_pagination_args()
296 *
297 * @since 3.1.0
298 * @abstract
299 */
300 public function prepare_items() {
301 die( 'function WP_List_Table::prepare_items() must be overridden in a subclass.' );
302 }
303
304 /**
305 * Sets all the necessary pagination arguments.
306 *
307 * @since 3.1.0
308 *
309 * @param array|string $args Array or string of arguments with information about the pagination.
310 */
311 protected function set_pagination_args( $args ) {
312 $args = wp_parse_args(
313 $args,
314 array(
315 'total_items' => 0,
316 'total_pages' => 0,
317 'per_page' => 0,
318 )
319 );
320
321 if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
322 $args['total_pages'] = (int) ceil( $args['total_items'] / $args['per_page'] );
323 }
324
325 // Redirect if page number is invalid and headers are not already sent.
326 if ( ! headers_sent() && ! wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
327 wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
328 exit;
329 }
330
331 $this->_pagination_args = $args;
332 }
333
334 /**
335 * Access the pagination args.
336 *
337 * @since 3.1.0
338 *
339 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
340 * 'total_pages', 'per_page', or 'infinite_scroll'.
341 * @return int Number of items that correspond to the given pagination argument.
342 */
343 public function get_pagination_arg( $key ) {
344 if ( 'page' === $key ) {
345 return $this->get_pagenum();
346 }
347
348 if ( isset( $this->_pagination_args[ $key ] ) ) {
349 return $this->_pagination_args[ $key ];
350 }
351
352 return 0;
353 }
354
355 /**
356 * Determines whether the table has items to display or not
357 *
358 * @since 3.1.0
359 *
360 * @return bool
361 */
362 public function has_items() {
363 return ! empty( $this->items );
364 }
365
366 /**
367 * Message to be displayed when there are no items
368 *
369 * @since 3.1.0
370 */
371 public function no_items() {
372 _e( 'No items found.' );
373 }
374
375 /**
376 * Displays the search box.
377 *
378 * @since 3.1.0
379 *
380 * @param string $text The 'submit' button label.
381 * @param string $input_id ID attribute value for the search input field.
382 */
383 public function search_box( $text, $input_id ) {
384 if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
385 return;
386 }
387
388 $input_id = $input_id . '-search-input';
389
390 if ( ! empty( $_REQUEST['orderby'] ) ) {
391 if ( is_array( $_REQUEST['orderby'] ) ) {
392 foreach ( $_REQUEST['orderby'] as $key => $value ) {
393 echo '<input type="hidden" name="orderby[' . esc_attr( $key ) . ']" value="' . esc_attr( $value ) . '" />';
394 }
395 } else {
396 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
397 }
398 }
399 if ( ! empty( $_REQUEST['order'] ) ) {
400 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
401 }
402 if ( ! empty( $_REQUEST['post_mime_type'] ) ) {
403 echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
404 }
405 if ( ! empty( $_REQUEST['detached'] ) ) {
406 echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
407 }
408 ?>
409<p class="search-box">
410 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
411 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
412 <?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
413</p>
414 <?php
415 }
416
417 /**
418 * Generates views links.
419 *
420 * @since 6.1.0
421 *
422 * @param array $link_data {
423 * An array of link data.
424 *
425 * @type string $url The link URL.
426 * @type string $label The link label.
427 * @type bool $current Optional. Whether this is the currently selected view.
428 * }
429 * @return string[] An array of link markup. Keys match the `$link_data` input array.
430 */
431 protected function get_views_links( $link_data = array() ) {
432 if ( ! is_array( $link_data ) ) {
433 _doing_it_wrong(
434 __METHOD__,
435 sprintf(
436 /* translators: %s: The $link_data argument. */
437 __( 'The %s argument must be an array.' ),
438 '<code>$link_data</code>'
439 ),
440 '6.1.0'
441 );
442
443 return array( '' );
444 }
445
446 $views_links = array();
447
448 foreach ( $link_data as $view => $link ) {
449 if ( empty( $link['url'] ) || ! is_string( $link['url'] ) || '' === trim( $link['url'] ) ) {
450 _doing_it_wrong(
451 __METHOD__,
452 sprintf(
453 /* translators: %1$s: The argument name. %2$s: The view name. */
454 __( 'The %1$s argument must be a non-empty string for %2$s.' ),
455 '<code>url</code>',
456 '<code>' . esc_html( $view ) . '</code>'
457 ),
458 '6.1.0'
459 );
460
461 continue;
462 }
463
464 if ( empty( $link['label'] ) || ! is_string( $link['label'] ) || '' === trim( $link['label'] ) ) {
465 _doing_it_wrong(
466 __METHOD__,
467 sprintf(
468 /* translators: %1$s: The argument name. %2$s: The view name. */
469 __( 'The %1$s argument must be a non-empty string for %2$s.' ),
470 '<code>label</code>',
471 '<code>' . esc_html( $view ) . '</code>'
472 ),
473 '6.1.0'
474 );
475
476 continue;
477 }
478
479 $views_links[ $view ] = sprintf(
480 '<a href="%s"%s>%s</a>',
481 esc_url( $link['url'] ),
482 isset( $link['current'] ) && true === $link['current'] ? ' class="current" aria-current="page"' : '',
483 $link['label']
484 );
485 }
486
487 return $views_links;
488 }
489
490 /**
491 * Gets the list of views available on this table.
492 *
493 * The format is an associative array:
494 * - `'id' => 'link'`
495 *
496 * @since 3.1.0
497 *
498 * @return array
499 */
500 protected function get_views() {
501 return array();
502 }
503
504 /**
505 * Displays the list of views available on this table.
506 *
507 * @since 3.1.0
508 */
509 public function views() {
510 $views = $this->get_views();
511 /**
512 * Filters the list of available list table views.
513 *
514 * The dynamic portion of the hook name, `$this->screen->id`, refers
515 * to the ID of the current screen.
516 *
517 * @since 3.1.0
518 *
519 * @param string[] $views An array of available list table views.
520 */
521 $views = apply_filters( "views_{$this->screen->id}", $views );
522
523 if ( empty( $views ) ) {
524 return;
525 }
526
527 $this->screen->render_screen_reader_content( 'heading_views' );
528
529 echo "<ul class='subsubsub'>\n";
530 foreach ( $views as $class => $view ) {
531 $views[ $class ] = "\t<li class='$class'>$view";
532 }
533 echo implode( " |</li>\n", $views ) . "</li>\n";
534 echo '</ul>';
535 }
536
537 /**
538 * Retrieves the list of bulk actions available for this table.
539 *
540 * The format is an associative array where each element represents either a top level option value and label, or
541 * an array representing an optgroup and its options.
542 *
543 * For a standard option, the array element key is the field value and the array element value is the field label.
544 *
545 * For an optgroup, the array element key is the label and the array element value is an associative array of
546 * options as above.
547 *
548 * Example:
549 *
550 * [
551 * 'edit' => 'Edit',
552 * 'delete' => 'Delete',
553 * 'Change State' => [
554 * 'feature' => 'Featured',
555 * 'sale' => 'On Sale',
556 * ]
557 * ]
558 *
559 * @since 3.1.0
560 * @since 5.6.0 A bulk action can now contain an array of options in order to create an optgroup.
561 *
562 * @return array
563 */
564 protected function get_bulk_actions() {
565 return array();
566 }
567
568 /**
569 * Displays the bulk actions dropdown.
570 *
571 * @since 3.1.0
572 *
573 * @param string $which The location of the bulk actions: Either 'top' or 'bottom'.
574 * This is designated as optional for backward compatibility.
575 */
576 protected function bulk_actions( $which = '' ) {
577 if ( is_null( $this->_actions ) ) {
578 $this->_actions = $this->get_bulk_actions();
579
580 /**
581 * Filters the items in the bulk actions menu of the list table.
582 *
583 * The dynamic portion of the hook name, `$this->screen->id`, refers
584 * to the ID of the current screen.
585 *
586 * @since 3.1.0
587 * @since 5.6.0 A bulk action can now contain an array of options in order to create an optgroup.
588 *
589 * @param array $actions An array of the available bulk actions.
590 */
591 $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
592
593 $two = '';
594 } else {
595 $two = '2';
596 }
597
598 if ( empty( $this->_actions ) ) {
599 return;
600 }
601
602 echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' .
603 /* translators: Hidden accessibility text. */
604 __( 'Select bulk action' ) .
605 '</label>';
606 echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
607 echo '<option value="-1">' . __( 'Bulk actions' ) . "</option>\n";
608
609 foreach ( $this->_actions as $key => $value ) {
610 if ( is_array( $value ) ) {
611 echo "\t" . '<optgroup label="' . esc_attr( $key ) . '">' . "\n";
612
613 foreach ( $value as $name => $title ) {
614 $class = ( 'edit' === $name ) ? ' class="hide-if-no-js"' : '';
615
616 echo "\t\t" . '<option value="' . esc_attr( $name ) . '"' . $class . '>' . $title . "</option>\n";
617 }
618 echo "\t" . "</optgroup>\n";
619 } else {
620 $class = ( 'edit' === $key ) ? ' class="hide-if-no-js"' : '';
621
622 echo "\t" . '<option value="' . esc_attr( $key ) . '"' . $class . '>' . $value . "</option>\n";
623 }
624 }
625
626 echo "</select>\n";
627
628 submit_button( __( 'Apply' ), 'action', 'bulk_action', false, array( 'id' => "doaction$two" ) );
629 echo "\n";
630 }
631
632 /**
633 * Gets the current action selected from the bulk actions dropdown.
634 *
635 * @since 3.1.0
636 *
637 * @return string|false The action name. False if no action was selected.
638 */
639 public function current_action() {
640 if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
641 return false;
642 }
643
644 if ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) {
645 return $_REQUEST['action'];
646 }
647
648 return false;
649 }
650
651 /**
652 * Generates the required HTML for a list of row action links.
653 *
654 * @since 3.1.0
655 *
656 * @param string[] $actions An array of action links.
657 * @param bool $always_visible Whether the actions should be always visible.
658 * @return string The HTML for the row actions.
659 */
660 protected function row_actions( $actions, $always_visible = false ) {
661 $action_count = count( $actions );
662
663 if ( ! $action_count ) {
664 return '';
665 }
666
667 $mode = get_user_setting( 'posts_list_mode', 'list' );
668
669 if ( 'excerpt' === $mode ) {
670 $always_visible = true;
671 }
672
673 $output = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
674
675 $i = 0;
676
677 foreach ( $actions as $action => $link ) {
678 ++$i;
679
680 $separator = ( $i < $action_count ) ? ' | ' : '';
681
682 $output .= "<span class='$action'>{$link}{$separator}</span>";
683 }
684
685 $output .= '</div>';
686
687 $output .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
688 /* translators: Hidden accessibility text. */
689 __( 'Show more details' ) .
690 '</span></button>';
691
692 return $output;
693 }
694
695 /**
696 * Displays a dropdown for filtering items in the list table by month.
697 *
698 * @since 3.1.0
699 *
700 * @global wpdb $wpdb WordPress database abstraction object.
701 * @global WP_Locale $wp_locale WordPress date and time locale object.
702 *
703 * @param string $post_type The post type.
704 */
705 protected function months_dropdown( $post_type ) {
706 global $wpdb, $wp_locale;
707
708 /**
709 * Filters whether to remove the 'Months' drop-down from the post list table.
710 *
711 * @since 4.2.0
712 *
713 * @param bool $disable Whether to disable the drop-down. Default false.
714 * @param string $post_type The post type.
715 */
716 if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
717 return;
718 }
719
720 /**
721 * Filters whether to short-circuit performing the months dropdown query.
722 *
723 * @since 5.7.0
724 *
725 * @param object[]|false $months 'Months' drop-down results. Default false.
726 * @param string $post_type The post type.
727 */
728 $months = apply_filters( 'pre_months_dropdown_query', false, $post_type );
729
730 if ( ! is_array( $months ) ) {
731 $extra_checks = "AND post_status != 'auto-draft'";
732 if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) {
733 $extra_checks .= " AND post_status != 'trash'";
734 } elseif ( isset( $_GET['post_status'] ) ) {
735 $extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
736 }
737
738 $months = $wpdb->get_results(
739 $wpdb->prepare(
740 "SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
741 FROM $wpdb->posts
742 WHERE post_type = %s
743 $extra_checks
744 ORDER BY post_date DESC",
745 $post_type
746 )
747 );
748 }
749
750 /**
751 * Filters the 'Months' drop-down results.
752 *
753 * @since 3.7.0
754 *
755 * @param object[] $months Array of the months drop-down query results.
756 * @param string $post_type The post type.
757 */
758 $months = apply_filters( 'months_dropdown_results', $months, $post_type );
759
760 $month_count = count( $months );
761
762 if ( ! $month_count || ( 1 === $month_count && 0 === (int) $months[0]->month ) ) {
763 return;
764 }
765
766 $selected_month = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
767 ?>
768 <label for="filter-by-date" class="screen-reader-text"><?php echo get_post_type_object( $post_type )->labels->filter_by_date; ?></label>
769 <select name="m" id="filter-by-date">
770 <option<?php selected( $selected_month, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
771 <?php
772 foreach ( $months as $arc_row ) {
773 if ( 0 === (int) $arc_row->year ) {
774 continue;
775 }
776
777 $month = zeroise( $arc_row->month, 2 );
778 $year = $arc_row->year;
779
780 printf(
781 "<option %s value='%s'>%s</option>\n",
782 selected( $selected_month, $year . $month, false ),
783 esc_attr( $year . $month ),
784 /* translators: 1: Month name, 2: 4-digit year. */
785 esc_html( sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year ) )
786 );
787 }
788 ?>
789 </select>
790 <?php
791 }
792
793 /**
794 * Displays a view switcher.
795 *
796 * @since 3.1.0
797 *
798 * @param string $current_mode
799 */
800 protected function view_switcher( $current_mode ) {
801 ?>
802 <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
803 <div class="view-switch">
804 <?php
805 foreach ( $this->modes as $mode => $title ) {
806 $classes = array( 'view-' . $mode );
807 $aria_current = '';
808
809 if ( $current_mode === $mode ) {
810 $classes[] = 'current';
811 $aria_current = ' aria-current="page"';
812 }
813
814 printf(
815 "<a href='%s' class='%s' id='view-switch-$mode'$aria_current>" .
816 "<span class='screen-reader-text'>%s</span>" .
817 "</a>\n",
818 esc_url( remove_query_arg( 'attachment-filter', add_query_arg( 'mode', $mode ) ) ),
819 implode( ' ', $classes ),
820 $title
821 );
822 }
823 ?>
824 </div>
825 <?php
826 }
827
828 /**
829 * Displays a comment count bubble.
830 *
831 * @since 3.1.0
832 *
833 * @param int $post_id The post ID.
834 * @param int $pending_comments Number of pending comments.
835 */
836 protected function comments_bubble( $post_id, $pending_comments ) {
837 $post_object = get_post( $post_id );
838 $edit_post_cap = $post_object ? 'edit_post' : 'edit_posts';
839
840 if ( ! current_user_can( $edit_post_cap, $post_id )
841 && ( post_password_required( $post_id )
842 || ! current_user_can( 'read_post', $post_id ) )
843 ) {
844 // The user has no access to the post and thus cannot see the comments.
845 return false;
846 }
847
848 $approved_comments = get_comments_number();
849
850 $approved_comments_number = number_format_i18n( $approved_comments );
851 $pending_comments_number = number_format_i18n( $pending_comments );
852
853 $approved_only_phrase = sprintf(
854 /* translators: %s: Number of comments. */
855 _n( '%s comment', '%s comments', $approved_comments ),
856 $approved_comments_number
857 );
858
859 $approved_phrase = sprintf(
860 /* translators: %s: Number of comments. */
861 _n( '%s approved comment', '%s approved comments', $approved_comments ),
862 $approved_comments_number
863 );
864
865 $pending_phrase = sprintf(
866 /* translators: %s: Number of comments. */
867 _n( '%s pending comment', '%s pending comments', $pending_comments ),
868 $pending_comments_number
869 );
870
871 if ( ! $approved_comments && ! $pending_comments ) {
872 // No comments at all.
873 printf(
874 '<span aria-hidden="true">&#8212;</span>' .
875 '<span class="screen-reader-text">%s</span>',
876 __( 'No comments' )
877 );
878 } elseif ( $approved_comments && 'trash' === get_post_status( $post_id ) ) {
879 // Don't link the comment bubble for a trashed post.
880 printf(
881 '<span class="post-com-count post-com-count-approved">' .
882 '<span class="comment-count-approved" aria-hidden="true">%s</span>' .
883 '<span class="screen-reader-text">%s</span>' .
884 '</span>',
885 $approved_comments_number,
886 $pending_comments ? $approved_phrase : $approved_only_phrase
887 );
888 } elseif ( $approved_comments ) {
889 // Link the comment bubble to approved comments.
890 printf(
891 '<a href="%s" class="post-com-count post-com-count-approved">' .
892 '<span class="comment-count-approved" aria-hidden="true">%s</span>' .
893 '<span class="screen-reader-text">%s</span>' .
894 '</a>',
895 esc_url(
896 add_query_arg(
897 array(
898 'p' => $post_id,
899 'comment_status' => 'approved',
900 ),
901 admin_url( 'edit-comments.php' )
902 )
903 ),
904 $approved_comments_number,
905 $pending_comments ? $approved_phrase : $approved_only_phrase
906 );
907 } else {
908 // Don't link the comment bubble when there are no approved comments.
909 printf(
910 '<span class="post-com-count post-com-count-no-comments">' .
911 '<span class="comment-count comment-count-no-comments" aria-hidden="true">%s</span>' .
912 '<span class="screen-reader-text">%s</span>' .
913 '</span>',
914 $approved_comments_number,
915 $pending_comments ?
916 /* translators: Hidden accessibility text. */
917 __( 'No approved comments' ) :
918 /* translators: Hidden accessibility text. */
919 __( 'No comments' )
920 );
921 }
922
923 if ( $pending_comments ) {
924 printf(
925 '<a href="%s" class="post-com-count post-com-count-pending">' .
926 '<span class="comment-count-pending" aria-hidden="true">%s</span>' .
927 '<span class="screen-reader-text">%s</span>' .
928 '</a>',
929 esc_url(
930 add_query_arg(
931 array(
932 'p' => $post_id,
933 'comment_status' => 'moderated',
934 ),
935 admin_url( 'edit-comments.php' )
936 )
937 ),
938 $pending_comments_number,
939 $pending_phrase
940 );
941 } else {
942 printf(
943 '<span class="post-com-count post-com-count-pending post-com-count-no-pending">' .
944 '<span class="comment-count comment-count-no-pending" aria-hidden="true">%s</span>' .
945 '<span class="screen-reader-text">%s</span>' .
946 '</span>',
947 $pending_comments_number,
948 $approved_comments ?
949 /* translators: Hidden accessibility text. */
950 __( 'No pending comments' ) :
951 /* translators: Hidden accessibility text. */
952 __( 'No comments' )
953 );
954 }
955 }
956
957 /**
958 * Gets the current page number.
959 *
960 * @since 3.1.0
961 *
962 * @return int
963 */
964 public function get_pagenum() {
965 $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
966
967 if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
968 $pagenum = $this->_pagination_args['total_pages'];
969 }
970
971 return max( 1, $pagenum );
972 }
973
974 /**
975 * Gets the number of items to display on a single page.
976 *
977 * @since 3.1.0
978 *
979 * @param string $option User option name.
980 * @param int $default_value Optional. The number of items to display. Default 20.
981 * @return int
982 */
983 protected function get_items_per_page( $option, $default_value = 20 ) {
984 $per_page = (int) get_user_option( $option );
985 if ( empty( $per_page ) || $per_page < 1 ) {
986 $per_page = $default_value;
987 }
988
989 /**
990 * Filters the number of items to be displayed on each page of the list table.
991 *
992 * The dynamic hook name, `$option`, refers to the `per_page` option depending
993 * on the type of list table in use. Possible filter names include:
994 *
995 * - `edit_comments_per_page`
996 * - `sites_network_per_page`
997 * - `site_themes_network_per_page`
998 * - `themes_network_per_page`
999 * - `users_network_per_page`
1000 * - `edit_post_per_page`
1001 * - `edit_page_per_page`
1002 * - `edit_{$post_type}_per_page`
1003 * - `edit_post_tag_per_page`
1004 * - `edit_category_per_page`
1005 * - `edit_{$taxonomy}_per_page`
1006 * - `site_users_network_per_page`
1007 * - `users_per_page`
1008 *
1009 * @since 2.9.0
1010 *
1011 * @param int $per_page Number of items to be displayed. Default 20.
1012 */
1013 return (int) apply_filters( "{$option}", $per_page );
1014 }
1015
1016 /**
1017 * Displays the pagination.
1018 *
1019 * @since 3.1.0
1020 *
1021 * @param string $which The location of the pagination: Either 'top' or 'bottom'.
1022 */
1023 protected function pagination( $which ) {
1024 if ( empty( $this->_pagination_args['total_items'] ) ) {
1025 return;
1026 }
1027
1028 $total_items = $this->_pagination_args['total_items'];
1029 $total_pages = $this->_pagination_args['total_pages'];
1030 $infinite_scroll = false;
1031 if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
1032 $infinite_scroll = $this->_pagination_args['infinite_scroll'];
1033 }
1034
1035 if ( 'top' === $which && $total_pages > 1 ) {
1036 $this->screen->render_screen_reader_content( 'heading_pagination' );
1037 }
1038
1039 $output = '<span class="displaying-num">' . sprintf(
1040 /* translators: %s: Number of items. */
1041 _n( '%s item', '%s items', $total_items ),
1042 number_format_i18n( $total_items )
1043 ) . '</span>';
1044
1045 $current = $this->get_pagenum();
1046 $removable_query_args = wp_removable_query_args();
1047
1048 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1049
1050 $current_url = remove_query_arg( $removable_query_args, $current_url );
1051
1052 $page_links = array();
1053
1054 $total_pages_before = '<span class="paging-input">';
1055 $total_pages_after = '</span></span>';
1056
1057 $disable_first = false;
1058 $disable_last = false;
1059 $disable_prev = false;
1060 $disable_next = false;
1061
1062 if ( 1 === $current ) {
1063 $disable_first = true;
1064 $disable_prev = true;
1065 }
1066 if ( $total_pages === $current ) {
1067 $disable_last = true;
1068 $disable_next = true;
1069 }
1070
1071 if ( $disable_first ) {
1072 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
1073 } else {
1074 $page_links[] = sprintf(
1075 "<a class='first-page button' href='%s'>" .
1076 "<span class='screen-reader-text'>%s</span>" .
1077 "<span aria-hidden='true'>%s</span>" .
1078 '</a>',
1079 esc_url( remove_query_arg( 'paged', $current_url ) ),
1080 /* translators: Hidden accessibility text. */
1081 __( 'First page' ),
1082 '&laquo;'
1083 );
1084 }
1085
1086 if ( $disable_prev ) {
1087 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
1088 } else {
1089 $page_links[] = sprintf(
1090 "<a class='prev-page button' href='%s'>" .
1091 "<span class='screen-reader-text'>%s</span>" .
1092 "<span aria-hidden='true'>%s</span>" .
1093 '</a>',
1094 esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
1095 /* translators: Hidden accessibility text. */
1096 __( 'Previous page' ),
1097 '&lsaquo;'
1098 );
1099 }
1100
1101 if ( 'bottom' === $which ) {
1102 $html_current_page = $current;
1103 $total_pages_before = sprintf(
1104 '<span class="screen-reader-text">%s</span>' .
1105 '<span id="table-paging" class="paging-input">' .
1106 '<span class="tablenav-paging-text">',
1107 /* translators: Hidden accessibility text. */
1108 __( 'Current Page' )
1109 );
1110 } else {
1111 $html_current_page = sprintf(
1112 '<label for="current-page-selector" class="screen-reader-text">%s</label>' .
1113 "<input class='current-page' id='current-page-selector' type='text'
1114 name='paged' value='%s' size='%d' aria-describedby='table-paging' />" .
1115 "<span class='tablenav-paging-text'>",
1116 /* translators: Hidden accessibility text. */
1117 __( 'Current Page' ),
1118 $current,
1119 strlen( $total_pages )
1120 );
1121 }
1122
1123 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
1124
1125 $page_links[] = $total_pages_before . sprintf(
1126 /* translators: 1: Current page, 2: Total pages. */
1127 _x( '%1$s of %2$s', 'paging' ),
1128 $html_current_page,
1129 $html_total_pages
1130 ) . $total_pages_after;
1131
1132 if ( $disable_next ) {
1133 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
1134 } else {
1135 $page_links[] = sprintf(
1136 "<a class='next-page button' href='%s'>" .
1137 "<span class='screen-reader-text'>%s</span>" .
1138 "<span aria-hidden='true'>%s</span>" .
1139 '</a>',
1140 esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
1141 /* translators: Hidden accessibility text. */
1142 __( 'Next page' ),
1143 '&rsaquo;'
1144 );
1145 }
1146
1147 if ( $disable_last ) {
1148 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
1149 } else {
1150 $page_links[] = sprintf(
1151 "<a class='last-page button' href='%s'>" .
1152 "<span class='screen-reader-text'>%s</span>" .
1153 "<span aria-hidden='true'>%s</span>" .
1154 '</a>',
1155 esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
1156 /* translators: Hidden accessibility text. */
1157 __( 'Last page' ),
1158 '&raquo;'
1159 );
1160 }
1161
1162 $pagination_links_class = 'pagination-links';
1163 if ( ! empty( $infinite_scroll ) ) {
1164 $pagination_links_class .= ' hide-if-js';
1165 }
1166 $output .= "\n<span class='$pagination_links_class'>" . implode( "\n", $page_links ) . '</span>';
1167
1168 if ( $total_pages ) {
1169 $page_class = $total_pages < 2 ? ' one-page' : '';
1170 } else {
1171 $page_class = ' no-pages';
1172 }
1173 $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
1174
1175 echo $this->_pagination;
1176 }
1177
1178 /**
1179 * Gets a list of columns.
1180 *
1181 * The format is:
1182 * - `'internal-name' => 'Title'`
1183 *
1184 * @since 3.1.0
1185 * @abstract
1186 *
1187 * @return array
1188 */
1189 public function get_columns() {
1190 die( 'function WP_List_Table::get_columns() must be overridden in a subclass.' );
1191 }
1192
1193 /**
1194 * Gets a list of sortable columns.
1195 *
1196 * The format is:
1197 * - `'internal-name' => 'orderby'`
1198 * - `'internal-name' => array( 'orderby', bool, 'abbr', 'orderby-text', 'initially-sorted-column-order' )` -
1199 * - `'internal-name' => array( 'orderby', 'asc' )` - The second element sets the initial sorting order.
1200 * - `'internal-name' => array( 'orderby', true )` - The second element makes the initial order descending.
1201 *
1202 * In the second format, passing true as second parameter will make the initial
1203 * sorting order be descending. Following parameters add a short column name to
1204 * be used as 'abbr' attribute, a translatable string for the current sorting,
1205 * and the initial order for the initial sorted column, 'asc' or 'desc' (default: false).
1206 *
1207 * @since 3.1.0
1208 * @since 6.3.0 Added 'abbr', 'orderby-text' and 'initially-sorted-column-order'.
1209 *
1210 * @return array
1211 */
1212 protected function get_sortable_columns() {
1213 return array();
1214 }
1215
1216 /**
1217 * Gets the name of the default primary column.
1218 *
1219 * @since 4.3.0
1220 *
1221 * @return string Name of the default primary column, in this case, an empty string.
1222 */
1223 protected function get_default_primary_column_name() {
1224 $columns = $this->get_columns();
1225 $column = '';
1226
1227 if ( empty( $columns ) ) {
1228 return $column;
1229 }
1230
1231 /*
1232 * We need a primary defined so responsive views show something,
1233 * so let's fall back to the first non-checkbox column.
1234 */
1235 foreach ( $columns as $col => $column_name ) {
1236 if ( 'cb' === $col ) {
1237 continue;
1238 }
1239
1240 $column = $col;
1241 break;
1242 }
1243
1244 return $column;
1245 }
1246
1247 /**
1248 * Gets the name of the primary column.
1249 *
1250 * Public wrapper for WP_List_Table::get_default_primary_column_name().
1251 *
1252 * @since 4.4.0
1253 *
1254 * @return string Name of the default primary column.
1255 */
1256 public function get_primary_column() {
1257 return $this->get_primary_column_name();
1258 }
1259
1260 /**
1261 * Gets the name of the primary column.
1262 *
1263 * @since 4.3.0
1264 *
1265 * @return string The name of the primary column.
1266 */
1267 protected function get_primary_column_name() {
1268 $columns = get_column_headers( $this->screen );
1269 $default = $this->get_default_primary_column_name();
1270
1271 /*
1272 * If the primary column doesn't exist,
1273 * fall back to the first non-checkbox column.
1274 */
1275 if ( ! isset( $columns[ $default ] ) ) {
1276 $default = self::get_default_primary_column_name();
1277 }
1278
1279 /**
1280 * Filters the name of the primary column for the current list table.
1281 *
1282 * @since 4.3.0
1283 *
1284 * @param string $default Column name default for the specific list table, e.g. 'name'.
1285 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
1286 */
1287 $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
1288
1289 if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
1290 $column = $default;
1291 }
1292
1293 return $column;
1294 }
1295
1296 /**
1297 * Gets a list of all, hidden, and sortable columns, with filter applied.
1298 *
1299 * @since 3.1.0
1300 *
1301 * @return array
1302 */
1303 protected function get_column_info() {
1304 // $_column_headers is already set / cached.
1305 if (
1306 isset( $this->_column_headers ) &&
1307 is_array( $this->_column_headers )
1308 ) {
1309 /*
1310 * Backward compatibility for `$_column_headers` format prior to WordPress 4.3.
1311 *
1312 * In WordPress 4.3 the primary column name was added as a fourth item in the
1313 * column headers property. This ensures the primary column name is included
1314 * in plugins setting the property directly in the three item format.
1315 */
1316 if ( 4 === count( $this->_column_headers ) ) {
1317 return $this->_column_headers;
1318 }
1319
1320 $column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
1321 foreach ( $this->_column_headers as $key => $value ) {
1322 $column_headers[ $key ] = $value;
1323 }
1324
1325 $this->_column_headers = $column_headers;
1326
1327 return $this->_column_headers;
1328 }
1329
1330 $columns = get_column_headers( $this->screen );
1331 $hidden = get_hidden_columns( $this->screen );
1332
1333 $sortable_columns = $this->get_sortable_columns();
1334 /**
1335 * Filters the list table sortable columns for a specific screen.
1336 *
1337 * The dynamic portion of the hook name, `$this->screen->id`, refers
1338 * to the ID of the current screen.
1339 *
1340 * @since 3.1.0
1341 *
1342 * @param array $sortable_columns An array of sortable columns.
1343 */
1344 $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
1345
1346 $sortable = array();
1347 foreach ( $_sortable as $id => $data ) {
1348 if ( empty( $data ) ) {
1349 continue;
1350 }
1351
1352 $data = (array) $data;
1353 // Descending initial sorting.
1354 if ( ! isset( $data[1] ) ) {
1355 $data[1] = false;
1356 }
1357 // Current sorting translatable string.
1358 if ( ! isset( $data[2] ) ) {
1359 $data[2] = '';
1360 }
1361 // Initial view sorted column and asc/desc order, default: false.
1362 if ( ! isset( $data[3] ) ) {
1363 $data[3] = false;
1364 }
1365 // Initial order for the initial sorted column, default: false.
1366 if ( ! isset( $data[4] ) ) {
1367 $data[4] = false;
1368 }
1369
1370 $sortable[ $id ] = $data;
1371 }
1372
1373 $primary = $this->get_primary_column_name();
1374 $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
1375
1376 return $this->_column_headers;
1377 }
1378
1379 /**
1380 * Returns the number of visible columns.
1381 *
1382 * @since 3.1.0
1383 *
1384 * @return int
1385 */
1386 public function get_column_count() {
1387 list ( $columns, $hidden ) = $this->get_column_info();
1388 $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
1389 return count( $columns ) - count( $hidden );
1390 }
1391
1392 /**
1393 * Prints column headers, accounting for hidden and sortable columns.
1394 *
1395 * @since 3.1.0
1396 *
1397 * @param bool $with_id Whether to set the ID attribute or not
1398 */
1399 public function print_column_headers( $with_id = true ) {
1400 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1401
1402 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1403 $current_url = remove_query_arg( 'paged', $current_url );
1404
1405 // When users click on a column header to sort by other columns.
1406 if ( isset( $_GET['orderby'] ) ) {
1407 $current_orderby = $_GET['orderby'];
1408 // In the initial view there's no orderby parameter.
1409 } else {
1410 $current_orderby = '';
1411 }
1412
1413 // Not in the initial view and descending order.
1414 if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
1415 $current_order = 'desc';
1416 } else {
1417 // The initial view is not always 'asc', we'll take care of this below.
1418 $current_order = 'asc';
1419 }
1420
1421 if ( ! empty( $columns['cb'] ) ) {
1422 static $cb_counter = 1;
1423 $columns['cb'] = '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />
1424 <label for="cb-select-all-' . $cb_counter . '">' .
1425 '<span class="screen-reader-text">' .
1426 /* translators: Hidden accessibility text. */
1427 __( 'Select All' ) .
1428 '</span>' .
1429 '</label>';
1430 ++$cb_counter;
1431 }
1432
1433 foreach ( $columns as $column_key => $column_display_name ) {
1434 $class = array( 'manage-column', "column-$column_key" );
1435 $aria_sort_attr = '';
1436 $abbr_attr = '';
1437 $order_text = '';
1438
1439 if ( in_array( $column_key, $hidden, true ) ) {
1440 $class[] = 'hidden';
1441 }
1442
1443 if ( 'cb' === $column_key ) {
1444 $class[] = 'check-column';
1445 } elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ), true ) ) {
1446 $class[] = 'num';
1447 }
1448
1449 if ( $column_key === $primary ) {
1450 $class[] = 'column-primary';
1451 }
1452
1453 if ( isset( $sortable[ $column_key ] ) ) {
1454 $orderby = isset( $sortable[ $column_key ][0] ) ? $sortable[ $column_key ][0] : '';
1455 $desc_first = isset( $sortable[ $column_key ][1] ) ? $sortable[ $column_key ][1] : false;
1456 $abbr = isset( $sortable[ $column_key ][2] ) ? $sortable[ $column_key ][2] : '';
1457 $orderby_text = isset( $sortable[ $column_key ][3] ) ? $sortable[ $column_key ][3] : '';
1458 $initial_order = isset( $sortable[ $column_key ][4] ) ? $sortable[ $column_key ][4] : '';
1459
1460 /*
1461 * We're in the initial view and there's no $_GET['orderby'] then check if the
1462 * initial sorting information is set in the sortable columns and use that.
1463 */
1464 if ( '' === $current_orderby && $initial_order ) {
1465 // Use the initially sorted column $orderby as current orderby.
1466 $current_orderby = $orderby;
1467 // Use the initially sorted column asc/desc order as initial order.
1468 $current_order = $initial_order;
1469 }
1470
1471 /*
1472 * True in the initial view when an initial orderby is set via get_sortable_columns()
1473 * and true in the sorted views when the actual $_GET['orderby'] is equal to $orderby.
1474 */
1475 if ( $current_orderby === $orderby ) {
1476 // The sorted column. The `aria-sort` attribute must be set only on the sorted column.
1477 if ( 'asc' === $current_order ) {
1478 $order = 'desc';
1479 $aria_sort_attr = ' aria-sort="ascending"';
1480 } else {
1481 $order = 'asc';
1482 $aria_sort_attr = ' aria-sort="descending"';
1483 }
1484
1485 $class[] = 'sorted';
1486 $class[] = $current_order;
1487 } else {
1488 // The other sortable columns.
1489 $order = strtolower( $desc_first );
1490
1491 if ( ! in_array( $order, array( 'desc', 'asc' ), true ) ) {
1492 $order = $desc_first ? 'desc' : 'asc';
1493 }
1494
1495 $class[] = 'sortable';
1496 $class[] = 'desc' === $order ? 'asc' : 'desc';
1497
1498 /* translators: Hidden accessibility text. */
1499 $asc_text = __( 'Sort ascending.' );
1500 /* translators: Hidden accessibility text. */
1501 $desc_text = __( 'Sort descending.' );
1502 $order_text = 'asc' === $order ? $asc_text : $desc_text;
1503 }
1504
1505 if ( '' !== $order_text ) {
1506 $order_text = ' <span class="screen-reader-text">' . $order_text . '</span>';
1507 }
1508
1509 // Print an 'abbr' attribute if a value is provided via get_sortable_columns().
1510 $abbr_attr = $abbr ? ' abbr="' . esc_attr( $abbr ) . '"' : '';
1511
1512 $column_display_name = sprintf(
1513 '<a href="%1$s">' .
1514 '<span>%2$s</span>' .
1515 '<span class="sorting-indicators">' .
1516 '<span class="sorting-indicator asc" aria-hidden="true"></span>' .
1517 '<span class="sorting-indicator desc" aria-hidden="true"></span>' .
1518 '</span>' .
1519 '%3$s' .
1520 '</a>',
1521 esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ),
1522 $column_display_name,
1523 $order_text
1524 );
1525 }
1526
1527 $tag = ( 'cb' === $column_key ) ? 'td' : 'th';
1528 $scope = ( 'th' === $tag ) ? 'scope="col"' : '';
1529 $id = $with_id ? "id='$column_key'" : '';
1530 $class_attr = "class='" . implode( ' ', $class ) . "'";
1531
1532 echo "<$tag $scope $id $class_attr $aria_sort_attr $abbr_attr>$column_display_name</$tag>";
1533 }
1534 }
1535
1536 /**
1537 * Print a table description with information about current sorting and order.
1538 *
1539 * For the table initial view, information about initial orderby and order
1540 * should be provided via get_sortable_columns().
1541 *
1542 * @since 6.3.0
1543 */
1544 public function print_table_description() {
1545 list( $columns, $hidden, $sortable ) = $this->get_column_info();
1546
1547 if ( empty( $sortable ) ) {
1548 return;
1549 }
1550
1551 // When users click on a column header to sort by other columns.
1552 if ( isset( $_GET['orderby'] ) ) {
1553 $current_orderby = $_GET['orderby'];
1554 // In the initial view there's no orderby parameter.
1555 } else {
1556 $current_orderby = '';
1557 }
1558
1559 // Not in the initial view and descending order.
1560 if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
1561 $current_order = 'desc';
1562 } else {
1563 // The initial view is not always 'asc', we'll take care of this below.
1564 $current_order = 'asc';
1565 }
1566
1567 foreach ( array_keys( $columns ) as $column_key ) {
1568
1569 if ( isset( $sortable[ $column_key ] ) ) {
1570 $orderby = isset( $sortable[ $column_key ][0] ) ? $sortable[ $column_key ][0] : '';
1571 $desc_first = isset( $sortable[ $column_key ][1] ) ? $sortable[ $column_key ][1] : false;
1572 $abbr = isset( $sortable[ $column_key ][2] ) ? $sortable[ $column_key ][2] : '';
1573 $orderby_text = isset( $sortable[ $column_key ][3] ) ? $sortable[ $column_key ][3] : '';
1574 $initial_order = isset( $sortable[ $column_key ][4] ) ? $sortable[ $column_key ][4] : '';
1575
1576 if ( ! is_string( $orderby_text ) || '' === $orderby_text ) {
1577 return;
1578 }
1579 /*
1580 * We're in the initial view and there's no $_GET['orderby'] then check if the
1581 * initial sorting information is set in the sortable columns and use that.
1582 */
1583 if ( '' === $current_orderby && $initial_order ) {
1584 // Use the initially sorted column $orderby as current orderby.
1585 $current_orderby = $orderby;
1586 // Use the initially sorted column asc/desc order as initial order.
1587 $current_order = $initial_order;
1588 }
1589
1590 /*
1591 * True in the initial view when an initial orderby is set via get_sortable_columns()
1592 * and true in the sorted views when the actual $_GET['orderby'] is equal to $orderby.
1593 */
1594 if ( $current_orderby === $orderby ) {
1595 /* translators: Hidden accessibility text. */
1596 $asc_text = __( 'Ascending.' );
1597 /* translators: Hidden accessibility text. */
1598 $desc_text = __( 'Descending.' );
1599 $order_text = 'asc' === $current_order ? $asc_text : $desc_text;
1600 echo '<caption class="screen-reader-text">' . $orderby_text . ' ' . $order_text . '</caption>';
1601
1602 return;
1603 }
1604 }
1605 }
1606 }
1607
1608 /**
1609 * Displays the table.
1610 *
1611 * @since 3.1.0
1612 */
1613 public function display() {
1614 $singular = $this->_args['singular'];
1615
1616 $this->display_tablenav( 'top' );
1617
1618 $this->screen->render_screen_reader_content( 'heading_list' );
1619 ?>
1620<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
1621 <?php $this->print_table_description(); ?>
1622 <thead>
1623 <tr>
1624 <?php $this->print_column_headers(); ?>
1625 </tr>
1626 </thead>
1627
1628 <tbody id="the-list"
1629 <?php
1630 if ( $singular ) {
1631 echo " data-wp-lists='list:$singular'";
1632 }
1633 ?>
1634 >
1635 <?php $this->display_rows_or_placeholder(); ?>
1636 </tbody>
1637
1638 <tfoot>
1639 <tr>
1640 <?php $this->print_column_headers( false ); ?>
1641 </tr>
1642 </tfoot>
1643
1644</table>
1645 <?php
1646 $this->display_tablenav( 'bottom' );
1647 }
1648
1649 /**
1650 * Gets a list of CSS classes for the WP_List_Table table tag.
1651 *
1652 * @since 3.1.0
1653 *
1654 * @return string[] Array of CSS classes for the table tag.
1655 */
1656 protected function get_table_classes() {
1657 $mode = get_user_setting( 'posts_list_mode', 'list' );
1658
1659 $mode_class = esc_attr( 'table-view-' . $mode );
1660
1661 return array( 'widefat', 'fixed', 'striped', $mode_class, $this->_args['plural'] );
1662 }
1663
1664 /**
1665 * Generates the table navigation above or below the table
1666 *
1667 * @since 3.1.0
1668 * @param string $which The location of the navigation: Either 'top' or 'bottom'.
1669 */
1670 protected function display_tablenav( $which ) {
1671 if ( 'bottom' === $which && ! $this->has_items() ) {
1672 return;
1673 }
1674 if ( 'top' === $which ) {
1675 wp_nonce_field( 'bulk-' . $this->_args['plural'] );
1676 }
1677 ?>
1678 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1679
1680 <?php if ( $this->has_items() ) : ?>
1681 <div class="alignleft actions bulkactions">
1682 <?php $this->bulk_actions( $which ); ?>
1683 </div>
1684 <?php
1685 endif;
1686 $this->extra_tablenav( $which );
1687 $this->pagination( $which );
1688 ?>
1689
1690 <br class="clear" />
1691 </div>
1692 <?php
1693 }
1694
1695 /**
1696 * Displays extra controls between bulk actions and pagination.
1697 *
1698 * @since 3.1.0
1699 *
1700 * @param string $which
1701 */
1702 protected function extra_tablenav( $which ) {}
1703
1704 /**
1705 * Generates the tbody element for the list table.
1706 *
1707 * @since 3.1.0
1708 */
1709 public function display_rows_or_placeholder() {
1710 if ( $this->has_items() ) {
1711 $this->display_rows();
1712 } else {
1713 echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
1714 $this->no_items();
1715 echo '</td></tr>';
1716 }
1717 }
1718
1719 /**
1720 * Generates the list table rows.
1721 *
1722 * @since 3.1.0
1723 */
1724 public function display_rows() {
1725 foreach ( $this->items as $item ) {
1726 $this->single_row( $item );
1727 }
1728 }
1729
1730 /**
1731 * Generates content for a single row of the table.
1732 *
1733 * @since 3.1.0
1734 *
1735 * @param object|array $item The current item
1736 */
1737 public function single_row( $item ) {
1738 echo '<tr>';
1739 $this->single_row_columns( $item );
1740 echo '</tr>';
1741 }
1742
1743 /**
1744 * @param object|array $item
1745 * @param string $column_name
1746 */
1747 protected function column_default( $item, $column_name ) {}
1748
1749 /**
1750 * @param object|array $item
1751 */
1752 protected function column_cb( $item ) {}
1753
1754 /**
1755 * Generates the columns for a single row of the table.
1756 *
1757 * @since 3.1.0
1758 *
1759 * @param object|array $item The current item.
1760 */
1761 protected function single_row_columns( $item ) {
1762 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1763
1764 foreach ( $columns as $column_name => $column_display_name ) {
1765 $classes = "$column_name column-$column_name";
1766 if ( $primary === $column_name ) {
1767 $classes .= ' has-row-actions column-primary';
1768 }
1769
1770 if ( in_array( $column_name, $hidden, true ) ) {
1771 $classes .= ' hidden';
1772 }
1773
1774 /*
1775 * Comments column uses HTML in the display name with screen reader text.
1776 * Strip tags to get closer to a user-friendly string.
1777 */
1778 $data = 'data-colname="' . esc_attr( wp_strip_all_tags( $column_display_name ) ) . '"';
1779
1780 $attributes = "class='$classes' $data";
1781
1782 if ( 'cb' === $column_name ) {
1783 echo '<th scope="row" class="check-column">';
1784 echo $this->column_cb( $item );
1785 echo '</th>';
1786 } elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1787 echo call_user_func(
1788 array( $this, '_column_' . $column_name ),
1789 $item,
1790 $classes,
1791 $data,
1792 $primary
1793 );
1794 } elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1795 echo "<td $attributes>";
1796 echo call_user_func( array( $this, 'column_' . $column_name ), $item );
1797 echo $this->handle_row_actions( $item, $column_name, $primary );
1798 echo '</td>';
1799 } else {
1800 echo "<td $attributes>";
1801 echo $this->column_default( $item, $column_name );
1802 echo $this->handle_row_actions( $item, $column_name, $primary );
1803 echo '</td>';
1804 }
1805 }
1806 }
1807
1808 /**
1809 * Generates and display row actions links for the list table.
1810 *
1811 * @since 4.3.0
1812 *
1813 * @param object|array $item The item being acted upon.
1814 * @param string $column_name Current column name.
1815 * @param string $primary Primary column name.
1816 * @return string The row actions HTML, or an empty string
1817 * if the current column is not the primary column.
1818 */
1819 protected function handle_row_actions( $item, $column_name, $primary ) {
1820 return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' .
1821 /* translators: Hidden accessibility text. */
1822 __( 'Show more details' ) .
1823 '</span></button>' : '';
1824 }
1825
1826 /**
1827 * Handles an incoming ajax request (called from admin-ajax.php)
1828 *
1829 * @since 3.1.0
1830 */
1831 public function ajax_response() {
1832 $this->prepare_items();
1833
1834 ob_start();
1835 if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1836 $this->display_rows();
1837 } else {
1838 $this->display_rows_or_placeholder();
1839 }
1840
1841 $rows = ob_get_clean();
1842
1843 $response = array( 'rows' => $rows );
1844
1845 if ( isset( $this->_pagination_args['total_items'] ) ) {
1846 $response['total_items_i18n'] = sprintf(
1847 /* translators: Number of items. */
1848 _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1849 number_format_i18n( $this->_pagination_args['total_items'] )
1850 );
1851 }
1852 if ( isset( $this->_pagination_args['total_pages'] ) ) {
1853 $response['total_pages'] = $this->_pagination_args['total_pages'];
1854 $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1855 }
1856
1857 die( wp_json_encode( $response ) );
1858 }
1859
1860 /**
1861 * Sends required variables to JavaScript land.
1862 *
1863 * @since 3.1.0
1864 */
1865 public function _js_vars() {
1866 $args = array(
1867 'class' => get_class( $this ),
1868 'screen' => array(
1869 'id' => $this->screen->id,
1870 'base' => $this->screen->base,
1871 ),
1872 );
1873
1874 printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) );
1875 }
1876}
1877
Ui Ux Design – Teachers Night Out

Get in Touch

© 2024 Teachers Night Out. All Rights Reserved.