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

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