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

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