PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 1.6.0
Tutor LMS – eLearning and online course solution v1.6.0
3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / Tutor_List_Table.php
tutor / classes Last commit date
Addons.php 6 years ago Admin.php 6 years ago Ajax.php 6 years ago Assets.php 6 years ago Course.php 6 years ago Course_Settings_Tabs.php 6 years ago Course_Widget.php 6 years ago Dashboard.php 6 years ago Email.php 6 years ago FormHandler.php 6 years ago Frontend.php 6 years ago Gutenberg.php 6 years ago Instructor.php 6 years ago Instructors_List.php 6 years ago Lesson.php 6 years ago Options.php 6 years ago Post_types.php 6 years ago Q_and_A.php 6 years ago Question_Answers_List.php 6 years ago Quiz.php 6 years ago Quiz_Attempts_List.php 6 years ago RestAPI.php 6 years ago Rewrite_Rules.php 6 years ago Shortcode.php 6 years ago Student.php 6 years ago Students_List.php 6 years ago Taxonomies.php 6 years ago Template.php 6 years ago Theme_Compatibility.php 6 years ago Tools.php 6 years ago Tutor.php 6 years ago TutorEDD.php 6 years ago Tutor_Base.php 6 years ago Tutor_List_Table.php 6 years ago Tutor_Setup.php 6 years ago Upgrader.php 6 years ago User.php 6 years ago Utils.php 6 years ago Video_Stream.php 6 years ago Withdraw.php 6 years ago Withdraw_Requests_List.php 6 years ago WooCommerce.php 6 years ago
Tutor_List_Table.php
1327 lines
1 <?php
2 /**
3 * Created by PhpStorm.
4 * User: mhshohel
5 * Date: 24/9/18
6 * Time: 12:03 PM
7 */
8
9
10 if ( ! defined( 'ABSPATH' ) )
11 exit;
12
13 /**
14 * Base class for displaying a list of items in an ajaxified HTML table.
15 *
16 * @since 3.1.0
17 * @access private
18 */
19 class Tutor_List_Table {
20
21 /**
22 * The current list of items.
23 *
24 * @since 3.1.0
25 * @var array
26 */
27 public $items;
28
29 /**
30 * Various information about the current table.
31 *
32 * @since 3.1.0
33 * @var array
34 */
35 protected $_args;
36
37 /**
38 * Various information needed for displaying the pagination.
39 *
40 * @since 3.1.0
41 * @var array
42 */
43 protected $_pagination_args = array();
44
45 /**
46 * The current screen.
47 *
48 * @since 3.1.0
49 * @var object
50 */
51 protected $screen;
52
53 /**
54 * Cached bulk actions.
55 *
56 * @since 3.1.0
57 * @var array
58 */
59 private $_actions;
60
61 /**
62 * Cached pagination output.
63 *
64 * @since 3.1.0
65 * @var string
66 */
67 private $_pagination;
68
69 /**
70 * The view switcher modes.
71 *
72 * @since 4.1.0
73 * @var array
74 */
75 protected $modes = array();
76
77 /**
78 * Stores the value returned by ->get_column_info().
79 *
80 * @since 4.1.0
81 * @var array
82 */
83 protected $_column_headers;
84
85 /**
86 * {@internal Missing Summary}
87 *
88 * @var array
89 */
90 protected $compat_fields = array( '_args', '_pagination_args', 'screen', '_actions', '_pagination' );
91
92 /**
93 * {@internal Missing Summary}
94 *
95 * @var array
96 */
97 protected $compat_methods = array( 'set_pagination_args', 'get_views', 'get_bulk_actions', 'bulk_actions',
98 'row_actions', 'months_dropdown', 'view_switcher', 'comments_bubble', 'get_items_per_page', 'pagination',
99 'get_sortable_columns', 'get_column_info', 'get_table_classes', 'display_tablenav', 'extra_tablenav',
100 'single_row_columns' );
101
102 /**
103 * Constructor.
104 *
105 * The child class should call this constructor from its own constructor to override
106 * the default $args.
107 *
108 * @since 3.1.0
109 *
110 * @param array|string $args {
111 * Array or string of arguments.
112 *
113 * @type string $plural Plural value used for labels and the objects being listed.
114 * This affects things such as CSS class-names and nonces used
115 * in the list table, e.g. 'posts'. Default empty.
116 * @type string $singular Singular label for an object being listed, e.g. 'post'.
117 * Default empty
118 * @type bool $ajax Whether the list table supports Ajax. This includes loading
119 * and sorting data, for example. If true, the class will call
120 * the _js_vars() method in the footer to provide variables
121 * to any scripts handling Ajax events. Default false.
122 * @type string $screen String containing the hook name used to determine the current
123 * screen. If left null, the current screen will be automatically set.
124 * Default null.
125 * }
126 */
127 public function __construct( $args = array() ) {
128 $args = wp_parse_args( $args, array(
129 'plural' => '',
130 'singular' => '',
131 'ajax' => false,
132 'screen' => null,
133 ) );
134
135 $this->screen = convert_to_screen( $args['screen'] );
136
137 add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
138
139 if ( !$args['plural'] )
140 $args['plural'] = $this->screen->base;
141
142 $args['plural'] = sanitize_key( $args['plural'] );
143 $args['singular'] = sanitize_key( $args['singular'] );
144
145 $this->_args = $args;
146
147 if ( $args['ajax'] ) {
148 // wp_enqueue_script( 'list-table' );
149 add_action( 'admin_footer', array( $this, '_js_vars' ) );
150 }
151
152 if ( empty( $this->modes ) ) {
153 $this->modes = array(
154 'list' => __( 'List View' ),
155 'excerpt' => __( 'Excerpt View' )
156 );
157 }
158 }
159
160 /**
161 * Make private properties readable for backward compatibility.
162 *
163 * @since 4.0.0
164 *
165 * @param string $name Property to get.
166 * @return mixed Property.
167 */
168 public function __get( $name ) {
169 if ( in_array( $name, $this->compat_fields ) ) {
170 return $this->$name;
171 }
172 }
173
174 /**
175 * Make private properties settable for backward compatibility.
176 *
177 * @since 4.0.0
178 *
179 * @param string $name Property to check if set.
180 * @param mixed $value Property value.
181 * @return mixed Newly-set property.
182 */
183 public function __set( $name, $value ) {
184 if ( in_array( $name, $this->compat_fields ) ) {
185 return $this->$name = $value;
186 }
187 }
188
189 /**
190 * Make private properties checkable for backward compatibility.
191 *
192 * @since 4.0.0
193 *
194 * @param string $name Property to check if set.
195 * @return bool Whether the property is set.
196 */
197 public function __isset( $name ) {
198 if ( in_array( $name, $this->compat_fields ) ) {
199 return isset( $this->$name );
200 }
201 }
202
203 /**
204 * Make private properties un-settable for backward compatibility.
205 *
206 * @since 4.0.0
207 *
208 * @param string $name Property to unset.
209 */
210 public function __unset( $name ) {
211 if ( in_array( $name, $this->compat_fields ) ) {
212 unset( $this->$name );
213 }
214 }
215
216 /**
217 * Make private/protected methods readable for backward compatibility.
218 *
219 * @since 4.0.0
220 *
221 * @param callable $name Method to call.
222 * @param array $arguments Arguments to pass when calling.
223 * @return mixed|bool Return value of the callback, false otherwise.
224 */
225 public function __call( $name, $arguments ) {
226 if ( in_array( $name, $this->compat_methods ) ) {
227 return call_user_func_array( array( $this, $name ), $arguments );
228 }
229 return false;
230 }
231
232 /**
233 * Checks the current user's permissions
234 *
235 * @since 3.1.0
236 * @abstract
237 */
238 public function ajax_user_can() {
239 die( 'function Tutor_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
240 }
241
242 /**
243 * Prepares the list of items for displaying.
244 * @uses Tutor_List_Table::set_pagination_args()
245 *
246 * @since 3.1.0
247 * @abstract
248 */
249 public function prepare_items() {
250 die( 'function Tutor_List_Table::prepare_items() must be over-ridden in a sub-class.' );
251 }
252
253 /**
254 * An internal method that sets all the necessary pagination arguments
255 *
256 * @since 3.1.0
257 *
258 * @param array|string $args Array or string of arguments with information about the pagination.
259 */
260 protected function set_pagination_args( $args ) {
261 $args = wp_parse_args( $args, array(
262 'total_items' => 0,
263 'total_pages' => 0,
264 'per_page' => 0,
265 ) );
266
267 if ( !$args['total_pages'] && $args['per_page'] > 0 )
268 $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
269
270 // Redirect if page number is invalid and headers are not already sent.
271 if ( ! headers_sent() && ! wp_doing_ajax() && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
272 wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
273 exit;
274 }
275
276 $this->_pagination_args = $args;
277 }
278
279 /**
280 * Access the pagination args.
281 *
282 * @since 3.1.0
283 *
284 * @param string $key Pagination argument to retrieve. Common values include 'total_items',
285 * 'total_pages', 'per_page', or 'infinite_scroll'.
286 * @return int Number of items that correspond to the given pagination argument.
287 */
288 public function get_pagination_arg( $key ) {
289 if ( 'page' === $key ) {
290 return $this->get_pagenum();
291 }
292
293 if ( isset( $this->_pagination_args[$key] ) ) {
294 return $this->_pagination_args[$key];
295 }
296 }
297
298 /**
299 * Whether the table has items to display or not
300 *
301 * @since 3.1.0
302 *
303 * @return bool
304 */
305 public function has_items() {
306 return !empty( $this->items );
307 }
308
309 /**
310 * Message to be displayed when there are no items
311 *
312 * @since 3.1.0
313 */
314 public function no_items() {
315 _e( 'No items found.' );
316 }
317
318 /**
319 * Displays the search box.
320 *
321 * @since 3.1.0
322 *
323 * @param string $text The 'submit' button label.
324 * @param string $input_id ID attribute value for the search input field.
325 */
326 public function search_box( $text, $input_id ) {
327 if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
328 return;
329
330 $input_id = $input_id . '-search-input';
331
332 if ( ! empty( $_REQUEST['orderby'] ) )
333 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
334 if ( ! empty( $_REQUEST['order'] ) )
335 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
336 if ( ! empty( $_REQUEST['post_mime_type'] ) )
337 echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
338 if ( ! empty( $_REQUEST['detached'] ) )
339 echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
340 ?>
341 <p class="search-box">
342 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label>
343 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
344 <?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
345 </p>
346 <?php
347 }
348
349 /**
350 * Get an associative array ( id => link ) with the list
351 * of views available on this table.
352 *
353 * @since 3.1.0
354 *
355 * @return array
356 */
357 protected function get_views() {
358 return array();
359 }
360
361 /**
362 * Display the list of views available on this table.
363 *
364 * @since 3.1.0
365 */
366 public function views() {
367 $views = $this->get_views();
368 /**
369 * Filters the list of available list table views.
370 *
371 * The dynamic portion of the hook name, `$this->screen->id`, refers
372 * to the ID of the current screen, usually a string.
373 *
374 * @since 3.5.0
375 *
376 * @param array $views An array of available list table views.
377 */
378 $views = apply_filters( "views_{$this->screen->id}", $views );
379
380 if ( empty( $views ) )
381 return;
382
383 $this->screen->render_screen_reader_content( 'heading_views' );
384
385 echo "<ul class='subsubsub'>\n";
386 foreach ( $views as $class => $view ) {
387 $views[ $class ] = "\t<li class='$class'>$view";
388 }
389 echo implode( " |</li>\n", $views ) . "</li>\n";
390 echo "</ul>";
391 }
392
393 /**
394 * Get an associative array ( option_name => option_title ) with the list
395 * of bulk actions available on this table.
396 *
397 * @since 3.1.0
398 *
399 * @return array
400 */
401 protected function get_bulk_actions() {
402 return array();
403 }
404
405 /**
406 * Display the bulk actions dropdown.
407 *
408 * @since 3.1.0
409 *
410 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
411 * This is designated as optional for backward compatibility.
412 */
413 protected function bulk_actions( $which = '' ) {
414 if ( is_null( $this->_actions ) ) {
415 $this->_actions = $this->get_bulk_actions();
416 /**
417 * Filters the list table Bulk Actions drop-down.
418 *
419 * The dynamic portion of the hook name, `$this->screen->id`, refers
420 * to the ID of the current screen, usually a string.
421 *
422 * This filter can currently only be used to remove bulk actions.
423 *
424 * @since 3.5.0
425 *
426 * @param array $actions An array of the available bulk actions.
427 */
428 $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
429 $two = '';
430 } else {
431 $two = '2';
432 }
433
434 if ( empty( $this->_actions ) )
435 return;
436
437 echo '<label for="bulk-action-selector-' . esc_attr( $which ) . '" class="screen-reader-text">' . __( 'Select bulk action' ) . '</label>';
438 echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
439 echo '<option value="-1">' . __( 'Bulk Actions' ) . "</option>\n";
440
441 foreach ( $this->_actions as $name => $title ) {
442 $class = 'edit' === $name ? ' class="hide-if-no-js"' : '';
443
444 echo "\t" . '<option value="' . $name . '"' . $class . '>' . $title . "</option>\n";
445 }
446
447 echo "</select>\n";
448
449 submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two" ) );
450 echo "\n";
451 }
452
453 /**
454 * Get the current action selected from the bulk actions dropdown.
455 *
456 * @since 3.1.0
457 *
458 * @return string|false The action name or False if no action was selected
459 */
460 public function current_action() {
461 if ( isset( $_REQUEST['filter_action'] ) && ! empty( $_REQUEST['filter_action'] ) )
462 return false;
463
464 if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
465 return $_REQUEST['action'];
466
467 if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
468 return $_REQUEST['action2'];
469
470 return false;
471 }
472
473 /**
474 * Generate row actions div
475 *
476 * @since 3.1.0
477 *
478 * @param array $actions The list of actions
479 * @param bool $always_visible Whether the actions should be always visible
480 * @return string
481 */
482 protected function row_actions( $actions, $always_visible = false ) {
483 $action_count = count( $actions );
484 $i = 0;
485
486 if ( !$action_count )
487 return '';
488
489 $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
490 foreach ( $actions as $action => $link ) {
491 ++$i;
492 ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
493 $out .= "<span class='$action'>$link$sep</span>";
494 }
495 $out .= '</div>';
496
497 $out .= '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>';
498
499 return $out;
500 }
501
502 /**
503 * Display a monthly dropdown for filtering items
504 *
505 * @since 3.1.0
506 *
507 * @global wpdb $wpdb
508 * @global WP_Locale $wp_locale
509 *
510 * @param string $post_type
511 */
512 protected function months_dropdown( $post_type ) {
513 global $wpdb, $wp_locale;
514
515 /**
516 * Filters whether to remove the 'Months' drop-down from the post list table.
517 *
518 * @since 4.2.0
519 *
520 * @param bool $disable Whether to disable the drop-down. Default false.
521 * @param string $post_type The post type.
522 */
523 if ( apply_filters( 'disable_months_dropdown', false, $post_type ) ) {
524 return;
525 }
526
527 $extra_checks = "AND post_status != 'auto-draft'";
528 if ( ! isset( $_GET['post_status'] ) || 'trash' !== $_GET['post_status'] ) {
529 $extra_checks .= " AND post_status != 'trash'";
530 } elseif ( isset( $_GET['post_status'] ) ) {
531 $extra_checks = $wpdb->prepare( ' AND post_status = %s', $_GET['post_status'] );
532 }
533
534 $months = $wpdb->get_results( $wpdb->prepare( "
535 SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
536 FROM $wpdb->posts
537 WHERE post_type = %s
538 $extra_checks
539 ORDER BY post_date DESC
540 ", $post_type ) );
541
542 /**
543 * Filters the 'Months' drop-down results.
544 *
545 * @since 3.7.0
546 *
547 * @param object $months The months drop-down query results.
548 * @param string $post_type The post type.
549 */
550 $months = apply_filters( 'months_dropdown_results', $months, $post_type );
551
552 $month_count = count( $months );
553
554 if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
555 return;
556
557 $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
558 ?>
559 <label for="filter-by-date" class="screen-reader-text"><?php _e( 'Filter by date' ); ?></label>
560 <select name="m" id="filter-by-date">
561 <option<?php selected( $m, 0 ); ?> value="0"><?php _e( 'All dates' ); ?></option>
562 <?php
563 foreach ( $months as $arc_row ) {
564 if ( 0 == $arc_row->year )
565 continue;
566
567 $month = zeroise( $arc_row->month, 2 );
568 $year = $arc_row->year;
569
570 printf( "<option %s value='%s'>%s</option>\n",
571 selected( $m, $year . $month, false ),
572 esc_attr( $arc_row->year . $month ),
573 /* translators: 1: month name, 2: 4-digit year */
574 sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
575 );
576 }
577 ?>
578 </select>
579 <?php
580 }
581
582 /**
583 * Display a view switcher
584 *
585 * @since 3.1.0
586 *
587 * @param string $current_mode
588 */
589 protected function view_switcher( $current_mode ) {
590 ?>
591 <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
592 <div class="view-switch">
593 <?php
594 foreach ( $this->modes as $mode => $title ) {
595 $classes = array( 'view-' . $mode );
596 if ( $current_mode === $mode )
597 $classes[] = 'current';
598 printf(
599 "<a href='%s' class='%s' id='view-switch-$mode'><span class='screen-reader-text'>%s</span></a>\n",
600 esc_url( add_query_arg( 'mode', $mode ) ),
601 implode( ' ', $classes ),
602 $title
603 );
604 }
605 ?>
606 </div>
607 <?php
608 }
609
610 /**
611 * Display a comment count bubble
612 *
613 * @since 3.1.0
614 *
615 * @param int $post_id The post ID.
616 * @param int $pending_comments Number of pending comments.
617 */
618 protected function comments_bubble( $post_id, $pending_comments ) {
619 $approved_comments = get_comments_number();
620
621 $approved_comments_number = number_format_i18n( $approved_comments );
622 $pending_comments_number = number_format_i18n( $pending_comments );
623
624 $approved_only_phrase = sprintf( _n( '%s comment', '%s comments', $approved_comments ), $approved_comments_number );
625 $approved_phrase = sprintf( _n( '%s approved comment', '%s approved comments', $approved_comments ), $approved_comments_number );
626 $pending_phrase = sprintf( _n( '%s pending comment', '%s pending comments', $pending_comments ), $pending_comments_number );
627
628 // No comments at all.
629 if ( ! $approved_comments && ! $pending_comments ) {
630 printf( '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">%s</span>',
631 __( 'No comments' )
632 );
633 // Approved comments have different display depending on some conditions.
634 } elseif ( $approved_comments ) {
635 printf( '<a href="%s" class="post-com-count post-com-count-approved"><span class="comment-count-approved" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',
636 esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'approved' ), admin_url( 'edit-comments.php' ) ) ),
637 $approved_comments_number,
638 $pending_comments ? $approved_phrase : $approved_only_phrase
639 );
640 } else {
641 printf( '<span class="post-com-count post-com-count-no-comments"><span class="comment-count comment-count-no-comments" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',
642 $approved_comments_number,
643 $pending_comments ? __( 'No approved comments' ) : __( 'No comments' )
644 );
645 }
646
647 if ( $pending_comments ) {
648 printf( '<a href="%s" class="post-com-count post-com-count-pending"><span class="comment-count-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',
649 esc_url( add_query_arg( array( 'p' => $post_id, 'comment_status' => 'moderated' ), admin_url( 'edit-comments.php' ) ) ),
650 $pending_comments_number,
651 $pending_phrase
652 );
653 } else {
654 printf( '<span class="post-com-count post-com-count-pending post-com-count-no-pending"><span class="comment-count comment-count-no-pending" aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></span>',
655 $pending_comments_number,
656 $approved_comments ? __( 'No pending comments' ) : __( 'No comments' )
657 );
658 }
659 }
660
661 /**
662 * Get the current page number
663 *
664 * @since 3.1.0
665 *
666 * @return int
667 */
668 public function get_pagenum() {
669 $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
670
671 if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
672 $pagenum = $this->_pagination_args['total_pages'];
673
674 return max( 1, $pagenum );
675 }
676
677 /**
678 * Get number of items to display on a single page
679 *
680 * @since 3.1.0
681 *
682 * @param string $option
683 * @param int $default
684 * @return int
685 */
686 protected function get_items_per_page( $option, $default = 20 ) {
687 $per_page = (int) get_user_option( $option );
688 if ( empty( $per_page ) || $per_page < 1 )
689 $per_page = $default;
690
691 /**
692 * Filters the number of items to be displayed on each page of the list table.
693 *
694 * The dynamic hook name, $option, refers to the `per_page` option depending
695 * on the type of list table in use. Possible values include: 'edit_comments_per_page',
696 * 'sites_network_per_page', 'site_themes_network_per_page', 'themes_network_per_page',
697 * 'users_network_per_page', 'edit_post_per_page', 'edit_page_per_page',
698 * 'edit_{$post_type}_per_page', etc.
699 *
700 * @since 2.9.0
701 *
702 * @param int $per_page Number of items to be displayed. Default 20.
703 */
704 return (int) apply_filters( "{$option}", $per_page );
705 }
706
707 /**
708 * Display the pagination.
709 *
710 * @since 3.1.0
711 *
712 * @param string $which
713 */
714 protected function pagination( $which ) {
715 if ( empty( $this->_pagination_args ) ) {
716 return;
717 }
718
719 $total_items = $this->_pagination_args['total_items'];
720 $total_pages = $this->_pagination_args['total_pages'];
721 $infinite_scroll = false;
722 if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
723 $infinite_scroll = $this->_pagination_args['infinite_scroll'];
724 }
725
726 if ( 'top' === $which && $total_pages > 1 ) {
727 $this->screen->render_screen_reader_content( 'heading_pagination' );
728 }
729
730 $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
731
732 $current = $this->get_pagenum();
733 $removable_query_args = wp_removable_query_args();
734
735 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
736
737 $current_url = remove_query_arg( $removable_query_args, $current_url );
738
739 $page_links = array();
740
741 $total_pages_before = '<span class="paging-input">';
742 $total_pages_after = '</span></span>';
743
744 $disable_first = $disable_last = $disable_prev = $disable_next = false;
745
746 if ( $current == 1 ) {
747 $disable_first = true;
748 $disable_prev = true;
749 }
750 if ( $current == 2 ) {
751 $disable_first = true;
752 }
753 if ( $current == $total_pages ) {
754 $disable_last = true;
755 $disable_next = true;
756 }
757 if ( $current == $total_pages - 1 ) {
758 $disable_last = true;
759 }
760
761 if ( $disable_first ) {
762 $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&laquo;</span>';
763 } else {
764 $page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
765 esc_url( remove_query_arg( 'paged', $current_url ) ),
766 __( 'First page' ),
767 '&laquo;'
768 );
769 }
770
771 if ( $disable_prev ) {
772 $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&lsaquo;</span>';
773 } else {
774 $page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
775 esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
776 __( 'Previous page' ),
777 '&lsaquo;'
778 );
779 }
780
781 if ( 'bottom' === $which ) {
782 $html_current_page = $current;
783 $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
784 } else {
785 $html_current_page = sprintf( "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
786 '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
787 $current,
788 strlen( $total_pages )
789 );
790 }
791 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
792 $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
793
794 if ( $disable_next ) {
795 $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&rsaquo;</span>';
796 } else {
797 $page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
798 esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
799 __( 'Next page' ),
800 '&rsaquo;'
801 );
802 }
803
804 if ( $disable_last ) {
805 $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&raquo;</span>';
806 } else {
807 $page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
808 esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
809 __( 'Last page' ),
810 '&raquo;'
811 );
812 }
813
814 $pagination_links_class = 'pagination-links';
815 if ( ! empty( $infinite_scroll ) ) {
816 $pagination_links_class .= ' hide-if-js';
817 }
818 $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
819
820 if ( $total_pages ) {
821 $page_class = $total_pages < 2 ? ' one-page' : '';
822 } else {
823 $page_class = ' no-pages';
824 }
825 $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
826
827 echo $this->_pagination;
828 }
829
830 /**
831 * Get a list of columns. The format is:
832 * 'internal-name' => 'Title'
833 *
834 * @since 3.1.0
835 * @abstract
836 *
837 * @return array
838 */
839 public function get_columns() {
840 die( 'function Tutor_List_Table::get_columns() must be over-ridden in a sub-class.' );
841 }
842
843 /**
844 * Get a list of sortable columns. The format is:
845 * 'internal-name' => 'orderby'
846 * or
847 * 'internal-name' => array( 'orderby', true )
848 *
849 * The second format will make the initial sorting order be descending
850 *
851 * @since 3.1.0
852 *
853 * @return array
854 */
855 protected function get_sortable_columns() {
856 return array();
857 }
858
859 /**
860 * Gets the name of the default primary column.
861 *
862 * @since 4.3.0
863 *
864 * @return string Name of the default primary column, in this case, an empty string.
865 */
866 protected function get_default_primary_column_name() {
867 $columns = $this->get_columns();
868 $column = '';
869
870 if ( empty( $columns ) ) {
871 return $column;
872 }
873
874 // We need a primary defined so responsive views show something,
875 // so let's fall back to the first non-checkbox column.
876 foreach ( $columns as $col => $column_name ) {
877 if ( 'cb' === $col ) {
878 continue;
879 }
880
881 $column = $col;
882 break;
883 }
884
885 return $column;
886 }
887
888 /**
889 * Public wrapper for Tutor_List_Table::get_default_primary_column_name().
890 *
891 * @since 4.4.0
892 *
893 * @return string Name of the default primary column.
894 */
895 public function get_primary_column() {
896 return $this->get_primary_column_name();
897 }
898
899 /**
900 * Gets the name of the primary column.
901 *
902 * @since 4.3.0
903 *
904 * @return string The name of the primary column.
905 */
906 protected function get_primary_column_name() {
907 $columns = get_column_headers( $this->screen );
908 $default = $this->get_default_primary_column_name();
909
910 // If the primary column doesn't exist fall back to the
911 // first non-checkbox column.
912 if ( ! isset( $columns[ $default ] ) ) {
913 $default = Tutor_List_Table::get_default_primary_column_name();
914 }
915
916 /**
917 * Filters the name of the primary column for the current list table.
918 *
919 * @since 4.3.0
920 *
921 * @param string $default Column name default for the specific list table, e.g. 'name'.
922 * @param string $context Screen ID for specific list table, e.g. 'plugins'.
923 */
924 $column = apply_filters( 'list_table_primary_column', $default, $this->screen->id );
925
926 if ( empty( $column ) || ! isset( $columns[ $column ] ) ) {
927 $column = $default;
928 }
929
930 return $column;
931 }
932
933 /**
934 * Get a list of all, hidden and sortable columns, with filter applied
935 *
936 * @since 3.1.0
937 *
938 * @return array
939 */
940 protected function get_column_info() {
941 // $_column_headers is already set / cached
942 if ( isset( $this->_column_headers ) && is_array( $this->_column_headers ) ) {
943 // Back-compat for list tables that have been manually setting $_column_headers for horse reasons.
944 // In 4.3, we added a fourth argument for primary column.
945 $column_headers = array( array(), array(), array(), $this->get_primary_column_name() );
946 foreach ( $this->_column_headers as $key => $value ) {
947 $column_headers[ $key ] = $value;
948 }
949
950 return $column_headers;
951 }
952
953 $columns = get_column_headers( $this->screen );
954 $hidden = get_hidden_columns( $this->screen );
955
956 $sortable_columns = $this->get_sortable_columns();
957 /**
958 * Filters the list table sortable columns for a specific screen.
959 *
960 * The dynamic portion of the hook name, `$this->screen->id`, refers
961 * to the ID of the current screen, usually a string.
962 *
963 * @since 3.5.0
964 *
965 * @param array $sortable_columns An array of sortable columns.
966 */
967 $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
968
969 $sortable = array();
970 foreach ( $_sortable as $id => $data ) {
971 if ( empty( $data ) )
972 continue;
973
974 $data = (array) $data;
975 if ( !isset( $data[1] ) )
976 $data[1] = false;
977
978 $sortable[$id] = $data;
979 }
980
981 $primary = $this->get_primary_column_name();
982 $this->_column_headers = array( $columns, $hidden, $sortable, $primary );
983
984 return $this->_column_headers;
985 }
986
987 /**
988 * Return number of visible columns
989 *
990 * @since 3.1.0
991 *
992 * @return int
993 */
994 public function get_column_count() {
995 list ( $columns, $hidden ) = $this->get_column_info();
996 $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
997 return count( $columns ) - count( $hidden );
998 }
999
1000 /**
1001 * Print column headers, accounting for hidden and sortable columns.
1002 *
1003 * @since 3.1.0
1004 *
1005 * @staticvar int $cb_counter
1006 *
1007 * @param bool $with_id Whether to set the id attribute or not
1008 */
1009 public function print_column_headers( $with_id = true ) {
1010 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1011
1012 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1013 $current_url = remove_query_arg( 'paged', $current_url );
1014
1015 if ( isset( $_GET['orderby'] ) ) {
1016 $current_orderby = $_GET['orderby'];
1017 } else {
1018 $current_orderby = '';
1019 }
1020
1021 if ( isset( $_GET['order'] ) && 'desc' === $_GET['order'] ) {
1022 $current_order = 'desc';
1023 } else {
1024 $current_order = 'asc';
1025 }
1026
1027 if ( ! empty( $columns['cb'] ) ) {
1028 static $cb_counter = 1;
1029 $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
1030 . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
1031 $cb_counter++;
1032 }
1033
1034 foreach ( $columns as $column_key => $column_display_name ) {
1035 $class = array( 'manage-column', "column-$column_key" );
1036
1037 if ( in_array( $column_key, $hidden ) ) {
1038 $class[] = 'hidden';
1039 }
1040
1041 if ( 'cb' === $column_key )
1042 $class[] = 'check-column';
1043 elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
1044 $class[] = 'num';
1045
1046 if ( $column_key === $primary ) {
1047 $class[] = 'column-primary';
1048 }
1049
1050 if ( isset( $sortable[$column_key] ) ) {
1051 list( $orderby, $desc_first ) = $sortable[$column_key];
1052
1053 if ( $current_orderby === $orderby ) {
1054 $order = 'asc' === $current_order ? 'desc' : 'asc';
1055 $class[] = 'sorted';
1056 $class[] = $current_order;
1057 } else {
1058 $order = $desc_first ? 'desc' : 'asc';
1059 $class[] = 'sortable';
1060 $class[] = $desc_first ? 'asc' : 'desc';
1061 }
1062
1063 $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
1064 }
1065
1066 $tag = ( 'cb' === $column_key ) ? 'td' : 'th';
1067 $scope = ( 'th' === $tag ) ? 'scope="col"' : '';
1068 $id = $with_id ? "id='$column_key'" : '';
1069
1070 if ( !empty( $class ) )
1071 $class = "class='" . join( ' ', $class ) . "'";
1072
1073 echo "<$tag $scope $id $class>$column_display_name</$tag>";
1074 }
1075 }
1076
1077 /**
1078 * Display the table
1079 *
1080 * @since 3.1.0
1081 */
1082 public function display() {
1083 $singular = $this->_args['singular'];
1084
1085 $this->display_tablenav( 'top' );
1086
1087 $this->screen->render_screen_reader_content( 'heading_list' );
1088 ?>
1089 <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
1090 <thead>
1091 <tr>
1092 <?php $this->print_column_headers(); ?>
1093 </tr>
1094 </thead>
1095
1096 <tbody id="the-list"<?php
1097 if ( $singular ) {
1098 echo " data-wp-lists='list:$singular'";
1099 } ?>>
1100 <?php $this->display_rows_or_placeholder(); ?>
1101 </tbody>
1102
1103 <tfoot>
1104 <tr>
1105 <?php $this->print_column_headers( false ); ?>
1106 </tr>
1107 </tfoot>
1108
1109 </table>
1110 <?php
1111 $this->display_tablenav( 'bottom' );
1112 }
1113
1114 /**
1115 * Get a list of CSS classes for the Tutor_List_Table table tag.
1116 *
1117 * @since 3.1.0
1118 *
1119 * @return array List of CSS classes for the table tag.
1120 */
1121 protected function get_table_classes() {
1122 return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
1123 }
1124
1125 /**
1126 * Generate the table navigation above or below the table
1127 *
1128 * @since 3.1.0
1129 * @param string $which
1130 */
1131 protected function display_tablenav( $which ) {
1132 if ( 'top' === $which ) {
1133 wp_nonce_field( 'bulk-' . $this->_args['plural'] );
1134 }
1135 ?>
1136 <div class="tablenav <?php echo esc_attr( $which ); ?>">
1137
1138 <?php if ( $this->has_items() ): ?>
1139 <div class="alignleft actions bulkactions">
1140 <?php $this->bulk_actions( $which ); ?>
1141 </div>
1142 <?php endif;
1143 $this->extra_tablenav( $which );
1144 $this->pagination( $which );
1145 ?>
1146
1147 <br class="clear" />
1148 </div>
1149 <?php
1150 }
1151
1152 /**
1153 * Extra controls to be displayed between bulk actions and pagination
1154 *
1155 * @since 3.1.0
1156 *
1157 * @param string $which
1158 */
1159 protected function extra_tablenav( $which ) {}
1160
1161 /**
1162 * Generate the tbody element for the list table.
1163 *
1164 * @since 3.1.0
1165 */
1166 public function display_rows_or_placeholder() {
1167 if ( $this->has_items() ) {
1168 $this->display_rows();
1169 } else {
1170 echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
1171 $this->no_items();
1172 echo '</td></tr>';
1173 }
1174 }
1175
1176 /**
1177 * Generate the table rows
1178 *
1179 * @since 3.1.0
1180 */
1181 public function display_rows() {
1182 foreach ( $this->items as $item )
1183 $this->single_row( $item );
1184 }
1185
1186 /**
1187 * Generates content for a single row of the table
1188 *
1189 * @since 3.1.0
1190 *
1191 * @param object $item The current item
1192 */
1193 public function single_row( $item ) {
1194 echo '<tr>';
1195 $this->single_row_columns( $item );
1196 echo '</tr>';
1197 }
1198
1199 /**
1200 *
1201 * @param object $item
1202 * @param string $column_name
1203 */
1204 protected function column_default( $item, $column_name ) {}
1205
1206 /**
1207 *
1208 * @param object $item
1209 */
1210 protected function column_cb( $item ) {}
1211
1212 /**
1213 * Generates the columns for a single row of the table
1214 *
1215 * @since 3.1.0
1216 *
1217 * @param object $item The current item
1218 */
1219 protected function single_row_columns( $item ) {
1220 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
1221
1222 foreach ( $columns as $column_name => $column_display_name ) {
1223 $classes = "$column_name column-$column_name";
1224 if ( $primary === $column_name ) {
1225 $classes .= ' has-row-actions column-primary';
1226 }
1227
1228 if ( in_array( $column_name, $hidden ) ) {
1229 $classes .= ' hidden';
1230 }
1231
1232 // Comments column uses HTML in the display name with screen reader text.
1233 // Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
1234 $data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';
1235
1236 $attributes = "class='$classes' $data";
1237
1238 if ( 'cb' === $column_name ) {
1239 echo '<th scope="row" class="check-column">';
1240 echo $this->column_cb( $item );
1241 echo '</th>';
1242 } elseif ( method_exists( $this, '_column_' . $column_name ) ) {
1243 echo call_user_func(
1244 array( $this, '_column_' . $column_name ),
1245 $item,
1246 $classes,
1247 $data,
1248 $primary
1249 );
1250 } elseif ( method_exists( $this, 'column_' . $column_name ) ) {
1251 echo "<td $attributes>";
1252 echo call_user_func( array( $this, 'column_' . $column_name ), $item );
1253 echo $this->handle_row_actions( $item, $column_name, $primary );
1254 echo "</td>";
1255 } else {
1256 echo "<td $attributes>";
1257 echo $this->column_default( $item, $column_name );
1258 echo $this->handle_row_actions( $item, $column_name, $primary );
1259 echo "</td>";
1260 }
1261 }
1262 }
1263
1264 /**
1265 * Generates and display row actions links for the list table.
1266 *
1267 * @since 4.3.0
1268 *
1269 * @param object $item The item being acted upon.
1270 * @param string $column_name Current column name.
1271 * @param string $primary Primary column name.
1272 * @return string The row actions HTML, or an empty string if the current column is the primary column.
1273 */
1274 protected function handle_row_actions( $item, $column_name, $primary ) {
1275 return $column_name === $primary ? '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details' ) . '</span></button>' : '';
1276 }
1277
1278 /**
1279 * Handle an incoming ajax request (called from admin-ajax.php)
1280 *
1281 * @since 3.1.0
1282 */
1283 public function ajax_response() {
1284 $this->prepare_items();
1285
1286 ob_start();
1287 if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
1288 $this->display_rows();
1289 } else {
1290 $this->display_rows_or_placeholder();
1291 }
1292
1293 $rows = ob_get_clean();
1294
1295 $response = array( 'rows' => $rows );
1296
1297 if ( isset( $this->_pagination_args['total_items'] ) ) {
1298 $response['total_items_i18n'] = sprintf(
1299 _n( '%s item', '%s items', $this->_pagination_args['total_items'] ),
1300 number_format_i18n( $this->_pagination_args['total_items'] )
1301 );
1302 }
1303 if ( isset( $this->_pagination_args['total_pages'] ) ) {
1304 $response['total_pages'] = $this->_pagination_args['total_pages'];
1305 $response['total_pages_i18n'] = number_format_i18n( $this->_pagination_args['total_pages'] );
1306 }
1307
1308 die( wp_json_encode( $response ) );
1309 }
1310
1311 /**
1312 * Send required variables to JavaScript land
1313 *
1314 */
1315 public function _js_vars() {
1316 $args = array(
1317 'class' => get_class( $this ),
1318 'screen' => array(
1319 'id' => $this->screen->id,
1320 'base' => $this->screen->base,
1321 )
1322 );
1323
1324 printf( "<script type='text/javascript'>list_args = %s;</script>\n", wp_json_encode( $args ) );
1325 }
1326 }
1327