Merge branch 'MDL-57490-master' of git://github.com/danpoltawski/moodle
[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');
28 require_once($CFG->dirroot.'/mod/lesson/pagetypes/branchtable.php'); // Needed for constant.
30 $id     = required_param('id', PARAM_INT);    // Course Module ID
31 $pageid = optional_param('pageid', null, PARAM_INT);    // Lesson Page ID
32 $action = optional_param('action', 'reportoverview', PARAM_ALPHA);  // action to take
33 $nothingtodisplay = false;
35 $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
36 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
37 $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
39 require_login($course, false, $cm);
41 $currentgroup = groups_get_activity_group($cm, true);
43 $context = context_module::instance($cm->id);
44 require_capability('mod/lesson:viewreports', $context);
46 $url = new moodle_url('/mod/lesson/report.php', array('id'=>$id));
47 $url->param('action', $action);
48 if ($pageid !== null) {
49     $url->param('pageid', $pageid);
50 }
51 $PAGE->set_url($url);
52 if ($action == 'reportoverview') {
53     $PAGE->navbar->add(get_string('reports', 'lesson'));
54     $PAGE->navbar->add(get_string('overview', 'lesson'));
55 }
57 $lessonoutput = $PAGE->get_renderer('mod_lesson');
59 if ($action === 'delete') {
60     /// Process any form data before fetching attempts, grades and times
61     if (has_capability('mod/lesson:edit', $context) and $form = data_submitted() and confirm_sesskey()) {
62     /// Cycle through array of userids with nested arrays of tries
63         if (!empty($form->attempts)) {
64             foreach ($form->attempts as $userid => $tries) {
65                 // Modifier IS VERY IMPORTANT!  What does it do?
66                 //      Well, it is for when you delete multiple attempts for the same user.
67                 //      If you delete try 1 and 3 for a user, then after deleting try 1, try 3 then
68                 //      becomes try 2 (because try 1 is gone and all tries after try 1 get decremented).
69                 //      So, the modifier makes sure that the submitted try refers to the current try in the
70                 //      database - hope this all makes sense :)
71                 $modifier = 0;
73                 foreach ($tries as $try => $junk) {
74                     $try -= $modifier;
76                 /// Clean up the timer table by removing using the order - this is silly, it should be linked to specific attempt (skodak)
77                     $params = array ("userid" => $userid, "lessonid" => $lesson->id);
78                     $timers = $DB->get_records_sql("SELECT id FROM {lesson_timer}
79                                                      WHERE userid = :userid AND lessonid = :lessonid
80                                                   ORDER BY starttime", $params, $try, 1);
81                     if ($timers) {
82                         $timer = reset($timers);
83                         $DB->delete_records('lesson_timer', array('id' => $timer->id));
84                     }
86                     // Remove the grade from the grades tables - this is silly, it should be linked to specific attempt (skodak).
87                     $grades = $DB->get_records_sql("SELECT id FROM {lesson_grades}
88                                                      WHERE userid = :userid AND lessonid = :lessonid
89                                                   ORDER BY completed", $params, $try, 1);
91                     if ($grades) {
92                         $grade = reset($grades);
93                         $DB->delete_records('lesson_grades', array('id' => $grade->id));
94                     }
96                 /// Remove attempts and update the retry number
97                     $DB->delete_records('lesson_attempts', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
98                     $DB->execute("UPDATE {lesson_attempts} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
100                 /// Remove seen branches and update the retry number
101                     $DB->delete_records('lesson_branch', array('userid' => $userid, 'lessonid' => $lesson->id, 'retry' => $try));
102                     $DB->execute("UPDATE {lesson_branch} SET retry = retry - 1 WHERE userid = ? AND lessonid = ? AND retry > ?", array($userid, $lesson->id, $try));
104                 /// update central gradebook
105                     lesson_update_grades($lesson, $userid);
107                     $modifier++;
108                 }
109             }
110         }
111     }
112     redirect(new moodle_url($PAGE->url, array('action'=>'reportoverview')));
114 } else if ($action === 'reportoverview') {
115     /**************************************************************************
116     this action is for default view and overview view
117     **************************************************************************/
119     // Count the number of branch and question pages in this lesson.
120     $branchcount = $DB->count_records('lesson_pages', array('lessonid' => $lesson->id, 'qtype' => LESSON_PAGE_BRANCHTABLE));
121     $questioncount = ($DB->count_records('lesson_pages', array('lessonid' => $lesson->id)) - $branchcount);
123     // Only load students if there attempts for this lesson.
124     $attempts = $DB->record_exists('lesson_attempts', array('lessonid' => $lesson->id));
125     $branches = $DB->record_exists('lesson_branch', array('lessonid' => $lesson->id));
126     $timer = $DB->record_exists('lesson_timer', array('lessonid' => $lesson->id));
127     if ($attempts or $branches or $timer) {
128         list($esql, $params) = get_enrolled_sql($context, '', $currentgroup, true);
129         list($sort, $sortparams) = users_order_by_sql('u');
131         $params['a1lessonid'] = $lesson->id;
132         $params['b1lessonid'] = $lesson->id;
133         $params['c1lessonid'] = $lesson->id;
134         $ufields = user_picture::fields('u');
135         $sql = "SELECT DISTINCT $ufields
136                 FROM {user} u
137                 JOIN (
138                     SELECT userid, lessonid FROM {lesson_attempts} a1
139                     WHERE a1.lessonid = :a1lessonid
140                         UNION
141                     SELECT userid, lessonid FROM {lesson_branch} b1
142                     WHERE b1.lessonid = :b1lessonid
143                         UNION
144                     SELECT userid, lessonid FROM {lesson_timer} c1
145                     WHERE c1.lessonid = :c1lessonid
146                     ) a ON u.id = a.userid
147                 JOIN ($esql) ue ON ue.id = a.userid
148                 ORDER BY $sort";
150         $students = $DB->get_recordset_sql($sql, $params);
151         if (!$students->valid()) {
152             $students->close();
153             $nothingtodisplay = true;
154         }
155     } else {
156         $nothingtodisplay = true;
157     }
159     if ($nothingtodisplay) {
160         echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('nolessonattempts', 'lesson'));
161         if (!empty($currentgroup)) {
162             $groupname = groups_get_group_name($currentgroup);
163             echo $OUTPUT->notification(get_string('nolessonattemptsgroup', 'lesson', $groupname));
164         } else {
165             echo $OUTPUT->notification(get_string('nolessonattempts', 'lesson'));
166         }
167         groups_print_activity_menu($cm, $url);
168         echo $OUTPUT->footer();
169         exit();
170     }
172     if (! $grades = $DB->get_records('lesson_grades', array('lessonid' => $lesson->id), 'completed')) {
173         $grades = array();
174     }
176     if (! $times = $DB->get_records('lesson_timer', array('lessonid' => $lesson->id), 'starttime')) {
177         $times = array();
178     }
180     echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('overview', 'lesson'));
181     groups_print_activity_menu($cm, $url);
183     $course_context = context_course::instance($course->id);
184     if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
185         $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
186         $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
187         echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
188     }
190     // Build an array for output.
191     $studentdata = array();
193     $attempts = $DB->get_recordset('lesson_attempts', array('lessonid' => $lesson->id), 'timeseen');
194     foreach ($attempts as $attempt) {
195         // if the user is not in the array or if the retry number is not in the sub array, add the data for that try.
196         if (empty($studentdata[$attempt->userid]) || empty($studentdata[$attempt->userid][$attempt->retry])) {
197             // restore/setup defaults
198             $n = 0;
199             $timestart = 0;
200             $timeend = 0;
201             $usergrade = null;
202             $eol = false;
204             // search for the grade record for this try. if not there, the nulls defined above will be used.
205             foreach($grades as $grade) {
206                 // check to see if the grade matches the correct user
207                 if ($grade->userid == $attempt->userid) {
208                     // see if n is = to the retry
209                     if ($n == $attempt->retry) {
210                         // get grade info
211                         $usergrade = round($grade->grade, 2); // round it here so we only have to do it once
212                         break;
213                     }
214                     $n++; // if not equal, then increment n
215                 }
216             }
217             $n = 0;
218             // search for the time record for this try. if not there, the nulls defined above will be used.
219             foreach($times as $time) {
220                 // check to see if the grade matches the correct user
221                 if ($time->userid == $attempt->userid) {
222                     // see if n is = to the retry
223                     if ($n == $attempt->retry) {
224                         // get grade info
225                         $timeend = $time->lessontime;
226                         $timestart = $time->starttime;
227                         $eol = $time->completed;
228                         break;
229                     }
230                     $n++; // if not equal, then increment n
231                 }
232             }
234             // build up the array.
235             // this array represents each student and all of their tries at the lesson
236             $studentdata[$attempt->userid][$attempt->retry] = array( "timestart" => $timestart,
237                                                                     "timeend" => $timeend,
238                                                                     "grade" => $usergrade,
239                                                                     "end" => $eol,
240                                                                     "try" => $attempt->retry,
241                                                                     "userid" => $attempt->userid);
242         }
243     }
244     $attempts->close();
246     $branches = $DB->get_recordset('lesson_branch', array('lessonid' => $lesson->id), 'timeseen');
247     foreach ($branches as $branch) {
248         // If the user is not in the array or if the retry number is not in the sub array, add the data for that try.
249         if (empty($studentdata[$branch->userid]) || empty($studentdata[$branch->userid][$branch->retry])) {
250             // Restore/setup defaults.
251             $n = 0;
252             $timestart = 0;
253             $timeend = 0;
254             $usergrade = null;
255             $eol = false;
256             // Search for the time record for this try. if not there, the nulls defined above will be used.
257             foreach ($times as $time) {
258                 // Check to see if the grade matches the correct user.
259                 if ($time->userid == $branch->userid) {
260                     // See if n is = to the retry.
261                     if ($n == $branch->retry) {
262                         // Get grade info.
263                         $timeend = $time->lessontime;
264                         $timestart = $time->starttime;
265                         $eol = $time->completed;
266                         break;
267                     }
268                     $n++; // If not equal, then increment n.
269                 }
270             }
272             // Build up the array.
273             // This array represents each student and all of their tries at the lesson.
274             $studentdata[$branch->userid][$branch->retry] = array( "timestart" => $timestart,
275                                                                     "timeend" => $timeend,
276                                                                     "grade" => $usergrade,
277                                                                     "end" => $eol,
278                                                                     "try" => $branch->retry,
279                                                                     "userid" => $branch->userid);
280         }
281     }
282     $branches->close();
284     // Need the same thing for timed entries that were not completed.
285     foreach ($times as $time) {
286         $endoflesson = $time->completed;
287         // If the time start is the same with another record then we shouldn't be adding another item to this array.
288         if (isset($studentdata[$time->userid])) {
289             $foundmatch = false;
290             $n = 0;
291             foreach ($studentdata[$time->userid] as $key => $value) {
292                 if ($value['timestart'] == $time->starttime) {
293                     // Don't add this to the array.
294                     $foundmatch = true;
295                     break;
296                 }
297             }
298             $n = count($studentdata[$time->userid]) + 1;
299             if (!$foundmatch) {
300                 // Add a record.
301                 $studentdata[$time->userid][] = array(
302                                 "timestart" => $time->starttime,
303                                 "timeend" => $time->lessontime,
304                                 "grade" => null,
305                                 "end" => $endoflesson,
306                                 "try" => $n,
307                                 "userid" => $time->userid
308                             );
309             }
310         } else {
311             $studentdata[$time->userid][] = array(
312                                 "timestart" => $time->starttime,
313                                 "timeend" => $time->lessontime,
314                                 "grade" => null,
315                                 "end" => $endoflesson,
316                                 "try" => 0,
317                                 "userid" => $time->userid
318                             );
319         }
320     }
321     // Determine if lesson should have a score.
322     if ($branchcount > 0 AND $questioncount == 0) {
323         // This lesson only contains content pages and is not graded.
324         $lessonscored = false;
325     } else {
326         // This lesson is graded.
327         $lessonscored = true;
328     }
329     // set all the stats variables
330     $numofattempts = 0;
331     $avescore      = 0;
332     $avetime       = 0;
333     $highscore     = null;
334     $lowscore      = null;
335     $hightime      = null;
336     $lowtime       = null;
338     $table = new html_table();
340     // Set up the table object.
341     if ($lessonscored) {
342         $table->head = array(get_string('name'), get_string('attempts', 'lesson'), get_string('highscore', 'lesson'));
343     } else {
344         $table->head = array(get_string('name'), get_string('attempts', 'lesson'));
345     }
346     $table->align = array('center', 'left', 'left');
347     $table->wrap = array('nowrap', 'nowrap', 'nowrap');
348     $table->attributes['class'] = 'standardtable generaltable';
349     $table->size = array(null, '70%', null);
351     // print out the $studentdata array
352     // going through each student that has attempted the lesson, so, each student should have something to be displayed
353     foreach ($students as $student) {
354         // check to see if the student has attempts to print out
355         if (array_key_exists($student->id, $studentdata)) {
356             // set/reset some variables
357             $attempts = array();
358             // gather the data for each user attempt
359             $bestgrade = 0;
360             $bestgradefound = false;
361             // $tries holds all the tries/retries a student has done
362             $tries = $studentdata[$student->id];
363             $studentname = fullname($student, true);
364             foreach ($tries as $try) {
365             // start to build up the checkbox and link
366                 if (has_capability('mod/lesson:edit', $context)) {
367                     $temp = '<input type="checkbox" id="attempts" name="attempts['.$try['userid'].']['.$try['try'].']" /> ';
368                 } else {
369                     $temp = '';
370                 }
372                 $temp .= "<a href=\"report.php?id=$cm->id&amp;action=reportdetail&amp;userid=".$try['userid']
373                         .'&amp;try='.$try['try'].'" class="lesson-attempt-link">';
374                 if ($try["grade"] !== null) { // if null then not done yet
375                     // this is what the link does when the user has completed the try
376                     $timetotake = $try["timeend"] - $try["timestart"];
378                     $temp .= $try["grade"]."%";
379                     $bestgradefound = true;
380                     if ($try["grade"] > $bestgrade) {
381                         $bestgrade = $try["grade"];
382                     }
383                     $temp .= "&nbsp;".userdate($try["timestart"]);
384                     $temp .= ",&nbsp;(".format_time($timetotake).")</a>";
385                 } else {
386                     if ($try["end"]) {
387                         // User finished the lesson but has no grade. (Happens when there are only content pages).
388                         $temp .= "&nbsp;".userdate($try["timestart"]);
389                         $timetotake = $try["timeend"] - $try["timestart"];
390                         $temp .= ",&nbsp;(".format_time($timetotake).")</a>";
391                     } else {
392                         // This is what the link does/looks like when the user has not completed the attempt.
393                         $temp .= get_string("notcompleted", "lesson");
394                         if ($try['timestart'] !== 0) {
395                             // Teacher previews do not track time spent.
396                             $temp .= "&nbsp;".userdate($try["timestart"]);
397                         }
398                         $temp .= "</a>";
399                         $timetotake = null;
400                     }
401                 }
402                 // build up the attempts array
403                 $attempts[] = $temp;
405                 // Run these lines for the stats only if the user finnished the lesson.
406                 if ($try["end"]) {
407                     // User has completed the lesson.
408                     $numofattempts++;
409                     $avetime += $timetotake;
410                     if ($timetotake > $hightime || $hightime == null) {
411                         $hightime = $timetotake;
412                     }
413                     if ($timetotake < $lowtime || $lowtime == null) {
414                         $lowtime = $timetotake;
415                     }
416                     if ($try["grade"] !== null) {
417                         // The lesson was scored.
418                         $avescore += $try["grade"];
419                         if ($try["grade"] > $highscore || $highscore === null) {
420                             $highscore = $try["grade"];
421                         }
422                         if ($try["grade"] < $lowscore || $lowscore === null) {
423                             $lowscore = $try["grade"];
424                         }
426                     }
427                 }
428             }
429             // get line breaks in after each attempt
430             $attempts = implode("<br />\n", $attempts);
432             if ($lessonscored) {
433                 // Add the grade if the lesson is graded.
434                 $bestgrade = $bestgrade."%";
435                 $table->data[] = array($studentname, $attempts, $bestgrade);
436             } else {
437                 // This lesson does not have a grade.
438                 $table->data[] = array($studentname, $attempts);
439             }
440         }
441     }
442     $students->close();
443     // Print it all out!
444     if (has_capability('mod/lesson:edit', $context)) {
445         echo  "<form id=\"mod-lesson-report-form\" method=\"post\" action=\"report.php\">\n
446                <input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n
447                <input type=\"hidden\" name=\"id\" value=\"$cm->id\" />\n";
448     }
449     echo html_writer::table($table);
450     if (has_capability('mod/lesson:edit', $context)) {
451         $checklinks  = '<a id="checkall" href="#">'.get_string('selectall').'</a> / ';
452         $checklinks .= '<a id="checknone" href="#">'.get_string('deselectall').'</a>';
453         $checklinks .= html_writer::label('action', 'menuaction', false, array('class' => 'accesshide'));
454         $options = array('delete' => get_string('deleteselected'));
455         $attributes = array('id' => 'actionid', 'class' => 'custom-select m-l-1');
456         $checklinks .= html_writer::select($options, 'action', 0, array('' => 'choosedots'), $attributes);
457         $PAGE->requires->js_amd_inline("
458         require(['jquery'], function($) {
459             $('#actionid').change(function() {
460                 $('#mod-lesson-report-form').submit();
461             });
462             $('#checkall').click(function(e) {
463                 $('#mod-lesson-report-form').find('input:checkbox').prop('checked', true);
464                 e.preventDefault();
465             });
466             $('#checknone').click(function(e) {
467                 $('#mod-lesson-report-form').find('input:checkbox').prop('checked', false);
468                 e.preventDefault();
469             });
470         });");
471         echo $OUTPUT->box($checklinks, 'center');
472         echo '</form>';
473     }
475     // Calculate the Statistics.
476     if ($avetime == null) {
477         $avetime = get_string("notcompleted", "lesson");
478     } else {
479         $avetime = format_float($avetime/$numofattempts, 0);
480         $avetime = format_time($avetime);
481     }
482     if ($hightime == null) {
483         $hightime = get_string("notcompleted", "lesson");
484     } else {
485         $hightime = format_time($hightime);
486     }
487     if ($lowtime == null) {
488         $lowtime = get_string("notcompleted", "lesson");
489     } else {
490         $lowtime = format_time($lowtime);
491     }
493     if ($lessonscored) {
494         if ($numofattempts == 0) {
495             $avescore = get_string("notcompleted", "lesson");
496         } else {
497             $avescore = format_float($avescore / $numofattempts, 2) . '%';
498         }
499         if ($highscore === null) {
500             $highscore = get_string("notcompleted", "lesson");
501         } else {
502             $highscore .= '%';
503         }
504         if ($lowscore === null) {
505             $lowscore = get_string("notcompleted", "lesson");
506         } else {
507             $lowscore .= '%';
508         }
510         // Display the full stats for the lesson.
511         echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
512         $stattable = new html_table();
513         $stattable->head = array(get_string('averagescore', 'lesson'), get_string('averagetime', 'lesson'),
514                                 get_string('highscore', 'lesson'), get_string('lowscore', 'lesson'),
515                                 get_string('hightime', 'lesson'), get_string('lowtime', 'lesson'));
516         $stattable->align = array('center', 'center', 'center', 'center', 'center', 'center');
517         $stattable->wrap = array('nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap');
518         $stattable->attributes['class'] = 'standardtable generaltable';
519         $stattable->data[] = array($avescore, $avetime, $highscore, $lowscore, $hightime, $lowtime);
521     } else {
522         // Display simple stats for the lesson.
523         echo $OUTPUT->heading(get_string('lessonstats', 'lesson'), 3);
524         $stattable = new html_table();
525         $stattable->head = array(get_string('averagetime', 'lesson'), get_string('hightime', 'lesson'),
526                                 get_string('lowtime', 'lesson'));
527         $stattable->align = array('center', 'center', 'center');
528         $stattable->wrap = array('nowrap', 'nowrap', 'nowrap');
529         $stattable->attributes['class'] = 'standardtable generaltable';
530         $stattable->data[] = array($avetime, $hightime, $lowtime);
531     }
533     echo html_writer::table($stattable);
534 } else if ($action === 'reportdetail') {
535     /**************************************************************************
536     this action is for a student detailed view and for the general detailed view
538     General flow of this section of the code
539     1.  Generate a object which holds values for the statistics for each question/answer
540     2.  Cycle through all the pages to create a object.  Foreach page, see if the student actually answered
541         the page.  Then process the page appropriatly.  Display all info about the question,
542         Highlight correct answers, show how the user answered the question, and display statistics
543         about each page
544     3.  Print out info about the try (if needed)
545     4.  Print out the object which contains all the try info
547 **************************************************************************/
548     echo $lessonoutput->header($lesson, $cm, $action, false, null, get_string('detailedstats', 'lesson'));
549     groups_print_activity_menu($cm, $url);
551     $course_context = context_course::instance($course->id);
552     if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
553         $seeallgradeslink = new moodle_url('/grade/report/grader/index.php', array('id'=>$course->id));
554         $seeallgradeslink = html_writer::link($seeallgradeslink, get_string('seeallcoursegrades', 'grades'));
555         echo $OUTPUT->box($seeallgradeslink, 'allcoursegrades');
556     }
558     $formattextdefoptions = new stdClass;
559     $formattextdefoptions->para = false;  //I'll use it widely in this page
560     $formattextdefoptions->overflowdiv = true;
562     $userid = optional_param('userid', null, PARAM_INT); // if empty, then will display the general detailed view
563     $try    = optional_param('try', null, PARAM_INT);
565     if (!empty($userid)) {
566         // Apply overrides.
567         $lesson->update_effective_access($userid);
568     }
570     $lessonpages = $lesson->load_all_pages();
571     foreach ($lessonpages as $lessonpage) {
572         if ($lessonpage->prevpageid == 0) {
573             $pageid = $lessonpage->id;
574         }
575     }
577     // now gather the stats into an object
578     $firstpageid = $pageid;
579     $pagestats = array();
580     while ($pageid != 0) { // EOL
581         $page = $lessonpages[$pageid];
582         $params = array ("lessonid" => $lesson->id, "pageid" => $page->id);
583         if ($allanswers = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND pageid = :pageid", $params, "timeseen")) {
584             // get them ready for processing
585             $orderedanswers = array();
586             foreach ($allanswers as $singleanswer) {
587                 // ordering them like this, will help to find the single attempt record that we want to keep.
588                 $orderedanswers[$singleanswer->userid][$singleanswer->retry][] = $singleanswer;
589             }
590             // this is foreach user and for each try for that user, keep one attempt record
591             foreach ($orderedanswers as $orderedanswer) {
592                 foreach($orderedanswer as $tries) {
593                     $page->stats($pagestats, $tries);
594                 }
595             }
596         } else {
597             // no one answered yet...
598         }
599         //unset($orderedanswers);  initialized above now
600         $pageid = $page->nextpageid;
601     }
603     $manager = lesson_page_type_manager::get($lesson);
604     $qtypes = $manager->get_page_type_strings();
606     $answerpages = array();
607     $answerpage = "";
608     $pageid = $firstpageid;
609     // cycle through all the pages
610     //  foreach page, add to the $answerpages[] array all the data that is needed
611     //  from the question, the users attempt, and the statistics
612     // grayout pages that the user did not answer and Branch, end of branch, cluster
613     // and end of cluster pages
614     while ($pageid != 0) { // EOL
615         $page = $lessonpages[$pageid];
616         $answerpage = new stdClass;
617         $data ='';
619         $answerdata = new stdClass;
620         // Set some defaults for the answer data.
621         $answerdata->score = null;
622         $answerdata->response = null;
623         $answerdata->responseformat = FORMAT_PLAIN;
625         $answerpage->title = format_string($page->title);
627         $options = new stdClass;
628         $options->noclean = true;
629         $options->overflowdiv = true;
630         $options->context = $context;
631         $answerpage->contents = format_text($page->contents, $page->contentsformat, $options);
633         $answerpage->qtype = $qtypes[$page->qtype].$page->option_description_string();
634         $answerpage->grayout = $page->grayout;
635         $answerpage->context = $context;
637         if (empty($userid)) {
638             // there is no userid, so set these vars and display stats.
639             $answerpage->grayout = 0;
640             $useranswer = null;
641         } elseif ($useranswers = $DB->get_records("lesson_attempts",array("lessonid"=>$lesson->id, "userid"=>$userid, "retry"=>$try,"pageid"=>$page->id), "timeseen")) {
642             // get the user's answer for this page
643             // need to find the right one
644             $i = 0;
645             foreach ($useranswers as $userattempt) {
646                 $useranswer = $userattempt;
647                 $i++;
648                 if ($lesson->maxattempts == $i) {
649                     break; // reached maxattempts, break out
650                 }
651             }
652         } else {
653             // user did not answer this page, gray it out and set some nulls
654             $answerpage->grayout = 1;
655             $useranswer = null;
656         }
657         $i = 0;
658         $n = 0;
659         $answerpages[] = $page->report_answers(clone($answerpage), clone($answerdata), $useranswer, $pagestats, $i, $n);
660         $pageid = $page->nextpageid;
661     }
663     /// actually start printing something
664     $table = new html_table();
665     $table->wrap = array();
666     $table->width = "60%";
667     if (!empty($userid)) {
668         // if looking at a students try, print out some basic stats at the top
670             // print out users name
671             //$headingobject->lastname = $students[$userid]->lastname;
672             //$headingobject->firstname = $students[$userid]->firstname;
673             //$headingobject->attempt = $try + 1;
674             //print_heading(get_string("studentattemptlesson", "lesson", $headingobject));
675         echo $OUTPUT->heading(get_string('attempt', 'lesson', $try+1), 3);
677         $table->head = array();
678         $table->align = array('right', 'left');
679         $table->attributes['class'] = 'compacttable generaltable form-inline';
681         $params = array("lessonid"=>$lesson->id, "userid"=>$userid);
682         if (!$grades = $DB->get_records_select("lesson_grades", "lessonid = :lessonid and userid = :userid", $params, "completed", "*", $try, 1)) {
683             $grade = -1;
684             $completed = -1;
685         } else {
686             $grade = current($grades);
687             $completed = $grade->completed;
688             $grade = round($grade->grade, 2);
689         }
690         if (!$times = $DB->get_records_select("lesson_timer", "lessonid = :lessonid and userid = :userid", $params, "starttime", "*", $try, 1)) {
691             $timetotake = -1;
692         } else {
693             $timetotake = current($times);
694             $timetotake = $timetotake->lessontime - $timetotake->starttime;
695         }
697         if ($timetotake == -1 || $completed == -1 || $grade == -1) {
698             $table->align = array("center");
700             $table->data[] = array(get_string("notcompleted", "lesson"));
701         } else {
702             $user = $DB->get_record('user', array('id' => $userid));
704             $gradeinfo = lesson_grade($lesson, $try, $user->id);
706             $table->data[] = array(get_string('name').':', $OUTPUT->user_picture($user, array('courseid'=>$course->id)).fullname($user, true));
707             $table->data[] = array(get_string("timetaken", "lesson").":", format_time($timetotake));
708             $table->data[] = array(get_string("completed", "lesson").":", userdate($completed));
709             $table->data[] = array(get_string('rawgrade', 'lesson').':', $gradeinfo->earned.'/'.$gradeinfo->total);
710             $table->data[] = array(get_string("grade", "lesson").":", $grade."%");
711         }
712         echo html_writer::table($table);
714         // Don't want this class for later tables
715         $table->attributes['class'] = '';
716     }
719     $table->align = array('left', 'left');
720     $table->size = array('70%', null);
721     $table->attributes['class'] = 'compacttable generaltable form-inline';
723     foreach ($answerpages as $page) {
724         unset($table->data);
725         if ($page->grayout) { // set the color of text
726             $fontstart = "<span class=\"dimmed\">";
727             $fontend = "</font>";
728             $fontstart2 = $fontstart;
729             $fontend2 = $fontend;
730         } else {
731             $fontstart = "";
732             $fontend = "";
733             $fontstart2 = "";
734             $fontend2 = "";
735         }
737         $table->head = array($fontstart2.$page->qtype.": ".format_string($page->title).$fontend2, $fontstart2.get_string("classstats", "lesson").$fontend2);
738         $table->data[] = array($fontstart.get_string("question", "lesson").": <br />".$fontend.$fontstart2.$page->contents.$fontend2, " ");
739         $table->data[] = array($fontstart.get_string("answer", "lesson").":".$fontend, ' ');
740         // apply the font to each answer
741         if (!empty($page->answerdata)) {
742             foreach ($page->answerdata->answers as $answer){
743                 $modified = array();
744                 foreach ($answer as $single) {
745                     // need to apply a font to each one
746                     $modified[] = $fontstart2.$single.$fontend2;
747                 }
748                 $table->data[] = $modified;
749             }
750             if (isset($page->answerdata->response)) {
751                 $table->data[] = array($fontstart.get_string("response", "lesson").": <br />".$fontend
752                         .$fontstart2.$page->answerdata->response.$fontend2, " ");
753             }
754             $table->data[] = array($page->answerdata->score, " ");
755         } else {
756             $table->data[] = array(get_string('didnotanswerquestion', 'lesson'), " ");
757         }
758         echo html_writer::start_tag('div', array('class' => 'no-overflow'));
759         echo html_writer::table($table);
760         echo html_writer::end_tag('div');
761     }
762 } else {
763     print_error('unknowaction');
766 /// Finish the page
767 echo $OUTPUT->footer();