Completed
Push — master ( 02cc92...6df54b )
by Stephanie
02:42
created

FrmListHelper::get_param()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 1
nop 1
dl 0
loc 8
rs 9.4285
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
	 * @since 3.0
159
	 */
160
	protected function get_param( $args ) {
161
		return FrmAppHelper::get_simple_request( array(
162
			'param'    => $args['param'],
163
			'default'  => isset( $args['default'] ) ? $args['default'] : '',
164
			'sanitize' => isset( $args['sanitize'] ) ? $args['sanitize'] : 'sanitize_title',
165
			'type'     => 'request',
166
		) );
167
	}
168
169
	/**
170
	 * An internal method that sets all the necessary pagination arguments
171
	 *
172
	 * @param array $args An associative array with information about the pagination
173
	 * @access protected
174
	 *
175
	 * @param array|string $args
176
	 */
177
	protected function set_pagination_args( $args ) {
178
		$args = wp_parse_args( $args, array(
179
			'total_items' => 0,
180
			'total_pages' => 0,
181
			'per_page' => 0,
182
		) );
183
184
		if ( ! $args['total_pages'] && $args['per_page'] > 0 ) {
185
			$args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
186
		}
187
188
		// Redirect if page number is invalid and headers are not already sent.
189
		if ( ! headers_sent() && ! FrmAppHelper::wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
190
			wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
191
			exit;
192
		}
193
194
		$this->_pagination_args = $args;
195
	}
196
197
	/**
198
	 * Access the pagination args.
199
	 *
200
	 * @since 2.0.18
201
	 * @access public
202
	 *
203
	 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
204
	 *                    'total_pages', 'per_page', or 'infinite_scroll'.
205
	 * @return int Number of items that correspond to the given pagination argument.
206
	 */
207
	public function get_pagination_arg( $key ) {
208
		if ( 'page' == $key ) {
209
			return $this->get_pagenum();
210
		}
211
212
		if ( isset( $this->_pagination_args[ $key ] ) ) {
213
			return $this->_pagination_args[ $key ];
214
		}
215
	}
216
217
	/**
218
	 * Whether the table has items to display or not
219
	 *
220
	 * @since 2.0.18
221
	 * @access public
222
	 *
223
	 * @return bool
224
	 */
225
	public function has_items() {
226
		return ! empty( $this->items );
227
	}
228
229
	/**
230
	 * Message to be displayed when there are no items
231
	 *
232
	 * @since 2.0.18
233
	 * @access public
234
	 */
235
	public function no_items() {
236
		_e( 'No items found.' );
237
	}
238
239
	/**
240
	 * Display the search box.
241
	 *
242
	 * @since 2.0.18
243
	 * @access public
244
	 *
245
	 * @param string $text The search button text
246
	 * @param string $input_id The search input id
247
	 */
248
	public function search_box( $text, $input_id ) {
249
		if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
250
			return;
251
		}
252
253
		$input_id = $input_id . '-search-input';
254
255
		foreach ( array( 'orderby', 'order' ) as $search_params ) {
256
			$this->hidden_search_inputs( $search_params );
257
		}
258
?>
259
<p class="search-box">
260
	<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ) ?>"><?php echo wp_kses( $text, array() ); ?>:</label>
261
	<input type="search" id="<?php echo esc_attr( $input_id ) ?>" name="s" value="<?php _admin_search_query(); ?>" />
262
	<?php submit_button( $text, 'button', '', false, array( 'id' => 'search-submit' ) ); ?>
263
</p>
264
<?php
265
	}
266
267
	private function hidden_search_inputs( $param_name ) {
268
		if ( ! empty( $_REQUEST[ $param_name ] ) ) {
269
			echo '<input type="hidden" name="' . esc_attr( $param_name ) . '" value="' . esc_attr( $_REQUEST[ $param_name ] ) . '" />';
270
		}
271
	}
272
273
	/**
274
	 * Get an associative array ( id => link ) with the list
275
	 * of views available on this table.
276
	 *
277
	 * @since 2.0.18
278
	 * @access protected
279
	 *
280
	 * @return array
281
	 */
282
	protected function get_views() {
283
		return array();
284
	}
285
286
	/**
287
	 * Display the list of views available on this table.
288
	 *
289
	 * @since 2.0.18
290
	 * @access public
291
	 */
292
	public function views() {
293
		$views = $this->get_views();
294
		/**
295
		 * Filter the list of available list table views.
296
		 *
297
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
298
		 * to the ID of the current screen, usually a string.
299
		 *
300
		 * @since 3.5.0
301
		 *
302
		 * @param array $views An array of available list table views.
303
		 */
304
		$views = apply_filters( 'views_' . $this->screen->id, $views );
305
306
		if ( empty( $views ) ) {
307
			return;
308
		}
309
310
		echo "<ul class='subsubsub'>\n";
311
		foreach ( $views as $class => $view ) {
312
			$views[ $class ] = "\t<li class='$class'>$view";
313
		}
314
		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...
315
		echo '</ul>';
316
	}
317
318
	/**
319
	 * Get an associative array ( option_name => option_title ) with the list
320
	 * of bulk actions available on this table.
321
	 *
322
	 * @since 2.0.18
323
	 * @access protected
324
	 *
325
	 * @return array
326
	 */
327
	protected function get_bulk_actions() {
328
		return array();
329
	}
330
331
	/**
332
	 * Display the bulk actions dropdown.
333
	 *
334
	 * @since 2.0.18
335
	 * @access protected
336
	 *
337
	 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
338
	 *                      This is designated as optional for backwards-compatibility.
339
	 */
340
	protected function bulk_actions( $which = '' ) {
341
		if ( is_null( $this->_actions ) ) {
342
			$no_new_actions = $this->get_bulk_actions();
343
			$this->_actions = $no_new_actions;
344
345
			/**
346
			 * Filter the list table Bulk Actions drop-down.
347
			 *
348
			 * The dynamic portion of the hook name, `$this->screen->id`, refers
349
			 * to the ID of the current screen, usually a string.
350
			 *
351
			 * This filter can currently only be used to remove bulk actions.
352
			 *
353
			 * @since 3.5.0
354
			 *
355
			 * @param array $actions An array of the available bulk actions.
356
			 */
357
			$this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
358
			$this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
359
			$two = '';
360
		} else {
361
			$two = '2';
362
		}
363
364
		if ( empty( $this->_actions ) ) {
365
			return;
366
		}
367
368
		echo "<label for='bulk-action-selector-" . esc_attr( $which ) . "' class='screen-reader-text'>" . esc_attr__( 'Select bulk action' ) . '</label>';
369
		echo "<select name='action" . esc_attr( $two ) . "' id='bulk-action-selector-" . esc_attr( $which ) . "'>\n";
370
		echo "<option value='-1' selected='selected'>" . esc_attr__( 'Bulk Actions' ) . "</option>\n";
371
372
		foreach ( $this->_actions as $name => $title ) {
373
			$class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
374
375
			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...
376
		}
377
378
		echo "</select>\n";
379
380
		submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
381
		echo "\n";
382
	}
383
384
	/**
385
	 * Get the current action selected from the bulk actions dropdown.
386
	 *
387
	 * @since 2.0.18
388
	 * @access public
389
	 *
390
	 * @return string|false The action name or False if no action was selected
391
	 */
392
	public function current_action() {
393
		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
394
			return false;
395
		}
396
397
		$action = $this->get_bulk_action( 'action' );
398
		if ( $action === false ) {
399
			$action = $this->get_bulk_action( 'action2' );
400
		}
401
402
		return $action;
403
	}
404
405
	private static function get_bulk_action( $action_name ) {
406
		$action = false;
407
		$action_param = self::get_param( array( 'param' => $action_name, 'sanitize' => 'sanitize_text_field' ) );
408
		if ( $action_param && -1 != $action_param ) {
409
			$action = $action_param;
410
		}
411
		return $action;
412
	}
413
414
	/**
415
	 * Generate row actions div
416
	 *
417
	 * @since 2.0.18
418
	 * @access protected
419
	 *
420
	 * @param array $actions The list of actions
421
	 * @param bool $always_visible Whether the actions should be always visible
422
	 * @return string
423
	 */
424
	protected function row_actions( $actions, $always_visible = false ) {
425
		$action_count = count( $actions );
426
		$i = 0;
427
428
		if ( ! $action_count ) {
429
			return '';
430
		}
431
432
		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
433
		foreach ( $actions as $action => $link ) {
434
			++$i;
435
			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
436
			$out .= "<span class='$action'>$link$sep</span>";
437
		}
438
		$out .= '</div>';
439
440
		$out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
441
442
		return $out;
443
	}
444
445
	/**
446
	 * Display a view switcher
447
	 *
448
	 * @since 2.0.18
449
	 * @access protected
450
	 *
451
	 * @param string $current_mode
452
	 */
453
	protected function view_switcher( $current_mode ) {
454
?>
455
		<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
456
		<div class="view-switch">
457
<?php
458
			foreach ( $this->modes as $mode => $title ) {
459
				$classes = array( 'view-' . $mode );
460
				if ( $current_mode == $mode ) {
461
					$classes[] = 'current';
462
				}
463
464
				printf(
465
					"<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
466
					esc_url( add_query_arg( 'mode', $mode ) ),
467
					implode( ' ', $classes ),
468
					$title
469
				);
470
			}
471
		?>
472
		</div>
473
<?php
474
	}
475
476
	/**
477
	 * Get the current page number
478
	 *
479
	 * @since 2.0.18
480
	 * @access public
481
	 *
482
	 * @return int
483
	 */
484
	public function get_pagenum() {
485
		$pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
486
487
		if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
488
			$pagenum = $this->_pagination_args['total_pages'];
489
		}
490
491
		return max( 1, $pagenum );
492
	}
493
494
	/**
495
	 * Get number of items to display on a single page
496
	 *
497
	 * @since 2.0.18
498
	 * @access protected
499
	 *
500
	 * @param string $option
501
	 * @param int    $default
502
	 * @return int
503
	 */
504
	protected function get_items_per_page( $option, $default = 20 ) {
505
		$per_page = (int) get_user_option( $option );
506
		if ( empty( $per_page ) || $per_page < 1 ) {
507
			$per_page = $default;
508
		}
509
510
		/**
511
		 * Filter the number of items to be displayed on each page of the list table.
512
		 *
513
		 * The dynamic hook name, $option, refers to the `per_page` option depending
514
		 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
515
		 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
516
		 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
517
		 * 'edit_{$post_type}_per_page', etc.
518
		 *
519
		 * @since 2.9.0
520
		 *
521
		 * @param int $per_page Number of items to be displayed. Default 20.
522
		 */
523
		return (int) apply_filters( $option, $per_page );
524
	}
525
526
	/**
527
	 * Display the pagination.
528
	 *
529
	 * @since 2.0.18
530
	 * @access protected
531
	 *
532
	 * @param string $which
533
	 */
534
	protected function pagination( $which ) {
535
		if ( empty( $this->_pagination_args ) ) {
536
			return;
537
		}
538
539
		$total_items = $this->_pagination_args['total_items'];
540
		$total_pages = $this->_pagination_args['total_pages'];
541
		$infinite_scroll = false;
542
		if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
543
			$infinite_scroll = $this->_pagination_args['infinite_scroll'];
544
		}
545
546
		$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
547
548
		$current = $this->get_pagenum();
549
550
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
551
552
		$current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
553
554
		$page_links = array();
555
556
		$total_pages_before = '<span class="paging-input">';
557
		$total_pages_after  = '</span>';
558
559
		$disable_first = false;
560
		$disable_last = false;
561
		$disable_prev = false;
562
		$disable_next = false;
563
564
 		if ( $current == 1 ) {
565
			$disable_first = true;
566
			$disable_prev = true;
567
 		}
568
		if ( $current == 2 ) {
569
			$disable_first = true;
570
		}
571
 		if ( $current == $total_pages ) {
572
			$disable_last = true;
573
			$disable_next = true;
574
 		}
575
		if ( $current == $total_pages - 1 ) {
576
			$disable_last = true;
577
		}
578
579 View Code Duplication
		if ( $disable_first ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
580
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&laquo;</span>';
581
		} else {
582
			$page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
583
				esc_url( remove_query_arg( 'paged', $current_url ) ),
584
				__( 'First page' ),
585
				'&laquo;'
586
			);
587
		}
588
589
		if ( $disable_prev ) {
590
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&lsaquo;</span>';
591 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
592
			$page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
593
				esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ),
594
				__( 'Previous page' ),
595
				'&lsaquo;'
596
			);
597
		}
598
599
		if ( 'bottom' == $which ) {
600
			$html_current_page  = $current;
601
			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input">';
602
		} else {
603
			$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' />",
604
				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
605
				$current,
606
				strlen( $total_pages )
607
			);
608
		}
609
		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
610
		$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...
611
612 View Code Duplication
		if ( $disable_next ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
613
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&rsaquo;</span>';
614
		} else {
615
			$page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
616
				esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $current_url ) ),
617
				__( 'Next page' ),
618
				'&rsaquo;'
619
			);
620
		}
621
622 View Code Duplication
		if ( $disable_last ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
623
			$page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&raquo;</span>';
624
		} else {
625
			$page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
626
				esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
627
				__( 'Last page' ),
628
				'&raquo;'
629
			);
630
		}
631
632
		$pagination_links_class = 'pagination-links';
633
		if ( ! empty( $infinite_scroll ) ) {
634
			$pagination_links_class = ' hide-if-js';
635
		}
636
		$output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
637
638
		if ( $total_pages ) {
639
			$page_class = $total_pages < 2 ? ' one-page' : '';
640
		} else {
641
			$page_class = ' no-pages';
642
		}
643
		$this->_pagination = "<div class='tablenav-pages" . esc_attr( $page_class ) . "'>$output</div>";
644
645
		echo $this->_pagination;
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
646
	}
647
648
	/**
649
	 * Get a list of sortable columns. The format is:
650
	 * 'internal-name' => 'orderby'
651
	 * or
652
	 * 'internal-name' => array( 'orderby', true )
653
	 *
654
	 * The second format will make the initial sorting order be descending
655
	 *
656
	 * @since 2.0.18
657
	 * @access protected
658
	 *
659
	 * @return array
660
	 */
661
	protected function get_sortable_columns() {
662
		return array();
663
	}
664
665
	/**
666
	 * Gets the name of the default primary column.
667
	 *
668
	 * @since 4.3.0
669
	 * @access protected
670
	 *
671
	 * @return string Name of the default primary column, in this case, an empty string.
672
	 */
673
	protected function get_default_primary_column_name() {
674
		$columns = $this->get_columns();
675
		$column = '';
676
677
		// We need a primary defined so responsive views show something,
678
		// so let's fall back to the first non-checkbox column.
679
		foreach ( $columns as $col => $column_name ) {
680
			if ( 'cb' === $col ) {
681
				continue;
682
			}
683
684
			$column = $col;
685
			break;
686
		}
687
688
		return $column;
689
	}
690
691
	/**
692
	 * Gets the name of the primary column.
693
	 *
694
	 * @since 4.3.0
695
	 * @access protected
696
	 *
697
	 * @return string The name of the primary column.
698
	 */
699
	protected function get_primary_column_name() {
700
		$columns = $this->get_columns();
701
		$default = $this->get_default_primary_column_name();
702
703
		// If the primary column doesn't exist fall back to the
704
		// first non-checkbox column.
705
		if ( ! isset( $columns[ $default ] ) ) {
706
			$default = FrmListHelper::get_default_primary_column_name();
707
		}
708
709
		/**
710
		 * Filter the name of the primary column for the current list table.
711
		 *
712
		 * @since 4.3.0
713
		 *
714
		 * @param string $default Column name default for the specific list table, e.g. 'name'.
715
		 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
716
		 */
717
		$column  = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
718
719
		if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
720
			$column = $default;
721
		}
722
723
		return $column;
724
	}
725
726
	/**
727
	 * Get a list of all, hidden and sortable columns, with filter applied
728
	 *
729
	 * @since 2.0.18
730
	 * @access protected
731
	 *
732
	 * @return array
733
	 */
734
	protected function get_column_info() {
735
		// $_column_headers is already set / cached
736
		if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
737
			// Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
738
			// In 4.3, we added a fourth argument for primary column.
739
			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
740
			foreach ( $this->_column_headers as $key => $value ) {
741
				$column_headers[ $key ] = $value;
742
			}
743
744
			return $column_headers;
745
		}
746
747
		$columns = get_column_headers( $this->screen );
748
		$hidden = get_hidden_columns( $this->screen );
749
		$this->hide_extra_columns( $columns, $hidden );
750
751
		$sortable_columns = $this->get_sortable_columns();
752
		/**
753
		 * Filter the list table sortable columns for a specific screen.
754
		 *
755
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
756
		 * to the ID of the current screen, usually a string.
757
		 *
758
		 * @since 3.5.0
759
		 *
760
		 * @param array $sortable_columns An array of sortable columns.
761
		 */
762
		$_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
763
764
		$sortable = array();
765
		foreach ( $_sortable as $id => $data ) {
766
			if ( empty( $data ) ) {
767
				continue;
768
			}
769
770
			$data = (array) $data;
771
			if ( ! isset( $data[1] ) ) {
772
				$data[1] = false;
773
			}
774
775
			$sortable[ $id ] = $data;
776
		}
777
778
		$primary = $this->get_primary_column_name();
779
		$this->_column_headers = array( $columns, $hidden, $sortable, $primary );
780
781
		return $this->_column_headers;
782
	}
783
784
	/**
785
	 * Prevent too many columns from showing on the page
786
	 * @since 2.05.07
787
	 */
788
	private function hide_extra_columns( $columns, &$hidden ) {
789
		$max_columns = 15;
790
		$shown = count( $columns ) - count( $hidden );
791
792
		if ( $shown > $max_columns ) {
793
			$remove = $shown - $max_columns;
794
			$columns = array_reverse( $columns );
795
			foreach ( $columns as $name => $c ) {
796
				if ( ! in_array( $name, $hidden ) ) {
797
					$hidden[] = $name;
798
					$remove--;
799
					if ( $remove <= 0 ) {
800
						break;
801
					}
802
				}
803
			}
804
		}
805
	}
806
807
	/**
808
	 * Return number of visible columns
809
	 *
810
	 * @since 2.0.18
811
	 * @access public
812
	 *
813
	 * @return int
814
	 */
815
	public function get_column_count() {
816
		list ( $columns, $hidden ) = $this->get_column_info();
817
		$hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
818
		return count( $columns ) - count( $hidden );
819
	}
820
821
	/**
822
	 * Print column headers, accounting for hidden and sortable columns.
823
	 *
824
	 * @since 2.0.18
825
	 * @access public
826
	 *
827
	 * @staticvar int $cb_counter
828
	 *
829
	 * @param bool $with_id Whether to set the id attribute or not
830
	 */
831
	public function print_column_headers( $with_id = true ) {
832
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
833
834
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
835
		$current_url = remove_query_arg( 'paged', $current_url );
836
837
		if ( isset( $_GET['orderby'] ) ) {
838
			$current_orderby = sanitize_text_field( $_GET['orderby'] );
839
		} else {
840
			$current_orderby = '';
841
		}
842
843
		if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] ) {
844
			$current_order = 'desc';
845
		} else {
846
			$current_order = 'asc';
847
		}
848
849
		if ( ! empty( $columns['cb'] ) ) {
850
			static $cb_counter = 1;
851
			$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
852
				. '<input id="cb-select-all-' . esc_attr( $cb_counter ) . '" type="checkbox" />';
853
			$cb_counter++;
854
		}
855
856
		foreach ( $columns as $column_key => $column_display_name ) {
857
			$class = array( 'manage-column', "column-$column_key" );
858
859
			if ( in_array( $column_key, $hidden ) ) {
860
				$class[] = 'hidden';
861
			}
862
863
			if ( 'cb' == $column_key ) {
864
				$class[] = 'check-column';
865
			} else if ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
866
				$class[] = 'num';
867
			}
868
869
			if ( $column_key === $primary ) {
870
				$class[] = 'column-primary';
871
			}
872
873
			if ( isset( $sortable[ $column_key ] ) ) {
874
				list( $orderby, $desc_first ) = $sortable[ $column_key ];
875
876
				if ( $current_orderby == $orderby ) {
877
					$order = 'asc' == $current_order ? 'desc' : 'asc';
878
					$class[] = 'sorted';
879
					$class[] = $current_order;
880
				} else {
881
					$order = $desc_first ? 'desc' : 'asc';
882
					$class[] = 'sortable';
883
					$class[] = $desc_first ? 'asc' : 'desc';
884
				}
885
886
				$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>';
887
			}
888
889
			$tag = ( 'cb' === $column_key ) ? 'td' : 'th';
890
			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
891
			$id = $with_id ? "id='" . esc_attr( $column_key ) . "'" : '';
892
893
			if ( ! empty( $class ) ) {
894
				$class = "class='" . join( ' ', $class ) . "'";
895
			}
896
897
			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...
898
		}
899
	}
900
901
	/**
902
	 * Display the table
903
	 *
904
	 * @since 2.0.18
905
	 * @access public
906
	 */
907
	public function display() {
908
		$singular = $this->_args['singular'];
909
910
		$this->display_tablenav( 'top' );
911
?>
912
<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
913
	<thead>
914
	<tr>
915
		<?php $this->print_column_headers(); ?>
916
	</tr>
917
	</thead>
918
919
	<tbody id="the-list"<?php
920
		if ( $singular ) {
921
			echo " data-wp-lists='list:" . esc_attr( $singular ) . "'";
922
		} ?>>
923
		<?php $this->display_rows_or_placeholder(); ?>
924
	</tbody>
925
926
	<tfoot>
927
	<tr>
928
		<?php $this->print_column_headers( false ); ?>
929
	</tr>
930
	</tfoot>
931
932
</table>
933
<?php
934
		$this->display_tablenav( 'bottom' );
935
	}
936
937
	/**
938
	 * Get a list of CSS classes for the list table table tag.
939
	 *
940
	 * @since 2.0.18
941
	 * @access protected
942
	 *
943
	 * @return array List of CSS classes for the table tag.
944
	 */
945
	protected function get_table_classes() {
946
		return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
947
	}
948
949
	/**
950
	 * Generate the table navigation above or below the table
951
	 *
952
	 * @since 2.0.18
953
	 * @access protected
954
	 * @param string $which
955
	 */
956
	protected function display_tablenav( $which ) {
957
		if ( 'top' == $which ) {
958
			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
959
		}
960
?>
961
	<div class="tablenav <?php echo esc_attr( $which ); ?>">
962
963
		<div class="alignleft actions bulkactions">
964
			<?php $this->bulk_actions( $which ); ?>
965
		</div>
966
<?php
967
		$this->extra_tablenav( $which );
968
		$this->pagination( $which );
969
?>
970
971
		<br class="clear" />
972
	</div>
973
<?php
974
	}
975
976
	/**
977
	 * Extra controls to be displayed between bulk actions and pagination
978
	 *
979
	 * @since 2.0.18
980
	 * @access protected
981
	 *
982
	 * @param string $which
983
	 */
984
	protected function extra_tablenav( $which ) {}
985
986
	/**
987
	 * Generate the tbody element for the list table.
988
	 *
989
	 * @since 2.0.18
990
	 * @access public
991
	 */
992
	public function display_rows_or_placeholder() {
993
		if ( $this->has_items() ) {
994
			$this->display_rows();
995
		} else {
996
			echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
997
			$this->no_items();
998
			echo '</td></tr>';
999
		}
1000
	}
1001
1002
	/**
1003
	 * Generates content for a single row of the table
1004
	 *
1005
	 * @since 2.0.18
1006
	 * @access public
1007
	 *
1008
	 * @param object $item The current item
1009
	 */
1010
	public function single_row( $item ) {
1011
		echo '<tr>';
1012
		$this->single_row_columns( $item );
1013
		echo '</tr>';
1014
	}
1015
1016
	/**
1017
	 * Generates the columns for a single row of the table
1018
	 *
1019
	 * @since 2.0.18
1020
	 * @access protected
1021
	 *
1022
	 * @param object $item The current item
1023
	 */
1024
	protected function single_row_columns( $item ) {
1025
		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...
1026
1027
		foreach ( $columns as $column_name => $column_display_name ) {
1028
			$classes = "$column_name column-$column_name";
1029
			if ( $primary === $column_name ) {
1030
				$classes .= ' has-row-actions column-primary';
1031
			}
1032
1033
			if ( in_array( $column_name, $hidden ) ) {
1034
				$classes .= ' hidden';
1035
			}
1036
1037
			// Comments column uses HTML in the display name with screen reader text.
1038
			// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1039
			$data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';
1040
1041
			$attributes = "class='$classes' $data";
1042
1043
			if ( 'cb' == $column_name ) {
1044
				echo '<th scope="row" class="check-column"></th>';
1045
			} elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1046
				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...
1047
					array( $this, '_column_' . $column_name ),
1048
					$item,
1049
					$classes,
1050
					$data,
1051
					$primary
1052
				);
1053
			} elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1054
				echo "<td $attributes>";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"<td $attributes>"'
Loading history...
1055
				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...
1056
				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...
1057
				echo '</td>';
1058
			} else {
1059
				echo "<td $attributes>";
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '"<td $attributes>"'
Loading history...
1060
				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...
1061
				echo '</td>';
1062
			}
1063
		}
1064
	}
1065
1066
	/**
1067
	 * Generates and display row actions links for the list table.
1068
	 *
1069
	 * @since 4.3.0
1070
	 * @access protected
1071
	 *
1072
	 * @param object $item        The item being acted upon.
1073
	 * @param string $column_name Current column name.
1074
	 * @param string $primary     Primary column name.
1075
	 * @return string The row actions output. In this case, an empty string.
1076
	 */
1077
	protected function handle_row_actions( $item, $column_name, $primary ) {
1078
		return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
1079
 	}
1080
1081
	/**
1082
	 * Handle an incoming ajax request (called from admin-ajax.php)
1083
	 *
1084
	 * @since 2.0.18
1085
	 * @access public
1086
	 */
1087
	public function ajax_response() {
1088
		$this->prepare_items();
1089
1090
		ob_start();
1091
		if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1092
			$this->display_rows();
1093
		} else {
1094
			$this->display_rows_or_placeholder();
1095
		}
1096
1097
		$rows = ob_get_clean();
1098
1099
		$response = array( 'rows' => $rows );
1100
1101
		if ( isset( $this->_pagination_args['total_items'] ) ) {
1102
			$response['total_items_i18n'] = sprintf(
1103
				_n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1104
				number_format_i18n( $this->_pagination_args['total_items'] )
1105
			);
1106
		}
1107
		if ( isset( $this->_pagination_args['total_pages'] ) ) {
1108
			$response['total_pages'] = $this->_pagination_args['total_pages'];
1109
			$response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1110
		}
1111
1112
		die( wp_json_encode( $response ) );
1113
	}
1114
1115
	/**
1116
	 * Send required variables to JavaScript land
1117
	 *
1118
	 * @access public
1119
	 */
1120
	public function _js_vars() {
1121
		$args = array(
1122
			'class'  => get_class( $this ),
1123
			'screen' => array(
1124
				'id'   => $this->screen->id,
1125
				'base' => $this->screen->base,
1126
			),
1127
		);
1128
1129
		printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1130
	}
1131
}
1132