7e95ae2f103007a79e48bb99dc2f372c51dfed37
[moodle.git] / mod / lesson / report.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Displays the lesson statistics.
20  *
21  * @package mod_lesson
22  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
24  **/
26 require_once('../../config.php');
27 require_once($CFG->dirroot.'/mod/lesson/locallib.php');
29 $id     = required_param('id', PARAM_INT);    // Course Module ID
30 $pageid = optional_param('pageid', null, PARAM_INT);    // Lesson Page ID
31 $action = optional_param('action', 'reportoverview', PARAM_ALPHA);  // action to take
32 $nothingtodisplay = false;
34 $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
35 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
36 $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
38 require_login($course, false, $cm);
40 $currentgroup = groups_get_activity_group($cm, true);
42 $context = context_module::instance($cm->id);
43 require_capability('mod/lesson:viewreports', $context);
45 $url = new moodle_url('/mod/lesson/report.php', array('id'=>$id));
46 $url->param('action', $action);
47 if ($pageid !== null) {
48     $url->param('pageid', $pageid);
49 }
50 $PAGE->set_url($url);
51 if ($action == 'reportoverview') {
52     $PAGE->navbar->add(get_string('reports', 'lesson'));
53     $PAGE->navbar->add(get_string('overview', 'lesson'));
54 }
56 $lessonoutput = $PAGE->get_renderer('mod_lesson');
58 if ($action === 'delete') {
59     /// Process any form data before fetching attempts, grades and times
60     if (has_capability('mod/lesson:edit', $context) and $form = data_submitted() and confirm_sesskey()) {
61     /// Cycle through array of userids with nested arrays of tries
62         if (!empty($form->attempts)) {
63             foreach ($form->attempts as $userid => $tries) {
64                 // Modifier IS VERY IMPORTANT!  What does it do?
65                 //      Well, it is for when you delete multiple attempts for the same user.
66                 //      If you delete try 1 and 3 for a user, then after deleting try 1, try 3 then
67                 //      becomes try 2 (because try 1 is gone and all tries after try 1 get decremented).
68                 //      So, the modifier makes sure that the submitted try refers to the current try in the
69                 //      database - hope this all makes sense :)
70                 $modifier = 0;
72                 foreach ($tries as $try => $junk) {
73                     $try -= $modifier;
75                 /// Clean up the timer table by removing using the order - this is silly, it should be linked to specific attempt (skodak)
76                     $timers = $lesson->get_user_timers($userid, 'starttime', 'id', $try, 1);
77                     if ($timers) {
78                         $timer = reset($timers);
79                         $DB->delete_records('lesson_timer', array('id' => $timer->id));
80                     }
82                     $params = array ("userid" => $userid, "lessonid" => $lesson->id);
83                     // Remove the grade from the grades tables - this is silly, it should be linked to specific attempt (skodak).
84                     $grades = $DB->get_records_sql("SELECT id FROM {lesson_grades}
85                                                      WHERE userid = :userid AND lessonid = :lessonid
86                                                   ORDER BY completed", $params, $try, 1);
88                     if ($grades) {
89                         $grade = reset($grades);
90                         $DB->delete_records('lesson_grades', array('id' => $grade->id));
91                     }
93                 /// Remove attempts and update the retry number
94                     $DB->delete_records('lesson_attempts', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
95                     $DB->execute("UPDATE {lesson_attempts} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
97                 /// Remove seen branches and update the retry number
98                     $DB->delete_records('lesson_branch', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
99                     $DB->execute("UPDATE {lesson_branch} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
101                 /// update central gradebook
102                     lesson_update_grades($lesson, $userid);
104                     $modifier++;
105                 }
106             }
107         }
108     }
109     redirect(new moodle_url($PAGE->url, array('action'=>'reportoverview')));
111 } else if ($action === 'reportoverview') {
112     /**************************************************************************
113     this action is for default view and overview view
114     **************************************************************************/
116     // Get the table and data for build statistics.
117     list($table, $data) = lesson_get_overview_report_table_and_data($lesson, $currentgroup);
119     if ($table === false) {
120         echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('nolessonattempts', 'lesson'));
121         if (!empty($currentgroup)) {
122             $groupname = groups_get_group_name($currentgroup);
123             echo $OUTPUT->notification(get_string('nolessonattemptsgroup', 'lesson', $groupname));
124         } else {
125             echo $OUTPUT->notification(get_string('nolessonattempts', 'lesson'));
126         }
127         groups_print_activity_menu($cm, $url);
128         echo $OUTPUT->footer();
129         exit();
130     }
132     echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('overview', 'lesson'));
133     groups_print_activity_menu($cm, $url);
135     $course_context = context_course::instance($course->id);
136     if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
137         $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
138         $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
139         echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
140     }
142     // Print it all out!
143     if (has_capability('mod/lesson:edit', $context)) {
144         echo  "<form id=\"mod-lesson-report-form\" method=\"post\" action=\"report.php\">\n
145                <input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n
146                <input type=\"hidden\" name=\"id\" value=\"$cm->id\" />\n";
147     }
149     echo html_writer::table($table);
151     if (has_capability('mod/lesson:edit', $context)) {
152         $checklinks  = '<a id="checkall" href="#">'.get_string('selectall').'</a> / ';
153         $checklinks .= '<a id="checknone" href="#">'.get_string('deselectall').'</a>';
154         $checklinks .= html_writer::label('action', 'menuaction', false, array('class' => 'accesshide'));
155         $options = array('delete' => get_string('deleteselected'));
156         $attributes = array('id' => 'actionid', 'class' => 'custom-select m-l-1');
157         $checklinks .= html_writer::select($options, 'action', 0, array('' => 'choosedots'), $attributes);
158         $PAGE->requires->js_amd_inline("
159         require(['jquery'], function($) {
160             $('#actionid').change(function() {
161                 $('#mod-lesson-report-form').submit();
162             });
163             $('#checkall').click(function(e) {
164                 $('#mod-lesson-report-form').find('input:checkbox').prop('checked', true);
165                 e.preventDefault();
166             });
167             $('#checknone').click(function(e) {
168                 $('#mod-lesson-report-form').find('input:checkbox').prop('checked', false);
169                 e.preventDefault();
170             });
171         });");
172         echo $OUTPUT->box($checklinks, 'center');
173         echo '</form>';
174     }
176     // Calculate the Statistics.
177     if ($data->avetime == null) {
178         $data->avetime = get_string("notcompleted", "lesson");
179     } else {
180         $data->avetime = format_float($data->avetime / $data->numofattempts, 0);
181         $data->avetime = format_time($data->avetime);
182     }
183     if ($data->hightime == null) {
184         $data->hightime = get_string("notcompleted", "lesson");
185     } else {
186         $data->hightime = format_time($data->hightime);
187     }
188     if ($data->lowtime == null) {
189         $data->lowtime = get_string("notcompleted", "lesson");
190     } else {
191         $data->lowtime = format_time($data->lowtime);
192     }
194     if ($data->lessonscored) {
195         if ($data->numofattempts == 0) {
196             $data->avescore = get_string("notcompleted", "lesson");
197         } else {
198             $data->avescore = format_float($data->avescore, 2) . '%';
199         }
200         if ($data->highscore === null) {
201             $data->highscore = get_string("notcompleted", "lesson");
202         } else {
203             $data->highscore .= '%';
204         }
205         if ($data->lowscore === null) {
206             $data->lowscore = get_string("notcompleted", "lesson");
207         } else {
208             $data->lowscore .= '%';
209         }
211         // Display the full stats for the lesson.
212         echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
213         $stattable = new html_table();
214         $stattable->head = array(get_string('averagescore', 'lesson'), get_string('averagetime', 'lesson'),
215                                 get_string('highscore', 'lesson'), get_string('lowscore', 'lesson'),
216                                 get_string('hightime', 'lesson'), get_string('lowtime', 'lesson'));
217         $stattable->align = array('center', 'center', 'center', 'center', 'center', 'center');
218         $stattable->wrap = array('nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap');
219         $stattable->attributes['class'] = 'standardtable generaltable';
220         $stattable->data[] = array($data->avescore, $data->avetime, $data->highscore, $data->lowscore, $data->hightime, $data->lowtime);
222     } else {
223         // Display simple stats for the lesson.
224         echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
225         $stattable = new html_table();
226         $stattable->head = array(get_string('averagetime', 'lesson'), get_string('hightime', 'lesson'),
227                                 get_string('lowtime', 'lesson'));
228         $stattable->align = array('center', 'center', 'center');
229         $stattable->wrap = array('nowrap', 'nowrap', 'nowrap');
230         $stattable->attributes['class'] = 'standardtable generaltable';
231         $stattable->data[] = array($data->avetime, $data->hightime, $data->lowtime);
232     }
234     echo html_writer::table($stattable);
235 } else if ($action === 'reportdetail') {
236     /**************************************************************************
237     this action is for a student detailed view and for the general detailed view
239     General flow of this section of the code
240     1.  Generate a object which holds values for the statistics for each question/answer
241     2.  Cycle through all the pages to create a object.  Foreach page, see if the student actually answered
242         the page.  Then process the page appropriatly.  Display all info about the question,
243         Highlight correct answers, show how the user answered the question, and display statistics
244         about each page
245     3.  Print out info about the try (if needed)
246     4.  Print out the object which contains all the try info
248 **************************************************************************/
249     echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('detailedstats', 'lesson'));
250     groups_print_activity_menu($cm, $url);
252     $course_context = context_course::instance($course->id);
253     if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
254         $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
255         $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
256         echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
257     }
259     $formattextdefoptions = new stdClass;
260     $formattextdefoptions->para = false;  //I'll use it widely in this page
261     $formattextdefoptions->overflowdiv = true;
263     $userid = optional_param('userid', null, PARAM_INT); // if empty, then will display the general detailed view
264     $try    = optional_param('try', null, PARAM_INT);
266     list($answerpages, $userstats) = lesson_get_user_detailed_report_data($lesson, $userid, $try);
268     /// actually start printing something
269     $table = new html_table();
270     $table->wrap = array();
271     $table->width = "60%";
272     if (!empty($userid)) {
273         // if looking at a students try, print out some basic stats at the top
275             // print out users name
276             //$headingobject->lastname = $students[$userid]->lastname;
277             //$headingobject->firstname = $students[$userid]->firstname;
278             //$headingobject->attempt = $try + 1;
279             //print_heading(get_string("studentattemptlesson", "lesson", $headingobject));
280         echo $OUTPUT->heading(get_string('attempt', 'lesson', $try+1), 3);
282         $table->head = array();
283         $table->align = array('right', 'left');
284         $table->attributes['class'] = 'compacttable generaltable form-inline';
286         if (empty($userstats->gradeinfo)) {
287             $table->align = array("center");
289             $table->data[] = array(get_string("notcompleted", "lesson"));
290         } else {
291             $user = $DB->get_record('user', array('id' => $userid));
293             $gradeinfo = lesson_grade($lesson, $try, $user->id);
295             $table->data[] = array(get_string('name').':', $OUTPUT->user_picture($user, array('courseid'=>$course->id)).fullname($user, true));
296             $table->data[] = array(get_string("timetaken", "lesson").":", format_time($userstats->timetotake));
297             $table->data[] = array(get_string("completed", "lesson").":", userdate($userstats->completed));
298             $table->data[] = array(get_string('rawgrade', 'lesson').':', $userstats->gradeinfo->earned.'/'.$userstats->gradeinfo->total);
299             $table->data[] = array(get_string("grade", "lesson").":", $userstats->grade."%");
300         }
301         echo html_writer::table($table);
303         // Don't want this class for later tables
304         $table->attributes['class'] = '';
305     }
308     $table->align = array('left', 'left');
309     $table->size = array('70%', null);
310     $table->attributes['class'] = 'compacttable generaltable form-inline';
312     foreach ($answerpages as $page) {
313         unset($table->data);
314         if ($page->grayout) { // set the color of text
315             $fontstart = "<span class=\"dimmed\">";
316             $fontend = "</font>";
317             $fontstart2 = $fontstart;
318             $fontend2 = $fontend;
319         } else {
320             $fontstart = "";
321             $fontend = "";
322             $fontstart2 = "";
323             $fontend2 = "";
324         }
326         $table->head = array($fontstart2.$page->qtype.": ".format_string($page->title).$fontend2, $fontstart2.get_string("classstats", "lesson").$fontend2);
327         $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
328         $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
329         // apply the font to each answer
330         if (!empty($page->answerdata) && !empty($page->answerdata->answers)) {
331             foreach ($page->answerdata->answers as $answer){
332                 $modified = array();
333                 foreach ($answer as $single) {
334                     // need to apply a font to each one
335                     $modified[] = $fontstart2.$single.$fontend2;
336                 }
337                 $table->data[] = $modified;
338             }
339             if (isset($page->answerdata->response)) {
340                 $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend
341                         .$fontstart2.$page->answerdata->response.$fontend2, " ");
342             }
343             $table->data[] = array($page->answerdata->score, " ");
344         } else {
345             $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
346         }
347         echo html_writer::start_tag('div', array('class' => 'no-overflow'));
348         echo html_writer::table($table);
349         echo html_writer::end_tag('div');
350     }
351 } else {
352     print_error('unknowaction');
355 /// Finish the page
356 echo $OUTPUT->footer();