MDL-57961 course: change wording of module availability
[moodle.git] / mod / lesson / lib.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  * Standard library of functions and constants for 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 later
24  **/
26 defined('MOODLE_INTERNAL') || die();
28 /* Do not include any libraries here! */
30 /**
31  * Given an object containing all the necessary data,
32  * (defined by the form in mod_form.php) this function
33  * will create a new instance and return the id number
34  * of the new instance.
35  *
36  * @global object
37  * @global object
38  * @param object $lesson Lesson post data from the form
39  * @return int
40  **/
41 function lesson_add_instance($data, $mform) {
42     global $DB;
44     $cmid = $data->coursemodule;
45     $draftitemid = $data->mediafile;
46     $context = context_module::instance($cmid);
48     lesson_process_pre_save($data);
50     unset($data->mediafile);
51     $lessonid = $DB->insert_record("lesson", $data);
52     $data->id = $lessonid;
54     lesson_update_media_file($lessonid, $context, $draftitemid);
56     lesson_process_post_save($data);
58     lesson_grade_item_update($data);
60     return $lessonid;
61 }
63 /**
64  * Given an object containing all the necessary data,
65  * (defined by the form in mod_form.php) this function
66  * will update an existing instance with new data.
67  *
68  * @global object
69  * @param object $lesson Lesson post data from the form
70  * @return boolean
71  **/
72 function lesson_update_instance($data, $mform) {
73     global $DB;
75     $data->id = $data->instance;
76     $cmid = $data->coursemodule;
77     $draftitemid = $data->mediafile;
78     $context = context_module::instance($cmid);
80     lesson_process_pre_save($data);
82     unset($data->mediafile);
83     $DB->update_record("lesson", $data);
85     lesson_update_media_file($data->id, $context, $draftitemid);
87     lesson_process_post_save($data);
89     // update grade item definition
90     lesson_grade_item_update($data);
92     // update grades - TODO: do it only when grading style changes
93     lesson_update_grades($data, 0, false);
95     return true;
96 }
98 /**
99  * This function updates the events associated to the lesson.
100  * If $override is non-zero, then it updates only the events
101  * associated with the specified override.
102  *
103  * @uses LESSON_MAX_EVENT_LENGTH
104  * @param object $lesson the lesson object.
105  * @param object $override (optional) limit to a specific override
106  */
107 function lesson_update_events($lesson, $override = null) {
108     global $CFG, $DB;
110     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
111     require_once($CFG->dirroot . '/calendar/lib.php');
113     // Load the old events relating to this lesson.
114     $conds = array('modulename' => 'lesson',
115                    'instance' => $lesson->id);
116     if (!empty($override)) {
117         // Only load events for this override.
118         if (isset($override->userid)) {
119             $conds['userid'] = $override->userid;
120         } else {
121             $conds['groupid'] = $override->groupid;
122         }
123     }
124     $oldevents = $DB->get_records('event', $conds);
126     // Now make a to-do list of all that needs to be updated.
127     if (empty($override)) {
128         // We are updating the primary settings for the lesson, so we need to add all the overrides.
129         $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id));
130         // As well as the original lesson (empty override).
131         $overrides[] = new stdClass();
132     } else {
133         // Just do the one override.
134         $overrides = array($override);
135     }
137     // Get group override priorities.
138     $grouppriorities = lesson_get_group_override_priorities($lesson->id);
140     foreach ($overrides as $current) {
141         $groupid   = isset($current->groupid) ? $current->groupid : 0;
142         $userid    = isset($current->userid) ? $current->userid : 0;
143         $available  = isset($current->available) ? $current->available : $lesson->available;
144         $deadline = isset($current->deadline) ? $current->deadline : $lesson->deadline;
146         // Only add open/close events for an override if they differ from the lesson default.
147         $addopen  = empty($current->id) || !empty($current->available);
148         $addclose = empty($current->id) || !empty($current->deadline);
150         if (!empty($lesson->coursemodule)) {
151             $cmid = $lesson->coursemodule;
152         } else {
153             $cmid = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)->id;
154         }
156         $event = new stdClass();
157         $event->description = format_module_intro('lesson', $lesson, $cmid);
158         // Events module won't show user events when the courseid is nonzero.
159         $event->courseid    = ($userid) ? 0 : $lesson->course;
160         $event->groupid     = $groupid;
161         $event->userid      = $userid;
162         $event->modulename  = 'lesson';
163         $event->instance    = $lesson->id;
164         $event->timestart   = $available;
165         $event->timeduration = max($deadline - $available, 0);
166         $event->visible     = instance_is_visible('lesson', $lesson);
167         $event->eventtype   = 'open';
169         // Determine the event name and priority.
170         if ($groupid) {
171             // Group override event.
172             $params = new stdClass();
173             $params->lesson = $lesson->name;
174             $params->group = groups_get_group_name($groupid);
175             if ($params->group === false) {
176                 // Group doesn't exist, just skip it.
177                 continue;
178             }
179             $eventname = get_string('overridegroupeventname', 'lesson', $params);
180             // Set group override priority.
181             if ($grouppriorities !== null) {
182                 $openpriorities = $grouppriorities['open'];
183                 if (isset($openpriorities[$available])) {
184                     $event->priority = $openpriorities[$available];
185                 }
186             }
187         } else if ($userid) {
188             // User override event.
189             $params = new stdClass();
190             $params->lesson = $lesson->name;
191             $eventname = get_string('overrideusereventname', 'lesson', $params);
192             // Set user override priority.
193             $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
194         } else {
195             // The parent event.
196             $eventname = $lesson->name;
197         }
199         if ($addopen or $addclose) {
200             // Separate start and end events.
201             $event->timeduration  = 0;
202             if ($available && $addopen) {
203                 if ($oldevent = array_shift($oldevents)) {
204                     $event->id = $oldevent->id;
205                 } else {
206                     unset($event->id);
207                 }
208                 $event->name = $eventname.' ('.get_string('lessonopens', 'lesson').')';
209                 // The method calendar_event::create will reuse a db record if the id field is set.
210                 calendar_event::create($event);
211             }
212             if ($deadline && $addclose) {
213                 if ($oldevent = array_shift($oldevents)) {
214                     $event->id = $oldevent->id;
215                 } else {
216                     unset($event->id);
217                 }
218                 $event->name      = $eventname.' ('.get_string('lessoncloses', 'lesson').')';
219                 $event->timestart = $deadline;
220                 $event->eventtype = 'close';
221                 if ($groupid && $grouppriorities !== null) {
222                     $closepriorities = $grouppriorities['close'];
223                     if (isset($closepriorities[$deadline])) {
224                         $event->priority = $closepriorities[$deadline];
225                     }
226                 }
227                 calendar_event::create($event);
228             }
229         }
230     }
232     // Delete any leftover events.
233     foreach ($oldevents as $badevent) {
234         $badevent = calendar_event::load($badevent);
235         $badevent->delete();
236     }
239 /**
240  * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson.
241  *
242  * @param int $lessonid The quiz ID.
243  * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
244  */
245 function lesson_get_group_override_priorities($lessonid) {
246     global $DB;
248     // Fetch group overrides.
249     $where = 'lessonid = :lessonid AND groupid IS NOT NULL';
250     $params = ['lessonid' => $lessonid];
251     $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline');
252     if (!$overrides) {
253         return null;
254     }
256     $grouptimeopen = [];
257     $grouptimeclose = [];
258     foreach ($overrides as $override) {
259         if ($override->available !== null && !in_array($override->available, $grouptimeopen)) {
260             $grouptimeopen[] = $override->available;
261         }
262         if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) {
263             $grouptimeclose[] = $override->deadline;
264         }
265     }
267     // Sort open times in descending manner. The earlier open time gets higher priority.
268     rsort($grouptimeopen);
269     // Set priorities.
270     $opengrouppriorities = [];
271     $openpriority = 1;
272     foreach ($grouptimeopen as $timeopen) {
273         $opengrouppriorities[$timeopen] = $openpriority++;
274     }
276     // Sort close times in ascending manner. The later close time gets higher priority.
277     sort($grouptimeclose);
278     // Set priorities.
279     $closegrouppriorities = [];
280     $closepriority = 1;
281     foreach ($grouptimeclose as $timeclose) {
282         $closegrouppriorities[$timeclose] = $closepriority++;
283     }
285     return [
286         'open' => $opengrouppriorities,
287         'close' => $closegrouppriorities
288     ];
291 /**
292  * This standard function will check all instances of this module
293  * and make sure there are up-to-date events created for each of them.
294  * If courseid = 0, then every lesson event in the site is checked, else
295  * only lesson events belonging to the course specified are checked.
296  * This function is used, in its new format, by restore_refresh_events()
297  *
298  * @param int $courseid
299  * @return bool
300  */
301 function lesson_refresh_events($courseid = 0) {
302     global $DB;
304     if ($courseid == 0) {
305         if (!$lessons = $DB->get_records('lesson')) {
306             return true;
307         }
308     } else {
309         if (!$lessons = $DB->get_records('lesson', array('course' => $courseid))) {
310             return true;
311         }
312     }
314     foreach ($lessons as $lesson) {
315         lesson_update_events($lesson);
316     }
318     return true;
321 /**
322  * Given an ID of an instance of this module,
323  * this function will permanently delete the instance
324  * and any data that depends on it.
325  *
326  * @global object
327  * @param int $id
328  * @return bool
329  */
330 function lesson_delete_instance($id) {
331     global $DB, $CFG;
332     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
334     $lesson = $DB->get_record("lesson", array("id"=>$id), '*', MUST_EXIST);
335     $lesson = new lesson($lesson);
336     return $lesson->delete();
339 /**
340  * Return a small object with summary information about what a
341  * user has done with a given particular instance of this module
342  * Used for user activity reports.
343  * $return->time = the time they did it
344  * $return->info = a short text description
345  *
346  * @global object
347  * @param object $course
348  * @param object $user
349  * @param object $mod
350  * @param object $lesson
351  * @return object
352  */
353 function lesson_user_outline($course, $user, $mod, $lesson) {
354     global $CFG, $DB;
356     require_once("$CFG->libdir/gradelib.php");
357     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
358     $return = new stdClass();
360     if (empty($grades->items[0]->grades)) {
361         $return->info = get_string("nolessonattempts", "lesson");
362     } else {
363         $grade = reset($grades->items[0]->grades);
364         if (empty($grade->grade)) {
366             // Check to see if it an ungraded / incomplete attempt.
367             $sql = "SELECT *
368                       FROM {lesson_timer}
369                      WHERE lessonid = :lessonid
370                        AND userid = :userid
371                   ORDER BY starttime DESC";
372             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
374             if ($attempts = $DB->get_records_sql($sql, $params, 0, 1)) {
375                 $attempt = reset($attempts);
376                 if ($attempt->completed) {
377                     $return->info = get_string("completed", "lesson");
378                 } else {
379                     $return->info = get_string("notyetcompleted", "lesson");
380                 }
381                 $return->time = $attempt->lessontime;
382             } else {
383                 $return->info = get_string("nolessonattempts", "lesson");
384             }
385         } else {
386             $return->info = get_string("grade") . ': ' . $grade->str_long_grade;
388             // Datesubmitted == time created. dategraded == time modified or time overridden.
389             // If grade was last modified by the user themselves use date graded. Otherwise use date submitted.
390             // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704.
391             if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
392                 $return->time = $grade->dategraded;
393             } else {
394                 $return->time = $grade->datesubmitted;
395             }
396         }
397     }
398     return $return;
401 /**
402  * Print a detailed representation of what a  user has done with
403  * a given particular instance of this module, for user activity reports.
404  *
405  * @global object
406  * @param object $course
407  * @param object $user
408  * @param object $mod
409  * @param object $lesson
410  * @return bool
411  */
412 function lesson_user_complete($course, $user, $mod, $lesson) {
413     global $DB, $OUTPUT, $CFG;
415     require_once("$CFG->libdir/gradelib.php");
417     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
419     // Display the grade and feedback.
420     if (empty($grades->items[0]->grades)) {
421         echo $OUTPUT->container(get_string("nolessonattempts", "lesson"));
422     } else {
423         $grade = reset($grades->items[0]->grades);
424         if (empty($grade->grade)) {
425             // Check to see if it an ungraded / incomplete attempt.
426             $sql = "SELECT *
427                       FROM {lesson_timer}
428                      WHERE lessonid = :lessonid
429                        AND userid = :userid
430                      ORDER by starttime desc";
431             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
433             if ($attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) {
434                 if ($attempt->completed) {
435                     $status = get_string("completed", "lesson");
436                 } else {
437                     $status = get_string("notyetcompleted", "lesson");
438                 }
439             } else {
440                 $status = get_string("nolessonattempts", "lesson");
441             }
442         } else {
443             $status = get_string("grade") . ': ' . $grade->str_long_grade;
444         }
446         // Display the grade or lesson status if there isn't one.
447         echo $OUTPUT->container($status);
449         if ($grade->str_feedback) {
450             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
451         }
452     }
454     // Display the lesson progress.
455     // Attempt, pages viewed, questions answered, correct answers, time.
456     $params = array ("lessonid" => $lesson->id, "userid" => $user->id);
457     $attempts = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
458     $branches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
459     if (!empty($attempts) or !empty($branches)) {
460         echo $OUTPUT->box_start();
461         $table = new html_table();
462         // Table Headings.
463         $table->head = array (get_string("attemptheader", "lesson"),
464             get_string("totalpagesviewedheader", "lesson"),
465             get_string("numberofpagesviewedheader", "lesson"),
466             get_string("numberofcorrectanswersheader", "lesson"),
467             get_string("time"));
468         $table->width = "100%";
469         $table->align = array ("center", "center", "center", "center", "center");
470         $table->size = array ("*", "*", "*", "*", "*");
471         $table->cellpadding = 2;
472         $table->cellspacing = 0;
474         $retry = 0;
475         $nquestions = 0;
476         $npages = 0;
477         $ncorrect = 0;
479         // Filter question pages (from lesson_attempts).
480         foreach ($attempts as $attempt) {
481             if ($attempt->retry == $retry) {
482                 $npages++;
483                 $nquestions++;
484                 if ($attempt->correct) {
485                     $ncorrect++;
486                 }
487                 $timeseen = $attempt->timeseen;
488             } else {
489                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
490                 $retry++;
491                 $nquestions = 1;
492                 $npages = 1;
493                 if ($attempt->correct) {
494                     $ncorrect = 1;
495                 } else {
496                     $ncorrect = 0;
497                 }
498             }
499         }
501         // Filter content pages (from lesson_branch).
502         foreach ($branches as $branch) {
503             if ($branch->retry == $retry) {
504                 $npages++;
506                 $timeseen = $branch->timeseen;
507             } else {
508                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
509                 $retry++;
510                 $npages = 1;
511             }
512         }
513         if ($npages > 0) {
514                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
515         }
516         echo html_writer::table($table);
517         echo $OUTPUT->box_end();
518     }
520     return true;
523 /**
524  * Prints lesson summaries on MyMoodle Page
525  *
526  * Prints lesson name, due date and attempt information on
527  * lessons that have a deadline that has not already passed
528  * and it is available for taking.
529  *
530  * @global object
531  * @global stdClass
532  * @global object
533  * @uses CONTEXT_MODULE
534  * @param array $courses An array of course objects to get lesson instances from
535  * @param array $htmlarray Store overview output array( course ID => 'lesson' => HTML output )
536  * @return void
537  */
538 function lesson_print_overview($courses, &$htmlarray) {
539     global $USER, $CFG, $DB, $OUTPUT;
541     if (!$lessons = get_all_instances_in_courses('lesson', $courses)) {
542         return;
543     }
545     // Get all of the current users attempts on all lessons.
546     $params = array($USER->id);
547     $sql = 'SELECT lessonid, userid, count(userid) as attempts
548               FROM {lesson_grades}
549              WHERE userid = ?
550           GROUP BY lessonid, userid';
551     $allattempts = $DB->get_records_sql($sql, $params);
552     $completedattempts = array();
553     foreach ($allattempts as $myattempt) {
554         $completedattempts[$myattempt->lessonid] = $myattempt->attempts;
555     }
557     // Get the current course ID.
558     $listoflessons = array();
559     foreach ($lessons as $lesson) {
560         $listoflessons[] = $lesson->id;
561     }
562     // Get the last page viewed by the current user for every lesson in this course.
563     list($insql, $inparams) = $DB->get_in_or_equal($listoflessons, SQL_PARAMS_NAMED);
564     $dbparams = array_merge($inparams, array('userid' => $USER->id));
566     // Get the lesson attempts for the user that have the maximum 'timeseen' value.
567     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.answerid as nextpageid, p.qtype ";
568     $from = "FROM {lesson_attempts} l
569              JOIN (
570                    SELECT idselect.lessonid, idselect.userid, MAX(idselect.id) AS id
571                      FROM {lesson_attempts} idselect
572                      JOIN (
573                            SELECT lessonid, userid, MAX(timeseen) AS timeseen
574                              FROM {lesson_attempts}
575                             WHERE userid = :userid
576                               AND lessonid $insql
577                          GROUP BY userid, lessonid
578                            ) timeselect
579                        ON timeselect.timeseen = idselect.timeseen
580                       AND timeselect.userid = idselect.userid
581                       AND timeselect.lessonid = idselect.lessonid
582                  GROUP BY idselect.userid, idselect.lessonid
583                    ) aid
584                ON l.id = aid.id
585              JOIN {lesson_pages} p
586                ON l.pageid = p.id ";
587     $lastattempts = $DB->get_records_sql($select . $from, $dbparams);
589     // Now, get the lesson branches for the user that have the maximum 'timeseen' value.
590     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.nextpageid, p.qtype ";
591     $from = str_replace('{lesson_attempts}', '{lesson_branch}', $from);
592     $lastbranches = $DB->get_records_sql($select . $from, $dbparams);
594     $lastviewed = array();
595     foreach ($lastattempts as $lastattempt) {
596         $lastviewed[$lastattempt->lessonid] = $lastattempt;
597     }
599     // Go through the branch times and record the 'timeseen' value if it doesn't exist
600     // for the lesson, or replace it if it exceeds the current recorded time.
601     foreach ($lastbranches as $lastbranch) {
602         if (!isset($lastviewed[$lastbranch->lessonid])) {
603             $lastviewed[$lastbranch->lessonid] = $lastbranch;
604         } else if ($lastviewed[$lastbranch->lessonid]->timeseen < $lastbranch->timeseen) {
605             $lastviewed[$lastbranch->lessonid] = $lastbranch;
606         }
607     }
609     // Since we have lessons in this course, now include the constants we need.
610     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
612     $now = time();
613     foreach ($lessons as $lesson) {
614         if ($lesson->deadline != 0                                         // The lesson has a deadline
615             and $lesson->deadline >= $now                                  // And it is before the deadline has been met
616             and ($lesson->available == 0 or $lesson->available <= $now)) { // And the lesson is available
618             // Visibility.
619             $class = (!$lesson->visible) ? 'dimmed' : '';
621             // Context.
622             $context = context_module::instance($lesson->coursemodule);
624             // Link to activity.
625             $url = new moodle_url('/mod/lesson/view.php', array('id' => $lesson->coursemodule));
626             $url = html_writer::link($url, format_string($lesson->name, true, array('context' => $context)), array('class' => $class));
627             $str = $OUTPUT->box(get_string('lessonname', 'lesson', $url), 'name');
629             // Deadline.
630             $str .= $OUTPUT->box(get_string('lessoncloseson', 'lesson', userdate($lesson->deadline)), 'info');
632             // Attempt information.
633             if (has_capability('mod/lesson:manage', $context)) {
634                 // This is a teacher, Get the Number of user attempts.
635                 $attempts = $DB->count_records('lesson_grades', array('lessonid' => $lesson->id));
636                 $str     .= $OUTPUT->box(get_string('xattempts', 'lesson', $attempts), 'info');
637                 $str      = $OUTPUT->box($str, 'lesson overview');
638             } else {
639                 // This is a student, See if the user has at least started the lesson.
640                 if (isset($lastviewed[$lesson->id]->timeseen)) {
641                     // See if the user has finished this attempt.
642                     if (isset($completedattempts[$lesson->id]) &&
643                              ($completedattempts[$lesson->id] == ($lastviewed[$lesson->id]->retry + 1))) {
644                         // Are additional attempts allowed?
645                         if ($lesson->retake) {
646                             // User can retake the lesson.
647                             $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
648                             $str = $OUTPUT->box($str, 'lesson overview');
649                         } else {
650                             // User has completed the lesson and no retakes are allowed.
651                             $str = '';
652                         }
654                     } else {
655                         // The last attempt was not finished or the lesson does not contain questions.
656                         // See if the last page viewed was a branchtable.
657                         require_once($CFG->dirroot . '/mod/lesson/pagetypes/branchtable.php');
658                         if ($lastviewed[$lesson->id]->qtype == LESSON_PAGE_BRANCHTABLE) {
659                             // See if the next pageid is the end of lesson.
660                             if ($lastviewed[$lesson->id]->nextpageid == LESSON_EOL) {
661                                 // The last page viewed was the End of Lesson.
662                                 if ($lesson->retake) {
663                                     // User can retake the lesson.
664                                     $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
665                                     $str = $OUTPUT->box($str, 'lesson overview');
666                                 } else {
667                                     // User has completed the lesson and no retakes are allowed.
668                                     $str = '';
669                                 }
671                             } else {
672                                 // The last page viewed was NOT the end of lesson.
673                                 $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
674                                 $str = $OUTPUT->box($str, 'lesson overview');
675                             }
677                         } else {
678                             // Last page was a question page, so the attempt is not completed yet.
679                             $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
680                             $str = $OUTPUT->box($str, 'lesson overview');
681                         }
682                     }
684                 } else {
685                     // User has not yet started this lesson.
686                     $str .= $OUTPUT->box(get_string('nolessonattempts', 'lesson'), 'info');
687                     $str = $OUTPUT->box($str, 'lesson overview');
688                 }
689             }
690             if (!empty($str)) {
691                 if (empty($htmlarray[$lesson->course]['lesson'])) {
692                     $htmlarray[$lesson->course]['lesson'] = $str;
693                 } else {
694                     $htmlarray[$lesson->course]['lesson'] .= $str;
695                 }
696             }
697         }
698     }
701 /**
702  * Function to be run periodically according to the moodle cron
703  * This function searches for things that need to be done, such
704  * as sending out mail, toggling flags etc ...
705  * @global stdClass
706  * @return bool true
707  */
708 function lesson_cron () {
709     global $CFG;
711     return true;
714 /**
715  * Return grade for given user or all users.
716  *
717  * @global stdClass
718  * @global object
719  * @param int $lessonid id of lesson
720  * @param int $userid optional user id, 0 means all users
721  * @return array array of grades
722  */
723 function lesson_get_user_grades($lesson, $userid=0) {
724     global $CFG, $DB;
726     $params = array("lessonid" => $lesson->id,"lessonid2" => $lesson->id);
728     if (!empty($userid)) {
729         $params["userid"] = $userid;
730         $params["userid2"] = $userid;
731         $user = "AND u.id = :userid";
732         $fuser = "AND uu.id = :userid2";
733     }
734     else {
735         $user="";
736         $fuser="";
737     }
739     if ($lesson->retake) {
740         if ($lesson->usemaxgrade) {
741             $sql = "SELECT u.id, u.id AS userid, MAX(g.grade) AS rawgrade
742                       FROM {user} u, {lesson_grades} g
743                      WHERE u.id = g.userid AND g.lessonid = :lessonid
744                            $user
745                   GROUP BY u.id";
746         } else {
747             $sql = "SELECT u.id, u.id AS userid, AVG(g.grade) AS rawgrade
748                       FROM {user} u, {lesson_grades} g
749                      WHERE u.id = g.userid AND g.lessonid = :lessonid
750                            $user
751                   GROUP BY u.id";
752         }
753         unset($params['lessonid2']);
754         unset($params['userid2']);
755     } else {
756         // use only first attempts (with lowest id in lesson_grades table)
757         $firstonly = "SELECT uu.id AS userid, MIN(gg.id) AS firstcompleted
758                         FROM {user} uu, {lesson_grades} gg
759                        WHERE uu.id = gg.userid AND gg.lessonid = :lessonid2
760                              $fuser
761                        GROUP BY uu.id";
763         $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade
764                   FROM {user} u, {lesson_grades} g, ($firstonly) f
765                  WHERE u.id = g.userid AND g.lessonid = :lessonid
766                        AND g.id = f.firstcompleted AND g.userid=f.userid
767                        $user";
768     }
770     return $DB->get_records_sql($sql, $params);
773 /**
774  * Update grades in central gradebook
775  *
776  * @category grade
777  * @param object $lesson
778  * @param int $userid specific user only, 0 means all
779  * @param bool $nullifnone
780  */
781 function lesson_update_grades($lesson, $userid=0, $nullifnone=true) {
782     global $CFG, $DB;
783     require_once($CFG->libdir.'/gradelib.php');
785     if ($lesson->grade == 0 || $lesson->practice) {
786         lesson_grade_item_update($lesson);
788     } else if ($grades = lesson_get_user_grades($lesson, $userid)) {
789         lesson_grade_item_update($lesson, $grades);
791     } else if ($userid and $nullifnone) {
792         $grade = new stdClass();
793         $grade->userid   = $userid;
794         $grade->rawgrade = null;
795         lesson_grade_item_update($lesson, $grade);
797     } else {
798         lesson_grade_item_update($lesson);
799     }
802 /**
803  * Create grade item for given lesson
804  *
805  * @category grade
806  * @uses GRADE_TYPE_VALUE
807  * @uses GRADE_TYPE_NONE
808  * @param object $lesson object with extra cmidnumber
809  * @param array|object $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
810  * @return int 0 if ok, error code otherwise
811  */
812 function lesson_grade_item_update($lesson, $grades=null) {
813     global $CFG;
814     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
815         require_once($CFG->libdir.'/gradelib.php');
816     }
818     if (array_key_exists('cmidnumber', $lesson)) { //it may not be always present
819         $params = array('itemname'=>$lesson->name, 'idnumber'=>$lesson->cmidnumber);
820     } else {
821         $params = array('itemname'=>$lesson->name);
822     }
824     if (!$lesson->practice and $lesson->grade > 0) {
825         $params['gradetype']  = GRADE_TYPE_VALUE;
826         $params['grademax']   = $lesson->grade;
827         $params['grademin']   = 0;
828     } else if (!$lesson->practice and $lesson->grade < 0) {
829         $params['gradetype']  = GRADE_TYPE_SCALE;
830         $params['scaleid']   = -$lesson->grade;
832         // Make sure current grade fetched correctly from $grades
833         $currentgrade = null;
834         if (!empty($grades)) {
835             if (is_array($grades)) {
836                 $currentgrade = reset($grades);
837             } else {
838                 $currentgrade = $grades;
839             }
840         }
842         // When converting a score to a scale, use scale's grade maximum to calculate it.
843         if (!empty($currentgrade) && $currentgrade->rawgrade !== null) {
844             $grade = grade_get_grades($lesson->course, 'mod', 'lesson', $lesson->id, $currentgrade->userid);
845             $params['grademax']   = reset($grade->items)->grademax;
846         }
847     } else {
848         $params['gradetype']  = GRADE_TYPE_NONE;
849     }
851     if ($grades  === 'reset') {
852         $params['reset'] = true;
853         $grades = null;
854     } else if (!empty($grades)) {
855         // Need to calculate raw grade (Note: $grades has many forms)
856         if (is_object($grades)) {
857             $grades = array($grades->userid => $grades);
858         } else if (array_key_exists('userid', $grades)) {
859             $grades = array($grades['userid'] => $grades);
860         }
861         foreach ($grades as $key => $grade) {
862             if (!is_array($grade)) {
863                 $grades[$key] = $grade = (array) $grade;
864             }
865             //check raw grade isnt null otherwise we erroneously insert a grade of 0
866             if ($grade['rawgrade'] !== null) {
867                 $grades[$key]['rawgrade'] = ($grade['rawgrade'] * $params['grademax'] / 100);
868             } else {
869                 //setting rawgrade to null just in case user is deleting a grade
870                 $grades[$key]['rawgrade'] = null;
871             }
872         }
873     }
875     return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, $grades, $params);
878 /**
879  * List the actions that correspond to a view of this module.
880  * This is used by the participation report.
881  *
882  * Note: This is not used by new logging system. Event with
883  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
884  *       be considered as view action.
885  *
886  * @return array
887  */
888 function lesson_get_view_actions() {
889     return array('view','view all');
892 /**
893  * List the actions that correspond to a post of this module.
894  * This is used by the participation report.
895  *
896  * Note: This is not used by new logging system. Event with
897  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
898  *       will be considered as post action.
899  *
900  * @return array
901  */
902 function lesson_get_post_actions() {
903     return array('end','start');
906 /**
907  * Runs any processes that must run before
908  * a lesson insert/update
909  *
910  * @global object
911  * @param object $lesson Lesson form data
912  * @return void
913  **/
914 function lesson_process_pre_save(&$lesson) {
915     global $DB;
917     $lesson->timemodified = time();
919     if (empty($lesson->timelimit)) {
920         $lesson->timelimit = 0;
921     }
922     if (empty($lesson->timespent) or !is_numeric($lesson->timespent) or $lesson->timespent < 0) {
923         $lesson->timespent = 0;
924     }
925     if (!isset($lesson->completed)) {
926         $lesson->completed = 0;
927     }
928     if (empty($lesson->gradebetterthan) or !is_numeric($lesson->gradebetterthan) or $lesson->gradebetterthan < 0) {
929         $lesson->gradebetterthan = 0;
930     } else if ($lesson->gradebetterthan > 100) {
931         $lesson->gradebetterthan = 100;
932     }
934     if (empty($lesson->width)) {
935         $lesson->width = 640;
936     }
937     if (empty($lesson->height)) {
938         $lesson->height = 480;
939     }
940     if (empty($lesson->bgcolor)) {
941         $lesson->bgcolor = '#FFFFFF';
942     }
944     // Conditions for dependency
945     $conditions = new stdClass;
946     $conditions->timespent = $lesson->timespent;
947     $conditions->completed = $lesson->completed;
948     $conditions->gradebetterthan = $lesson->gradebetterthan;
949     $lesson->conditions = serialize($conditions);
950     unset($lesson->timespent);
951     unset($lesson->completed);
952     unset($lesson->gradebetterthan);
954     if (empty($lesson->password)) {
955         unset($lesson->password);
956     }
959 /**
960  * Runs any processes that must be run
961  * after a lesson insert/update
962  *
963  * @global object
964  * @param object $lesson Lesson form data
965  * @return void
966  **/
967 function lesson_process_post_save(&$lesson) {
968     // Update the events relating to this lesson.
969     lesson_update_events($lesson);
973 /**
974  * Implementation of the function for printing the form elements that control
975  * whether the course reset functionality affects the lesson.
976  *
977  * @param $mform form passed by reference
978  */
979 function lesson_reset_course_form_definition(&$mform) {
980     $mform->addElement('header', 'lessonheader', get_string('modulenameplural', 'lesson'));
981     $mform->addElement('advcheckbox', 'reset_lesson', get_string('deleteallattempts','lesson'));
982     $mform->addElement('advcheckbox', 'reset_lesson_user_overrides',
983             get_string('removealluseroverrides', 'lesson'));
984     $mform->addElement('advcheckbox', 'reset_lesson_group_overrides',
985             get_string('removeallgroupoverrides', 'lesson'));
988 /**
989  * Course reset form defaults.
990  * @param object $course
991  * @return array
992  */
993 function lesson_reset_course_form_defaults($course) {
994     return array('reset_lesson' => 1,
995             'reset_lesson_group_overrides' => 1,
996             'reset_lesson_user_overrides' => 1);
999 /**
1000  * Removes all grades from gradebook
1001  *
1002  * @global stdClass
1003  * @global object
1004  * @param int $courseid
1005  * @param string optional type
1006  */
1007 function lesson_reset_gradebook($courseid, $type='') {
1008     global $CFG, $DB;
1010     $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid
1011               FROM {lesson} l, {course_modules} cm, {modules} m
1012              WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id AND l.course=:course";
1013     $params = array ("course" => $courseid);
1014     if ($lessons = $DB->get_records_sql($sql,$params)) {
1015         foreach ($lessons as $lesson) {
1016             lesson_grade_item_update($lesson, 'reset');
1017         }
1018     }
1021 /**
1022  * Actual implementation of the reset course functionality, delete all the
1023  * lesson attempts for course $data->courseid.
1024  *
1025  * @global stdClass
1026  * @global object
1027  * @param object $data the data submitted from the reset course.
1028  * @return array status array
1029  */
1030 function lesson_reset_userdata($data) {
1031     global $CFG, $DB;
1033     $componentstr = get_string('modulenameplural', 'lesson');
1034     $status = array();
1036     if (!empty($data->reset_lesson)) {
1037         $lessonssql = "SELECT l.id
1038                          FROM {lesson} l
1039                         WHERE l.course=:course";
1041         $params = array ("course" => $data->courseid);
1042         $lessons = $DB->get_records_sql($lessonssql, $params);
1044         // Get rid of attempts files.
1045         $fs = get_file_storage();
1046         if ($lessons) {
1047             foreach ($lessons as $lessonid => $unused) {
1048                 if (!$cm = get_coursemodule_from_instance('lesson', $lessonid)) {
1049                     continue;
1050                 }
1051                 $context = context_module::instance($cm->id);
1052                 $fs->delete_area_files($context->id, 'mod_lesson', 'essay_responses');
1053             }
1054         }
1056         $DB->delete_records_select('lesson_timer', "lessonid IN ($lessonssql)", $params);
1057         $DB->delete_records_select('lesson_grades', "lessonid IN ($lessonssql)", $params);
1058         $DB->delete_records_select('lesson_attempts', "lessonid IN ($lessonssql)", $params);
1059         $DB->delete_records_select('lesson_branch', "lessonid IN ($lessonssql)", $params);
1061         // remove all grades from gradebook
1062         if (empty($data->reset_gradebook_grades)) {
1063             lesson_reset_gradebook($data->courseid);
1064         }
1066         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'lesson'), 'error'=>false);
1067     }
1069     // Remove user overrides.
1070     if (!empty($data->reset_lesson_user_overrides)) {
1071         $DB->delete_records_select('lesson_overrides',
1072                 'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid));
1073         $status[] = array(
1074         'component' => $componentstr,
1075         'item' => get_string('useroverridesdeleted', 'lesson'),
1076         'error' => false);
1077     }
1078     // Remove group overrides.
1079     if (!empty($data->reset_lesson_group_overrides)) {
1080         $DB->delete_records_select('lesson_overrides',
1081         'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid));
1082         $status[] = array(
1083         'component' => $componentstr,
1084         'item' => get_string('groupoverridesdeleted', 'lesson'),
1085         'error' => false);
1086     }
1087     /// updating dates - shift may be negative too
1088     if ($data->timeshift) {
1089         $DB->execute("UPDATE {lesson_overrides}
1090                          SET available = available + ?
1091                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
1092                          AND available <> 0", array($data->timeshift, $data->courseid));
1093         $DB->execute("UPDATE {lesson_overrides}
1094                          SET deadline = deadline + ?
1095                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
1096                          AND deadline <> 0", array($data->timeshift, $data->courseid));
1098         shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid);
1099         $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
1100     }
1102     return $status;
1105 /**
1106  * Returns all other caps used in module
1107  * @return array
1108  */
1109 function lesson_get_extra_capabilities() {
1110     return array('moodle/site:accessallgroups');
1113 /**
1114  * @uses FEATURE_GROUPS
1115  * @uses FEATURE_GROUPINGS
1116  * @uses FEATURE_MOD_INTRO
1117  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
1118  * @uses FEATURE_GRADE_HAS_GRADE
1119  * @uses FEATURE_GRADE_OUTCOMES
1120  * @param string $feature FEATURE_xx constant for requested feature
1121  * @return mixed True if module supports feature, false if not, null if doesn't know
1122  */
1123 function lesson_supports($feature) {
1124     switch($feature) {
1125         case FEATURE_GROUPS:
1126             return true;
1127         case FEATURE_GROUPINGS:
1128             return true;
1129         case FEATURE_MOD_INTRO:
1130             return true;
1131         case FEATURE_COMPLETION_TRACKS_VIEWS:
1132             return true;
1133         case FEATURE_GRADE_HAS_GRADE:
1134             return true;
1135         case FEATURE_COMPLETION_HAS_RULES:
1136             return true;
1137         case FEATURE_GRADE_OUTCOMES:
1138             return true;
1139         case FEATURE_BACKUP_MOODLE2:
1140             return true;
1141         case FEATURE_SHOW_DESCRIPTION:
1142             return true;
1143         default:
1144             return null;
1145     }
1148 /**
1149  * Obtains the automatic completion state for this lesson based on any conditions
1150  * in lesson settings.
1151  *
1152  * @param object $course Course
1153  * @param object $cm course-module
1154  * @param int $userid User ID
1155  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1156  * @return bool True if completed, false if not, $type if conditions not set.
1157  */
1158 function lesson_get_completion_state($course, $cm, $userid, $type) {
1159     global $CFG, $DB;
1161     // Get lesson details.
1162     $lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*',
1163             MUST_EXIST);
1165     $result = $type; // Default return value.
1166     // If completion option is enabled, evaluate it and return true/false.
1167     if ($lesson->completionendreached) {
1168         $value = $DB->record_exists('lesson_timer', array(
1169                 'lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1));
1170         if ($type == COMPLETION_AND) {
1171             $result = $result && $value;
1172         } else {
1173             $result = $result || $value;
1174         }
1175     }
1176     if ($lesson->completiontimespent != 0) {
1177         $duration = $DB->get_field_sql(
1178                         "SELECT SUM(lessontime - starttime)
1179                                FROM {lesson_timer}
1180                               WHERE lessonid = :lessonid
1181                                 AND userid = :userid",
1182                         array('userid' => $userid, 'lessonid' => $lesson->id));
1183         if (!$duration) {
1184             $duration = 0;
1185         }
1186         if ($type == COMPLETION_AND) {
1187             $result = $result && ($lesson->completiontimespent < $duration);
1188         } else {
1189             $result = $result || ($lesson->completiontimespent < $duration);
1190         }
1191     }
1192     return $result;
1194 /**
1195  * This function extends the settings navigation block for the site.
1196  *
1197  * It is safe to rely on PAGE here as we will only ever be within the module
1198  * context when this is called
1199  *
1200  * @param settings_navigation $settings
1201  * @param navigation_node $lessonnode
1202  */
1203 function lesson_extend_settings_navigation($settings, $lessonnode) {
1204     global $PAGE, $DB;
1206     // We want to add these new nodes after the Edit settings node, and before the
1207     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
1208     $keys = $lessonnode->get_children_key_list();
1209     $beforekey = null;
1210     $i = array_search('modedit', $keys);
1211     if ($i === false and array_key_exists(0, $keys)) {
1212         $beforekey = $keys[0];
1213     } else if (array_key_exists($i + 1, $keys)) {
1214         $beforekey = $keys[$i + 1];
1215     }
1217     if (has_capability('mod/lesson:manageoverrides', $PAGE->cm->context)) {
1218         $url = new moodle_url('/mod/lesson/overrides.php', array('cmid' => $PAGE->cm->id));
1219         $node = navigation_node::create(get_string('groupoverrides', 'lesson'),
1220                 new moodle_url($url, array('mode' => 'group')),
1221                 navigation_node::TYPE_SETTING, null, 'mod_lesson_groupoverrides');
1222         $lessonnode->add_node($node, $beforekey);
1224         $node = navigation_node::create(get_string('useroverrides', 'lesson'),
1225                 new moodle_url($url, array('mode' => 'user')),
1226                 navigation_node::TYPE_SETTING, null, 'mod_lesson_useroverrides');
1227         $lessonnode->add_node($node, $beforekey);
1228     }
1230     if (has_capability('mod/lesson:edit', $PAGE->cm->context)) {
1231         $url = new moodle_url('/mod/lesson/view.php', array('id' => $PAGE->cm->id));
1232         $lessonnode->add(get_string('preview', 'lesson'), $url);
1233         $editnode = $lessonnode->add(get_string('edit', 'lesson'));
1234         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'collapsed'));
1235         $editnode->add(get_string('collapsed', 'lesson'), $url);
1236         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'full'));
1237         $editnode->add(get_string('full', 'lesson'), $url);
1238     }
1240     if (has_capability('mod/lesson:viewreports', $PAGE->cm->context)) {
1241         $reportsnode = $lessonnode->add(get_string('reports', 'lesson'));
1242         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportoverview'));
1243         $reportsnode->add(get_string('overview', 'lesson'), $url);
1244         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportdetail'));
1245         $reportsnode->add(get_string('detailedstats', 'lesson'), $url);
1246     }
1248     if (has_capability('mod/lesson:grade', $PAGE->cm->context)) {
1249         $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$PAGE->cm->id));
1250         $lessonnode->add(get_string('manualgrading', 'lesson'), $url);
1251     }
1255 /**
1256  * Get list of available import or export formats
1257  *
1258  * Copied and modified from lib/questionlib.php
1259  *
1260  * @param string $type 'import' if import list, otherwise export list assumed
1261  * @return array sorted list of import/export formats available
1262  */
1263 function lesson_get_import_export_formats($type) {
1264     global $CFG;
1265     $fileformats = core_component::get_plugin_list("qformat");
1267     $fileformatname=array();
1268     foreach ($fileformats as $fileformat=>$fdir) {
1269         $format_file = "$fdir/format.php";
1270         if (file_exists($format_file) ) {
1271             require_once($format_file);
1272         } else {
1273             continue;
1274         }
1275         $classname = "qformat_$fileformat";
1276         $format_class = new $classname();
1277         if ($type=='import') {
1278             $provided = $format_class->provide_import();
1279         } else {
1280             $provided = $format_class->provide_export();
1281         }
1282         if ($provided) {
1283             $fileformatnames[$fileformat] = get_string('pluginname', 'qformat_'.$fileformat);
1284         }
1285     }
1286     natcasesort($fileformatnames);
1288     return $fileformatnames;
1291 /**
1292  * Serves the lesson attachments. Implements needed access control ;-)
1293  *
1294  * @package mod_lesson
1295  * @category files
1296  * @param stdClass $course course object
1297  * @param stdClass $cm course module object
1298  * @param stdClass $context context object
1299  * @param string $filearea file area
1300  * @param array $args extra arguments
1301  * @param bool $forcedownload whether or not force download
1302  * @param array $options additional options affecting the file serving
1303  * @return bool false if file not found, does not return if found - justsend the file
1304  */
1305 function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
1306     global $CFG, $DB;
1308     if ($context->contextlevel != CONTEXT_MODULE) {
1309         return false;
1310     }
1312     $fileareas = lesson_get_file_areas();
1313     if (!array_key_exists($filearea, $fileareas)) {
1314         return false;
1315     }
1317     if (!$lesson = $DB->get_record('lesson', array('id'=>$cm->instance))) {
1318         return false;
1319     }
1321     require_course_login($course, true, $cm);
1323     if ($filearea === 'page_contents') {
1324         $pageid = (int)array_shift($args);
1325         if (!$page = $DB->get_record('lesson_pages', array('id'=>$pageid))) {
1326             return false;
1327         }
1328         $fullpath = "/$context->id/mod_lesson/$filearea/$pageid/".implode('/', $args);
1330     } else if ($filearea === 'page_answers' || $filearea === 'page_responses') {
1331         $itemid = (int)array_shift($args);
1332         if (!$pageanswers = $DB->get_record('lesson_answers', array('id' => $itemid))) {
1333             return false;
1334         }
1335         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
1337     } else if ($filearea === 'essay_responses') {
1338         $itemid = (int)array_shift($args);
1339         if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $itemid))) {
1340             return false;
1341         }
1342         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
1344     } else if ($filearea === 'mediafile') {
1345         if (count($args) > 1) {
1346             // Remove the itemid when it appears to be part of the arguments. If there is only one argument
1347             // then it is surely the file name. The itemid is sometimes used to prevent browser caching.
1348             array_shift($args);
1349         }
1350         $fullpath = "/$context->id/mod_lesson/$filearea/0/".implode('/', $args);
1352     } else {
1353         return false;
1354     }
1356     $fs = get_file_storage();
1357     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1358         return false;
1359     }
1361     // finally send the file
1362     send_stored_file($file, 0, 0, $forcedownload, $options); // download MUST be forced - security!
1365 /**
1366  * Returns an array of file areas
1367  *
1368  * @package  mod_lesson
1369  * @category files
1370  * @return array a list of available file areas
1371  */
1372 function lesson_get_file_areas() {
1373     $areas = array();
1374     $areas['page_contents'] = get_string('pagecontents', 'mod_lesson');
1375     $areas['mediafile'] = get_string('mediafile', 'mod_lesson');
1376     $areas['page_answers'] = get_string('pageanswers', 'mod_lesson');
1377     $areas['page_responses'] = get_string('pageresponses', 'mod_lesson');
1378     $areas['essay_responses'] = get_string('essayresponses', 'mod_lesson');
1379     return $areas;
1382 /**
1383  * Returns a file_info_stored object for the file being requested here
1384  *
1385  * @package  mod_lesson
1386  * @category files
1387  * @global stdClass $CFG
1388  * @param file_browse $browser file browser instance
1389  * @param array $areas file areas
1390  * @param stdClass $course course object
1391  * @param stdClass $cm course module object
1392  * @param stdClass $context context object
1393  * @param string $filearea file area
1394  * @param int $itemid item ID
1395  * @param string $filepath file path
1396  * @param string $filename file name
1397  * @return file_info_stored
1398  */
1399 function lesson_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1400     global $CFG, $DB;
1402     if (!has_capability('moodle/course:managefiles', $context)) {
1403         // No peaking here for students!
1404         return null;
1405     }
1407     // Mediafile area does not have sub directories, so let's select the default itemid to prevent
1408     // the user from selecting a directory to access the mediafile content.
1409     if ($filearea == 'mediafile' && is_null($itemid)) {
1410         $itemid = 0;
1411     }
1413     if (is_null($itemid)) {
1414         return new mod_lesson_file_info($browser, $course, $cm, $context, $areas, $filearea);
1415     }
1417     $fs = get_file_storage();
1418     $filepath = is_null($filepath) ? '/' : $filepath;
1419     $filename = is_null($filename) ? '.' : $filename;
1420     if (!$storedfile = $fs->get_file($context->id, 'mod_lesson', $filearea, $itemid, $filepath, $filename)) {
1421         return null;
1422     }
1424     $itemname = $filearea;
1425     if ($filearea == 'page_contents') {
1426         $itemname = $DB->get_field('lesson_pages', 'title', array('lessonid' => $cm->instance, 'id' => $itemid));
1427         $itemname = format_string($itemname, true, array('context' => $context));
1428     } else {
1429         $areas = lesson_get_file_areas();
1430         if (isset($areas[$filearea])) {
1431             $itemname = $areas[$filearea];
1432         }
1433     }
1435     $urlbase = $CFG->wwwroot . '/pluginfile.php';
1436     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemname, $itemid, true, true, false);
1440 /**
1441  * Return a list of page types
1442  * @param string $pagetype current page type
1443  * @param stdClass $parentcontext Block's parent context
1444  * @param stdClass $currentcontext Current context of block
1445  */
1446 function lesson_page_type_list($pagetype, $parentcontext, $currentcontext) {
1447     $module_pagetype = array(
1448         'mod-lesson-*'=>get_string('page-mod-lesson-x', 'lesson'),
1449         'mod-lesson-view'=>get_string('page-mod-lesson-view', 'lesson'),
1450         'mod-lesson-edit'=>get_string('page-mod-lesson-edit', 'lesson'));
1451     return $module_pagetype;
1454 /**
1455  * Update the lesson activity to include any file
1456  * that was uploaded, or if there is none, set the
1457  * mediafile field to blank.
1458  *
1459  * @param int $lessonid the lesson id
1460  * @param stdClass $context the context
1461  * @param int $draftitemid the draft item
1462  */
1463 function lesson_update_media_file($lessonid, $context, $draftitemid) {
1464     global $DB;
1466     // Set the filestorage object.
1467     $fs = get_file_storage();
1468     // Save the file if it exists that is currently in the draft area.
1469     file_save_draft_area_files($draftitemid, $context->id, 'mod_lesson', 'mediafile', 0);
1470     // Get the file if it exists.
1471     $files = $fs->get_area_files($context->id, 'mod_lesson', 'mediafile', 0, 'itemid, filepath, filename', false);
1472     // Check that there is a file to process.
1473     if (count($files) == 1) {
1474         // Get the first (and only) file.
1475         $file = reset($files);
1476         // Set the mediafile column in the lessons table.
1477         $DB->set_field('lesson', 'mediafile', '/' . $file->get_filename(), array('id' => $lessonid));
1478     } else {
1479         // Set the mediafile column in the lessons table.
1480         $DB->set_field('lesson', 'mediafile', '', array('id' => $lessonid));
1481     }
1484 /**
1485  * Get icon mapping for font-awesome.
1486  */
1487 function mod_lesson_get_fontawesome_icon_map() {
1488     return [
1489         'mod_lesson:e/copy' => 'fa-clone',
1490     ];
1493 /*
1494  * Check if the module has any update that affects the current user since a given time.
1495  *
1496  * @param  cm_info $cm course module data
1497  * @param  int $from the time to check updates from
1498  * @param  array $filter  if we need to check only specific updates
1499  * @return stdClass an object with the different type of areas indicating if they were updated or not
1500  * @since Moodle 3.3
1501  */
1502 function lesson_check_updates_since(cm_info $cm, $from, $filter = array()) {
1503     global $DB, $USER;
1505     $updates = course_check_module_updates_since($cm, $from, array(), $filter);
1507     // Check if there are new pages or answers in the lesson.
1508     $updates->pages = (object) array('updated' => false);
1509     $updates->answers = (object) array('updated' => false);
1510     $select = 'lessonid = ? AND (timecreated > ? OR timemodified > ?)';
1511     $params = array($cm->instance, $from, $from);
1513     $pages = $DB->get_records_select('lesson_pages', $select, $params, '', 'id');
1514     if (!empty($pages)) {
1515         $updates->pages->updated = true;
1516         $updates->pages->itemids = array_keys($pages);
1517     }
1518     $answers = $DB->get_records_select('lesson_answers', $select, $params, '', 'id');
1519     if (!empty($answers)) {
1520         $updates->answers->updated = true;
1521         $updates->answers->itemids = array_keys($answers);
1522     }
1524     // Check for new question attempts, grades, pages viewed and timers.
1525     $updates->questionattempts = (object) array('updated' => false);
1526     $updates->grades = (object) array('updated' => false);
1527     $updates->pagesviewed = (object) array('updated' => false);
1528     $updates->timers = (object) array('updated' => false);
1530     $select = 'lessonid = ? AND userid = ? AND timeseen > ?';
1531     $params = array($cm->instance, $USER->id, $from);
1533     $questionattempts = $DB->get_records_select('lesson_attempts', $select, $params, '', 'id');
1534     if (!empty($questionattempts)) {
1535         $updates->questionattempts->updated = true;
1536         $updates->questionattempts->itemids = array_keys($questionattempts);
1537     }
1538     $pagesviewed = $DB->get_records_select('lesson_branch', $select, $params, '', 'id');
1539     if (!empty($pagesviewed)) {
1540         $updates->pagesviewed->updated = true;
1541         $updates->pagesviewed->itemids = array_keys($pagesviewed);
1542     }
1544     $select = 'lessonid = ? AND userid = ? AND completed > ?';
1545     $grades = $DB->get_records_select('lesson_grades', $select, $params, '', 'id');
1546     if (!empty($grades)) {
1547         $updates->grades->updated = true;
1548         $updates->grades->itemids = array_keys($grades);
1549     }
1551     $select = 'lessonid = ? AND userid = ? AND (starttime > ? OR lessontime > ? OR timemodifiedoffline > ?)';
1552     $params = array($cm->instance, $USER->id, $from, $from, $from);
1553     $timers = $DB->get_records_select('lesson_timer', $select, $params, '', 'id');
1554     if (!empty($timers)) {
1555         $updates->timers->updated = true;
1556         $updates->timers->itemids = array_keys($timers);
1557     }
1559     // Now, teachers should see other students updates.
1560     if (has_capability('mod/lesson:viewreports', $cm->context)) {
1561         $select = 'lessonid = ? AND timeseen > ?';
1562         $params = array($cm->instance, $from);
1564         $insql = '';
1565         $inparams = [];
1566         if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
1567             $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
1568             if (empty($groupusers)) {
1569                 return $updates;
1570             }
1571             list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
1572             $select .= ' AND userid ' . $insql;
1573             $params = array_merge($params, $inparams);
1574         }
1576         $updates->userquestionattempts = (object) array('updated' => false);
1577         $updates->usergrades = (object) array('updated' => false);
1578         $updates->userpagesviewed = (object) array('updated' => false);
1579         $updates->usertimers = (object) array('updated' => false);
1581         $questionattempts = $DB->get_records_select('lesson_attempts', $select, $params, '', 'id');
1582         if (!empty($questionattempts)) {
1583             $updates->userquestionattempts->updated = true;
1584             $updates->userquestionattempts->itemids = array_keys($questionattempts);
1585         }
1586         $pagesviewed = $DB->get_records_select('lesson_branch', $select, $params, '', 'id');
1587         if (!empty($pagesviewed)) {
1588             $updates->userpagesviewed->updated = true;
1589             $updates->userpagesviewed->itemids = array_keys($pagesviewed);
1590         }
1592         $select = 'lessonid = ? AND completed > ? ' . $insql;
1593         $grades = $DB->get_records_select('lesson_grades', $select, $params, '', 'id');
1594         if (!empty($grades)) {
1595             $updates->usergrades->updated = true;
1596             $updates->usergrades->itemids = array_keys($grades);
1597         }
1599         $select = 'lessonid = ? AND (starttime > ? OR lessontime > ? OR timemodifiedoffline > ?) ' . $insql;
1600         $params = array($cm->instance, $from, $from, $from);
1601         $params = array_merge($params, $inparams);
1602         $timers = $DB->get_records_select('lesson_timer', $select, $params, '', 'id');
1603         if (!empty($timers)) {
1604             $updates->usertimers->updated = true;
1605             $updates->usertimers->itemids = array_keys($timers);
1606         }
1607     }
1608     return $updates;