PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.27
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.27
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.27, at classes/helpers/FrmListHelper.php

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