Completed
Branch 3.0 (7d679c)
by Stephanie
02:50
created

FrmListHelper::disabled_pages()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 16
nop 1
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
if ( ! defined('ABSPATH') ) {
3
	die( 'You are not allowed to call this page directly.' );
4
}
5
6
class FrmListHelper {
7
	/**
8
	 * The current list of items
9
	 *
10
	 * @since 2.0.18
11
	 * @var array
12
	 * @access public
13
	 */
14
	public $items;
15
16
	/**
17
	 * Various information about the current table
18
	 *
19
	 * @since 2.0.18
20
	 * @var array
21
	 * @access protected
22
	 */
23
	protected $_args;
24
25
	/**
26
	 * Various information needed for displaying the pagination
27
	 *
28
	 * @since 2.0.18
29
	 * @var array
30
	 */
31
	protected $_pagination_args = array();
32
33
	/**
34
	 * The current screen
35
	 *
36
	 * @since 2.0.18
37
	 * @var object
38
	 * @access protected
39
	 */
40
	protected $screen;
41
42
	/**
43
	 * Cached bulk actions
44
	 *
45
	 * @since 2.0.18
46
	 * @var array
47
	 * @access private
48
	 */
49
	private $_actions;
50
51
	/**
52
	 * Cached pagination output
53
	 *
54
	 * @since 2.0.18
55
	 * @var string
56
	 * @access private
57
	 */
58
	private $_pagination;
59
60
	/**
61
	 * The view switcher modes.
62
	 *
63
	 * @since 2.0.18
64
	 * @var array
65
	 * @access protected
66
	 */
67
	protected $modes = array();
68
69
	/**
70
	*
71
	* @var array
72
	*/
73
    protected $params;
74
75
	/**
76
	 * Stores the value returned by ->get_column_info()
77
	 *
78
	 * @var array
79
	 */
80
	protected $_column_headers;
81
82
	protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' );
83
84
	protected $compat_methods = array(
85
		'set_pagination_args', 'get_views', 'get_bulk_actions', 'bulk_actions',
86
		'row_actions', 'view_switcher', 'get_items_per_page', 'pagination',
87
		'get_sortable_columns', 'get_column_info', 'get_table_classes', 'display_tablenav', 'extra_tablenav',
88
		'single_row_columns',
89
	);
90
91
	/**
92
	* Construct the table object
93
	*/
94
	public function __construct( $args ) {
95
	    $args = wp_parse_args( $args, array(
96
			'params' => array(),
97
			'plural' => '',
98
			'singular' => '',
99
			'ajax' => false,
100
			'screen' => null,
101
		) );
102
103
		$this->params = $args['params'];
104
105
		$this->screen = convert_to_screen( $args['screen'] );
106
107
		add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
108
109
		if ( ! $args['plural'] ) {
110
			$args['plural'] = $this->screen->base;
111
		}
112
113
		$args['plural'] = sanitize_key( $args['plural'] );
114
		$args['singular'] = sanitize_key( $args['singular'] );
115
116
		$this->_args = $args;
117
118
		if ( $args['ajax'] ) {
119
			// wp_enqueue_script( 'list-table' );
120
			add_action( 'admin_footer', array( $this, '_js_vars' ) );
121
		}
122
123
		if ( empty( $this->modes ) ) {
124
			$this->modes = array(
125
				'list'    => __( 'List View' ),
126
				'excerpt' => __( 'Excerpt View' ),
127
			);
128
		}
129
	}
130
131
	public function ajax_user_can() {
132
		return current_user_can( 'administrator' );
133
	}
134
135
	public function get_columns() {
136
		return array();
137
	}
138
139
	public function display_rows() {
140
		foreach ( $this->items as $item ) {
141
			echo "\n\t", $this->single_row( $item );
142
		}
143
	}
144
145
	/**
146
	 * Prepares the list of items for displaying.
147
	 * @uses FrmListHelper::set_pagination_args()
148
	 *
149
	 * @since 2.0.18
150
	 * @access public
151
	 * @abstract
152
	 */
153
	public function prepare_items() {
154
		die( 'function FrmListHelper::prepare_items() must be over-ridden in a sub-class.' );
155
	}
156
157
	/**
158
	 * An internal method that sets all the necessary pagination arguments
159
	 *
160
	 * @param array $args An associative array with information about the pagination
161
	 * @access protected
162
	 *
163
	 * @param array|string $args
164
	 */
165
	protected function set_pagination_args( $args ) {
166
		$args = wp_parse_args( $args, array(
167
			'total_items' => 0,
168
			'total_pages' => 0,
169
			'per_page' => 0,
170
		) );
171
172
		if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
173
			$args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
174
		}
175
176
		// Redirect if page number is invalid and headers are not already sent.
177
		if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
178
			wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
179
			exit;
180
		}
181
182
		$this->_pagination_args = $args;
183
	}
184
185
	/**
186
	 * Access the pagination args.
187
	 *
188
	 * @since 2.0.18
189
	 * @access public
190
	 *
191
	 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
192
	 *                    'total_pages', 'per_page', or 'infinite_scroll'.
193
	 * @return int Number of items that correspond to the given pagination argument.
194
	 */
195
	public function get_pagination_arg( $key ) {
196
		if ( 'page' == $key ) {
197
			return $this->get_pagenum();
198
		}
199
200
		if ( isset( $this->_pagination_args[ $key ] ) ) {
201
			return $this->_pagination_args[ $key ];
202
		}
203
	}
204
205
	/**
206
	 * Whether the table has items to display or not
207
	 *
208
	 * @since 2.0.18
209
	 * @access public
210
	 *
211
	 * @return bool
212
	 */
213
	public function has_items() {
214
		return ! empty( $this->items );
215
	}
216
217
	/**
218
	 * Message to be displayed when there are no items
219
	 *
220
	 * @since 2.0.18
221
	 * @access public
222
	 */
223
	public function no_items() {
224
		_e( 'No items found.' );
225
	}
226
227
	/**
228
	 * Display the search box.
229
	 *
230
	 * @since 2.0.18
231
	 * @access public
232
	 *
233
	 * @param string $text The search button text
234
	 * @param string $input_id The search input id
235
	 */
236
	public function search_box( $text, $input_id ) {
237
		if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
238
			return;
239
		}
240
241
		$input_id = $input_id . '-search-input';
242
243
		foreach ( array( 'orderby', 'order' ) as $search_params ) {
244
			$this->hidden_search_inputs( $search_params );
245
		}
246
?>
247
<p class="search-box">
248
	<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ) ?>"><?php echo wp_kses( $text, array() ); ?>:</label>
249
	<input type="search" id="<?php echo esc_attr( $input_id ) ?>" name="s" value="<?php _admin_search_query(); ?>" />
250
	<?php submit_button( $text, 'button', '', false, array( 'id' => 'search-submit' ) ); ?>
251
</p>
252
<?php
253
	}
254
255
	private function hidden_search_inputs( $param_name ) {
256
		if ( ! empty( $_REQUEST[ $param_name ] ) ) {
257
			echo '<input type="hidden" name="' . esc_attr( $param_name ) . '" value="' . esc_attr( $_REQUEST[ $param_name ] ) . '" />';
258
		}
259
	}
260
261
	/**
262
	 * Get an associative array ( id => link ) with the list
263
	 * of views available on this table.
264
	 *
265
	 * @since 2.0.18
266
	 * @access protected
267
	 *
268
	 * @return array
269
	 */
270
	protected function get_views() {
271
		return array();
272
	}
273
274
	/**
275
	 * Display the list of views available on this table.
276
	 *
277
	 * @since 2.0.18
278
	 * @access public
279
	 */
280
	public function views() {
281
		$views = $this->get_views();
282
		/**
283
		 * Filter the list of available list table views.
284
		 *
285
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
286
		 * to the ID of the current screen, usually a string.
287
		 *
288
		 * @since 3.5.0
289
		 *
290
		 * @param array $views An array of available list table views.
291
		 */
292
		$views = apply_filters( 'views_' . $this->screen->id, $views );
293
294
		if ( empty( $views ) ) {
295
			return;
296
		}
297
298
		echo "<ul class='subsubsub'>\n";
299
		foreach ( $views as $class => $view ) {
300
			$views[ $class ] = "\t<li class='$class'>$view";
301
		}
302
		echo implode( " |</li>\n", $views ) . "</li>\n";
0 ignored issues
show
introduced by
Expected a sanitizing function (see Codex for 'Data Validation'), but instead saw 'implode'
Loading history...
303
		echo '</ul>';
304
	}
305
306
	/**
307
	 * Get an associative array ( option_name => option_title ) with the list
308
	 * of bulk actions available on this table.
309
	 *
310
	 * @since 2.0.18
311
	 * @access protected
312
	 *
313
	 * @return array
314
	 */
315
	protected function get_bulk_actions() {
316
		return array();
317
	}
318
319
	/**
320
	 * Display the bulk actions dropdown.
321
	 *
322
	 * @since 2.0.18
323
	 * @access protected
324
	 *
325
	 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
326
	 *                      This is designated as optional for backwards-compatibility.
327
	 */
328
	protected function bulk_actions( $which = '' ) {
329
		if ( is_null( $this->_actions ) ) {
330
			$no_new_actions = $this->get_bulk_actions();
331
			$this->_actions = $no_new_actions;
332
333
			/**
334
			 * Filter the list table Bulk Actions drop-down.
335
			 *
336
			 * The dynamic portion of the hook name, `$this->screen->id`, refers
337
			 * to the ID of the current screen, usually a string.
338
			 *
339
			 * This filter can currently only be used to remove bulk actions.
340
			 *
341
			 * @since 3.5.0
342
			 *
343
			 * @param array $actions An array of the available bulk actions.
344
			 */
345
			$this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
346
			$this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
347
			$two = '';
348
		} else {
349
			$two = '2';
350
		}
351
352
		if ( empty( $this->_actions ) ) {
353
			return;
354
		}
355
356
		echo "<label for='bulk-action-selector-" . esc_attr( $which ) . "' class='screen-reader-text'>" . esc_attr__( 'Select bulk action' ) . '</label>';
357
		echo "<select name='action" . esc_attr( $two ) . "' id='bulk-action-selector-" . esc_attr( $which ) . "'>\n";
358
		echo "<option value='-1' selected='selected'>" . esc_attr__( 'Bulk Actions' ) . "</option>\n";
359
360
		foreach ( $this->_actions as $name => $title ) {
361
			$class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
362
363
			echo "\t<option value='" . esc_attr( $name ) . "'$class>$title</option>\n";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"'$class>$title</option>\n"'
Loading history...
364
		}
365
366
		echo "</select>\n";
367
368
		submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
369
		echo "\n";
370
	}
371
372
	/**
373
	 * Get the current action selected from the bulk actions dropdown.
374
	 *
375
	 * @since 2.0.18
376
	 * @access public
377
	 *
378
	 * @return string|false The action name or False if no action was selected
379
	 */
380
	public function current_action() {
381
		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
382
			return false;
383
		}
384
385
		$action = $this->get_bulk_action( 'action' );
386
		if ( $action === false ) {
387
			$action = $this->get_bulk_action( 'action2' );
388
		}
389
390
		return $action;
391
	}
392
393
	private static function get_bulk_action( $action_name ) {
394
		$action = false;
395
		if ( isset( $_REQUEST[ $action_name ] ) && -1 != sanitize_text_field( $_REQUEST[ $action_name ] ) ) {
396
			$action = sanitize_text_field( $_REQUEST[ $action_name ] );
397
		}
398
		return $action;
399
	}
400
401
	/**
402
	 * Generate row actions div
403
	 *
404
	 * @since 2.0.18
405
	 * @access protected
406
	 *
407
	 * @param array $actions The list of actions
408
	 * @param bool $always_visible Whether the actions should be always visible
409
	 * @return string
410
	 */
411
	protected function row_actions( $actions, $always_visible = false ) {
412
		$action_count = count( $actions );
413
		$i = 0;
414
415
		if ( ! $action_count ) {
416
			return '';
417
		}
418
419
		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
420
		foreach ( $actions as $action => $link ) {
421
			++$i;
422
			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
423
			$out .= "<span class='$action'>$link$sep</span>";
424
		}
425
		$out .= '</div>';
426
427
		$out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
428
429
		return $out;
430
	}
431
432
	/**
433
	 * Display a view switcher
434
	 *
435
	 * @since 2.0.18
436
	 * @access protected
437
	 *
438
	 * @param string $current_mode
439
	 */
440
	protected function view_switcher( $current_mode ) {
441
?>
442
		<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
443
		<div class="view-switch">
444
<?php
445
			foreach ( $this->modes as $mode => $title ) {
446
				$classes = array( 'view-' . $mode );
447
				if ( $current_mode == $mode ) {
448
					$classes[] = 'current';
449
				}
450
451
				printf(
452
					"<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
453
					esc_url( add_query_arg( 'mode', $mode ) ),
454
					implode( ' ', $classes ),
455
					$title
456
				);
457
			}
458
		?>
459
		</div>
460
<?php
461
	}
462
463
	/**
464
	 * Get the current page number
465
	 *
466
	 * @since 2.0.18
467
	 * @access public
468
	 *
469
	 * @return int
470
	 */
471
	public function get_pagenum() {
472
		$pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
473
474
		if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
475
			$pagenum = $this->_pagination_args['total_pages'];
476
		}
477
478
		return max( 1, $pagenum );
479
	}
480
481
	/**
482
	 * Get number of items to display on a single page
483
	 *
484
	 * @since 2.0.18
485
	 * @access protected
486
	 *
487
	 * @param string $option
488
	 * @param int    $default
489
	 * @return int
490
	 */
491
	protected function get_items_per_page( $option, $default = 20 ) {
492
		$per_page = (int) get_user_option( $option );
493
		if ( empty( $per_page ) || $per_page < 1 ) {
494
			$per_page = $default;
495
		}
496
497
		/**
498
		 * Filter the number of items to be displayed on each page of the list table.
499
		 *
500
		 * The dynamic hook name, $option, refers to the `per_page` option depending
501
		 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
502
		 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
503
		 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
504
		 * 'edit_{$post_type}_per_page', etc.
505
		 *
506
		 * @since 2.9.0
507
		 *
508
		 * @param int $per_page Number of items to be displayed. Default 20.
509
		 */
510
		return (int) apply_filters( $option, $per_page );
511
	}
512
513
	/**
514
	 * Display the pagination.
515
	 *
516
	 * @since 2.0.18
517
	 * @access protected
518
	 *
519
	 * @param string $which
520
	 */
521
	protected function pagination( $which ) {
522
		if ( empty( $this->_pagination_args ) ) {
523
			return;
524
		}
525
526
		$total_items = $this->_pagination_args['total_items'];
527
		$total_pages = $this->_pagination_args['total_pages'];
528
		$infinite_scroll = false;
529
		if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
530
			$infinite_scroll = $this->_pagination_args['infinite_scroll'];
531
		}
532
533
		$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
534
535
		$current = $this->get_pagenum();
536
537
		$page_links = array();
538
539
		$total_pages_before = '<span class="paging-input">';
540
		$total_pages_after  = '</span>';
541
542
		$disable = $this->disabled_pages( $total_pages );
543
544
		$page_links[] = $this->add_page_link( array(
545
			'page'     => 'first',
546
			'arrow'    => '&laquo;',
547
			'number'   => '',
548
			'disabled' => $disable['first'],
549
		) );
550
551
		$page_links[] = $this->add_page_link( array(
552
			'page'     => 'prev',
553
			'arrow'    => '&lsaquo;',
554
			'number'   => max( 1, $current - 1 ),
555
			'disabled' => $disable['prev'],
556
		) );
557
558
		if ( 'bottom' == $which ) {
559
			$html_current_page  = $current;
560
			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input">';
561
		} else {
562
			$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' />",
563
				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
564
				$current,
565
				strlen( $total_pages )
566
			);
567
		}
568
		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
569
		$page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$total_pages_after'
Loading history...
570
571
		$page_links[] = $this->add_page_link( array(
572
			'page'     => 'next',
573
			'arrow'    => '&rsaquo;',
574
			'number'   => min( $total_pages, $current + 1 ),
575
			'disabled' => $disable['next'],
576
		) );
577
578
		$page_links[] = $this->add_page_link( array(
579
			'page'     => 'last',
580
			'arrow'    => '&raquo;',
581
			'number'   => $total_pages,
582
			'disabled' => $disable['last'],
583
		) );
584
585
		$pagination_links_class = 'pagination-links';
586
		if ( ! empty( $infinite_scroll ) ) {
587
			$pagination_links_class = ' hide-if-js';
588
		}
589
		$output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
590
591
		if ( $total_pages ) {
592
			$page_class = $total_pages < 2 ? ' one-page' : '';
593
		} else {
594
			$page_class = ' no-pages';
595
		}
596
		$this->_pagination = "<div class='tablenav-pages" . esc_attr( $page_class ) . "'>$output</div>";
597
598
		echo $this->_pagination;
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
599
	}
600
601
	private function disabled_pages( $total_pages ) {
602
		$current = $this->get_pagenum();
603
		$disable = array( 'first' => false, 'last' => false, 'prev' => false, 'next' => false );
604
605
 		if ( $current == 1 ) {
606
			$disable['first'] = true;
607
			$disable['prev'] = true;
608
 		}
609
		if ( $current == 2 ) {
610
			$disable['first'] = true;
611
		}
612
 		if ( $current == $total_pages ) {
613
			$disable['last'] = true;
614
			$disable['next'] = true;
615
 		}
616
		if ( $current == $total_pages - 1 ) {
617
			$disable['last'] = true;
618
		}
619
620
		return $disable;
621
	}
622
623
	private function link_label( $link ) {
624
		$labels = array(
625
			'first' => __( 'First page' ),
626
			'last'  => __( 'Last page' ),
627
			'prev'  => __( 'Previous page' ),
628
			'next'  => __( 'Next page' ),
629
		);
630
		return $labels[ $link ];
631
	}
632
633
	private function current_url() {
634
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
635
636
		return remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
637
	}
638
639
	private function add_page_link( $atts ) {
640
		if ( $atts['disabled'] ) {
641
			$link = $this->add_disabled_link( $atts['arrow'] );
642
		} else {
643
			$link = $this->add_active_link( $atts );
644
		}
645
		return $link;
646
	}
647
648
	private function add_disabled_link( $label ) {
649
		return '<span class="tablenav-pages-navspan" aria-hidden="true">' . $label . '</span>';
650
	}
651
652
	private function add_active_link( $atts ) {
653
		$url = esc_url( add_query_arg( 'paged', $atts['number'], $this->current_url() ) );
654
		$label = $this->link_label( $atts['page'] );
655
		return sprintf(
656
			"<a class='%s-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
657
			$atts['page'], $url, $label, $atts['arrow']
658
		);
659
	}
660
661
	/**
662
	 * Get a list of sortable columns. The format is:
663
	 * 'internal-name' => 'orderby'
664
	 * or
665
	 * 'internal-name' => array( 'orderby', true )
666
	 *
667
	 * The second format will make the initial sorting order be descending
668
	 *
669
	 * @since 2.0.18
670
	 * @access protected
671
	 *
672
	 * @return array
673
	 */
674
	protected function get_sortable_columns() {
675
		return array();
676
	}
677
678
	/**
679
	 * Gets the name of the default primary column.
680
	 *
681
	 * @since 4.3.0
682
	 * @access protected
683
	 *
684
	 * @return string Name of the default primary column, in this case, an empty string.
685
	 */
686
	protected function get_default_primary_column_name() {
687
		$columns = $this->get_columns();
688
		$column = '';
689
690
		// We need a primary defined so responsive views show something,
691
		// so let's fall back to the first non-checkbox column.
692
		foreach ( $columns as $col => $column_name ) {
693
			if ( 'cb' === $col ) {
694
				continue;
695
			}
696
697
			$column = $col;
698
			break;
699
		}
700
701
		return $column;
702
	}
703
704
	/**
705
	 * Gets the name of the primary column.
706
	 *
707
	 * @since 4.3.0
708
	 * @access protected
709
	 *
710
	 * @return string The name of the primary column.
711
	 */
712
	protected function get_primary_column_name() {
713
		$columns = $this->get_columns();
714
		$default = $this->get_default_primary_column_name();
715
716
		// If the primary column doesn't exist fall back to the
717
		// first non-checkbox column.
718
		if ( ! isset( $columns[ $default ] ) ) {
719
			$default = FrmListHelper::get_default_primary_column_name();
720
		}
721
722
		/**
723
		 * Filter the name of the primary column for the current list table.
724
		 *
725
		 * @since 4.3.0
726
		 *
727
		 * @param string $default Column name default for the specific list table, e.g. 'name'.
728
		 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
729
		 */
730
		$column  = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
731
732
		if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
733
			$column = $default;
734
		}
735
736
		return $column;
737
	}
738
739
	/**
740
	 * Get a list of all, hidden and sortable columns, with filter applied
741
	 *
742
	 * @since 2.0.18
743
	 * @access protected
744
	 *
745
	 * @return array
746
	 */
747
	protected function get_column_info() {
748
		// $_column_headers is already set / cached
749
		if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
750
			// Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
751
			// In 4.3, we added a fourth argument for primary column.
752
			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
753
			foreach ( $this->_column_headers as $key => $value ) {
754
				$column_headers[ $key ] = $value;
755
			}
756
757
			return $column_headers;
758
		}
759
760
		$columns = get_column_headers( $this->screen );
761
		$hidden = get_hidden_columns( $this->screen );
762
763
		$sortable_columns = $this->get_sortable_columns();
764
		/**
765
		 * Filter the list table sortable columns for a specific screen.
766
		 *
767
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
768
		 * to the ID of the current screen, usually a string.
769
		 *
770
		 * @since 3.5.0
771
		 *
772
		 * @param array $sortable_columns An array of sortable columns.
773
		 */
774
		$_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
775
776
		$sortable = array();
777
		foreach ( $_sortable as $id => $data ) {
778
			if ( empty( $data ) ) {
779
				continue;
780
			}
781
782
			$data = (array) $data;
783
			if ( ! isset( $data[1] ) ) {
784
				$data[1] = false;
785
			}
786
787
			$sortable[ $id ] = $data;
788
		}
789
790
		$primary = $this->get_primary_column_name();
791
		$this->_column_headers = array( $columns, $hidden, $sortable, $primary );
792
793
		return $this->_column_headers;
794
	}
795
796
	/**
797
	 * Return number of visible columns
798
	 *
799
	 * @since 2.0.18
800
	 * @access public
801
	 *
802
	 * @return int
803
	 */
804
	public function get_column_count() {
805
		list ( $columns, $hidden ) = $this->get_column_info();
806
		$hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
807
		return count( $columns ) - count( $hidden );
808
	}
809
810
	/**
811
	 * Print column headers, accounting for hidden and sortable columns.
812
	 *
813
	 * @since 2.0.18
814
	 * @access public
815
	 *
816
	 * @staticvar int $cb_counter
817
	 *
818
	 * @param bool $with_id Whether to set the id attribute or not
819
	 */
820
	public function print_column_headers( $with_id = true ) {
821
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
822
823
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
824
		$current_url = remove_query_arg( 'paged', $current_url );
825
826
		if ( isset( $_GET['orderby'] ) ) {
827
			$current_orderby = sanitize_text_field( $_GET['orderby'] );
828
		} else {
829
			$current_orderby = '';
830
		}
831
832
		if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] ) {
833
			$current_order = 'desc';
834
		} else {
835
			$current_order = 'asc';
836
		}
837
838
		if ( ! empty( $columns['cb'] ) ) {
839
			static $cb_counter = 1;
840
			$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
841
				. '<input id="cb-select-all-' . esc_attr( $cb_counter ) . '" type="checkbox" />';
842
			$cb_counter++;
843
		}
844
845
		foreach ( $columns as $column_key => $column_display_name ) {
846
			$class = array( 'manage-column', "column-$column_key" );
847
848
			if ( in_array( $column_key, $hidden ) ) {
849
				$class[] = 'hidden';
850
			}
851
852
			if ( 'cb' == $column_key ) {
853
				$class[] = 'check-column';
854
			} else if ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
855
				$class[] = 'num';
856
			}
857
858
			if ( $column_key === $primary ) {
859
				$class[] = 'column-primary';
860
			}
861
862
			if ( isset( $sortable[ $column_key ] ) ) {
863
				list( $orderby, $desc_first ) = $sortable[ $column_key ];
864
865
				if ( $current_orderby == $orderby ) {
866
					$order = 'asc' == $current_order ? 'desc' : 'asc';
867
					$class[] = 'sorted';
868
					$class[] = $current_order;
869
				} else {
870
					$order = $desc_first ? 'desc' : 'asc';
871
					$class[] = 'sortable';
872
					$class[] = $desc_first ? 'asc' : 'desc';
873
				}
874
875
				$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>';
876
			}
877
878
			$tag = ( 'cb' === $column_key ) ? 'td' : 'th';
879
			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
880
			$id = $with_id ? "id='" . esc_attr( $column_key ) . "'" : '';
881
882
			if ( ! empty( $class ) ) {
883
				$class = "class='" . join( ' ', $class ) . "'";
884
			}
885
886
			echo "<$tag $scope $id $class>$column_display_name</$tag>";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"<$tag $scope $id $class>$column_display_name</$tag>"'
Loading history...
887
		}
888
	}
889
890
	/**
891
	 * Display the table
892
	 *
893
	 * @since 2.0.18
894
	 * @access public
895
	 */
896
	public function display() {
897
		$singular = $this->_args['singular'];
898
899
		$this->display_tablenav( 'top' );
900
?>
901
<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
902
	<thead>
903
	<tr>
904
		<?php $this->print_column_headers(); ?>
905
	</tr>
906
	</thead>
907
908
	<tbody id="the-list"<?php
909
		if ( $singular ) {
910
			echo " data-wp-lists='list:" . esc_attr( $singular ) . "'";
911
		} ?>>
912
		<?php $this->display_rows_or_placeholder(); ?>
913
	</tbody>
914
915
	<tfoot>
916
	<tr>
917
		<?php $this->print_column_headers( false ); ?>
918
	</tr>
919
	</tfoot>
920
921
</table>
922
<?php
923
		$this->display_tablenav( 'bottom' );
924
	}
925
926
	/**
927
	 * Get a list of CSS classes for the list table table tag.
928
	 *
929
	 * @since 2.0.18
930
	 * @access protected
931
	 *
932
	 * @return array List of CSS classes for the table tag.
933
	 */
934
	protected function get_table_classes() {
935
		return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
936
	}
937
938
	/**
939
	 * Generate the table navigation above or below the table
940
	 *
941
	 * @since 2.0.18
942
	 * @access protected
943
	 * @param string $which
944
	 */
945
	protected function display_tablenav( $which ) {
946
		if ( 'top' == $which ) {
947
			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
948
		}
949
?>
950
	<div class="tablenav <?php echo esc_attr( $which ); ?>">
951
952
		<div class="alignleft actions bulkactions">
953
			<?php $this->bulk_actions( $which ); ?>
954
		</div>
955
<?php
956
		$this->extra_tablenav( $which );
957
		$this->pagination( $which );
958
?>
959
960
		<br class="clear" />
961
	</div>
962
<?php
963
	}
964
965
	/**
966
	 * Extra controls to be displayed between bulk actions and pagination
967
	 *
968
	 * @since 2.0.18
969
	 * @access protected
970
	 *
971
	 * @param string $which
972
	 */
973
	protected function extra_tablenav( $which ) {}
974
975
	/**
976
	 * Generate the tbody element for the list table.
977
	 *
978
	 * @since 2.0.18
979
	 * @access public
980
	 */
981
	public function display_rows_or_placeholder() {
982
		if ( $this->has_items() ) {
983
			$this->display_rows();
984
		} else {
985
			echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
986
			$this->no_items();
987
			echo '</td></tr>';
988
		}
989
	}
990
991
	/**
992
	 * Generates content for a single row of the table
993
	 *
994
	 * @since 2.0.18
995
	 * @access public
996
	 *
997
	 * @param object $item The current item
998
	 */
999
	public function single_row( $item ) {
1000
		echo '<tr>';
1001
		$this->single_row_columns( $item );
1002
		echo '</tr>';
1003
	}
1004
1005
	/**
1006
	 * Generates the columns for a single row of the table
1007
	 *
1008
	 * @since 2.0.18
1009
	 * @access protected
1010
	 *
1011
	 * @param object $item The current item
1012
	 */
1013
	protected function single_row_columns( $item ) {
1014
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
0 ignored issues
show
Unused Code introduced by
The assignment to $sortable is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1015
1016
		foreach ( $columns as $column_name => $column_display_name ) {
1017
			$classes = "$column_name column-$column_name";
1018
			if ( $primary === $column_name ) {
1019
				$classes .= ' has-row-actions column-primary';
1020
			}
1021
1022
			if ( in_array( $column_name, $hidden ) ) {
1023
				$classes .= ' hidden';
1024
			}
1025
1026
			// Comments column uses HTML in the display name with screen reader text.
1027
			// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1028
			$data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';
1029
1030
			$attributes = "class='$classes' $data";
1031
1032
			if ( 'cb' == $column_name ) {
1033
				echo '<th scope="row" class="check-column"></th>';
1034
			} elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1035
				echo call_user_func(
0 ignored issues
show
introduced by
Expected a sanitizing function (see Codex for 'Data Validation'), but instead saw 'call_user_func'
Loading history...
1036
					array( $this, '_column_' . $column_name ),
1037
					$item,
1038
					$classes,
1039
					$data,
1040
					$primary
1041
				);
1042
			} elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1043
				echo "<td $attributes>";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"<td $attributes>"'
Loading history...
1044
				echo call_user_func( array( $this, 'column_' . $column_name ), $item );
0 ignored issues
show
introduced by
Expected a sanitizing function (see Codex for 'Data Validation'), but instead saw 'call_user_func'
Loading history...
1045
				echo $this->handle_row_actions( $item, $column_name, $primary );
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
1046
				echo '</td>';
1047
			} else {
1048
				echo "<td $attributes>";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"<td $attributes>"'
Loading history...
1049
				echo $this->handle_row_actions( $item, $column_name, $primary );
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
1050
				echo '</td>';
1051
			}
1052
		}
1053
	}
1054
1055
	/**
1056
	 * Generates and display row actions links for the list table.
1057
	 *
1058
	 * @since 4.3.0
1059
	 * @access protected
1060
	 *
1061
	 * @param object $item        The item being acted upon.
1062
	 * @param string $column_name Current column name.
1063
	 * @param string $primary     Primary column name.
1064
	 * @return string The row actions output. In this case, an empty string.
1065
	 */
1066
	protected function handle_row_actions( $item, $column_name, $primary ) {
1067
		return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
1068
 	}
1069
1070
	/**
1071
	 * Handle an incoming ajax request (called from admin-ajax.php)
1072
	 *
1073
	 * @since 2.0.18
1074
	 * @access public
1075
	 */
1076
	public function ajax_response() {
1077
		$this->prepare_items();
1078
1079
		ob_start();
1080
		if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1081
			$this->display_rows();
1082
		} else {
1083
			$this->display_rows_or_placeholder();
1084
		}
1085
1086
		$rows = ob_get_clean();
1087
1088
		$response = array( 'rows' => $rows );
1089
1090
		if ( isset( $this->_pagination_args['total_items'] ) ) {
1091
			$response['total_items_i18n'] = sprintf(
1092
				_n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1093
				number_format_i18n( $this->_pagination_args['total_items'] )
1094
			);
1095
		}
1096
		if ( isset( $this->_pagination_args['total_pages'] ) ) {
1097
			$response['total_pages'] = $this->_pagination_args['total_pages'];
1098
			$response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1099
		}
1100
1101
		die( wp_json_encode( $response ) );
1102
	}
1103
1104
	/**
1105
	 * Send required variables to JavaScript land
1106
	 *
1107
	 * @access public
1108
	 */
1109
	public function _js_vars() {
1110
		$args = array(
1111
			'class'  => get_class( $this ),
1112
			'screen' => array(
1113
				'id'   => $this->screen->id,
1114
				'base' => $this->screen->base,
1115
			),
1116
		);
1117
1118
		printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1119
	}
1120
}
1121