PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.26.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.26.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / helpers / FrmListHelper.php

FrmListHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.26.1, at classes/helpers/FrmListHelper.php

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