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

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