Merge branch 'MDL-57643-master' of git://github.com/jleyva/moodle
[moodle.git] / mod / lesson / view.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  * This page prints a particular instance of lesson
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(__DIR__ . '/../../config.php');
27 require_once($CFG->dirroot.'/mod/lesson/locallib.php');
28 require_once($CFG->dirroot.'/mod/lesson/view_form.php');
29 require_once($CFG->libdir . '/completionlib.php');
30 require_once($CFG->libdir . '/grade/constants.php');
32 $id      = required_param('id', PARAM_INT);             // Course Module ID
33 $pageid  = optional_param('pageid', null, PARAM_INT);   // Lesson Page ID
34 $edit    = optional_param('edit', -1, PARAM_BOOL);
35 $userpassword = optional_param('userpassword','',PARAM_RAW);
36 $backtocourse = optional_param('backtocourse', false, PARAM_RAW);
38 $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST);
39 $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
40 $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST), $cm);
42 require_login($course, false, $cm);
44 if ($backtocourse) {
45     redirect(new moodle_url('/course/view.php', array('id'=>$course->id)));
46 }
48 // Apply overrides.
49 $lesson->update_effective_access($USER->id);
51 // Mark as viewed
52 $completion = new completion_info($course);
53 $completion->set_module_viewed($cm);
55 $url = new moodle_url('/mod/lesson/view.php', array('id'=>$id));
56 if ($pageid !== null) {
57     $url->param('pageid', $pageid);
58 }
59 $PAGE->set_url($url);
60 $PAGE->force_settings_menu();
62 $context = $lesson->context;
63 $canmanage = $lesson->can_manage();
65 $lessonoutput = $PAGE->get_renderer('mod_lesson');
67 $reviewmode = $lesson->is_in_review_mode();
69 if ($lesson->usepassword && !empty($userpassword)) {
70     require_sesskey();
71 }
73 // Check these for students only TODO: Find a better method for doing this!
74 if ($timerestriction = $lesson->get_time_restriction_status()) {  // Deadline restrictions.
75     echo $lessonoutput->header($lesson, $cm, '', false, null, get_string('notavailable'));
76     echo $lessonoutput->lesson_inaccessible(get_string($timerestriction->reason, 'lesson', userdate($timerestriction->time)));
77     echo $lessonoutput->footer();
78     exit();
79 } else if ($passwordrestriction = $lesson->get_password_restriction_status($userpassword)) { // Password protected lesson code.
80     echo $lessonoutput->header($lesson, $cm, '', false, null, get_string('passwordprotectedlesson', 'lesson', format_string($lesson->name)));
81     echo $lessonoutput->login_prompt($lesson, $userpassword !== '');
82     echo $lessonoutput->footer();
83     exit();
84 } else if ($dependenciesrestriction = $lesson->get_dependencies_restriction_status()) { // Check for dependencies.
85     echo $lessonoutput->header($lesson, $cm, '', false, null, get_string('completethefollowingconditions', 'lesson', format_string($lesson->name)));
86     echo $lessonoutput->dependancy_errors($dependenciesrestriction->dependentlesson, $dependenciesrestriction->errors);
87     echo $lessonoutput->footer();
88     exit();
89 }
91 // This is called if a student leaves during a lesson.
92 if ($pageid == LESSON_UNSEENBRANCHPAGE) {
93     $pageid = lesson_unseen_question_jump($lesson, $USER->id, $pageid);
94 }
96 // To avoid multiple calls, store the magic property firstpage.
97 $lessonfirstpage = $lesson->firstpage;
98 $lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
100 // display individual pages and their sets of answers
101 // if pageid is EOL then the end of the lesson has been reached
102 // for flow, changed to simple echo for flow styles, michaelp, moved lesson name and page title down
103 $attemptflag = false;
104 if (empty($pageid)) {
105     // make sure there are pages to view
106     if (!$lessonfirstpageid) {
107         if (!$canmanage) {
108             $lesson->add_message(get_string('lessonnotready2', 'lesson')); // a nice message to the student
109         } else {
110             if (!$DB->count_records('lesson_pages', array('lessonid'=>$lesson->id))) {
111                 redirect("$CFG->wwwroot/mod/lesson/edit.php?id=$cm->id"); // no pages - redirect to add pages
112             } else {
113                 $lesson->add_message(get_string('lessonpagelinkingbroken', 'lesson'));  // ok, bad mojo
114             }
115         }
116     }
118     // if no pageid given see if the lesson has been started
119     $retries = $lesson->count_user_retries($USER->id);
120     if ($retries > 0) {
121         $attemptflag = true;
122     }
124     if (isset($USER->modattempts[$lesson->id])) {
125         unset($USER->modattempts[$lesson->id]);  // if no pageid, then student is NOT reviewing
126     }
128     $lastpageseen = $lesson->get_last_page_seen($retries);
130     // Check to see if end of lesson was reached.
131     if (($lastpageseen !== false && ($lastpageseen != LESSON_EOL))) {
132         // End not reached. Check if the user left.
133         if ($lesson->left_during_timed_session($retries)) {
135             echo $lessonoutput->header($lesson, $cm, '', false, null, get_string('leftduringtimedsession', 'lesson'));
136             if ($lesson->timelimit) {
137                 if ($lesson->retake) {
138                     $continuelink = new single_button(new moodle_url('/mod/lesson/view.php',
139                             array('id' => $cm->id, 'pageid' => $lesson->firstpageid, 'startlastseen' => 'no')),
140                             get_string('continue', 'lesson'), 'get');
142                     echo html_writer::div($lessonoutput->message(get_string('leftduringtimed', 'lesson'), $continuelink),
143                             'center leftduring');
145                 } else {
146                     $courselink = new single_button(new moodle_url('/course/view.php',
147                             array('id' => $PAGE->course->id)), get_string('returntocourse', 'lesson'), 'get');
149                     echo html_writer::div($lessonoutput->message(get_string('leftduringtimednoretake', 'lesson'), $courselink),
150                             'center leftduring');
151                 }
152             } else {
153                 echo $lessonoutput->continue_links($lesson, $lastpageseen);
154             }
155             echo $lessonoutput->footer();
156             exit();
157         }
158     }
160     if ($attemptflag) {
161         if (!$lesson->retake) {
162             echo $lessonoutput->header($lesson, $cm, 'view', '', null, get_string("noretake", "lesson"));
163             $courselink = new single_button(new moodle_url('/course/view.php', array('id'=>$PAGE->course->id)), get_string('returntocourse', 'lesson'), 'get');
164             echo $lessonoutput->message(get_string("noretake", "lesson"), $courselink);
165             echo $lessonoutput->footer();
166             exit();
167         }
168     }
169     // start at the first page
170     if (!$pageid = $lessonfirstpageid) {
171         echo $lessonoutput->header($lesson, $cm, 'view', '', null);
172         // Lesson currently has no content. A message for display has been prepared and will be displayed by the header method
173         // of the lesson renderer.
174         echo $lessonoutput->footer();
175         exit();
176     }
177     /// This is the code for starting a timed test
178     if(!isset($USER->startlesson[$lesson->id]) && !$canmanage) {
179         $lesson->start_timer();
180     }
183 $currenttab = 'view';
184 $extraeditbuttons = false;
185 $lessonpageid = null;
186 $timer = null;
188 if ($pageid != LESSON_EOL) {
189     /// This is the code updates the lessontime for a timed test
190     $startlastseen = optional_param('startlastseen', '', PARAM_ALPHA);
192     $page = $lesson->load_page($pageid);
193     // Check if the page is of a special type and if so take any nessecary action
194     $newpageid = $page->callback_on_view($canmanage);
195     if (is_numeric($newpageid)) {
196         $page = $lesson->load_page($newpageid);
197     }
199     // Trigger module viewed event.
200     $event = \mod_lesson\event\course_module_viewed::create(array(
201         'objectid' => $lesson->id,
202         'context' => $context
203     ));
204     $event->add_record_snapshot('course_modules', $cm);
205     $event->add_record_snapshot('course', $course);
206     $event->trigger();
208     // This is where several messages (usually warnings) are displayed
209     // all of this is displayed above the actual page
211     // check to see if the user can see the left menu
212     if (!$canmanage) {
213         $lesson->displayleft = lesson_displayleftif($lesson);
215         $continue = ($startlastseen !== '');
216         $restart  = ($continue && $startlastseen == 'yes');
217         $timer = $lesson->update_timer($continue, $restart);
219         if ($lesson->timelimit) {
220             $timeleft = $timer->starttime + $lesson->timelimit - time();
221             if ($timeleft <= 0) {
222                 // Out of time
223                 $lesson->add_message(get_string('eolstudentoutoftime', 'lesson'));
224                 redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$cm->id,'pageid'=>LESSON_EOL, 'outoftime'=>'normal')));
225                 die; // Shouldn't be reached, but make sure
226             } else if ($timeleft < 60) {
227                 // One minute warning
228                 $lesson->add_message(get_string('studentoneminwarning', 'lesson'));
229             }
230         }
232         if ($page->qtype == LESSON_PAGE_BRANCHTABLE && $lesson->minquestions) {
233             // tell student how many questions they have seen, how many are required and their grade
234             $ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
235             $gradeinfo = lesson_grade($lesson, $ntries);
236             if ($gradeinfo->attempts) {
237                 if ($gradeinfo->nquestions < $lesson->minquestions) {
238                     $a = new stdClass;
239                     $a->nquestions   = $gradeinfo->nquestions;
240                     $a->minquestions = $lesson->minquestions;
241                     $lesson->add_message(get_string('numberofpagesviewednotice', 'lesson', $a));
242                 }
244                 if (!$reviewmode && !$lesson->retake){
245                     $lesson->add_message(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'notify');
246                     if ($lesson->grade != GRADE_TYPE_NONE) {
247                         $a = new stdClass;
248                         $a->grade = number_format($gradeinfo->grade * $lesson->grade / 100, 1);
249                         $a->total = $lesson->grade;
250                         $lesson->add_message(get_string('yourcurrentgradeisoutof', 'lesson', $a), 'notify');
251                     }
252                 }
253             }
254         }
255     } else {
256         $timer = null;
257         if ($lesson->timelimit) {
258             $lesson->add_message(get_string('teachertimerwarning', 'lesson'));
259         }
260         if (lesson_display_teacher_warning($lesson)) {
261             // This is the warning msg for teachers to inform them that cluster
262             // and unseen does not work while logged in as a teacher
263             $warningvars = new stdClass();
264             $warningvars->cluster = get_string('clusterjump', 'lesson');
265             $warningvars->unseen = get_string('unseenpageinbranch', 'lesson');
266             $lesson->add_message(get_string('teacherjumpwarning', 'lesson', $warningvars));
267         }
268     }
270     $PAGE->set_subpage($page->id);
271     $currenttab = 'view';
272     $extraeditbuttons = true;
273     $lessonpageid = $page->id;
274     $extrapagetitle = $page->title;
276     if (($edit != -1) && $PAGE->user_allowed_editing()) {
277         $USER->editing = $edit;
278     }
280     if (is_array($page->answers) && count($page->answers)>0) {
281         // this is for modattempts option.  Find the users previous answer to this page,
282         //   and then display it below in answer processing
283         if (isset($USER->modattempts[$lesson->id])) {
284             $retries = $lesson->count_user_retries($USER->id);
285             if (!$attempts = $lesson->get_attempts($retries-1, false, $page->id)) {
286                 print_error('cannotfindpreattempt', 'lesson');
287             }
288             $attempt = end($attempts);
289             $USER->modattempts[$lesson->id] = $attempt;
290         } else {
291             $attempt = false;
292         }
293         $lessoncontent = $lessonoutput->display_page($lesson, $page, $attempt);
294     } else {
295         $data = new stdClass;
296         $data->id = $PAGE->cm->id;
297         $data->pageid = $page->id;
298         $data->newpageid = $lesson->get_next_page($page->nextpageid);
300         $customdata = array(
301             'title'     => $page->title,
302             'contents'  => $page->get_contents()
303         );
304         $mform = new lesson_page_without_answers($CFG->wwwroot.'/mod/lesson/continue.php', $customdata);
305         $mform->set_data($data);
306         ob_start();
307         $mform->display();
308         $lessoncontent = ob_get_contents();
309         ob_end_clean();
310     }
312     lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer);
313     echo $lessonoutput->header($lesson, $cm, $currenttab, $extraeditbuttons, $lessonpageid, $extrapagetitle);
314     if ($attemptflag) {
315         // We are using level 3 header because attempt heading is a sub-heading of lesson title (MDL-30911).
316         echo $OUTPUT->heading(get_string('attempt', 'lesson', $retries), 3);
317     }
318     /// This calculates and prints the ongoing score
319     if ($lesson->ongoing && !empty($pageid) && !$reviewmode) {
320         echo $lessonoutput->ongoing_score($lesson);
321     }
322     if ($lesson->displayleft) {
323         echo '<a name="maincontent" id="maincontent" title="' . get_string('anchortitle', 'lesson') . '"></a>';
324     }
325     echo $lessoncontent;
326     echo $lessonoutput->progress_bar($lesson);
327     echo $lessonoutput->footer();
329 } else {
331     $lessoncontent = '';
332     // end of lesson reached work out grade
333     // Used to check to see if the student ran out of time
334     $outoftime = optional_param('outoftime', '', PARAM_ALPHA);
336     $ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
337     if (isset($USER->modattempts[$lesson->id])) {
338         $ntries--;  // need to look at the old attempts :)
339     }
340     $gradelesson = true;
341     $gradeinfo = lesson_grade($lesson, $ntries);
342     if ($lesson->custom && !$canmanage) {
343         // Before we calculate the custom score make sure they answered the minimum
344         // number of questions. We only need to do this for custom scoring as we can
345         // not get the miniumum score the user should achieve. If we are not using
346         // custom scoring (so all questions are valued as 1) then we simply check if
347         // they answered more than the minimum questions, if not, we mark it out of the
348         // number specified in the minimum questions setting - which is done in lesson_grade().
349         // Get the number of answers given.
350         if ($gradeinfo->nquestions < $lesson->minquestions) {
351             $gradelesson = false;
352             $a = new stdClass;
353             $a->nquestions = $gradeinfo->nquestions;
354             $a->minquestions = $lesson->minquestions;
355             $lessoncontent .= $OUTPUT->box_start('generalbox boxaligncenter');
356             $lesson->add_message(get_string('numberofpagesviewednotice', 'lesson', $a));
357         }
358     }
359     if ($gradelesson) {
360         // We are using level 3 header because the page title is a sub-heading of lesson title (MDL-30911).
361         $lessoncontent .= $OUTPUT->heading(get_string("congratulations", "lesson"), 3);
362         $lessoncontent .= $OUTPUT->box_start('generalbox boxaligncenter');
363     }
364     if (!$canmanage) {
365         if ($gradelesson) {
366             // Store this now before any modifications to pages viewed.
367             $progressbar = $lessonoutput->progress_bar($lesson);
368             // Update the clock / get time information for this user.
369             $lesson->stop_timer();
371             // Update completion state.
372             $completion = new completion_info($course);
373             if ($completion->is_enabled($cm) && $lesson->completionendreached) {
374                 $completion->update_state($cm, COMPLETION_COMPLETE);
375             }
377             if ($lesson->completiontimespent > 0) {
378                 $duration = $DB->get_field_sql(
379                     "SELECT SUM(lessontime - starttime)
380                                    FROM {lesson_timer}
381                                   WHERE lessonid = :lessonid
382                                     AND userid = :userid",
383                     array('userid' => $USER->id, 'lessonid' => $lesson->id));
384                 if (!$duration) {
385                     $duration = 0;
386                 }
388                 // If student has not spend enough time in the lesson, display a message.
389                 if ($duration < $lesson->completiontimespent) {
390                     $a = new stdClass;
391                     $a->timespent = format_time($duration);
392                     $a->timerequired = format_time($lesson->completiontimespent);
393                     $lessoncontent .= $lessonoutput->paragraph(get_string("notenoughtimespent", "lesson", $a), 'center');
394                 }
395             }
398             if ($gradeinfo->attempts) {
399                 if (!$lesson->custom) {
400                     $lessoncontent .= $lessonoutput->paragraph(get_string("numberofpagesviewed", "lesson", $gradeinfo->nquestions), 'center');
401                     if ($lesson->minquestions) {
402                         if ($gradeinfo->nquestions < $lesson->minquestions) {
403                             // print a warning and set nviewed to minquestions
404                             $lessoncontent .= $lessonoutput->paragraph(get_string("youshouldview", "lesson", $lesson->minquestions), 'center');
405                         }
406                     }
407                     $lessoncontent .= $lessonoutput->paragraph(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'center');
408                 }
409                 $a = new stdClass;
410                 $a->score = $gradeinfo->earned;
411                 $a->grade = $gradeinfo->total;
412                 if ($gradeinfo->nmanual) {
413                     $a->tempmaxgrade = $gradeinfo->total - $gradeinfo->manualpoints;
414                     $a->essayquestions = $gradeinfo->nmanual;
415                     $lessoncontent .= $OUTPUT->box(get_string("displayscorewithessays", "lesson", $a), 'center');
416                 } else {
417                     $lessoncontent .= $OUTPUT->box(get_string("displayscorewithoutessays", "lesson", $a), 'center');
418                 }
419                 if ($lesson->grade != GRADE_TYPE_NONE) {
420                     $a = new stdClass;
421                     $a->grade = number_format($gradeinfo->grade * $lesson->grade / 100, 1);
422                     $a->total = $lesson->grade;
423                     $lessoncontent .= $lessonoutput->paragraph(get_string("yourcurrentgradeisoutof", "lesson", $a), 'center');
424                 }
426                 $grade = new stdClass();
427                 $grade->lessonid = $lesson->id;
428                 $grade->userid = $USER->id;
429                 $grade->grade = $gradeinfo->grade;
430                 $grade->completed = time();
431                 if (isset($USER->modattempts[$lesson->id])) { // If reviewing, make sure update old grade record.
432                     if (!$grades = $DB->get_records("lesson_grades",
433                         array("lessonid" => $lesson->id, "userid" => $USER->id), "completed DESC", '*', 0, 1)) {
434                         print_error('cannotfindgrade', 'lesson');
435                     }
436                     $oldgrade = array_shift($grades);
437                     $grade->id = $oldgrade->id;
438                     $DB->update_record("lesson_grades", $grade);
439                 } else {
440                     $newgradeid = $DB->insert_record("lesson_grades", $grade);
441                 }
442             } else {
443                 if ($lesson->timelimit) {
444                     if ($outoftime == 'normal') {
445                         $grade = new stdClass();
446                         $grade->lessonid = $lesson->id;
447                         $grade->userid = $USER->id;
448                         $grade->grade = 0;
449                         $grade->completed = time();
450                         $newgradeid = $DB->insert_record("lesson_grades", $grade);
451                         $lessoncontent .= $lessonoutput->paragraph(get_string("eolstudentoutoftimenoanswers", "lesson"));
452                     }
453                 } else {
454                     $lessoncontent .= $lessonoutput->paragraph(get_string("welldone", "lesson"));
455                 }
456             }
458             // update central gradebook
459             lesson_update_grades($lesson, $USER->id);
460             $lessoncontent .= $progressbar;
461         }
462     } else {
463         // display for teacher
464         if ($lesson->grade != GRADE_TYPE_NONE) {
465             $lessoncontent .= $lessonoutput->paragraph(get_string("displayofgrade", "lesson"), 'center');
466         }
467     }
468     $lessoncontent .= $OUTPUT->box_end(); //End of Lesson button to Continue.
470     if ($lesson->modattempts && !$canmanage) {
471         // make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time
472         // look at the attempt records to find the first QUESTION page that the user answered, then use that page id
473         // to pass to view again.  This is slick cause it wont call the empty($pageid) code
474         // $ntries is decremented above
475         if (!$attempts = $lesson->get_attempts($ntries)) {
476             $attempts = array();
477             $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id));
478         } else {
479             $firstattempt = current($attempts);
480             $pageid = $firstattempt->pageid;
481             // IF the student wishes to review, need to know the last question page that the student answered.  This will help to make
482             // sure that the student can leave the lesson via pushing the continue button.
483             $lastattempt = end($attempts);
484             $USER->modattempts[$lesson->id] = $lastattempt->pageid;
486             $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$pageid));
487         }
488         $lessoncontent .= html_writer::link($url, get_string('reviewlesson', 'lesson'),
489                 array('class' => 'centerpadded lessonbutton standardbutton p-r-1'));
490     } elseif ($lesson->modattempts && $canmanage) {
491         $lessoncontent .= $lessonoutput->paragraph(get_string("modattemptsnoteacher", "lesson"), 'centerpadded');
492     }
494     if ($lesson->activitylink) {
495         $lessoncontent .= $lesson->link_for_activitylink();
496     }
498     $url = new moodle_url('/course/view.php', array('id'=>$course->id));
499     $lessoncontent .= html_writer::link($url, get_string('returnto', 'lesson', format_string($course->fullname, true)),
500             array('class' => 'centerpadded lessonbutton standardbutton p-r-1'));
502     if (has_capability('gradereport/user:view', context_course::instance($course->id))
503             && $course->showgrades && $lesson->grade != 0 && !$lesson->practice) {
504         $url = new moodle_url('/grade/index.php', array('id' => $course->id));
505         $lessoncontent .= html_writer::link($url, get_string('viewgrades', 'lesson'),
506             array('class' => 'centerpadded lessonbutton standardbutton p-r-1'));
507     }
509     lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer);
510     echo $lessonoutput->header($lesson, $cm, $currenttab, $extraeditbuttons, $lessonpageid, get_string("congratulations", "lesson"));
511     echo $lessoncontent;
512     echo $lessonoutput->footer();