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

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