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

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