Completed
Push — master ( 4f1b77...ce9b57 )
by Stephanie
02:42 queued 10s
created

FrmListHelper::search_box()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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