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

1,228 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 ); // WPCS: XSS ok.
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"; // WPCS: XSS ok.
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 $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
395
396 echo "\t<option value='" . esc_attr( $name ) . "'$class>" . esc_html( $title ) . "</option>\n"; // WPCS: XSS ok.
397 }
398
399 echo "</select>\n";
400
401 if ( isset( $this->_actions['bulk_delete'] ) ) {
402 $verify = $this->confirm_bulk_delete();
403
404 if ( $verify ) {
405 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>";
406 }
407 }
408
409 submit_button( __( 'Apply', 'formidable' ), 'action', '', false, array( 'id' => "doaction$two" ) );
410 echo "\n";
411 }
412
413 /**
414 * @return string if empty there will be no confirmation pop up
415 */
416 protected function confirm_bulk_delete() {
417 return '';
418 }
419
420 /**
421 * Get the current action selected from the bulk actions dropdown.
422 *
423 * @since 2.0.18
424 * @access public
425 *
426 * @return string|false The action name or False if no action was selected
427 */
428 public function current_action() {
429 if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) ) {
430 return false;
431 }
432
433 $action = $this->get_bulk_action( 'action' );
434 if ( $action === false ) {
435 $action = $this->get_bulk_action( 'action2' );
436 }
437
438 return $action;
439 }
440
441 private static function get_bulk_action( $action_name ) {
442 $action = false;
443 $action_param = self::get_param(
444 array(
445 'param' => $action_name,
446 'sanitize' => 'sanitize_text_field',
447 )
448 );
449 if ( $action_param && - 1 != $action_param ) {
450 $action = $action_param;
451 }
452
453 return $action;
454 }
455
456 /**
457 * Generate row actions div
458 *
459 * @since 2.0.18
460 * @access protected
461 *
462 * @param array $actions The list of actions
463 * @param bool $always_visible Whether the actions should be always visible
464 *
465 * @return string
466 */
467 protected function row_actions( $actions, $always_visible = false ) {
468 $action_count = count( $actions );
469
470 $i = 0;
471
472 if ( ! $action_count ) {
473 return '';
474 }
475
476 $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
477 foreach ( $actions as $action => $link ) {
478 ++ $i;
479 ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
480 $out .= "<span class='$action'>$link$sep</span>";
481 }
482 $out .= '</div>';
483
484 $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details', 'formidable' ) . '</span></button>';
485
486 return $out;
487 }
488
489 /**
490 * Display a view switcher
491 *
492 * @since 2.0.18
493 * @access protected
494 *
495 * @param string $current_mode
496 */
497 protected function view_switcher( $current_mode ) {
498 ?>
499 <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>"/>
500 <div class="view-switch">
501 <?php
502 foreach ( $this->modes as $mode => $title ) {
503 $classes = array( 'view-' . $mode );
504 if ( $current_mode == $mode ) {
505 $classes[] = 'current';
506 }
507
508 printf(
509 '<a href="%s" class="%s" id="view-switch-' . esc_attr( $mode ) . '"><span class="screen-reader-text">%s</span></a>' . "\n",
510 esc_url( add_query_arg( 'mode', $mode ) ),
511 esc_attr( implode( ' ', $classes ) ),
512 esc_html( $title )
513 );
514 }
515 ?>
516 </div>
517 <?php
518 }
519
520 /**
521 * Get the current page number
522 *
523 * @since 2.0.18
524 * @access public
525 *
526 * @return int
527 */
528 public function get_pagenum() {
529 $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
530
531 if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
532 $pagenum = $this->_pagination_args['total_pages'];
533 }
534
535 return max( 1, $pagenum );
536 }
537
538 /**
539 * Get number of items to display on a single page
540 *
541 * @since 2.0.18
542 * @access protected
543 *
544 * @param string $option
545 * @param int $default
546 *
547 * @return int
548 */
549 protected function get_items_per_page( $option, $default = 20 ) {
550 $per_page = (int) get_user_option( $option );
551 if ( empty( $per_page ) || $per_page < 1 ) {
552 $per_page = $default;
553 }
554
555 /**
556 * Filter the number of items to be displayed on each page of the list table.
557 *
558 * The dynamic hook name, $option, refers to the `per_page` option depending
559 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
560 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
561 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
562 * 'edit_{$post_type}_per_page', etc.
563 *
564 * @since 2.9.0
565 *
566 * @param int $per_page Number of items to be displayed. Default 20.
567 */
568 return (int) apply_filters( $option, $per_page );
569 }
570
571 /**
572 * Display the pagination.
573 *
574 * @since 2.0.18
575 * @access protected
576 *
577 * @param string $which
578 */
579 protected function pagination( $which ) {
580 if ( empty( $this->_pagination_args ) ) {
581 return;
582 }
583
584 $total_items = $this->_pagination_args['total_items'];
585 $total_pages = $this->_pagination_args['total_pages'];
586 $infinite_scroll = false;
587 if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
588 $infinite_scroll = $this->_pagination_args['infinite_scroll'];
589 }
590
591 /* translators: %s: Number of items */
592 $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items, 'formidable' ), number_format_i18n( $total_items ) ) . '</span>';
593
594 $current = $this->get_pagenum();
595
596 $page_links = array();
597
598 $total_pages_before = '<span class="paging-input">';
599 $total_pages_after = '</span>';
600
601 $disable = $this->disabled_pages( $total_pages );
602
603 $page_links[] = $this->add_page_link(
604 array(
605 'page' => 'first',
606 'arrow' => '&laquo;',
607 'number' => '',
608 'disabled' => $disable['first'],
609 )
610 );
611
612 $page_links[] = $this->add_page_link(
613 array(
614 'page' => 'prev',
615 'arrow' => '&lsaquo;',
616 'number' => max( 1, $current - 1 ),
617 'disabled' => $disable['prev'],
618 )
619 );
620
621 if ( 'bottom' == $which ) {
622 $html_current_page = $current;
623 $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page', 'formidable' ) . '</span><span id="table-paging" class="paging-input">';
624 } else {
625 $html_current_page = sprintf(
626 "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' />",
627 '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page', 'formidable' ) . '</label>',
628 $current,
629 strlen( $total_pages )
630 );
631 }
632 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
633
634 /* translators: %1$s: Current page number, %2$s: Total pages */
635 $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging', 'formidable' ), $html_current_page, $html_total_pages ) . $total_pages_after;
636
637 $page_links[] = $this->add_page_link(
638 array(
639 'page' => 'next',
640 'arrow' => '&rsaquo;',
641 'number' => min( $total_pages, $current + 1 ),
642 'disabled' => $disable['next'],
643 )
644 );
645
646 $page_links[] = $this->add_page_link(
647 array(
648 'page' => 'last',
649 'arrow' => '&raquo;',
650 'number' => $total_pages,
651 'disabled' => $disable['last'],
652 )
653 );
654
655 $pagination_links_class = 'pagination-links';
656 if ( ! empty( $infinite_scroll ) ) {
657 $pagination_links_class = ' hide-if-js';
658 }
659 $output .= "\n" . '<span class="' . esc_attr( $pagination_links_class ) . '">' . join( "\n", $page_links ) . '</span>';
660
661 if ( $total_pages ) {
662 $page_class = $total_pages < 2 ? ' one-page' : '';
663 } else {
664 $page_class = ' no-pages';
665 }
666 $this->_pagination = "<div class='tablenav-pages" . esc_attr( $page_class ) . "'>$output</div>";
667
668 echo $this->_pagination; // WPCS: XSS ok.
669 }
670
671 private function disabled_pages( $total_pages ) {
672 $current = $this->get_pagenum();
673 $disable = array(
674 'first' => false,
675 'last' => false,
676 'prev' => false,
677 'next' => false,
678 );
679
680 if ( $current == 1 ) {
681 $disable['first'] = true;
682 $disable['prev'] = true;
683 } elseif ( $current == 2 ) {
684 $disable['first'] = true;
685 }
686
687 if ( $current == $total_pages ) {
688 $disable['last'] = true;
689 $disable['next'] = true;
690 } elseif ( $current == $total_pages - 1 ) {
691 $disable['last'] = true;
692 }
693
694 return $disable;
695 }
696
697 private function link_label( $link ) {
698 $labels = array(
699 'first' => __( 'First page', 'formidable' ),
700 'last' => __( 'Last page', 'formidable' ),
701 'prev' => __( 'Previous page', 'formidable' ),
702 'next' => __( 'Next page', 'formidable' ),
703 );
704
705 return $labels[ $link ];
706 }
707
708 private function current_url() {
709 $current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
710
711 return remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
712 }
713
714 private function add_page_link( $atts ) {
715 if ( $atts['disabled'] ) {
716 $link = $this->add_disabled_link( $atts['arrow'] );
717 } else {
718 $link = $this->add_active_link( $atts );
719 }
720
721 return $link;
722 }
723
724 private function add_disabled_link( $label ) {
725 return '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">' . $label . '</span>';
726 }
727
728 private function add_active_link( $atts ) {
729 $url = esc_url( add_query_arg( 'paged', $atts['number'], $this->current_url() ) );
730 $label = $this->link_label( $atts['page'] );
731
732 return sprintf(
733 "<a class='button %s-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
734 $atts['page'],
735 $url,
736 $label,
737 $atts['arrow']
738 );
739 }
740
741 /**
742 * Get a list of sortable columns. The format is:
743 * 'internal-name' => 'orderby'
744 * or
745 * 'internal-name' => array( 'orderby', true )
746 *
747 * The second format will make the initial sorting order be descending
748 *
749 * @since 2.0.18
750 * @access protected
751 *
752 * @return array
753 */
754 protected function get_sortable_columns() {
755 return array();
756 }
757
758 /**
759 * Gets the name of the default primary column.
760 *
761 * @since 4.3.0
762 * @access protected
763 *
764 * @return string Name of the default primary column, in this case, an empty string.
765 */
766 protected function get_default_primary_column_name() {
767 $columns = $this->get_columns();
768 $column = '';
769
770 // We need a primary defined so responsive views show something,
771 // so let's fall back to the first non-checkbox column.
772 foreach ( $columns as $col => $column_name ) {
773 if ( 'cb' === $col ) {
774 continue;
775 }
776
777 $column = $col;
778 break;
779 }
780
781 return $column;
782 }
783
784 /**
785 * Gets the name of the primary column.
786 *
787 * @since 4.3.0
788 * @access protected
789 *
790 * @return string The name of the primary column.
791 */
792 protected function get_primary_column_name() {
793 $columns = $this->get_columns();
794 $default = $this->get_default_primary_column_name();
795
796 // If the primary column doesn't exist fall back to the
797 // first non-checkbox column.
798 if ( ! isset( $columns[ $default ] ) ) {
799 $default = self::get_default_primary_column_name();
800 }
801
802 /**
803 * Filter the name of the primary column for the current list table.
804 *
805 * @since 4.3.0
806 *
807 * @param string $default Column name default for the specific list table, e.g. 'name'.
808 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
809 */
810 $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
811
812 if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
813 $column = $default;
814 }
815
816 return $column;
817 }
818
819 /**
820 * Get a list of all, hidden and sortable columns, with filter applied
821 *
822 * @since 2.0.18
823 * @access protected
824 *
825 * @return array
826 */
827 protected function get_column_info() {
828 // $_column_headers is already set / cached
829 if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
830 // Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
831 // In 4.3, we added a fourth argument for primary column.
832 $column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
833 foreach ( $this->_column_headers as $key => $value ) {
834 $column_headers[ $key ] = $value;
835 }
836
837 return $column_headers;
838 }
839
840 $columns = get_column_headers( $this->screen );
841 $hidden = get_hidden_columns( $this->screen );
842
843 $sortable_columns = $this->get_sortable_columns();
844 /**
845 * Filter the list table sortable columns for a specific screen.
846 *
847 * The dynamic portion of the hook name, `$this->screen->id`, refers
848 * to the ID of the current screen, usually a string.
849 *
850 * @since 3.5.0
851 *
852 * @param array $sortable_columns An array of sortable columns.
853 */
854 $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
855
856 $sortable = array();
857 foreach ( $_sortable as $id => $data ) {
858 if ( empty( $data ) ) {
859 continue;
860 }
861
862 $data = (array) $data;
863 if ( ! isset( $data[1] ) ) {
864 $data[1] = false;
865 }
866
867 $sortable[ $id ] = $data;
868 }
869
870 $primary = $this->get_primary_column_name();
871
872 $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
873
874 return $this->_column_headers;
875 }
876
877 /**
878 * Return number of visible columns
879 *
880 * @since 2.0.18
881 * @access public
882 *
883 * @return int
884 */
885 public function get_column_count() {
886 list ( $columns, $hidden ) = $this->get_column_info();
887 $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
888
889 return count( $columns ) - count( $hidden );
890 }
891
892 /**
893 * Print column headers, accounting for hidden and sortable columns.
894 *
895 * @since 2.0.18
896 * @access public
897 *
898 * @staticvar int $cb_counter
899 *
900 * @param bool $with_id Whether to set the id attribute or not
901 */
902 public function print_column_headers( $with_id = true ) {
903 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
904
905 $current_url = set_url_scheme( 'http://' . FrmAppHelper::get_server_value( 'HTTP_HOST' ) . FrmAppHelper::get_server_value( 'REQUEST_URI' ) );
906 $current_url = remove_query_arg( 'paged', $current_url );
907
908 if ( isset( $_GET['orderby'] ) ) {
909 $current_orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
910 } else {
911 $current_orderby = '';
912 }
913
914 if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] ) {
915 $current_order = 'desc';
916 } else {
917 $current_order = 'asc';
918 }
919
920 if ( ! empty( $columns['cb'] ) ) {
921 static $cb_counter = 1;
922 $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All', 'formidable' ) . '</label>';
923 $columns['cb'] .= '<input id="cb-select-all-' . esc_attr( $cb_counter ) . '" type="checkbox" />';
924 $cb_counter ++;
925 }
926
927 foreach ( $columns as $column_key => $column_display_name ) {
928 $class = array( 'manage-column', "column-$column_key" );
929
930 if ( in_array( $column_key, $hidden ) ) {
931 $class[] = 'hidden';
932 }
933
934 if ( 'cb' == $column_key ) {
935 $class[] = 'check-column';
936 } elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
937 $class[] = 'num';
938 }
939
940 if ( $column_key === $primary ) {
941 $class[] = 'column-primary';
942 }
943
944 if ( isset( $sortable[ $column_key ] ) ) {
945 list( $orderby, $desc_first ) = $sortable[ $column_key ];
946
947 if ( $current_orderby == $orderby ) {
948 $order = 'asc' == $current_order ? 'desc' : 'asc';
949 $class[] = 'sorted';
950 $class[] = $current_order;
951 } else {
952 $order = $desc_first ? 'desc' : 'asc';
953 $class[] = 'sortable';
954 $class[] = $desc_first ? 'asc' : 'desc';
955 }
956
957 $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>';
958 }
959
960 $tag = ( 'cb' === $column_key ) ? 'td' : 'th';
961 $scope = ( 'th' === $tag ) ? 'scope="col"' : '';
962 $id = $with_id ? "id='" . esc_attr( $column_key ) . "'" : '';
963
964 if ( ! empty( $class ) ) {
965 $class = "class='" . esc_attr( join( ' ', $class ) ) . "'";
966 }
967
968 if ( ! $this->has_min_items() && ! $with_id ) {
969 // Hide the labels but show the border.
970 $column_display_name = '';
971 }
972 echo "<$tag $scope $id $class>$column_display_name</$tag>"; // WPCS: XSS ok.
973 }
974 }
975
976 /**
977 * Display the table
978 *
979 * @since 2.0.18
980 * @access public
981 */
982 public function display() {
983 $singular = $this->_args['singular'];
984
985 $this->display_tablenav( 'top' );
986 ?>
987 <table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
988 <?php if ( $this->has_min_items( 1 ) ) { ?>
989 <thead>
990 <tr>
991 <?php $this->print_column_headers(); ?>
992 </tr>
993 </thead>
994 <?php } ?>
995
996 <tbody id="the-list"<?php echo( $singular ? " data-wp-lists='list:" . esc_attr( $singular ) . "'" : '' ); // WPCS: XSS ok. ?>>
997 <?php $this->display_rows_or_placeholder(); ?>
998 </tbody>
999
1000 <?php if ( $this->has_min_items( 1 ) ) { ?>
1001 <tfoot>
1002 <tr>
1003 <?php $this->print_column_headers( false ); ?>
1004 </tr>
1005 </tfoot>
1006 <?php } ?>
1007 </table>
1008 <?php
1009 $this->display_tablenav( 'bottom' );
1010 }
1011
1012 /**
1013 * Get a list of CSS classes for the list table table tag.
1014 *
1015 * @since 2.0.18
1016 * @access protected
1017 *
1018 * @return array List of CSS classes for the table tag.
1019 */
1020 protected function get_table_classes() {
1021 return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
1022 }
1023
1024 /**
1025 * Generate the table navigation above or below the table
1026 *
1027 * @since 2.0.18
1028 * @access protected
1029 *
1030 * @param string $which
1031 */
1032 protected function display_tablenav( $which ) {
1033 if ( 'top' == $which ) {
1034 wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false );
1035 if ( ! $this->has_min_items( 1 ) ) {
1036 // Don't show bulk actions if no items.
1037 return;
1038 }
1039 } elseif ( ! $this->has_min_items() ) {
1040 // don't show the bulk actions when there aren't many rows.
1041 return;
1042 }
1043 ?>
1044 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1045
1046 <div class="alignleft actions bulkactions">
1047 <?php $this->bulk_actions( $which ); ?>
1048 </div>
1049 <?php
1050 $this->extra_tablenav( $which );
1051 $this->pagination( $which );
1052 ?>
1053
1054 <br class="clear"/>
1055 </div>
1056 <?php
1057 }
1058
1059 /**
1060 * Use this to exclude the footer labels and bulk items.
1061 * When close together, it feels like duplicates.
1062 *
1063 * @since 4.07
1064 */
1065 protected function has_min_items( $limit = 5 ) {
1066 return $this->has_items() && ( $this->total_items === false || $this->total_items >= $limit );
1067 }
1068
1069 /**
1070 * Extra controls to be displayed between bulk actions and pagination
1071 *
1072 * @since 2.0.18
1073 * @access protected
1074 *
1075 * @param string $which
1076 */
1077 protected function extra_tablenav( $which ) {
1078 }
1079
1080 /**
1081 * Generate the tbody element for the list table.
1082 *
1083 * @since 2.0.18
1084 * @access public
1085 */
1086 public function display_rows_or_placeholder() {
1087 if ( $this->has_items() ) {
1088 $this->display_rows();
1089 } else {
1090 echo '<tr class="no-items"><td class="colspanchange" colspan="' . esc_attr( $this->get_column_count() ) . '">';
1091 $this->no_items();
1092 echo '</td></tr>';
1093 }
1094 }
1095
1096 /**
1097 * Generates content for a single row of the table
1098 *
1099 * @since 2.0.18
1100 * @access public
1101 *
1102 * @param object $item The current item
1103 */
1104 public function single_row( $item ) {
1105 echo '<tr>';
1106 $this->single_row_columns( $item );
1107 echo '</tr>';
1108 }
1109
1110 /**
1111 * Generates the columns for a single row of the table
1112 *
1113 * @since 2.0.18
1114 * @access protected
1115 *
1116 * @param object $item The current item
1117 */
1118 protected function single_row_columns( $item ) {
1119 list( $columns, $hidden,, $primary ) = $this->get_column_info();
1120
1121 foreach ( $columns as $column_name => $column_display_name ) {
1122 $classes = "$column_name column-$column_name";
1123 if ( $primary === $column_name ) {
1124 $classes .= ' has-row-actions column-primary';
1125 }
1126
1127 if ( in_array( $column_name, $hidden ) ) {
1128 $classes .= ' hidden';
1129 }
1130
1131 // Comments column uses HTML in the display name with screen reader text.
1132 // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1133 $data = 'data-colname="' . esc_attr( $column_display_name ) . '"';
1134
1135 $attributes = 'class="' . esc_attr( $classes ) . '" ' . $data;
1136
1137 if ( 'cb' == $column_name ) {
1138 echo '<th scope="row" class="check-column"></th>';
1139 } elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1140 echo call_user_func( // WPCS: XSS ok.
1141 array( $this, '_column_' . $column_name ),
1142 $item,
1143 $classes,
1144 $data,
1145 $primary
1146 );
1147 } elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1148 echo "<td $attributes>"; // WPCS: XSS ok.
1149 echo call_user_func( array( $this, 'column_' . $column_name ), $item ); // WPCS: XSS ok.
1150 echo $this->handle_row_actions( $item, $column_name, $primary ); // WPCS: XSS ok.
1151 echo '</td>';
1152 } else {
1153 echo "<td $attributes>"; // WPCS: XSS ok.
1154 echo $this->handle_row_actions( $item, $column_name, $primary ); // WPCS: XSS ok.
1155 echo '</td>';
1156 }
1157 }
1158 }
1159
1160 /**
1161 * Generates and display row actions links for the list table.
1162 *
1163 * @since 4.3.0
1164 * @access protected
1165 *
1166 * @param object $item The item being acted upon.
1167 * @param string $column_name Current column name.
1168 * @param string $primary Primary column name.
1169 *
1170 * @return string The row actions output. In this case, an empty string.
1171 */
1172 protected function handle_row_actions( $item, $column_name, $primary ) {
1173 return $column_name == $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . esc_html__( 'Show more details', 'formidable' ) . '</span></button>' : '';
1174 }
1175
1176 /**
1177 * Handle an incoming ajax request (called from admin-ajax.php)
1178 *
1179 * @since 2.0.18
1180 * @access public
1181 */
1182 public function ajax_response() {
1183 $this->prepare_items();
1184
1185 ob_start();
1186 if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1187 $this->display_rows();
1188 } else {
1189 $this->display_rows_or_placeholder();
1190 }
1191
1192 $rows = ob_get_clean();
1193
1194 $response = array( 'rows' => $rows );
1195
1196 if ( isset( $this->_pagination_args['total_items'] ) ) {
1197 $response['total_items_i18n'] = sprintf(
1198 /* translators: %s: Number of items */
1199 _n( '%s item', '%s items', $this->_pagination_args['total_items'], 'formidable' ),
1200 number_format_i18n( $this->_pagination_args['total_items'] )
1201 );
1202 }
1203 if ( isset( $this->_pagination_args['total_pages'] ) ) {
1204 $response['total_pages'] = $this->_pagination_args['total_pages'];
1205 $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1206 }
1207
1208 die( wp_json_encode( $response ) );
1209 }
1210
1211 /**
1212 * Send required variables to JavaScript land
1213 *
1214 * @access public
1215 */
1216 public function _js_vars() {
1217 $args = array(
1218 'class' => get_class( $this ),
1219 'screen' => array(
1220 'id' => $this->screen->id,
1221 'base' => $this->screen->base,
1222 ),
1223 );
1224
1225 printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1226 }
1227 }
1228