WP_List_Table::pagination()   F
last analyzed

Complexity

Conditions 17
Paths 12289

Size

Total Lines 115
Code Lines 77

Duplication

Lines 34
Ratio 29.57 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
eloc 77
c 1
b 0
f 0
nc 12289
nop 1
dl 34
loc 115
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * @access private
15
 */
16
class WP_List_Table {
17
18
	/**
19
	 * The current list of items.
20
	 *
21
	 * @since 3.1.0
22
	 * @access public
23
	 * @var array
24
	 */
25
	public $items;
26
27
	/**
28
	 * Various information about the current table.
29
	 *
30
	 * @since 3.1.0
31
	 * @access protected
32
	 * @var array
33
	 */
34
	protected $_args;
35
36
	/**
37
	 * Various information needed for displaying the pagination.
38
	 *
39
	 * @since 3.1.0
40
	 * @access protected
41
	 * @var array
42
	 */
43
	protected $_pagination_args = array();
44
45
	/**
46
	 * The current screen.
47
	 *
48
	 * @since 3.1.0
49
	 * @access protected
50
	 * @var object
51
	 */
52
	protected $screen;
53
54
	/**
55
	 * Cached bulk actions.
56
	 *
57
	 * @since 3.1.0
58
	 * @access private
59
	 * @var array
60
	 */
61
	private $_actions;
62
63
	/**
64
	 * Cached pagination output.
65
	 *
66
	 * @since 3.1.0
67
	 * @access private
68
	 * @var string
69
	 */
70
	private $_pagination;
71
72
	/**
73
	 * The view switcher modes.
74
	 *
75
	 * @since 4.1.0
76
	 * @access protected
77
	 * @var array
78
	 */
79
	protected $modes = array();
80
81
	/**
82
	 * Stores the value returned by ->get_column_info().
83
	 *
84
	 * @since 4.1.0
85
	 * @access protected
86
	 * @var array
87
	 */
88
	protected $_column_headers;
89
90
	/**
91
	 * {@internal Missing Summary}
92
	 *
93
	 * @access protected
94
	 * @var array
95
	 */
96
	protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' );
97
98
	/**
99
	 * {@internal Missing Summary}
100
	 *
101
	 * @access protected
102
	 * @var array
103
	 */
104
	protected $compat_methods = array( 'set_pagination_args', 'get_views', 'get_bulk_actions', 'bulk_actions',
105
		'row_actions', 'months_dropdown', 'view_switcher', 'comments_bubble', 'get_items_per_page', 'pagination',
106
		'get_sortable_columns', 'get_column_info', 'get_table_classes', 'display_tablenav', 'extra_tablenav',
107
		'single_row_columns' );
108
109
	/**
110
	 * Constructor.
111
	 *
112
	 * The child class should call this constructor from its own constructor to override
113
	 * the default $args.
114
	 *
115
	 * @since 3.1.0
116
	 * @access public
117
	 *
118
	 * @param array|string $args {
119
	 *     Array or string of arguments.
120
	 *
121
	 *     @type string $plural   Plural value used for labels and the objects being listed.
122
	 *                            This affects things such as CSS class-names and nonces used
123
	 *                            in the list table, e.g. 'posts'. Default empty.
124
	 *     @type string $singular Singular label for an object being listed, e.g. 'post'.
125
	 *                            Default empty
126
	 *     @type bool   $ajax     Whether the list table supports Ajax. This includes loading
127
	 *                            and sorting data, for example. If true, the class will call
128
	 *                            the _js_vars() method in the footer to provide variables
129
	 *                            to any scripts handling Ajax events. Default false.
130
	 *     @type string $screen   String containing the hook name used to determine the current
131
	 *                            screen. If left null, the current screen will be automatically set.
132
	 *                            Default null.
133
	 * }
134
	 */
135
	public function __construct( $args = array() ) {
136
		$args = wp_parse_args( $args, array(
137
			'plural' => '',
138
			'singular' => '',
139
			'ajax' => false,
140
			'screen' => null,
141
		) );
142
143
		$this->screen = convert_to_screen( $args['screen'] );
144
145
		add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
146
147
		if ( !$args['plural'] )
148
			$args['plural'] = $this->screen->base;
149
150
		$args['plural'] = sanitize_key( $args['plural'] );
151
		$args['singular'] = sanitize_key( $args['singular'] );
152
153
		$this->_args = $args;
154
155
		if ( $args['ajax'] ) {
156
			// wp_enqueue_script( 'list-table' );
157
			add_action( 'admin_footer', array( $this, '_js_vars' ) );
158
		}
159
160
		if ( empty( $this->modes ) ) {
161
			$this->modes = array(
162
				'list'    => __( 'List View' ),
163
				'excerpt' => __( 'Excerpt View' )
164
			);
165
		}
166
	}
167
168
	/**
169
	 * Make private properties readable for backward compatibility.
170
	 *
171
	 * @since 4.0.0
172
	 * @access public
173
	 *
174
	 * @param string $name Property to get.
175
	 * @return mixed Property.
176
	 */
177
	public function __get( $name ) {
178
		if ( in_array( $name, $this->compat_fields ) ) {
179
			return $this->$name;
180
		}
181
	}
182
183
	/**
184
	 * Make private properties settable for backward compatibility.
185
	 *
186
	 * @since 4.0.0
187
	 * @access public
188
	 *
189
	 * @param string $name  Property to check if set.
190
	 * @param mixed  $value Property value.
191
	 * @return mixed Newly-set property.
192
	 */
193
	public function __set( $name, $value ) {
194
		if ( in_array( $name, $this->compat_fields ) ) {
195
			return $this->$name = $value;
196
		}
197
	}
198
199
	/**
200
	 * Make private properties checkable for backward compatibility.
201
	 *
202
	 * @since 4.0.0
203
	 * @access public
204
	 *
205
	 * @param string $name Property to check if set.
206
	 * @return bool Whether the property is set.
207
	 */
208
	public function __isset( $name ) {
209
		if ( in_array( $name, $this->compat_fields ) ) {
210
			return isset( $this->$name );
211
		}
212
	}
213
214
	/**
215
	 * Make private properties un-settable for backward compatibility.
216
	 *
217
	 * @since 4.0.0
218
	 * @access public
219
	 *
220
	 * @param string $name Property to unset.
221
	 */
222
	public function __unset( $name ) {
223
		if ( in_array( $name, $this->compat_fields ) ) {
224
			unset( $this->$name );
225
		}
226
	}
227
228
	/**
229
	 * Make private/protected methods readable for backward compatibility.
230
	 *
231
	 * @since 4.0.0
232
	 * @access public
233
	 *
234
	 * @param callable $name      Method to call.
235
	 * @param array    $arguments Arguments to pass when calling.
236
	 * @return mixed|bool Return value of the callback, false otherwise.
237
	 */
238
	public function __call( $name, $arguments ) {
239
		if ( in_array( $name, $this->compat_methods ) ) {
240
			return call_user_func_array( array( $this, $name ), $arguments );
241
		}
242
		return false;
243
	}
244
245
	/**
246
	 * Checks the current user's permissions
247
	 *
248
	 * @since 3.1.0
249
	 * @access public
250
	 * @abstract
251
	 */
252
	public function ajax_user_can() {
253
		die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
254
	}
255
256
	/**
257
	 * Prepares the list of items for displaying.
258
	 * @uses WP_List_Table::set_pagination_args()
259
	 *
260
	 * @since 3.1.0
261
	 * @access public
262
	 * @abstract
263
	 */
264
	public function prepare_items() {
265
		die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
266
	}
267
268
	/**
269
	 * An internal method that sets all the necessary pagination arguments
270
	 *
271
	 * @since 3.1.0
272
	 * @access protected
273
	 *
274
	 * @param array|string $args Array or string of arguments with information about the pagination.
275
	 */
276
	protected function set_pagination_args( $args ) {
277
		$args = wp_parse_args( $args, array(
278
			'total_items' => 0,
279
			'total_pages' => 0,
280
			'per_page' => 0,
281
		) );
282
283
		if ( !$args['total_pages'] && $args['per_page'] > 0 )
284
			$args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
285
286
		// Redirect if page number is invalid and headers are not already sent.
287
		if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
288
			wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
289
			exit;
290
		}
291
292
		$this->_pagination_args = $args;
293
	}
294
295
	/**
296
	 * Access the pagination args.
297
	 *
298
	 * @since 3.1.0
299
	 * @access public
300
	 *
301
	 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
302
	 *                    'total_pages', 'per_page', or 'infinite_scroll'.
303
	 * @return int Number of items that correspond to the given pagination argument.
304
	 */
305
	public function get_pagination_arg( $key ) {
306
		if ( 'page' === $key ) {
307
			return $this->get_pagenum();
308
		}
309
310
		if ( isset( $this->_pagination_args[$key] ) ) {
311
			return $this->_pagination_args[$key];
312
		}
313
	}
314
315
	/**
316
	 * Whether the table has items to display or not
317
	 *
318
	 * @since 3.1.0
319
	 * @access public
320
	 *
321
	 * @return bool
322
	 */
323
	public function has_items() {
324
		return !empty( $this->items );
325
	}
326
327
	/**
328
	 * Message to be displayed when there are no items
329
	 *
330
	 * @since 3.1.0
331
	 * @access public
332
	 */
333
	public function no_items() {
334
		_e( 'No items found.' );
335
	}
336
337
	/**
338
	 * Displays the search box.
339
	 *
340
	 * @since 3.1.0
341
	 * @access public
342
	 *
343
	 * @param string $text     The 'submit' button label.
344
	 * @param string $input_id ID attribute value for the search input field.
345
	 */
346
	public function search_box( $text, $input_id ) {
347
		if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
348
			return;
349
350
		$input_id = $input_id . '-search-input';
351
352 View Code Duplication
		if ( ! empty( $_REQUEST['orderby'] ) )
353
			echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
354 View Code Duplication
		if ( ! empty( $_REQUEST['order'] ) )
355
			echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
356 View Code Duplication
		if ( ! empty( $_REQUEST['post_mime_type'] ) )
357
			echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
358 View Code Duplication
		if ( ! empty( $_REQUEST['detached'] ) )
359
			echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
360
?>
361
<p class="search-box">
362
	<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
363
	<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
364
	<?php submit_button( $text, 'button', '', false, array( 'id' => 'search-submit' ) ); ?>
365
</p>
366
<?php
367
	}
368
369
	/**
370
	 * Get an associative array ( id => link ) with the list
371
	 * of views available on this table.
372
	 *
373
	 * @since 3.1.0
374
	 * @access protected
375
	 *
376
	 * @return array
377
	 */
378
	protected function get_views() {
379
		return array();
380
	}
381
382
	/**
383
	 * Display the list of views available on this table.
384
	 *
385
	 * @since 3.1.0
386
	 * @access public
387
	 */
388
	public function views() {
389
		$views = $this->get_views();
390
		/**
391
		 * Filters the list of available list table views.
392
		 *
393
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
394
		 * to the ID of the current screen, usually a string.
395
		 *
396
		 * @since 3.5.0
397
		 *
398
		 * @param array $views An array of available list table views.
399
		 */
400
		$views = apply_filters( "views_{$this->screen->id}", $views );
401
402
		if ( empty( $views ) )
403
			return;
404
405
		$this->screen->render_screen_reader_content( 'heading_views' );
406
407
		echo "<ul class='subsubsub'>\n";
408
		foreach ( $views as $class => $view ) {
409
			$views[ $class ] = "\t<li class='$class'>$view";
410
		}
411
		echo implode( " |</li>\n", $views ) . "</li>\n";
412
		echo "</ul>";
413
	}
414
415
	/**
416
	 * Get an associative array ( option_name => option_title ) with the list
417
	 * of bulk actions available on this table.
418
	 *
419
	 * @since 3.1.0
420
	 * @access protected
421
	 *
422
	 * @return array
423
	 */
424
	protected function get_bulk_actions() {
425
		return array();
426
	}
427
428
	/**
429
	 * Display the bulk actions dropdown.
430
	 *
431
	 * @since 3.1.0
432
	 * @access protected
433
	 *
434
	 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
435
	 *                      This is designated as optional for backward compatibility.
436
	 */
437
	protected function bulk_actions( $which = '' ) {
438
		if ( is_null( $this->_actions ) ) {
439
			$no_new_actions = $this->_actions = $this->get_bulk_actions();
440
			/**
441
			 * Filters the list table Bulk Actions drop-down.
442
			 *
443
			 * The dynamic portion of the hook name, `$this->screen->id`, refers
444
			 * to the ID of the current screen, usually a string.
445
			 *
446
			 * This filter can currently only be used to remove bulk actions.
447
			 *
448
			 * @since 3.5.0
449
			 *
450
			 * @param array $actions An array of the available bulk actions.
451
			 */
452
			$this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters("bulk_acti...>id}", $this->_actions) of type * is incompatible with the declared type array of property $_actions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
453
			$this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
454
			$two = '';
455
		} else {
456
			$two = '2';
457
		}
458
459
		if ( empty( $this->_actions ) )
460
			return;
461
462
		echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' . __( 'Select bulk action' ) . '</label>';
463
		echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
464
		echo '<option value="-1">' . __( 'Bulk Actions' ) . "</option>\n";
465
466
		foreach ( $this->_actions as $name => $title ) {
467
			$class = 'edit' === $name ? ' class="hide-if-no-js"' : '';
468
469
			echo "\t" . '<option value="' . $name . '"' . $class . '>' . $title . "</option>\n";
470
		}
471
472
		echo "</select>\n";
473
474
		submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
475
		echo "\n";
476
	}
477
478
	/**
479
	 * Get the current action selected from the bulk actions dropdown.
480
	 *
481
	 * @since 3.1.0
482
	 * @access public
483
	 *
484
	 * @return string|false The action name or False if no action was selected
485
	 */
486
	public function current_action() {
487
		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) )
488
			return false;
489
490 View Code Duplication
		if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
491
			return $_REQUEST['action'];
492
493 View Code Duplication
		if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
494
			return $_REQUEST['action2'];
495
496
		return false;
497
	}
498
499
	/**
500
	 * Generate row actions div
501
	 *
502
	 * @since 3.1.0
503
	 * @access protected
504
	 *
505
	 * @param array $actions The list of actions
506
	 * @param bool $always_visible Whether the actions should be always visible
507
	 * @return string
508
	 */
509
	protected function row_actions( $actions, $always_visible = false ) {
510
		$action_count = count( $actions );
511
		$i = 0;
512
513
		if ( !$action_count )
514
			return '';
515
516
		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
517 View Code Duplication
		foreach ( $actions as $action => $link ) {
518
			++$i;
519
			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
520
			$out .= "<span class='$action'>$link$sep</span>";
521
		}
522
		$out .= '</div>';
523
524
		$out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
525
526
		return $out;
527
	}
528
529
	/**
530
	 * Display a monthly dropdown for filtering items
531
	 *
532
	 * @since 3.1.0
533
	 * @access protected
534
	 *
535
	 * @global wpdb      $wpdb
536
	 * @global WP_Locale $wp_locale
537
	 *
538
	 * @param string $post_type
539
	 */
540
	protected function months_dropdown( $post_type ) {
541
		global $wpdb, $wp_locale;
542
543
		/**
544
		 * Filters whether to remove the 'Months' drop-down from the post list table.
545
		 *
546
		 * @since 4.2.0
547
		 *
548
		 * @param bool   $disable   Whether to disable the drop-down. Default false.
549
		 * @param string $post_type The post type.
550
		 */
551
		if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
552
			return;
553
		}
554
555
		$extra_checks = "AND post_status != 'auto-draft'";
556
		if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) {
557
			$extra_checks .= " AND post_status != 'trash'";
558
		} elseif ( isset( $_GET['post_status'] ) ) {
559
			$extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
560
		}
561
562
		$months = $wpdb->get_results( $wpdb->prepare( "
563
			SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
564
			FROM $wpdb->posts
565
			WHERE post_type = %s
566
			$extra_checks
567
			ORDER BY post_date DESC
568
		", $post_type ) );
569
570
		/**
571
		 * Filters the 'Months' drop-down results.
572
		 *
573
		 * @since 3.7.0
574
		 *
575
		 * @param object $months    The months drop-down query results.
576
		 * @param string $post_type The post type.
577
		 */
578
		$months = apply_filters( 'months_dropdown_results', $months, $post_type );
579
580
		$month_count = count( $months );
581
582
		if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
583
			return;
584
585
		$m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
586
?>
587
		<label for="filter-by-date" class="screen-reader-text"><?php _e( 'Filter by date' ); ?></label>
588
		<select name="m" id="filter-by-date">
589
			<option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
590
<?php
591
		foreach ( $months as $arc_row ) {
592
			if ( 0 == $arc_row->year )
593
				continue;
594
595
			$month = zeroise( $arc_row->month, 2 );
596
			$year = $arc_row->year;
597
598
			printf( "<option %s value='%s'>%s</option>\n",
599
				selected( $m, $year . $month, false ),
600
				esc_attr( $arc_row->year . $month ),
601
				/* translators: 1: month name, 2: 4-digit year */
602
				sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
603
			);
604
		}
605
?>
606
		</select>
607
<?php
608
	}
609
610
	/**
611
	 * Display a view switcher
612
	 *
613
	 * @since 3.1.0
614
	 * @access protected
615
	 *
616
	 * @param string $current_mode
617
	 */
618
	protected function view_switcher( $current_mode ) {
619
?>
620
		<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
621
		<div class="view-switch">
622
<?php
623
			foreach ( $this->modes as $mode => $title ) {
624
				$classes = array( 'view-' . $mode );
625
				if ( $current_mode === $mode )
626
					$classes[] = 'current';
627
				printf(
628
					"<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
629
					esc_url( add_query_arg( 'mode', $mode ) ),
630
					implode( ' ', $classes ),
631
					$title
632
				);
633
			}
634
		?>
635
		</div>
636
<?php
637
	}
638
639
	/**
640
	 * Display a comment count bubble
641
	 *
642
	 * @since 3.1.0
643
	 * @access protected
644
	 *
645
	 * @param int $post_id          The post ID.
646
	 * @param int $pending_comments Number of pending comments.
647
	 */
648
	protected function comments_bubble( $post_id, $pending_comments ) {
649
		$approved_comments = get_comments_number();
650
651
		$approved_comments_number = number_format_i18n( $approved_comments );
652
		$pending_comments_number = number_format_i18n( $pending_comments );
653
654
		$approved_only_phrase = sprintf( _n( '%s comment', '%s comments', $approved_comments ), $approved_comments_number );
655
		$approved_phrase = sprintf( _n( '%s approved comment', '%s approved comments', $approved_comments ), $approved_comments_number );
656
		$pending_phrase = sprintf( _n( '%s pending comment', '%s pending comments', $pending_comments ), $pending_comments_number );
657
658
		// No comments at all.
659
		if ( ! $approved_comments && ! $pending_comments ) {
660
			printf( '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
661
				__( 'No comments' )
662
			);
663
		// Approved comments have different display depending on some conditions.
664
		} elseif ( $approved_comments ) {
665
			printf( '<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>',
666
				esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'approved' ), admin_url( 'edit-comments.php' ) ) ),
667
				$approved_comments_number,
668
				$pending_comments ? $approved_phrase : $approved_only_phrase
669
			);
670
		} else {
671
			printf( '<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>',
672
				$approved_comments_number,
673
				$pending_comments ? __( 'No approved comments' ) : __( 'No comments' )
674
			);
675
		}
676
677
		if ( $pending_comments ) {
678
			printf( '<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>',
679
				esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'moderated' ), admin_url( 'edit-comments.php' ) ) ),
680
				$pending_comments_number,
681
				$pending_phrase
682
			);
683
		} else {
684
			printf( '<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>',
685
				$pending_comments_number,
686
				$approved_comments ? __( 'No pending comments' ) : __( 'No comments' )
687
			);
688
		}
689
	}
690
691
	/**
692
	 * Get the current page number
693
	 *
694
	 * @since 3.1.0
695
	 * @access public
696
	 *
697
	 * @return int
698
	 */
699
	public function get_pagenum() {
700
		$pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
701
702
		if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
703
			$pagenum = $this->_pagination_args['total_pages'];
704
705
		return max( 1, $pagenum );
706
	}
707
708
	/**
709
	 * Get number of items to display on a single page
710
	 *
711
	 * @since 3.1.0
712
	 * @access protected
713
	 *
714
	 * @param string $option
715
	 * @param int    $default
716
	 * @return int
717
	 */
718
	protected function get_items_per_page( $option, $default = 20 ) {
719
		$per_page = (int) get_user_option( $option );
720
		if ( empty( $per_page ) || $per_page < 1 )
721
			$per_page = $default;
722
723
		/**
724
		 * Filters the number of items to be displayed on each page of the list table.
725
		 *
726
		 * The dynamic hook name, $option, refers to the `per_page` option depending
727
		 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
728
		 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
729
		 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
730
		 * 'edit_{$post_type}_per_page', etc.
731
		 *
732
		 * @since 2.9.0
733
		 *
734
		 * @param int $per_page Number of items to be displayed. Default 20.
735
		 */
736
		return (int) apply_filters( $option, $per_page );
737
	}
738
739
	/**
740
	 * Display the pagination.
741
	 *
742
	 * @since 3.1.0
743
	 * @access protected
744
	 *
745
	 * @param string $which
746
	 */
747
	protected function pagination( $which ) {
748
		if ( empty( $this->_pagination_args ) ) {
749
			return;
750
		}
751
752
		$total_items = $this->_pagination_args['total_items'];
753
		$total_pages = $this->_pagination_args['total_pages'];
754
		$infinite_scroll = false;
755
		if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
756
			$infinite_scroll = $this->_pagination_args['infinite_scroll'];
757
		}
758
759
		if ( 'top' === $which && $total_pages > 1 ) {
760
			$this->screen->render_screen_reader_content( 'heading_pagination' );
761
		}
762
763
		$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
764
765
		$current = $this->get_pagenum();
766
		$removable_query_args = wp_removable_query_args();
767
768
		$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
769
770
		$current_url = remove_query_arg( $removable_query_args, $current_url );
771
772
		$page_links = array();
773
774
		$total_pages_before = '<span class="paging-input">';
775
		$total_pages_after  = '</span></span>';
776
777
		$disable_first = $disable_last = $disable_prev = $disable_next = false;
778
779
 		if ( $current == 1 ) {
780
			$disable_first = true;
781
			$disable_prev = true;
782
 		}
783
		if ( $current == 2 ) {
784
			$disable_first = true;
785
		}
786
 		if ( $current == $total_pages ) {
787
			$disable_last = true;
788
			$disable_next = true;
789
 		}
790
		if ( $current == $total_pages - 1 ) {
791
			$disable_last = true;
792
		}
793
794 View Code Duplication
		if ( $disable_first ) {
795
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&laquo;</span>';
796
		} else {
797
			$page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
798
				esc_url( remove_query_arg( 'paged', $current_url ) ),
0 ignored issues
show
Bug introduced by
It seems like remove_query_arg('paged', $current_url) targeting remove_query_arg() can also be of type boolean; however, esc_url() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
799
				__( 'First page' ),
800
				'&laquo;'
801
			);
802
		}
803
804
		if ( $disable_prev ) {
805
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&lsaquo;</span>';
806 View Code Duplication
		} else {
807
			$page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
808
				esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
809
				__( 'Previous page' ),
810
				'&lsaquo;'
811
			);
812
		}
813
814
		if ( 'bottom' === $which ) {
815
			$html_current_page  = $current;
816
			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
817
		} else {
818
			$html_current_page = sprintf( "%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'>",
819
				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
820
				$current,
821
				strlen( $total_pages )
822
			);
823
		}
824
		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
825
		$page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
826
827 View Code Duplication
		if ( $disable_next ) {
828
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&rsaquo;</span>';
829
		} else {
830
			$page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
831
				esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
832
				__( 'Next page' ),
833
				'&rsaquo;'
834
			);
835
		}
836
837 View Code Duplication
		if ( $disable_last ) {
838
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&raquo;</span>';
839
		} else {
840
			$page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
841
				esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
842
				__( 'Last page' ),
843
				'&raquo;'
844
			);
845
		}
846
847
		$pagination_links_class = 'pagination-links';
848
		if ( ! empty( $infinite_scroll ) ) {
849
			$pagination_links_class = ' hide-if-js';
850
		}
851
		$output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
852
853
		if ( $total_pages ) {
854
			$page_class = $total_pages < 2 ? ' one-page' : '';
855
		} else {
856
			$page_class = ' no-pages';
857
		}
858
		$this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
859
860
		echo $this->_pagination;
861
	}
862
863
	/**
864
	 * Get a list of columns. The format is:
865
	 * 'internal-name' => 'Title'
866
	 *
867
	 * @since 3.1.0
868
	 * @access public
869
	 * @abstract
870
	 *
871
	 * @return array
872
	 */
873
	public function get_columns() {
874
		die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
875
	}
876
877
	/**
878
	 * Get a list of sortable columns. The format is:
879
	 * 'internal-name' => 'orderby'
880
	 * or
881
	 * 'internal-name' => array( 'orderby', true )
882
	 *
883
	 * The second format will make the initial sorting order be descending
884
	 *
885
	 * @since 3.1.0
886
	 * @access protected
887
	 *
888
	 * @return array
889
	 */
890
	protected function get_sortable_columns() {
891
		return array();
892
	}
893
894
	/**
895
	 * Gets the name of the default primary column.
896
	 *
897
	 * @since 4.3.0
898
	 * @access protected
899
	 *
900
	 * @return string Name of the default primary column, in this case, an empty string.
901
	 */
902
	protected function get_default_primary_column_name() {
903
		$columns = $this->get_columns();
904
		$column = '';
905
906
		if ( empty( $columns ) ) {
907
			return $column;
908
		}
909
910
		// We need a primary defined so responsive views show something,
911
		// so let's fall back to the first non-checkbox column.
912
		foreach ( $columns as $col => $column_name ) {
913
			if ( 'cb' === $col ) {
914
				continue;
915
			}
916
917
			$column = $col;
918
			break;
919
		}
920
921
		return $column;
922
	}
923
924
	/**
925
	 * Public wrapper for WP_List_Table::get_default_primary_column_name().
926
	 *
927
	 * @since 4.4.0
928
	 * @access public
929
	 *
930
	 * @return string Name of the default primary column.
931
	 */
932
	public function get_primary_column() {
933
		return $this->get_primary_column_name();
934
	}
935
936
	/**
937
	 * Gets the name of the primary column.
938
	 *
939
	 * @since 4.3.0
940
	 * @access protected
941
	 *
942
	 * @return string The name of the primary column.
943
	 */
944
	protected function get_primary_column_name() {
945
		$columns = get_column_headers( $this->screen );
946
		$default = $this->get_default_primary_column_name();
947
948
		// If the primary column doesn't exist fall back to the
949
		// first non-checkbox column.
950
		if ( ! isset( $columns[ $default ] ) ) {
951
			$default = WP_List_Table::get_default_primary_column_name();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
952
		}
953
954
		/**
955
		 * Filters the name of the primary column for the current list table.
956
		 *
957
		 * @since 4.3.0
958
		 *
959
		 * @param string $default Column name default for the specific list table, e.g. 'name'.
960
		 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
961
		 */
962
		$column  = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
963
964
		if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
965
			$column = $default;
966
		}
967
968
		return $column;
969
	}
970
971
	/**
972
	 * Get a list of all, hidden and sortable columns, with filter applied
973
	 *
974
	 * @since 3.1.0
975
	 * @access protected
976
	 *
977
	 * @return array
978
	 */
979
	protected function get_column_info() {
980
		// $_column_headers is already set / cached
981
		if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
982
			// Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
983
			// In 4.3, we added a fourth argument for primary column.
984
			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
985
			foreach ( $this->_column_headers as $key => $value ) {
986
				$column_headers[ $key ] = $value;
987
			}
988
989
			return $column_headers;
990
		}
991
992
		$columns = get_column_headers( $this->screen );
993
		$hidden = get_hidden_columns( $this->screen );
994
995
		$sortable_columns = $this->get_sortable_columns();
996
		/**
997
		 * Filters the list table sortable columns for a specific screen.
998
		 *
999
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
1000
		 * to the ID of the current screen, usually a string.
1001
		 *
1002
		 * @since 3.5.0
1003
		 *
1004
		 * @param array $sortable_columns An array of sortable columns.
1005
		 */
1006
		$_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
1007
1008
		$sortable = array();
1009
		foreach ( $_sortable as $id => $data ) {
1010
			if ( empty( $data ) )
1011
				continue;
1012
1013
			$data = (array) $data;
1014
			if ( !isset( $data[1] ) )
1015
				$data[1] = false;
1016
1017
			$sortable[$id] = $data;
1018
		}
1019
1020
		$primary = $this->get_primary_column_name();
1021
		$this->_column_headers = array( $columns, $hidden, $sortable, $primary );
1022
1023
		return $this->_column_headers;
1024
	}
1025
1026
	/**
1027
	 * Return number of visible columns
1028
	 *
1029
	 * @since 3.1.0
1030
	 * @access public
1031
	 *
1032
	 * @return int
1033
	 */
1034
	public function get_column_count() {
1035
		list ( $columns, $hidden ) = $this->get_column_info();
1036
		$hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
1037
		return count( $columns ) - count( $hidden );
1038
	}
1039
1040
	/**
1041
	 * Print column headers, accounting for hidden and sortable columns.
1042
	 *
1043
	 * @since 3.1.0
1044
	 * @access public
1045
	 *
1046
	 * @staticvar int $cb_counter
1047
	 *
1048
	 * @param bool $with_id Whether to set the id attribute or not
1049
	 */
1050
	public function print_column_headers( $with_id = true ) {
1051
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1052
1053
		$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1054
		$current_url = remove_query_arg( 'paged', $current_url );
1055
1056
		if ( isset( $_GET['orderby'] ) ) {
1057
			$current_orderby = $_GET['orderby'];
1058
		} else {
1059
			$current_orderby = '';
1060
		}
1061
1062
		if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
1063
			$current_order = 'desc';
1064
		} else {
1065
			$current_order = 'asc';
1066
		}
1067
1068
		if ( ! empty( $columns['cb'] ) ) {
1069
			static $cb_counter = 1;
1070
			$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
1071
				. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
1072
			$cb_counter++;
1073
		}
1074
1075
		foreach ( $columns as $column_key => $column_display_name ) {
1076
			$class = array( 'manage-column', "column-$column_key" );
1077
1078
			if ( in_array( $column_key, $hidden ) ) {
1079
				$class[] = 'hidden';
1080
			}
1081
1082
			if ( 'cb' === $column_key )
1083
				$class[] = 'check-column';
1084
			elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
1085
				$class[] = 'num';
1086
1087
			if ( $column_key === $primary ) {
1088
				$class[] = 'column-primary';
1089
			}
1090
1091
			if ( isset( $sortable[$column_key] ) ) {
1092
				list( $orderby, $desc_first ) = $sortable[$column_key];
1093
1094
				if ( $current_orderby === $orderby ) {
1095
					$order = 'asc' === $current_order ? 'desc' : 'asc';
1096
					$class[] = 'sorted';
1097
					$class[] = $current_order;
1098
				} else {
1099
					$order = $desc_first ? 'desc' : 'asc';
1100
					$class[] = 'sortable';
1101
					$class[] = $desc_first ? 'asc' : 'desc';
1102
				}
1103
1104
				$column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
1105
			}
1106
1107
			$tag = ( 'cb' === $column_key ) ? 'td' : 'th';
1108
			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
1109
			$id = $with_id ? "id='$column_key'" : '';
1110
1111
			if ( !empty( $class ) )
1112
				$class = "class='" . join( ' ', $class ) . "'";
1113
1114
			echo "<$tag $scope $id $class>$column_display_name</$tag>";
1115
		}
1116
	}
1117
1118
	/**
1119
	 * Display the table
1120
	 *
1121
	 * @since 3.1.0
1122
	 * @access public
1123
	 */
1124
	public function display() {
1125
		$singular = $this->_args['singular'];
1126
1127
		$this->display_tablenav( 'top' );
1128
1129
		$this->screen->render_screen_reader_content( 'heading_list' );
1130
?>
1131
<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
1132
	<thead>
1133
	<tr>
1134
		<?php $this->print_column_headers(); ?>
1135
	</tr>
1136
	</thead>
1137
1138
	<tbody id="the-list"<?php
1139
		if ( $singular ) {
1140
			echo " data-wp-lists='list:$singular'";
1141
		} ?>>
1142
		<?php $this->display_rows_or_placeholder(); ?>
1143
	</tbody>
1144
1145
	<tfoot>
1146
	<tr>
1147
		<?php $this->print_column_headers( false ); ?>
1148
	</tr>
1149
	</tfoot>
1150
1151
</table>
1152
<?php
1153
		$this->display_tablenav( 'bottom' );
1154
	}
1155
1156
	/**
1157
	 * Get a list of CSS classes for the WP_List_Table table tag.
1158
	 *
1159
	 * @since 3.1.0
1160
	 * @access protected
1161
	 *
1162
	 * @return array List of CSS classes for the table tag.
1163
	 */
1164
	protected function get_table_classes() {
1165
		return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
1166
	}
1167
1168
	/**
1169
	 * Generate the table navigation above or below the table
1170
	 *
1171
	 * @since 3.1.0
1172
	 * @access protected
1173
	 * @param string $which
1174
	 */
1175
	protected function display_tablenav( $which ) {
1176
		if ( 'top' === $which ) {
1177
			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
1178
		}
1179
		?>
1180
	<div class="tablenav <?php echo esc_attr( $which ); ?>">
1181
1182
		<?php if ( $this->has_items() ): ?>
1183
		<div class="alignleft actions bulkactions">
1184
			<?php $this->bulk_actions( $which ); ?>
1185
		</div>
1186
		<?php endif;
1187
		$this->extra_tablenav( $which );
1188
		$this->pagination( $which );
1189
?>
1190
1191
		<br class="clear" />
1192
	</div>
1193
<?php
1194
	}
1195
1196
	/**
1197
	 * Extra controls to be displayed between bulk actions and pagination
1198
	 *
1199
	 * @since 3.1.0
1200
	 * @access protected
1201
	 *
1202
	 * @param string $which
1203
	 */
1204
	protected function extra_tablenav( $which ) {}
1205
1206
	/**
1207
	 * Generate the tbody element for the list table.
1208
	 *
1209
	 * @since 3.1.0
1210
	 * @access public
1211
	 */
1212
	public function display_rows_or_placeholder() {
1213
		if ( $this->has_items() ) {
1214
			$this->display_rows();
1215
		} else {
1216
			echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
1217
			$this->no_items();
1218
			echo '</td></tr>';
1219
		}
1220
	}
1221
1222
	/**
1223
	 * Generate the table rows
1224
	 *
1225
	 * @since 3.1.0
1226
	 * @access public
1227
	 */
1228
	public function display_rows() {
1229
		foreach ( $this->items as $item )
1230
			$this->single_row( $item );
1231
	}
1232
1233
	/**
1234
	 * Generates content for a single row of the table
1235
	 *
1236
	 * @since 3.1.0
1237
	 * @access public
1238
	 *
1239
	 * @param object $item The current item
1240
	 */
1241
	public function single_row( $item ) {
1242
		echo '<tr>';
1243
		$this->single_row_columns( $item );
1244
		echo '</tr>';
1245
	}
1246
1247
	/**
1248
	 *
1249
	 * @param object $item
1250
	 * @param string $column_name
1251
	 */
1252
	protected function column_default( $item, $column_name ) {}
1253
1254
	/**
1255
	 *
1256
	 * @param object $item
1257
	 */
1258
	protected function column_cb( $item ) {}
1259
1260
	/**
1261
	 * Generates the columns for a single row of the table
1262
	 *
1263
	 * @since 3.1.0
1264
	 * @access protected
1265
	 *
1266
	 * @param object $item The current item
1267
	 */
1268
	protected function single_row_columns( $item ) {
1269
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1270
1271
		foreach ( $columns as $column_name => $column_display_name ) {
1272
			$classes = "$column_name column-$column_name";
1273
			if ( $primary === $column_name ) {
1274
				$classes .= ' has-row-actions column-primary';
1275
			}
1276
1277
			if ( in_array( $column_name, $hidden ) ) {
1278
				$classes .= ' hidden';
1279
			}
1280
1281
			// Comments column uses HTML in the display name with screen reader text.
1282
			// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1283
			$data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';
1284
1285
			$attributes = "class='$classes' $data";
1286
1287
			if ( 'cb' === $column_name ) {
1288
				echo '<th scope="row" class="check-column">';
1289
				echo $this->column_cb( $item );
1290
				echo '</th>';
1291
			} elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1292
				echo call_user_func(
1293
					array( $this, '_column_' . $column_name ),
1294
					$item,
1295
					$classes,
1296
					$data,
1297
					$primary
1298
				);
1299
			} elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1300
				echo "<td $attributes>";
1301
				echo call_user_func( array( $this, 'column_' . $column_name ), $item );
1302
				echo $this->handle_row_actions( $item, $column_name, $primary );
1303
				echo "</td>";
1304
			} else {
1305
				echo "<td $attributes>";
1306
				echo $this->column_default( $item, $column_name );
1307
				echo $this->handle_row_actions( $item, $column_name, $primary );
1308
				echo "</td>";
1309
			}
1310
		}
1311
	}
1312
1313
	/**
1314
	 * Generates and display row actions links for the list table.
1315
	 *
1316
	 * @since 4.3.0
1317
	 * @access protected
1318
	 *
1319
	 * @param object $item        The item being acted upon.
1320
	 * @param string $column_name Current column name.
1321
	 * @param string $primary     Primary column name.
1322
	 * @return string The row actions HTML, or an empty string if the current column is the primary column.
1323
	 */
1324
	protected function handle_row_actions( $item, $column_name, $primary ) {
1325
		return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
1326
 	}
1327
1328
	/**
1329
	 * Handle an incoming ajax request (called from admin-ajax.php)
1330
	 *
1331
	 * @since 3.1.0
1332
	 * @access public
1333
	 */
1334
	public function ajax_response() {
1335
		$this->prepare_items();
1336
1337
		ob_start();
1338
		if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1339
			$this->display_rows();
1340
		} else {
1341
			$this->display_rows_or_placeholder();
1342
		}
1343
1344
		$rows = ob_get_clean();
1345
1346
		$response = array( 'rows' => $rows );
1347
1348
		if ( isset( $this->_pagination_args['total_items'] ) ) {
1349
			$response['total_items_i18n'] = sprintf(
1350
				_n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1351
				number_format_i18n( $this->_pagination_args['total_items'] )
1352
			);
1353
		}
1354
		if ( isset( $this->_pagination_args['total_pages'] ) ) {
1355
			$response['total_pages'] = $this->_pagination_args['total_pages'];
1356
			$response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1357
		}
1358
1359
		die( wp_json_encode( $response ) );
1360
	}
1361
1362
	/**
1363
	 * Send required variables to JavaScript land
1364
	 *
1365
	 * @access public
1366
	 */
1367
	public function _js_vars() {
1368
		$args = array(
1369
			'class'  => get_class( $this ),
1370
			'screen' => array(
1371
				'id'   => $this->screen->id,
1372
				'base' => $this->screen->base,
1373
			)
1374
		);
1375
1376
		printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1377
	}
1378
}
1379