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

1,250 lines 32.3 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 ?>
1001 <table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
1002 <?php if ( $this->has_min_items( 1 ) ) { ?>
1003 <thead>
1004 <tr>
1005 <?php $this->print_column_headers(); ?>
1006 </tr>
1007 </thead>
1008 <?php } ?>
1009
1010 <tbody id="the-list"<?php FrmAppHelper::array_to_html_params( $tbody_params, true ); ?>>
1011 <?php $this->display_rows_or_placeholder(); ?>
1012 </tbody>
1013
1014 <?php if ( $this->has_min_items( 1 ) && $this->should_display( $args, 'display-bottom-headers' ) ) { ?>
1015 <tfoot>
1016 <tr>
1017 <?php $this->print_column_headers( false ); ?>
1018 </tr>
1019 </tfoot>
1020 <?php } ?>
1021 </table>
1022 <?php
1023 if ( $this->should_display( $args, 'display-bottom-nav' ) ) {
1024 $this->display_tablenav( 'bottom' );
1025 }
1026 }
1027
1028 /**
1029 * Determines if a particular feature or element should be displayed.
1030 *
1031 * @param array $args An associative array of arguments.
1032 * @param string $settings The specific setting key to check within the arguments array.
1033 *
1034 * @return bool Returns true if the setting is not set or if it is not false; otherwise, returns false.
1035 */
1036 protected function should_display( $args, $settings ) {
1037 return ! isset( $args[ $settings ] ) || false !== $args[ $settings ];
1038 }
1039
1040 /**
1041 * Get a list of CSS classes for the list table table tag.
1042 *
1043 * @since 2.0.18
1044 *
1045 * @return array List of CSS classes for the table tag.
1046 */
1047 protected function get_table_classes() {
1048 return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
1049 }
1050
1051 /**
1052 * Generate the table navigation above or below the table
1053 *
1054 * @since 2.0.18
1055 *
1056 * @param string $which
1057 */
1058 protected function display_tablenav( $which ) {
1059 if ( 'top' === $which ) {
1060 wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false );
1061 if ( ! $this->has_min_items( 1 ) ) {
1062 // Don't show bulk actions if no items.
1063 return;
1064 }
1065 } elseif ( ! $this->has_min_items() ) {
1066 // don't show the bulk actions when there aren't many rows.
1067 return;
1068 }
1069 ?>
1070 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1071
1072 <div class="alignleft actions bulkactions">
1073 <?php $this->bulk_actions( $which ); ?>
1074 </div>
1075 <?php
1076 $this->extra_tablenav( $which );
1077 $this->pagination( $which );
1078 ?>
1079
1080 <br class="clear"/>
1081 </div>
1082 <?php
1083 }
1084
1085 /**
1086 * Use this to exclude the footer labels and bulk items.
1087 * When close together, it feels like duplicates.
1088 *
1089 * @since 4.07
1090 */
1091 protected function has_min_items( $limit = 5 ) {
1092 return $this->has_items() && ( $this->total_items === false || $this->total_items >= $limit );
1093 }
1094
1095 /**
1096 * Extra controls to be displayed between bulk actions and pagination
1097 *
1098 * @since 2.0.18
1099 *
1100 * @param string $which
1101 */
1102 protected function extra_tablenav( $which ) {
1103 }
1104
1105 /**
1106 * Generate the tbody element for the list table.
1107 *
1108 * @since 2.0.18
1109 */
1110 public function display_rows_or_placeholder() {
1111 if ( $this->has_items() ) {
1112 $this->display_rows();
1113 } else {
1114 echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
1115 $this->no_items();
1116 echo '</td></tr>';
1117 }
1118 }
1119
1120 /**
1121 * Generates content for a single row of the table
1122 *
1123 * @since 2.0.18
1124 *
1125 * @param object $item The current item.
1126 */
1127 public function single_row( $item ) {
1128 echo '<tr>';
1129 $this->single_row_columns( $item );
1130 echo '</tr>';
1131 }
1132
1133 /**
1134 * Generates the columns for a single row of the table
1135 *
1136 * @since 2.0.18
1137 *
1138 * @param object $item The current item.
1139 */
1140 protected function single_row_columns( $item ) {
1141 list( $columns, $hidden,, $primary ) = $this->get_column_info();
1142
1143 foreach ( $columns as $column_name => $column_display_name ) {
1144 $classes = "$column_name column-$column_name";
1145 if ( $primary === $column_name ) {
1146 $classes .= ' has-row-actions column-primary';
1147 }
1148
1149 if ( in_array( $column_name, $hidden ) ) {
1150 $classes .= ' hidden';
1151 }
1152
1153 $params = array(
1154 'class' => $classes,
1155 // Comments column uses HTML in the display name with screen reader text.
1156 // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1157 'data-colname' => $column_display_name,
1158 );
1159
1160 if ( 'cb' === $column_name ) {
1161 echo '<th scope="row" class="check-column"></th>';
1162 } else {
1163 echo '<td ';
1164 FrmAppHelper::array_to_html_params( $params, true );
1165 echo '>';
1166
1167 if ( method_exists( $this, 'column_' . $column_name ) ) {
1168 echo call_user_func( array( $this, 'column_' . $column_name ), $item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1169 }
1170
1171 echo $this->handle_row_actions( $item, $column_name, $primary ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1172 echo '</td>';
1173 }
1174 }//end foreach
1175 }
1176
1177 /**
1178 * Generates and display row actions links for the list table.
1179 *
1180 * @since 4.3.0
1181 *
1182 * @param object $item The item being acted upon.
1183 * @param string $column_name Current column name.
1184 * @param string $primary Primary column name.
1185 *
1186 * @return string The row actions output. In this case, an empty string.
1187 */
1188 protected function handle_row_actions( $item, $column_name, $primary ) {
1189 return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . esc_html__( 'Show more details', 'formidable' ) . '</span></button>' : '';
1190 }
1191
1192 /**
1193 * Handle an incoming ajax request (called from admin-ajax.php)
1194 *
1195 * @since 2.0.18
1196 */
1197 public function ajax_response() {
1198 $this->prepare_items();
1199
1200 ob_start();
1201 if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1202 $this->display_rows();
1203 } else {
1204 $this->display_rows_or_placeholder();
1205 }
1206
1207 $rows = ob_get_clean();
1208
1209 $response = array( 'rows' => $rows );
1210
1211 if ( isset( $this->_pagination_args['total_items'] ) ) {
1212 $response['total_items_i18n'] = sprintf(
1213 /* translators: %s: Number of items */
1214 _n( '%s item', '%s items', $this->_pagination_args['total_items'], 'formidable' ),
1215 number_format_i18n( $this->_pagination_args['total_items'] )
1216 );
1217 }
1218 if ( isset( $this->_pagination_args['total_pages'] ) ) {
1219 $response['total_pages'] = $this->_pagination_args['total_pages'];
1220 $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1221 }
1222
1223 die( wp_json_encode( $response ) );
1224 }
1225
1226 /**
1227 * Send required variables to JavaScript land
1228 *
1229 * @return void
1230 */
1231 public function _js_vars() {
1232 $args = array(
1233 'class' => get_class( $this ),
1234 'screen' => array(
1235 'id' => $this->screen->id,
1236 'base' => $this->screen->base,
1237 ),
1238 );
1239
1240 printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1241 }
1242
1243 /**
1244 * @return string
1245 */
1246 protected function loaded_from() {
1247 return '';
1248 }
1249 }
1250