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