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

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