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

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