Completed
Push — master ( 1e1c87...a1ea0d )
by Stephanie
02:47
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
		if ( isset( $this->_actions['bulk_delete'] ) ) {
402
			$verify = $this->confirm_bulk_delete();
403
404
			if ( $verify ) {
405
				echo "<a id='confirm-bulk-delete-" . esc_attr( $which ) . "' class='frm-hidden' href='confirm-bulk-delete' data-frmcaution='" . esc_html__( 'Heads up', 'formidable' ) . "' data-frmverify='" . esc_attr( $verify ) . "'></a>";
406
			}
407
		}
408
409
		submit_button( __( 'Apply', 'formidable' ), 'action', '', false, array( 'id' => "doaction$two" ) );
410
		echo "\n";
411
	}
412
413
	/**
414
	 * @return string if empty there will be no confirmation pop up
415
	 */
416
	protected function confirm_bulk_delete() {
417
		return '';
418
	}
419
420
	/**
421
	 * Get the current action selected from the bulk actions dropdown.
422
	 *
423
	 * @since 2.0.18
424
	 * @access public
425
	 *
426
	 * @return string|false The action name or False if no action was selected
427
	 */
428
	public function current_action() {
429
		if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
430
			return false;
431
		}
432
433
		$action = $this->get_bulk_action( 'action' );
434
		if ( $action === false ) {
435
			$action = $this->get_bulk_action( 'action2' );
436
		}
437
438
		return $action;
439
	}
440
441
	private static function get_bulk_action( $action_name ) {
442
		$action       = false;
443
		$action_param = self::get_param(
444
			array(
445
				'param'    => $action_name,
446
				'sanitize' => 'sanitize_text_field',
447
			)
448
		);
449
		if ( $action_param && - 1 != $action_param ) {
450
			$action = $action_param;
451
		}
452
453
		return $action;
454
	}
455
456
	/**
457
	 * Generate row actions div
458
	 *
459
	 * @since 2.0.18
460
	 * @access protected
461
	 *
462
	 * @param array $actions The list of actions
463
	 * @param bool $always_visible Whether the actions should be always visible
464
	 *
465
	 * @return string
466
	 */
467
	protected function row_actions( $actions, $always_visible = false ) {
468
		$action_count = count( $actions );
469
470
		$i = 0;
471
472
		if ( ! $action_count ) {
473
			return '';
474
		}
475
476
		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
477
		foreach ( $actions as $action => $link ) {
478
			++ $i;
479
			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
480
			$out .= "<span class='$action'>$link$sep</span>";
481
		}
482
		$out .= '</div>';
483
484
		$out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details', 'formidable' ) . '</span></button>';
485
486
		return $out;
487
	}
488
489
	/**
490
	 * Display a view switcher
491
	 *
492
	 * @since 2.0.18
493
	 * @access protected
494
	 *
495
	 * @param string $current_mode
496
	 */
497
	protected function view_switcher( $current_mode ) {
498
		?>
499
		<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>"/>
500
		<div class="view-switch">
501
			<?php
502
			foreach ( $this->modes as $mode => $title ) {
503
				$classes = array( 'view-' . $mode );
504
				if ( $current_mode == $mode ) {
505
					$classes[] = 'current';
506
				}
507
508
				printf(
509
					'<a href="%s" class="%s" id="view-switch-' . esc_attr( $mode ) . '"><span class="screen-reader-text">%s</span></a>' . "\n",
510
					esc_url( add_query_arg( 'mode', $mode ) ),
511
					esc_attr( implode( ' ', $classes ) ),
512
					esc_html( $title )
513
				);
514
			}
515
			?>
516
		</div>
517
		<?php
518
	}
519
520
	/**
521
	 * Get the current page number
522
	 *
523
	 * @since 2.0.18
524
	 * @access public
525
	 *
526
	 * @return int
527
	 */
528
	public function get_pagenum() {
529
		$pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
530
531
		if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
532
			$pagenum = $this->_pagination_args['total_pages'];
533
		}
534
535
		return max( 1, $pagenum );
536
	}
537
538
	/**
539
	 * Get number of items to display on a single page
540
	 *
541
	 * @since 2.0.18
542
	 * @access protected
543
	 *
544
	 * @param string $option
545
	 * @param int $default
546
	 *
547
	 * @return int
548
	 */
549
	protected function get_items_per_page( $option, $default = 20 ) {
550
		$per_page = (int) get_user_option( $option );
551
		if ( empty( $per_page ) || $per_page < 1 ) {
552
			$per_page = $default;
553
		}
554
555
		/**
556
		 * Filter the number of items to be displayed on each page of the list table.
557
		 *
558
		 * The dynamic hook name, $option, refers to the `per_page` option depending
559
		 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
560
		 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
561
		 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
562
		 * 'edit_{$post_type}_per_page', etc.
563
		 *
564
		 * @since 2.9.0
565
		 *
566
		 * @param int $per_page Number of items to be displayed. Default 20.
567
		 */
568
		return (int) apply_filters( $option, $per_page );
569
	}
570
571
	/**
572
	 * Display the pagination.
573
	 *
574
	 * @since 2.0.18
575
	 * @access protected
576
	 *
577
	 * @param string $which
578
	 */
579
	protected function pagination( $which ) {
580
		if ( empty( $this->_pagination_args ) ) {
581
			return;
582
		}
583
584
		$total_items     = $this->_pagination_args['total_items'];
585
		$total_pages     = $this->_pagination_args['total_pages'];
586
		$infinite_scroll = false;
587
		if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
588
			$infinite_scroll = $this->_pagination_args['infinite_scroll'];
589
		}
590
591
		/* translators: %s: Number of items */
592
		$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items, 'formidable' ), number_format_i18n( $total_items ) ) . '</span>';
593
594
		$current = $this->get_pagenum();
595
596
		$page_links = array();
597
598
		$total_pages_before = '<span class="paging-input">';
599
		$total_pages_after  = '</span>';
600
601
		$disable = $this->disabled_pages( $total_pages );
602
603
		$page_links[] = $this->add_page_link(
604
			array(
605
				'page'     => 'first',
606
				'arrow'    => '&laquo;',
607
				'number'   => '',
608
				'disabled' => $disable['first'],
609
			)
610
		);
611
612
		$page_links[] = $this->add_page_link(
613
			array(
614
				'page'     => 'prev',
615
				'arrow'    => '&lsaquo;',
616
				'number'   => max( 1, $current - 1 ),
617
				'disabled' => $disable['prev'],
618
			)
619
		);
620
621
		if ( 'bottom' == $which ) {
622
			$html_current_page  = $current;
623
			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page', 'formidable' ) . '</span><span id="table-paging" class="paging-input">';
624
		} else {
625
			$html_current_page = sprintf(
626
				"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' />",
627
				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page', 'formidable' ) . '</label>',
628
				$current,
629
				strlen( $total_pages )
630
			);
631
		}
632
		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
633
634
		/* translators: %1$s: Current page number, %2$s: Total pages */
635
		$page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging', 'formidable' ), $html_current_page, $html_total_pages ) . $total_pages_after;
636
637
		$page_links[] = $this->add_page_link(
638
			array(
639
				'page'     => 'next',
640
				'arrow'    => '&rsaquo;',
641
				'number'   => min( $total_pages, $current + 1 ),
642
				'disabled' => $disable['next'],
643
			)
644
		);
645
646
		$page_links[] = $this->add_page_link(
647
			array(
648
				'page'     => 'last',
649
				'arrow'    => '&raquo;',
650
				'number'   => $total_pages,
651
				'disabled' => $disable['last'],
652
			)
653
		);
654
655
		$pagination_links_class = 'pagination-links';
656
		if ( ! empty( $infinite_scroll ) ) {
657
			$pagination_links_class = ' hide-if-js';
658
		}
659
		$output .= "\n" . '<span class="' . esc_attr( $pagination_links_class ) . '">' . join( "\n", $page_links ) . '</span>';
660
661
		if ( $total_pages ) {
662
			$page_class = $total_pages < 2 ? ' one-page' : '';
663
		} else {
664
			$page_class = ' no-pages';
665
		}
666
		$this->_pagination = "<div class='tablenav-pages" . esc_attr( $page_class ) . "'>$output</div>";
667
668
		echo $this->_pagination; // WPCS: XSS ok.
669
	}
670
671
	private function disabled_pages( $total_pages ) {
672
		$current = $this->get_pagenum();
673
		$disable = array(
674
			'first' => false,
675
			'last'  => false,
676
			'prev'  => false,
677
			'next'  => false,
678
		);
679
680
		if ( $current == 1 ) {
681
			$disable['first'] = true;
682
			$disable['prev']  = true;
683
		} elseif ( $current == 2 ) {
684
			$disable['first'] = true;
685
		}
686
687
		if ( $current == $total_pages ) {
688
			$disable['last'] = true;
689
			$disable['next'] = true;
690
		} elseif ( $current == $total_pages - 1 ) {
691
			$disable['last'] = true;
692
		}
693
694
		return $disable;
695
	}
696
697
	private function link_label( $link ) {
698
		$labels = array(
699
			'first' => __( 'First page', 'formidable' ),
700
			'last'  => __( 'Last page', 'formidable' ),
701
			'prev'  => __( 'Previous page', 'formidable' ),
702
			'next'  => __( 'Next page', 'formidable' ),
703
		);
704
705
		return $labels[ $link ];
706
	}
707
708
	private function current_url() {
709
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
710
711
		return remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
712
	}
713
714
	private function add_page_link( $atts ) {
715
		if ( $atts['disabled'] ) {
716
			$link = $this->add_disabled_link( $atts['arrow'] );
717
		} else {
718
			$link = $this->add_active_link( $atts );
719
		}
720
721
		return $link;
722
	}
723
724
	private function add_disabled_link( $label ) {
725
		return '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">' . $label . '</span>';
726
	}
727
728
	private function add_active_link( $atts ) {
729
		$url   = esc_url( add_query_arg( 'paged', $atts['number'], $this->current_url() ) );
730
		$label = $this->link_label( $atts['page'] );
731
732
		return sprintf(
733
			"<a class='button %s-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
734
			$atts['page'],
735
			$url,
736
			$label,
737
			$atts['arrow']
738
		);
739
	}
740
741
	/**
742
	 * Get a list of sortable columns. The format is:
743
	 * 'internal-name' => 'orderby'
744
	 * or
745
	 * 'internal-name' => array( 'orderby', true )
746
	 *
747
	 * The second format will make the initial sorting order be descending
748
	 *
749
	 * @since 2.0.18
750
	 * @access protected
751
	 *
752
	 * @return array
753
	 */
754
	protected function get_sortable_columns() {
755
		return array();
756
	}
757
758
	/**
759
	 * Gets the name of the default primary column.
760
	 *
761
	 * @since 4.3.0
762
	 * @access protected
763
	 *
764
	 * @return string Name of the default primary column, in this case, an empty string.
765
	 */
766
	protected function get_default_primary_column_name() {
767
		$columns = $this->get_columns();
768
		$column  = '';
769
770
		// We need a primary defined so responsive views show something,
771
		// so let's fall back to the first non-checkbox column.
772
		foreach ( $columns as $col => $column_name ) {
773
			if ( 'cb' === $col ) {
774
				continue;
775
			}
776
777
			$column = $col;
778
			break;
779
		}
780
781
		return $column;
782
	}
783
784
	/**
785
	 * Gets the name of the primary column.
786
	 *
787
	 * @since 4.3.0
788
	 * @access protected
789
	 *
790
	 * @return string The name of the primary column.
791
	 */
792
	protected function get_primary_column_name() {
793
		$columns = $this->get_columns();
794
		$default = $this->get_default_primary_column_name();
795
796
		// If the primary column doesn't exist fall back to the
797
		// first non-checkbox column.
798
		if ( ! isset( $columns[ $default ] ) ) {
799
			$default = self::get_default_primary_column_name();
800
		}
801
802
		/**
803
		 * Filter the name of the primary column for the current list table.
804
		 *
805
		 * @since 4.3.0
806
		 *
807
		 * @param string $default Column name default for the specific list table, e.g. 'name'.
808
		 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
809
		 */
810
		$column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
811
812
		if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
813
			$column = $default;
814
		}
815
816
		return $column;
817
	}
818
819
	/**
820
	 * Get a list of all, hidden and sortable columns, with filter applied
821
	 *
822
	 * @since 2.0.18
823
	 * @access protected
824
	 *
825
	 * @return array
826
	 */
827
	protected function get_column_info() {
828
		// $_column_headers is already set / cached
829
		if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
830
			// Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
831
			// In 4.3, we added a fourth argument for primary column.
832
			$column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
833
			foreach ( $this->_column_headers as $key => $value ) {
834
				$column_headers[ $key ] = $value;
835
			}
836
837
			return $column_headers;
838
		}
839
840
		$columns = get_column_headers( $this->screen );
841
		$hidden  = get_hidden_columns( $this->screen );
842
843
		$sortable_columns = $this->get_sortable_columns();
844
		/**
845
		 * Filter the list table sortable columns for a specific screen.
846
		 *
847
		 * The dynamic portion of the hook name, `$this->screen->id`, refers
848
		 * to the ID of the current screen, usually a string.
849
		 *
850
		 * @since 3.5.0
851
		 *
852
		 * @param array $sortable_columns An array of sortable columns.
853
		 */
854
		$_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
855
856
		$sortable = array();
857
		foreach ( $_sortable as $id => $data ) {
858
			if ( empty( $data ) ) {
859
				continue;
860
			}
861
862
			$data = (array) $data;
863
			if ( ! isset( $data[1] ) ) {
864
				$data[1] = false;
865
			}
866
867
			$sortable[ $id ] = $data;
868
		}
869
870
		$primary = $this->get_primary_column_name();
871
872
		$this->_column_headers = array( $columns, $hidden, $sortable, $primary );
873
874
		return $this->_column_headers;
875
	}
876
877
	/**
878
	 * Return number of visible columns
879
	 *
880
	 * @since 2.0.18
881
	 * @access public
882
	 *
883
	 * @return int
884
	 */
885
	public function get_column_count() {
886
		list ( $columns, $hidden ) = $this->get_column_info();
887
		$hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
888
889
		return count( $columns ) - count( $hidden );
890
	}
891
892
	/**
893
	 * Print column headers, accounting for hidden and sortable columns.
894
	 *
895
	 * @since 2.0.18
896
	 * @access public
897
	 *
898
	 * @staticvar int $cb_counter
899
	 *
900
	 * @param bool $with_id Whether to set the id attribute or not
901
	 */
902
	public function print_column_headers( $with_id = true ) {
903
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
904
905
		$current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
906
		$current_url = remove_query_arg( 'paged', $current_url );
907
908
		if ( isset( $_GET['orderby'] ) ) {
909
			$current_orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
910
		} else {
911
			$current_orderby = '';
912
		}
913
914
		if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] ) {
915
			$current_order = 'desc';
916
		} else {
917
			$current_order = 'asc';
918
		}
919
920
		if ( ! empty( $columns['cb'] ) ) {
921
			static $cb_counter = 1;
922
			$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All', 'formidable' ) . '</label>';
923
			$columns['cb'] .= '<input id="cb-select-all-' . esc_attr( $cb_counter ) . '" type="checkbox" />';
924
			$cb_counter ++;
925
		}
926
927
		foreach ( $columns as $column_key => $column_display_name ) {
928
			$class = array( 'manage-column', "column-$column_key" );
929
930
			if ( in_array( $column_key, $hidden ) ) {
931
				$class[] = 'hidden';
932
			}
933
934
			if ( 'cb' == $column_key ) {
935
				$class[] = 'check-column';
936
			} elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
937
				$class[] = 'num';
938
			}
939
940
			if ( $column_key === $primary ) {
941
				$class[] = 'column-primary';
942
			}
943
944
			if ( isset( $sortable[ $column_key ] ) ) {
945
				list( $orderby, $desc_first ) = $sortable[ $column_key ];
946
947
				if ( $current_orderby == $orderby ) {
948
					$order   = 'asc' == $current_order ? 'desc' : 'asc';
949
					$class[] = 'sorted';
950
					$class[] = $current_order;
951
				} else {
952
					$order   = $desc_first ? 'desc' : 'asc';
953
					$class[] = 'sortable';
954
					$class[] = $desc_first ? 'asc' : 'desc';
955
				}
956
957
				$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>';
958
			}
959
960
			$tag   = ( 'cb' === $column_key ) ? 'td' : 'th';
961
			$scope = ( 'th' === $tag ) ? 'scope="col"' : '';
962
			$id    = $with_id ? "id='" . esc_attr( $column_key ) . "'" : '';
963
964
			if ( ! empty( $class ) ) {
965
				$class = "class='" . esc_attr( join( ' ', $class ) ) . "'";
966
			}
967
968
			if ( ! $this->has_min_items() && ! $with_id ) {
969
				// Hide the labels but show the border.
970
				$column_display_name = '';
971
			}
972
			echo "<$tag $scope $id $class>$column_display_name</$tag>"; // WPCS: XSS ok.
973
		}
974
	}
975
976
	/**
977
	 * Display the table
978
	 *
979
	 * @since 2.0.18
980
	 * @access public
981
	 */
982
	public function display() {
983
		$singular = $this->_args['singular'];
984
985
		$this->display_tablenav( 'top' );
986
		?>
987
		<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
988
			<?php if ( $this->has_min_items( 1 ) ) { ?>
989
			<thead>
990
				<tr>
991
					<?php $this->print_column_headers(); ?>
992
				</tr>
993
			</thead>
994
			<?php } ?>
995
996
			<tbody id="the-list"<?php echo( $singular ? " data-wp-lists='list:" . esc_attr( $singular ) . "'" : '' ); // WPCS: XSS ok. ?>>
997
				<?php $this->display_rows_or_placeholder(); ?>
998
			</tbody>
999
1000
			<?php if ( $this->has_min_items( 1 ) ) { ?>
1001
			<tfoot>
1002
				<tr>
1003
					<?php $this->print_column_headers( false ); ?>
1004
				</tr>
1005
			</tfoot>
1006
			<?php } ?>
1007
		</table>
1008
		<?php
1009
		$this->display_tablenav( 'bottom' );
1010
	}
1011
1012
	/**
1013
	 * Get a list of CSS classes for the list table table tag.
1014
	 *
1015
	 * @since 2.0.18
1016
	 * @access protected
1017
	 *
1018
	 * @return array List of CSS classes for the table tag.
1019
	 */
1020
	protected function get_table_classes() {
1021
		return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
1022
	}
1023
1024
	/**
1025
	 * Generate the table navigation above or below the table
1026
	 *
1027
	 * @since 2.0.18
1028
	 * @access protected
1029
	 *
1030
	 * @param string $which
1031
	 */
1032
	protected function display_tablenav( $which ) {
1033
		if ( 'top' == $which ) {
1034
			wp_nonce_field( 'bulk-' . $this->_args['plural'] );
1035
			if ( ! $this->has_min_items( 1 ) ) {
1036
				// Don't show bulk actions if no items.
1037
				return;
1038
			}
1039
		} elseif ( ! $this->has_min_items() ) {
1040
			// don't show the bulk actions when there aren't many rows.
1041
			return;
1042
		}
1043
		?>
1044
		<div class="tablenav <?php echo esc_attr( $which ); ?>">
1045
1046
			<div class="alignleft actions bulkactions">
1047
				<?php $this->bulk_actions( $which ); ?>
1048
			</div>
1049
			<?php
1050
			$this->extra_tablenav( $which );
1051
			$this->pagination( $which );
1052
			?>
1053
1054
			<br class="clear"/>
1055
		</div>
1056
		<?php
1057
	}
1058
1059
	/**
1060
	 * Use this to exclude the footer labels and bulk items.
1061
	 * When close together, it feels like duplicates.
1062
	 *
1063
	 * @since 4.07
1064
	 */
1065
	protected function has_min_items( $limit = 5 ) {
1066
		return $this->has_items() && ( $this->total_items === false || $this->total_items >= $limit );
1067
	}
1068
1069
	/**
1070
	 * Extra controls to be displayed between bulk actions and pagination
1071
	 *
1072
	 * @since 2.0.18
1073
	 * @access protected
1074
	 *
1075
	 * @param string $which
1076
	 */
1077
	protected function extra_tablenav( $which ) {
1078
	}
1079
1080
	/**
1081
	 * Generate the tbody element for the list table.
1082
	 *
1083
	 * @since 2.0.18
1084
	 * @access public
1085
	 */
1086
	public function display_rows_or_placeholder() {
1087
		if ( $this->has_items() ) {
1088
			$this->display_rows();
1089
		} else {
1090
			echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
1091
			$this->no_items();
1092
			echo '</td></tr>';
1093
		}
1094
	}
1095
1096
	/**
1097
	 * Generates content for a single row of the table
1098
	 *
1099
	 * @since 2.0.18
1100
	 * @access public
1101
	 *
1102
	 * @param object $item The current item
1103
	 */
1104
	public function single_row( $item ) {
1105
		echo '<tr>';
1106
		$this->single_row_columns( $item );
1107
		echo '</tr>';
1108
	}
1109
1110
	/**
1111
	 * Generates the columns for a single row of the table
1112
	 *
1113
	 * @since 2.0.18
1114
	 * @access protected
1115
	 *
1116
	 * @param object $item The current item
1117
	 */
1118
	protected function single_row_columns( $item ) {
1119
		list( $columns, $hidden,, $primary ) = $this->get_column_info();
1120
1121
		foreach ( $columns as $column_name => $column_display_name ) {
1122
			$classes = "$column_name column-$column_name";
1123
			if ( $primary === $column_name ) {
1124
				$classes .= ' has-row-actions column-primary';
1125
			}
1126
1127
			if ( in_array( $column_name, $hidden ) ) {
1128
				$classes .= ' hidden';
1129
			}
1130
1131
			// Comments column uses HTML in the display name with screen reader text.
1132
			// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1133
			$data = 'data-colname="' . esc_attr( $column_display_name ) . '"';
1134
1135
			$attributes = 'class="' . esc_attr( $classes ) . '" ' . $data;
1136
1137
			if ( 'cb' == $column_name ) {
1138
				echo '<th scope="row" class="check-column"></th>';
1139
			} elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1140
				echo call_user_func( // WPCS: XSS ok.
1141
					array( $this, '_column_' . $column_name ),
1142
					$item,
1143
					$classes,
1144
					$data,
1145
					$primary
1146
				);
1147
			} elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1148
				echo "<td $attributes>"; // WPCS: XSS ok.
1149
				echo call_user_func( array( $this, 'column_' . $column_name ), $item ); // WPCS: XSS ok.
1150
				echo $this->handle_row_actions( $item, $column_name, $primary ); // WPCS: XSS ok.
1151
				echo '</td>';
1152
			} else {
1153
				echo "<td $attributes>"; // WPCS: XSS ok.
1154
				echo $this->handle_row_actions( $item, $column_name, $primary ); // WPCS: XSS ok.
1155
				echo '</td>';
1156
			}
1157
		}
1158
	}
1159
1160
	/**
1161
	 * Generates and display row actions links for the list table.
1162
	 *
1163
	 * @since 4.3.0
1164
	 * @access protected
1165
	 *
1166
	 * @param object $item The item being acted upon.
1167
	 * @param string $column_name Current column name.
1168
	 * @param string $primary Primary column name.
1169
	 *
1170
	 * @return string The row actions output. In this case, an empty string.
1171
	 */
1172
	protected function handle_row_actions( $item, $column_name, $primary ) {
1173
		return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . esc_html__( 'Show more details', 'formidable' ) . '</span></button>' : '';
1174
	}
1175
1176
	/**
1177
	 * Handle an incoming ajax request (called from admin-ajax.php)
1178
	 *
1179
	 * @since 2.0.18
1180
	 * @access public
1181
	 */
1182
	public function ajax_response() {
1183
		$this->prepare_items();
1184
1185
		ob_start();
1186
		if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1187
			$this->display_rows();
1188
		} else {
1189
			$this->display_rows_or_placeholder();
1190
		}
1191
1192
		$rows = ob_get_clean();
1193
1194
		$response = array( 'rows' => $rows );
1195
1196
		if ( isset( $this->_pagination_args['total_items'] ) ) {
1197
			$response['total_items_i18n'] = sprintf(
1198
				/* translators: %s: Number of items */
1199
				_n( '%s item', '%s items', $this->_pagination_args['total_items'], 'formidable' ),
1200
				number_format_i18n( $this->_pagination_args['total_items'] )
1201
			);
1202
		}
1203
		if ( isset( $this->_pagination_args['total_pages'] ) ) {
1204
			$response['total_pages']      = $this->_pagination_args['total_pages'];
1205
			$response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1206
		}
1207
1208
		die( wp_json_encode( $response ) );
1209
	}
1210
1211
	/**
1212
	 * Send required variables to JavaScript land
1213
	 *
1214
	 * @access public
1215
	 */
1216
	public function _js_vars() {
1217
		$args = array(
1218
			'class'  => get_class( $this ),
1219
			'screen' => array(
1220
				'id'   => $this->screen->id,
1221
				'base' => $this->screen->base,
1222
			),
1223
		);
1224
1225
		printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1226
	}
1227
}
1228