1: <?php
2: if ( ! defined( 'ABSPATH' ) ) exit;
3:
4: 5: 6: 7: 8: 9: 10:
11: class Sensei_Analysis_Course_List_Table extends WooThemes_Sensei_List_Table {
12: public $user_id;
13: public $course_id;
14: public $total_lessons;
15: public $user_ids;
16: public $view = 'lesson';
17: public $page_slug = 'sensei_analysis';
18:
19: 20: 21: 22:
23: public function __construct ( $course_id = 0, $user_id = 0 ) {
24: $this->course_id = intval( $course_id );
25: $this->user_id = intval( $user_id );
26:
27: if( isset( $_GET['view'] ) && in_array( $_GET['view'], array( 'user', 'lesson' ) ) ) {
28: $this->view = $_GET['view'];
29: }
30:
31:
32: if( $this->user_id ) {
33: $this->view = 'lesson';
34: }
35:
36:
37: parent::__construct( 'analysis_course' );
38:
39:
40: add_action( 'sensei_before_list_table', array( $this, 'data_table_header' ) );
41: add_action( 'sensei_after_list_table', array( $this, 'data_table_footer' ) );
42:
43: add_filter( 'sensei_list_table_search_button_text', array( $this, 'search_button' ) );
44:
45: }
46:
47: 48: 49: 50: 51:
52: function get_columns() {
53:
54: switch( $this->view ) {
55: case 'user' :
56: $columns = array(
57: 'title' => __( 'Learner', 'woothemes-sensei' ),
58: 'started' => __( 'Date Started', 'woothemes-sensei' ),
59: 'completed' => __( 'Date Completed', 'woothemes-sensei' ),
60: 'user_status' => __( 'Status', 'woothemes-sensei' ),
61: 'percent' => __( 'Percent Complete', 'woothemes-sensei' ),
62: );
63: break;
64:
65: case 'lesson' :
66: default:
67: if ( $this->user_id ) {
68:
69: $columns = array(
70: 'title' => __( 'Lesson', 'woothemes-sensei' ),
71: 'started' => __( 'Date Started', 'woothemes-sensei' ),
72: 'completed' => __( 'Date Completed', 'woothemes-sensei' ),
73: 'user_status' => __( 'Status', 'woothemes-sensei' ),
74: 'grade' => __( 'Grade', 'woothemes-sensei' ),
75: );
76:
77: } else {
78:
79: $columns = array(
80: 'title' => __( 'Lesson', 'woothemes-sensei' ),
81: 'num_learners' => __( 'Learners', 'woothemes-sensei' ),
82: 'completions' => __( 'Completed', 'woothemes-sensei' ),
83: 'average_grade' => __( 'Average Grade', 'woothemes-sensei' ),
84: );
85:
86: }
87: break;
88: }
89:
90: $columns = apply_filters( 'sensei_analysis_course_' . $this->view . '_columns', $columns, $this );
91:
92: $columns = apply_filters( 'sensei_analysis_course_columns', $columns, $this );
93: return $columns;
94: }
95:
96: 97: 98: 99: 100:
101: function get_sortable_columns() {
102:
103: switch( $this->view ) {
104: case 'user' :
105: $columns = array(
106: 'title' => array( 'title', false ),
107: 'started' => array( 'started', false ),
108: 'completed' => array( 'completed', false ),
109: 'user_status' => array( 'user_status', false ),
110:
111: 'percent' => array( 'percent', false )
112: );
113: break;
114:
115: case 'lesson' :
116: default:
117: if ( $this->user_id ) {
118:
119: $columns = array(
120: 'title' => array( 'title', false ),
121: 'started' => array( 'started', false ),
122: 'completed' => array( 'completed', false ),
123: 'user_status' => array( 'user_status', false ),
124: 'grade' => array( 'grade', false ),
125: );
126:
127: } else {
128:
129: $columns = array(
130: 'title' => array( 'title', false ),
131: 'num_learners' => array( 'num_learners', false ),
132: 'completions' => array( 'completions', false ),
133: 'average_grade' => array( 'average_grade', false )
134: );
135:
136: }
137: break;
138: }
139:
140: $columns = apply_filters( 'sensei_analysis_course_' . $this->view . '_columns_sortable', $columns, $this );
141:
142: $columns = apply_filters( 'sensei_analysis_course_columns_sortable', $columns, $this );
143: return $columns;
144: }
145:
146: 147: 148: 149: 150:
151: public function prepare_items() {
152: global $per_page;
153:
154:
155: $orderby = '';
156: if ( !empty( $_GET['orderby'] ) ) {
157: if ( array_key_exists( esc_html( $_GET['orderby'] ), $this->get_sortable_columns() ) ) {
158: $orderby = esc_html( $_GET['orderby'] );
159: }
160: }
161:
162:
163: $order = 'ASC';
164: if ( !empty( $_GET['order'] ) ) {
165: $order = ( 'ASC' == strtoupper($_GET['order']) ) ? 'ASC' : 'DESC';
166: }
167:
168:
169: $search = false;
170: if ( !empty( $_GET['s'] ) ) {
171: $search = esc_html( $_GET['s'] );
172: }
173: $this->search = $search;
174:
175: $per_page = $this->get_items_per_page( 'sensei_comments_per_page' );
176: $per_page = apply_filters( 'sensei_comments_per_page', $per_page, 'sensei_comments' );
177:
178: $paged = $this->get_pagenum();
179: $offset = 0;
180: if ( !empty($paged) ) {
181: $offset = $per_page * ( $paged - 1 );
182: }
183:
184: $args = array(
185: 'number' => $per_page,
186: 'offset' => $offset,
187: 'orderby' => $orderby,
188: 'order' => $order,
189: );
190: if ( $this->search ) {
191: $args['search'] = $this->search;
192: }
193:
194: switch( $this->view ) {
195: case 'user' :
196: $this->items = $this->get_course_statuses( $args );
197: break;
198:
199: case 'lesson':
200: default:
201: $this->items = $this->get_lessons( $args );
202: break;
203: }
204:
205: $total_items = $this->total_items;
206: $total_pages = ceil( $total_items / $per_page );
207: $this->set_pagination_args( array(
208: 'total_items' => $total_items,
209: 'total_pages' => $total_pages,
210: 'per_page' => $per_page
211: ) );
212: }
213:
214: 215: 216: 217: 218:
219: public function generate_report( $report ) {
220:
221: $data = array();
222:
223: $this->csv_output = true;
224:
225:
226: $orderby = '';
227: if ( !empty( $_GET['orderby'] ) ) {
228: if ( array_key_exists( esc_html( $_GET['orderby'] ), $this->get_sortable_columns() ) ) {
229: $orderby = esc_html( $_GET['orderby'] );
230: }
231: }
232:
233:
234: $order = 'ASC';
235: if ( !empty( $_GET['order'] ) ) {
236: $order = ( 'ASC' == strtoupper($_GET['order']) ) ? 'ASC' : 'DESC';
237: }
238:
239:
240: $search = false;
241: if ( !empty( $_GET['s'] ) ) {
242: $search = esc_html( $_GET['s'] );
243: }
244: $this->search = $search;
245:
246: $args = array(
247: 'orderby' => $orderby,
248: 'order' => $order,
249: );
250: if ( $this->search ) {
251: $args['search'] = $this->search;
252: }
253:
254:
255: $column_headers = array();
256: $columns = $this->get_columns();
257: foreach( $columns AS $key => $title ) {
258: $column_headers[] = $title;
259: }
260: $data[] = $column_headers;
261:
262: switch( $this->view ) {
263: case 'user' :
264: $this->items = $this->get_course_statuses( $args );
265: break;
266:
267: case 'lesson':
268: default:
269: $this->items = $this->get_lessons( $args );
270: break;
271: }
272:
273:
274: foreach( $this->items AS $item) {
275: $data[] = $this->get_row_data( $item );
276: }
277:
278: return $data;
279: }
280:
281: 282: 283: 284: 285: 286:
287: protected function get_row_data( $item ) {
288:
289: switch( $this->view ) {
290: case 'user' :
291: $user_start_date = get_comment_meta( $item->comment_ID, 'start', true );
292: $user_end_date = $item->comment_date;
293:
294: if( 'complete' == $item->comment_approved ) {
295:
296: $status = __( 'Completed', 'woothemes-sensei' );
297: $status_class = 'graded';
298:
299: } else {
300:
301: $status = __( 'In Progress', 'woothemes-sensei' );
302: $status_class = 'in-progress';
303: $user_end_date = '';
304:
305: }
306: $course_percent = get_comment_meta( $item->comment_ID, 'percent', true );
307:
308:
309: $user_name = Sensei_Learner::get_full_name( $item->user_id );
310:
311: if ( !$this->csv_output ) {
312:
313: $url = add_query_arg( array( 'page' => $this->page_slug, 'user_id' => $item->user_id, 'course_id' => $this->course_id ), admin_url( 'admin.php' ) );
314:
315: $user_name = '<strong><a class="row-title" href="' . esc_url( $url ) . '">' . $user_name . '</a></strong>';
316: $status = sprintf( '<span class="%s">%s</span>', $status_class, $status );
317: if ( is_numeric($course_percent) ) {
318:
319: $course_percent .= '%';
320:
321: }
322:
323: }
324:
325: $column_data = apply_filters( 'sensei_analysis_course_column_data', array( 'title' => $user_name,
326: 'started' => $user_start_date,
327: 'completed' => $user_end_date,
328: 'user_status' => $status,
329: 'percent' => $course_percent,
330: ), $item, $this );
331: break;
332:
333: case 'lesson':
334: default:
335:
336: if ( $this->user_id ) {
337: $status = __( 'Not started', 'woothemes-sensei' );
338: $user_start_date = $user_end_date = $status_class = $grade = '';
339:
340: $lesson_args = array(
341: 'post_id' => $item->ID,
342: 'user_id' => $this->user_id,
343: 'type' => 'sensei_lesson_status',
344: 'status' => 'any',
345: );
346: $lesson_status = Sensei_Utils::sensei_check_for_activity( apply_filters( 'sensei_analysis_course_user_lesson', $lesson_args, $item, $this->user_id ), true );
347:
348: if ( !empty($lesson_status) ) {
349: $user_start_date = get_comment_meta( $lesson_status->comment_ID, 'start', true );
350: $user_end_date = $lesson_status->comment_date;
351:
352: if( 'complete' == $lesson_status->comment_approved ) {
353: $status = __( 'Completed', 'woothemes-sensei' );
354: $status_class = 'graded';
355:
356: $grade = __( 'No Grade', 'woothemes-sensei' );
357: }
358: elseif( 'graded' == $lesson_status->comment_approved ) {
359: $status = __( 'Graded', 'woothemes-sensei' );
360: $status_class = 'graded';
361:
362: $grade = get_comment_meta( $lesson_status->comment_ID, 'grade', true);
363: }
364: elseif( 'passed' == $lesson_status->comment_approved ) {
365: $status = __( 'Passed', 'woothemes-sensei' );
366: $status_class = 'graded';
367:
368: $grade = get_comment_meta( $lesson_status->comment_ID, 'grade', true);
369: }
370: elseif( 'failed' == $lesson_status->comment_approved ) {
371: $status = __( 'Failed', 'woothemes-sensei' );
372: $status_class = 'failed';
373:
374: $grade = get_comment_meta( $lesson_status->comment_ID, 'grade', true);
375: }
376: elseif( 'ungraded' == $lesson_status->comment_approved ) {
377: $status = __( 'Ungraded', 'woothemes-sensei' );
378: $status_class = 'ungraded';
379:
380: }
381: elseif( 'in-progress' == $lesson_status->comment_approved ) {
382: $status = __( 'In Progress', 'woothemes-sensei' );
383: $user_end_date = '';
384: }
385: }
386:
387:
388: if ( $this->csv_output ) {
389: $lesson_title = apply_filters( 'the_title', $item->post_title, $item->ID );
390: }
391: else {
392: $url = add_query_arg( array( 'page' => $this->page_slug, 'lesson_id' => $item->ID ), admin_url( 'admin.php' ) );
393: $lesson_title = '<strong><a class="row-title" href="' . esc_url( $url ) . '">' . apply_filters( 'the_title', $item->post_title, $item->ID ) . '</a></strong>';
394:
395: $status = sprintf( '<span class="%s">%s</span>', $status_class, $status );
396: if ( is_numeric($grade) ) {
397: $grade .= '%';
398: }
399: }
400: $column_data = apply_filters( 'sensei_analysis_course_column_data', array( 'title' => $lesson_title,
401: 'started' => $user_start_date,
402: 'completed' => $user_end_date,
403: 'user_status' => $status,
404: 'grade' => $grade,
405: ), $item, $this );
406: }
407:
408: else {
409:
410: $lesson_args = array(
411: 'post_id' => $item->ID,
412: 'type' => 'sensei_lesson_status',
413: 'status' => 'any',
414: );
415: $lesson_students = Sensei_Utils::sensei_check_for_activity( apply_filters( 'sensei_analysis_lesson_learners', $lesson_args, $item ) );
416:
417:
418: $lesson_args = array(
419: 'post_id' => $item->ID,
420: 'type' => 'sensei_lesson_status',
421: 'status' => array( 'complete', 'graded', 'passed', 'failed' ),
422: 'count' => true,
423: );
424: $lesson_completions = Sensei_Utils::sensei_check_for_activity( apply_filters( 'sensei_analysis_lesson_completions', $lesson_args, $item ) );
425:
426: $lesson_average_grade = __('n/a', 'woothemes-sensei');
427: if ( false != get_post_meta($item->ID, '_quiz_has_questions', true) ) {
428:
429: $grade_args = array(
430: 'post_id' => $item->ID,
431: 'type' => 'sensei_lesson_status',
432: 'status' => array( 'graded', 'passed', 'failed' ),
433: 'meta_key' => 'grade',
434: );
435: add_filter( 'comments_clauses', array( 'WooThemes_Sensei_Utils', 'comment_total_sum_meta_value_filter' ) );
436: $lesson_grades = Sensei_Utils::sensei_check_for_activity( apply_filters( 'sensei_analysis_lesson_grades', $grade_args, $item ), true );
437: remove_filter( 'comments_clauses', array( 'WooThemes_Sensei_Utils', 'comment_total_sum_meta_value_filter' ) );
438:
439: $grade_count = !empty( $lesson_grades->total ) ? $lesson_grades->total : 1;
440: $grade_total = !empty( $lesson_grades->meta_sum ) ? doubleval( $lesson_grades->meta_sum ) : 0;
441: $lesson_average_grade = abs( round( doubleval( $grade_total / $grade_count ), 2 ) );
442: }
443:
444: if ( $this->csv_output ) {
445: $lesson_title = apply_filters( 'the_title', $item->post_title, $item->ID );
446: }
447: else {
448: $url = add_query_arg( array( 'page' => $this->page_slug, 'lesson_id' => $item->ID ), admin_url( 'admin.php' ) );
449: $lesson_title = '<strong><a class="row-title" href="' . esc_url( $url ) . '">' . apply_filters( 'the_title', $item->post_title, $item->ID ) . '</a></strong>';
450:
451: if ( is_numeric( $lesson_average_grade ) ) {
452: $lesson_average_grade .= '%';
453: }
454: }
455: $column_data = apply_filters( 'sensei_analysis_course_column_data', array( 'title' => $lesson_title,
456: 'num_learners' => $lesson_students,
457: 'completions' => $lesson_completions,
458: 'average_grade' => $lesson_average_grade,
459: ), $item, $this );
460: }
461: break;
462: }
463:
464: return $column_data;
465: }
466:
467: 468: 469: 470: 471:
472: private function get_course_statuses( $args ) {
473:
474: $activity_args = array(
475: 'post_id' => $this->course_id,
476: 'type' => 'sensei_course_status',
477: 'number' => $args['number'],
478: 'offset' => $args['offset'],
479: 'orderby' => $args['orderby'],
480: 'order' => $args['order'],
481: 'status' => 'any',
482: );
483:
484:
485: if ( $this->search ) {
486: $user_args = array(
487: 'search' => '*' . $this->search . '*',
488: 'fields' => 'ID',
489: );
490:
491: $user_args = apply_filters( 'sensei_analysis_course_search_users', $user_args );
492: if ( !empty( $user_args ) ) {
493: $learners_search = new WP_User_Query( $user_args );
494:
495: $activity_args['user_id'] = (array) $learners_search->get_results();
496: }
497: }
498:
499: $activity_args = apply_filters( 'sensei_analysis_course_filter_statuses', $activity_args );
500:
501:
502: $this->total_items = Sensei_Utils::sensei_check_for_activity( array_merge( $activity_args, array('count' => true, 'offset' => 0, 'number' => 0) ) );
503:
504:
505: if ( $this->total_items < $activity_args['offset'] ) {
506: $new_paged = floor( $this->total_items / $activity_args['number'] );
507: $activity_args['offset'] = $new_paged * $activity_args['number'];
508: }
509: $statuses = Sensei_Utils::sensei_check_for_activity( $activity_args, true );
510:
511: if ( !is_array($statuses) ) {
512: $statuses = array( $statuses );
513: }
514: return $statuses;
515: }
516:
517: 518: 519: 520: 521:
522: private function get_lessons( $args ) {
523:
524: $lessons_args = array( 'post_type' => 'lesson',
525: 'posts_per_page' => $args['number'],
526: 'offset' => $args['offset'],
527: 'meta_key' => '_order_' . $this->course_id,
528:
529: 'order' => $args['order'],
530: 'meta_query' => array(
531: array(
532: 'key' => '_lesson_course',
533: 'value' => intval( $this->course_id ),
534: ),
535: ),
536: 'post_status' => array('publish', 'private'),
537: 'suppress_filters' => 0
538: );
539: if ( $this->search ) {
540: $lessons_args['s'] = $this->search;
541: }
542: if ( $this->csv_output ) {
543: $lessons_args['posts_per_page'] = '-1';
544: }
545:
546:
547: $lessons_query = new WP_Query( apply_filters( 'sensei_analysis_course_filter_lessons', $lessons_args ) );
548: $this->total_items = $lessons_query->found_posts;
549: return $lessons_query->posts;
550: }
551:
552: 553: 554: 555: 556: 557:
558: public function no_items() {
559: switch( $this->view ) {
560: case 'user' :
561: $text = __( 'No learners found.', 'woothemes-sensei' );
562: break;
563:
564: case 'lesson':
565: default:
566: $text = __( 'No lessons found.', 'woothemes-sensei' );
567: break;
568: }
569: echo apply_filters( 'sensei_analysis_course_no_items_text', $text );
570: }
571:
572: 573: 574: 575: 576:
577: public function data_table_header() {
578: if ( $this->user_id ) {
579: $learners_text = __( 'Other Learners taking this Course', 'woothemes-sensei' );
580: }
581: else {
582: $learners_text = __( 'Learners taking this Course', 'woothemes-sensei' );
583: }
584: $lessons_text = __( 'Lessons in this Course', 'woothemes-sensei' );
585:
586: $url_args = array(
587: 'page' => $this->page_slug,
588: 'course_id' => $this->course_id,
589: );
590: $learners_url = esc_url( add_query_arg( array_merge( $url_args, array( 'view' => 'user' ) ), admin_url( 'admin.php' ) ) );
591: $lessons_url = esc_url( add_query_arg( array_merge( $url_args, array( 'view' => 'lesson' ) ), admin_url( 'admin.php' ) ) );
592:
593: $learners_class = $lessons_class = '';
594:
595: $menu = array();
596: switch( $this->view ) {
597: case 'user' :
598: $learners_class = 'current';
599: break;
600:
601: case 'lesson':
602: default:
603: $lessons_class = 'current';
604: break;
605: }
606: $menu['lesson'] = sprintf( '<a href="%s" class="%s">%s</a>', $lessons_url, $lessons_class, $lessons_text );
607: $menu['user'] = sprintf( '<a href="%s" class="%s">%s</a>', $learners_url, $learners_class, $learners_text );
608:
609: $menu = apply_filters( 'sensei_analysis_course_sub_menu', $menu );
610: if ( !empty($menu) ) {
611: echo '<ul class="subsubsub">' . "\n";
612: foreach ( $menu as $class => $item ) {
613: $menu[ $class ] = "\t<li class='$class'>$item";
614: }
615: echo implode( " |</li>\n", $menu ) . "</li>\n";
616: echo '</ul>' . "\n";
617: }
618: }
619:
620: 621: 622: 623: 624:
625: public function data_table_footer() {
626:
627: $course = get_post( $this->course_id );
628: $report = sanitize_title( $course->post_title ) . '-' . $this->view . 's-overview';
629: if ( $this->user_id ) {
630: $user_name = Sensei_Learner::get_full_name( $this->user_id );
631: $report = sanitize_title( $user_name ) . '-' . $report;
632: }
633:
634: $url_args = array( 'page' => $this->page_slug, 'course_id' => $this->course_id, 'view' => $this->view, 'sensei_report_download' => $report );
635: if ( $this->user_id ) {
636: $url_args['user_id'] = $this->user_id;
637: }
638: $url = add_query_arg( $url_args, admin_url( 'admin.php' ) );
639: echo '<a class="button button-primary" href="' . esc_url( wp_nonce_url( $url, 'sensei_csv_download-' . $report, '_sdl_nonce' ) ) . '">' . __( 'Export all rows (CSV)', 'woothemes-sensei' ) . '</a>';
640: }
641:
642: 643: 644: 645: 646:
647: public function search_button( $text = '' ) {
648: switch( $this->view ) {
649: case 'user':
650: $text = __( 'Search Learners', 'woothemes-sensei' );
651: break;
652:
653: case 'lesson':
654: default:
655: $text = __( 'Search Lessons', 'woothemes-sensei' );
656: break;
657: }
658:
659: return $text;
660: }
661:
662: }
663:
664: 665: 666: 667: 668: 669:
670: class WooThemes_Sensei_Analysis_Course_List_Table extends Sensei_Analysis_Course_List_Table {}
671: