Completed
Push — master ( 77c454...d13d64 )
by Stephanie
02:44
created

FrmListHelper::display()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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