MDL-55956 mod: New function *_get_group_override_priorities()
[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 todo list of all that needs to be updated.
127     if (empty($override)) {
128         // We are updating the primary settings for the lesson, so we
129         // need to add all the overrides.
130         $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id));
131         // As well as the original lesson (empty override).
132         $overrides[] = new stdClass();
133     } else {
134         // Just do the one override.
135         $overrides = array($override);
136     }
138     foreach ($overrides as $current) {
139         $groupid   = isset($current->groupid) ? $current->groupid : 0;
140         $userid    = isset($current->userid) ? $current->userid : 0;
141         $available  = isset($current->available) ? $current->available : $lesson->available;
142         $deadline = isset($current->deadline) ? $current->deadline : $lesson->deadline;
144         // Only add open/close events for an override if they differ from the lesson default.
145         $addopen  = empty($current->id) || !empty($current->available);
146         $addclose = empty($current->id) || !empty($current->deadline);
148         if (!empty($lesson->coursemodule)) {
149             $cmid = $lesson->coursemodule;
150         } else {
151             $cmid = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)->id;
152         }
154         $event = new stdClass();
155         $event->description = format_module_intro('lesson', $lesson, $cmid);
156         // Events module won't show user events when the courseid is nonzero.
157         $event->courseid    = ($userid) ? 0 : $lesson->course;
158         $event->groupid     = $groupid;
159         $event->userid      = $userid;
160         $event->modulename  = 'lesson';
161         $event->instance    = $lesson->id;
162         $event->timestart   = $available;
163         $event->timeduration = max($deadline - $available, 0);
164         $event->visible     = instance_is_visible('lesson', $lesson);
165         $event->eventtype   = 'open';
167         // Determine the event name.
168         if ($groupid) {
169             $params = new stdClass();
170             $params->lesson = $lesson->name;
171             $params->group = groups_get_group_name($groupid);
172             if ($params->group === false) {
173                 // Group doesn't exist, just skip it.
174                 continue;
175             }
176             $eventname = get_string('overridegroupeventname', 'lesson', $params);
177         } else if ($userid) {
178             $params = new stdClass();
179             $params->lesson = $lesson->name;
180             $eventname = get_string('overrideusereventname', 'lesson', $params);
181         } else {
182             $eventname = $lesson->name;
183         }
184         if ($addopen or $addclose) {
185             if ($deadline and $available and $event->timeduration <= LESSON_MAX_EVENT_LENGTH) {
186                 // Single event for the whole lesson.
187                 if ($oldevent = array_shift($oldevents)) {
188                     $event->id = $oldevent->id;
189                 } else {
190                     unset($event->id);
191                 }
192                 $event->name = $eventname;
193                 // The method calendar_event::create will reuse a db record if the id field is set.
194                 calendar_event::create($event);
195             } else {
196                 // Separate start and end events.
197                 $event->timeduration  = 0;
198                 if ($available && $addopen) {
199                     if ($oldevent = array_shift($oldevents)) {
200                         $event->id = $oldevent->id;
201                     } else {
202                         unset($event->id);
203                     }
204                     $event->name = $eventname.' ('.get_string('lessonopens', 'lesson').')';
205                     // The method calendar_event::create will reuse a db record if the id field is set.
206                     calendar_event::create($event);
207                 }
208                 if ($deadline && $addclose) {
209                     if ($oldevent = array_shift($oldevents)) {
210                         $event->id = $oldevent->id;
211                     } else {
212                         unset($event->id);
213                     }
214                     $event->name      = $eventname.' ('.get_string('lessoncloses', 'lesson').')';
215                     $event->timestart = $deadline;
216                     $event->eventtype = 'close';
217                     calendar_event::create($event);
218                 }
219             }
220         }
221     }
223     // Delete any leftover events.
224     foreach ($oldevents as $badevent) {
225         $badevent = calendar_event::load($badevent);
226         $badevent->delete();
227     }
230 /**
231  * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson.
232  *
233  * @param int $lessonid The quiz ID.
234  * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
235  */
236 function lesson_get_group_override_priorities($lessonid) {
237     global $DB;
239     // Fetch group overrides.
240     $where = 'lessonid = :lessonid AND groupid IS NOT NULL';
241     $params = ['lessonid' => $lessonid];
242     $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline');
243     if (!$overrides) {
244         return null;
245     }
247     $grouptimeopen = [];
248     $grouptimeclose = [];
249     foreach ($overrides as $override) {
250         if ($override->available !== null && !in_array($override->available, $grouptimeopen)) {
251             $grouptimeopen[] = $override->available;
252         }
253         if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) {
254             $grouptimeclose[] = $override->deadline;
255         }
256     }
258     // Sort open times in descending manner. The earlier open time gets higher priority.
259     rsort($grouptimeopen);
260     // Set priorities.
261     $opengrouppriorities = [];
262     $openpriority = 1;
263     foreach ($grouptimeopen as $timeopen) {
264         $opengrouppriorities[$timeopen] = $openpriority++;
265     }
267     // Sort close times in ascending manner. The later close time gets higher priority.
268     sort($grouptimeclose);
269     // Set priorities.
270     $closegrouppriorities = [];
271     $closepriority = 1;
272     foreach ($grouptimeclose as $timeclose) {
273         $closegrouppriorities[$timeclose] = $closepriority++;
274     }
276     return [
277         'open' => $opengrouppriorities,
278         'close' => $closegrouppriorities
279     ];
282 /**
283  * This standard function will check all instances of this module
284  * and make sure there are up-to-date events created for each of them.
285  * If courseid = 0, then every lesson event in the site is checked, else
286  * only lesson events belonging to the course specified are checked.
287  * This function is used, in its new format, by restore_refresh_events()
288  *
289  * @param int $courseid
290  * @return bool
291  */
292 function lesson_refresh_events($courseid = 0) {
293     global $DB;
295     if ($courseid == 0) {
296         if (!$lessons = $DB->get_records('lessons')) {
297             return true;
298         }
299     } else {
300         if (!$lessons = $DB->get_records('lesson', array('course' => $courseid))) {
301             return true;
302         }
303     }
305     foreach ($lessons as $lesson) {
306         lesson_update_events($lesson);
307     }
309     return true;
312 /**
313  * Given an ID of an instance of this module,
314  * this function will permanently delete the instance
315  * and any data that depends on it.
316  *
317  * @global object
318  * @param int $id
319  * @return bool
320  */
321 function lesson_delete_instance($id) {
322     global $DB, $CFG;
323     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
325     $lesson = $DB->get_record("lesson", array("id"=>$id), '*', MUST_EXIST);
326     $lesson = new lesson($lesson);
327     return $lesson->delete();
330 /**
331  * Return a small object with summary information about what a
332  * user has done with a given particular instance of this module
333  * Used for user activity reports.
334  * $return->time = the time they did it
335  * $return->info = a short text description
336  *
337  * @global object
338  * @param object $course
339  * @param object $user
340  * @param object $mod
341  * @param object $lesson
342  * @return object
343  */
344 function lesson_user_outline($course, $user, $mod, $lesson) {
345     global $CFG, $DB;
347     require_once("$CFG->libdir/gradelib.php");
348     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
349     $return = new stdClass();
351     if (empty($grades->items[0]->grades)) {
352         $return->info = get_string("nolessonattempts", "lesson");
353     } else {
354         $grade = reset($grades->items[0]->grades);
355         if (empty($grade->grade)) {
357             // Check to see if it an ungraded / incomplete attempt.
358             $sql = "SELECT *
359                       FROM {lesson_timer}
360                      WHERE lessonid = :lessonid
361                        AND userid = :userid
362                   ORDER BY starttime DESC";
363             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
365             if ($attempts = $DB->get_records_sql($sql, $params, 0, 1)) {
366                 $attempt = reset($attempts);
367                 if ($attempt->completed) {
368                     $return->info = get_string("completed", "lesson");
369                 } else {
370                     $return->info = get_string("notyetcompleted", "lesson");
371                 }
372                 $return->time = $attempt->lessontime;
373             } else {
374                 $return->info = get_string("nolessonattempts", "lesson");
375             }
376         } else {
377             $return->info = get_string("grade") . ': ' . $grade->str_long_grade;
379             // Datesubmitted == time created. dategraded == time modified or time overridden.
380             // If grade was last modified by the user themselves use date graded. Otherwise use date submitted.
381             // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704.
382             if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
383                 $return->time = $grade->dategraded;
384             } else {
385                 $return->time = $grade->datesubmitted;
386             }
387         }
388     }
389     return $return;
392 /**
393  * Print a detailed representation of what a  user has done with
394  * a given particular instance of this module, for user activity reports.
395  *
396  * @global object
397  * @param object $course
398  * @param object $user
399  * @param object $mod
400  * @param object $lesson
401  * @return bool
402  */
403 function lesson_user_complete($course, $user, $mod, $lesson) {
404     global $DB, $OUTPUT, $CFG;
406     require_once("$CFG->libdir/gradelib.php");
408     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
410     // Display the grade and feedback.
411     if (empty($grades->items[0]->grades)) {
412         echo $OUTPUT->container(get_string("nolessonattempts", "lesson"));
413     } else {
414         $grade = reset($grades->items[0]->grades);
415         if (empty($grade->grade)) {
416             // Check to see if it an ungraded / incomplete attempt.
417             $sql = "SELECT *
418                       FROM {lesson_timer}
419                      WHERE lessonid = :lessonid
420                        AND userid = :userid
421                      ORDER by starttime desc";
422             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
424             if ($attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) {
425                 if ($attempt->completed) {
426                     $status = get_string("completed", "lesson");
427                 } else {
428                     $status = get_string("notyetcompleted", "lesson");
429                 }
430             } else {
431                 $status = get_string("nolessonattempts", "lesson");
432             }
433         } else {
434             $status = get_string("grade") . ': ' . $grade->str_long_grade;
435         }
437         // Display the grade or lesson status if there isn't one.
438         echo $OUTPUT->container($status);
440         if ($grade->str_feedback) {
441             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
442         }
443     }
445     // Display the lesson progress.
446     // Attempt, pages viewed, questions answered, correct answers, time.
447     $params = array ("lessonid" => $lesson->id, "userid" => $user->id);
448     $attempts = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
449     $branches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
450     if (!empty($attempts) or !empty($branches)) {
451         echo $OUTPUT->box_start();
452         $table = new html_table();
453         // Table Headings.
454         $table->head = array (get_string("attemptheader", "lesson"),
455             get_string("totalpagesviewedheader", "lesson"),
456             get_string("numberofpagesviewedheader", "lesson"),
457             get_string("numberofcorrectanswersheader", "lesson"),
458             get_string("time"));
459         $table->width = "100%";
460         $table->align = array ("center", "center", "center", "center", "center");
461         $table->size = array ("*", "*", "*", "*", "*");
462         $table->cellpadding = 2;
463         $table->cellspacing = 0;
465         $retry = 0;
466         $nquestions = 0;
467         $npages = 0;
468         $ncorrect = 0;
470         // Filter question pages (from lesson_attempts).
471         foreach ($attempts as $attempt) {
472             if ($attempt->retry == $retry) {
473                 $npages++;
474                 $nquestions++;
475                 if ($attempt->correct) {
476                     $ncorrect++;
477                 }
478                 $timeseen = $attempt->timeseen;
479             } else {
480                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
481                 $retry++;
482                 $nquestions = 1;
483                 $npages = 1;
484                 if ($attempt->correct) {
485                     $ncorrect = 1;
486                 } else {
487                     $ncorrect = 0;
488                 }
489             }
490         }
492         // Filter content pages (from lesson_branch).
493         foreach ($branches as $branch) {
494             if ($branch->retry == $retry) {
495                 $npages++;
497                 $timeseen = $branch->timeseen;
498             } else {
499                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
500                 $retry++;
501                 $npages = 1;
502             }
503         }
504         if ($npages > 0) {
505                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
506         }
507         echo html_writer::table($table);
508         echo $OUTPUT->box_end();
509     }
511     return true;
514 /**
515  * Prints lesson summaries on MyMoodle Page
516  *
517  * Prints lesson name, due date and attempt information on
518  * lessons that have a deadline that has not already passed
519  * and it is available for taking.
520  *
521  * @global object
522  * @global stdClass
523  * @global object
524  * @uses CONTEXT_MODULE
525  * @param array $courses An array of course objects to get lesson instances from
526  * @param array $htmlarray Store overview output array( course ID => 'lesson' => HTML output )
527  * @return void
528  */
529 function lesson_print_overview($courses, &$htmlarray) {
530     global $USER, $CFG, $DB, $OUTPUT;
532     if (!$lessons = get_all_instances_in_courses('lesson', $courses)) {
533         return;
534     }
536     // Get all of the current users attempts on all lessons.
537     $params = array($USER->id);
538     $sql = 'SELECT lessonid, userid, count(userid) as attempts
539               FROM {lesson_grades}
540              WHERE userid = ?
541           GROUP BY lessonid, userid';
542     $allattempts = $DB->get_records_sql($sql, $params);
543     $completedattempts = array();
544     foreach ($allattempts as $myattempt) {
545         $completedattempts[$myattempt->lessonid] = $myattempt->attempts;
546     }
548     // Get the current course ID.
549     $listoflessons = array();
550     foreach ($lessons as $lesson) {
551         $listoflessons[] = $lesson->id;
552     }
553     // Get the last page viewed by the current user for every lesson in this course.
554     list($insql, $inparams) = $DB->get_in_or_equal($listoflessons, SQL_PARAMS_NAMED);
555     $dbparams = array_merge($inparams, array('userid' => $USER->id));
557     // Get the lesson attempts for the user that have the maximum 'timeseen' value.
558     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.answerid as nextpageid, p.qtype ";
559     $from = "FROM {lesson_attempts} l
560              JOIN (
561                    SELECT idselect.lessonid, idselect.userid, MAX(idselect.id) AS id
562                      FROM {lesson_attempts} idselect
563                      JOIN (
564                            SELECT lessonid, userid, MAX(timeseen) AS timeseen
565                              FROM {lesson_attempts}
566                             WHERE userid = :userid
567                               AND lessonid $insql
568                          GROUP BY userid, lessonid
569                            ) timeselect
570                        ON timeselect.timeseen = idselect.timeseen
571                       AND timeselect.userid = idselect.userid
572                       AND timeselect.lessonid = idselect.lessonid
573                  GROUP BY idselect.userid, idselect.lessonid
574                    ) aid
575                ON l.id = aid.id
576              JOIN {lesson_pages} p
577                ON l.pageid = p.id ";
578     $lastattempts = $DB->get_records_sql($select . $from, $dbparams);
580     // Now, get the lesson branches for the user that have the maximum 'timeseen' value.
581     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.nextpageid, p.qtype ";
582     $from = str_replace('{lesson_attempts}', '{lesson_branch}', $from);
583     $lastbranches = $DB->get_records_sql($select . $from, $dbparams);
585     $lastviewed = array();
586     foreach ($lastattempts as $lastattempt) {
587         $lastviewed[$lastattempt->lessonid] = $lastattempt;
588     }
590     // Go through the branch times and record the 'timeseen' value if it doesn't exist
591     // for the lesson, or replace it if it exceeds the current recorded time.
592     foreach ($lastbranches as $lastbranch) {
593         if (!isset($lastviewed[$lastbranch->lessonid])) {
594             $lastviewed[$lastbranch->lessonid] = $lastbranch;
595         } else if ($lastviewed[$lastbranch->lessonid]->timeseen < $lastbranch->timeseen) {
596             $lastviewed[$lastbranch->lessonid] = $lastbranch;
597         }
598     }
600     // Since we have lessons in this course, now include the constants we need.
601     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
603     $now = time();
604     foreach ($lessons as $lesson) {
605         if ($lesson->deadline != 0                                         // The lesson has a deadline
606             and $lesson->deadline >= $now                                  // And it is before the deadline has been met
607             and ($lesson->available == 0 or $lesson->available <= $now)) { // And the lesson is available
609             // Visibility.
610             $class = (!$lesson->visible) ? 'dimmed' : '';
612             // Context.
613             $context = context_module::instance($lesson->coursemodule);
615             // Link to activity.
616             $url = new moodle_url('/mod/lesson/view.php', array('id' => $lesson->coursemodule));
617             $url = html_writer::link($url, format_string($lesson->name, true, array('context' => $context)), array('class' => $class));
618             $str = $OUTPUT->box(get_string('lessonname', 'lesson', $url), 'name');
620             // Deadline.
621             $str .= $OUTPUT->box(get_string('lessoncloseson', 'lesson', userdate($lesson->deadline)), 'info');
623             // Attempt information.
624             if (has_capability('mod/lesson:manage', $context)) {
625                 // This is a teacher, Get the Number of user attempts.
626                 $attempts = $DB->count_records('lesson_grades', array('lessonid' => $lesson->id));
627                 $str     .= $OUTPUT->box(get_string('xattempts', 'lesson', $attempts), 'info');
628                 $str      = $OUTPUT->box($str, 'lesson overview');
629             } else {
630                 // This is a student, See if the user has at least started the lesson.
631                 if (isset($lastviewed[$lesson->id]->timeseen)) {
632                     // See if the user has finished this attempt.
633                     if (isset($completedattempts[$lesson->id]) &&
634                              ($completedattempts[$lesson->id] == ($lastviewed[$lesson->id]->retry + 1))) {
635                         // Are additional attempts allowed?
636                         if ($lesson->retake) {
637                             // User can retake the lesson.
638                             $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
639                             $str = $OUTPUT->box($str, 'lesson overview');
640                         } else {
641                             // User has completed the lesson and no retakes are allowed.
642                             $str = '';
643                         }
645                     } else {
646                         // The last attempt was not finished or the lesson does not contain questions.
647                         // See if the last page viewed was a branchtable.
648                         require_once($CFG->dirroot . '/mod/lesson/pagetypes/branchtable.php');
649                         if ($lastviewed[$lesson->id]->qtype == LESSON_PAGE_BRANCHTABLE) {
650                             // See if the next pageid is the end of lesson.
651                             if ($lastviewed[$lesson->id]->nextpageid == LESSON_EOL) {
652                                 // The last page viewed was the End of Lesson.
653                                 if ($lesson->retake) {
654                                     // User can retake the lesson.
655                                     $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
656                                     $str = $OUTPUT->box($str, 'lesson overview');
657                                 } else {
658                                     // User has completed the lesson and no retakes are allowed.
659                                     $str = '';
660                                 }
662                             } else {
663                                 // The last page viewed was NOT the end of lesson.
664                                 $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
665                                 $str = $OUTPUT->box($str, 'lesson overview');
666                             }
668                         } else {
669                             // Last page was a question page, so the attempt is not completed yet.
670                             $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
671                             $str = $OUTPUT->box($str, 'lesson overview');
672                         }
673                     }
675                 } else {
676                     // User has not yet started this lesson.
677                     $str .= $OUTPUT->box(get_string('nolessonattempts', 'lesson'), 'info');
678                     $str = $OUTPUT->box($str, 'lesson overview');
679                 }
680             }
681             if (!empty($str)) {
682                 if (empty($htmlarray[$lesson->course]['lesson'])) {
683                     $htmlarray[$lesson->course]['lesson'] = $str;
684                 } else {
685                     $htmlarray[$lesson->course]['lesson'] .= $str;
686                 }
687             }
688         }
689     }
692 /**
693  * Function to be run periodically according to the moodle cron
694  * This function searches for things that need to be done, such
695  * as sending out mail, toggling flags etc ...
696  * @global stdClass
697  * @return bool true
698  */
699 function lesson_cron () {
700     global $CFG;
702     return true;
705 /**
706  * Return grade for given user or all users.
707  *
708  * @global stdClass
709  * @global object
710  * @param int $lessonid id of lesson
711  * @param int $userid optional user id, 0 means all users
712  * @return array array of grades, false if none
713  */
714 function lesson_get_user_grades($lesson, $userid=0) {
715     global $CFG, $DB;
717     $params = array("lessonid" => $lesson->id,"lessonid2" => $lesson->id);
719     if (!empty($userid)) {
720         $params["userid"] = $userid;
721         $params["userid2"] = $userid;
722         $user = "AND u.id = :userid";
723         $fuser = "AND uu.id = :userid2";
724     }
725     else {
726         $user="";
727         $fuser="";
728     }
730     if ($lesson->retake) {
731         if ($lesson->usemaxgrade) {
732             $sql = "SELECT u.id, u.id AS userid, MAX(g.grade) AS rawgrade
733                       FROM {user} u, {lesson_grades} g
734                      WHERE u.id = g.userid AND g.lessonid = :lessonid
735                            $user
736                   GROUP BY u.id";
737         } else {
738             $sql = "SELECT u.id, u.id AS userid, AVG(g.grade) AS rawgrade
739                       FROM {user} u, {lesson_grades} g
740                      WHERE u.id = g.userid AND g.lessonid = :lessonid
741                            $user
742                   GROUP BY u.id";
743         }
744         unset($params['lessonid2']);
745         unset($params['userid2']);
746     } else {
747         // use only first attempts (with lowest id in lesson_grades table)
748         $firstonly = "SELECT uu.id AS userid, MIN(gg.id) AS firstcompleted
749                         FROM {user} uu, {lesson_grades} gg
750                        WHERE uu.id = gg.userid AND gg.lessonid = :lessonid2
751                              $fuser
752                        GROUP BY uu.id";
754         $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade
755                   FROM {user} u, {lesson_grades} g, ($firstonly) f
756                  WHERE u.id = g.userid AND g.lessonid = :lessonid
757                        AND g.id = f.firstcompleted AND g.userid=f.userid
758                        $user";
759     }
761     return $DB->get_records_sql($sql, $params);
764 /**
765  * Update grades in central gradebook
766  *
767  * @category grade
768  * @param object $lesson
769  * @param int $userid specific user only, 0 means all
770  * @param bool $nullifnone
771  */
772 function lesson_update_grades($lesson, $userid=0, $nullifnone=true) {
773     global $CFG, $DB;
774     require_once($CFG->libdir.'/gradelib.php');
776     if ($lesson->grade == 0 || $lesson->practice) {
777         lesson_grade_item_update($lesson);
779     } else if ($grades = lesson_get_user_grades($lesson, $userid)) {
780         lesson_grade_item_update($lesson, $grades);
782     } else if ($userid and $nullifnone) {
783         $grade = new stdClass();
784         $grade->userid   = $userid;
785         $grade->rawgrade = null;
786         lesson_grade_item_update($lesson, $grade);
788     } else {
789         lesson_grade_item_update($lesson);
790     }
793 /**
794  * Create grade item for given lesson
795  *
796  * @category grade
797  * @uses GRADE_TYPE_VALUE
798  * @uses GRADE_TYPE_NONE
799  * @param object $lesson object with extra cmidnumber
800  * @param array|object $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
801  * @return int 0 if ok, error code otherwise
802  */
803 function lesson_grade_item_update($lesson, $grades=null) {
804     global $CFG;
805     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
806         require_once($CFG->libdir.'/gradelib.php');
807     }
809     if (array_key_exists('cmidnumber', $lesson)) { //it may not be always present
810         $params = array('itemname'=>$lesson->name, 'idnumber'=>$lesson->cmidnumber);
811     } else {
812         $params = array('itemname'=>$lesson->name);
813     }
815     if (!$lesson->practice and $lesson->grade > 0) {
816         $params['gradetype']  = GRADE_TYPE_VALUE;
817         $params['grademax']   = $lesson->grade;
818         $params['grademin']   = 0;
819     } else if (!$lesson->practice and $lesson->grade < 0) {
820         $params['gradetype']  = GRADE_TYPE_SCALE;
821         $params['scaleid']   = -$lesson->grade;
823         // Make sure current grade fetched correctly from $grades
824         $currentgrade = null;
825         if (!empty($grades)) {
826             if (is_array($grades)) {
827                 $currentgrade = reset($grades);
828             } else {
829                 $currentgrade = $grades;
830             }
831         }
833         // When converting a score to a scale, use scale's grade maximum to calculate it.
834         if (!empty($currentgrade) && $currentgrade->rawgrade !== null) {
835             $grade = grade_get_grades($lesson->course, 'mod', 'lesson', $lesson->id, $currentgrade->userid);
836             $params['grademax']   = reset($grade->items)->grademax;
837         }
838     } else {
839         $params['gradetype']  = GRADE_TYPE_NONE;
840     }
842     if ($grades  === 'reset') {
843         $params['reset'] = true;
844         $grades = null;
845     } else if (!empty($grades)) {
846         // Need to calculate raw grade (Note: $grades has many forms)
847         if (is_object($grades)) {
848             $grades = array($grades->userid => $grades);
849         } else if (array_key_exists('userid', $grades)) {
850             $grades = array($grades['userid'] => $grades);
851         }
852         foreach ($grades as $key => $grade) {
853             if (!is_array($grade)) {
854                 $grades[$key] = $grade = (array) $grade;
855             }
856             //check raw grade isnt null otherwise we erroneously insert a grade of 0
857             if ($grade['rawgrade'] !== null) {
858                 $grades[$key]['rawgrade'] = ($grade['rawgrade'] * $params['grademax'] / 100);
859             } else {
860                 //setting rawgrade to null just in case user is deleting a grade
861                 $grades[$key]['rawgrade'] = null;
862             }
863         }
864     }
866     return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, $grades, $params);
869 /**
870  * List the actions that correspond to a view of this module.
871  * This is used by the participation report.
872  *
873  * Note: This is not used by new logging system. Event with
874  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
875  *       be considered as view action.
876  *
877  * @return array
878  */
879 function lesson_get_view_actions() {
880     return array('view','view all');
883 /**
884  * List the actions that correspond to a post of this module.
885  * This is used by the participation report.
886  *
887  * Note: This is not used by new logging system. Event with
888  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
889  *       will be considered as post action.
890  *
891  * @return array
892  */
893 function lesson_get_post_actions() {
894     return array('end','start');
897 /**
898  * Runs any processes that must run before
899  * a lesson insert/update
900  *
901  * @global object
902  * @param object $lesson Lesson form data
903  * @return void
904  **/
905 function lesson_process_pre_save(&$lesson) {
906     global $DB;
908     $lesson->timemodified = time();
910     if (empty($lesson->timelimit)) {
911         $lesson->timelimit = 0;
912     }
913     if (empty($lesson->timespent) or !is_numeric($lesson->timespent) or $lesson->timespent < 0) {
914         $lesson->timespent = 0;
915     }
916     if (!isset($lesson->completed)) {
917         $lesson->completed = 0;
918     }
919     if (empty($lesson->gradebetterthan) or !is_numeric($lesson->gradebetterthan) or $lesson->gradebetterthan < 0) {
920         $lesson->gradebetterthan = 0;
921     } else if ($lesson->gradebetterthan > 100) {
922         $lesson->gradebetterthan = 100;
923     }
925     if (empty($lesson->width)) {
926         $lesson->width = 640;
927     }
928     if (empty($lesson->height)) {
929         $lesson->height = 480;
930     }
931     if (empty($lesson->bgcolor)) {
932         $lesson->bgcolor = '#FFFFFF';
933     }
935     // Conditions for dependency
936     $conditions = new stdClass;
937     $conditions->timespent = $lesson->timespent;
938     $conditions->completed = $lesson->completed;
939     $conditions->gradebetterthan = $lesson->gradebetterthan;
940     $lesson->conditions = serialize($conditions);
941     unset($lesson->timespent);
942     unset($lesson->completed);
943     unset($lesson->gradebetterthan);
945     if (empty($lesson->password)) {
946         unset($lesson->password);
947     }
950 /**
951  * Runs any processes that must be run
952  * after a lesson insert/update
953  *
954  * @global object
955  * @param object $lesson Lesson form data
956  * @return void
957  **/
958 function lesson_process_post_save(&$lesson) {
959     // Update the events relating to this lesson.
960     lesson_update_events($lesson);
964 /**
965  * Implementation of the function for printing the form elements that control
966  * whether the course reset functionality affects the lesson.
967  *
968  * @param $mform form passed by reference
969  */
970 function lesson_reset_course_form_definition(&$mform) {
971     $mform->addElement('header', 'lessonheader', get_string('modulenameplural', 'lesson'));
972     $mform->addElement('advcheckbox', 'reset_lesson', get_string('deleteallattempts','lesson'));
973     $mform->addElement('advcheckbox', 'reset_lesson_user_overrides',
974             get_string('removealluseroverrides', 'lesson'));
975     $mform->addElement('advcheckbox', 'reset_lesson_group_overrides',
976             get_string('removeallgroupoverrides', 'lesson'));
979 /**
980  * Course reset form defaults.
981  * @param object $course
982  * @return array
983  */
984 function lesson_reset_course_form_defaults($course) {
985     return array('reset_lesson' => 1,
986             'reset_lesson_group_overrides' => 1,
987             'reset_lesson_user_overrides' => 1);
990 /**
991  * Removes all grades from gradebook
992  *
993  * @global stdClass
994  * @global object
995  * @param int $courseid
996  * @param string optional type
997  */
998 function lesson_reset_gradebook($courseid, $type='') {
999     global $CFG, $DB;
1001     $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid
1002               FROM {lesson} l, {course_modules} cm, {modules} m
1003              WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id AND l.course=:course";
1004     $params = array ("course" => $courseid);
1005     if ($lessons = $DB->get_records_sql($sql,$params)) {
1006         foreach ($lessons as $lesson) {
1007             lesson_grade_item_update($lesson, 'reset');
1008         }
1009     }
1012 /**
1013  * Actual implementation of the reset course functionality, delete all the
1014  * lesson attempts for course $data->courseid.
1015  *
1016  * @global stdClass
1017  * @global object
1018  * @param object $data the data submitted from the reset course.
1019  * @return array status array
1020  */
1021 function lesson_reset_userdata($data) {
1022     global $CFG, $DB;
1024     $componentstr = get_string('modulenameplural', 'lesson');
1025     $status = array();
1027     if (!empty($data->reset_lesson)) {
1028         $lessonssql = "SELECT l.id
1029                          FROM {lesson} l
1030                         WHERE l.course=:course";
1032         $params = array ("course" => $data->courseid);
1033         $lessons = $DB->get_records_sql($lessonssql, $params);
1035         // Get rid of attempts files.
1036         $fs = get_file_storage();
1037         if ($lessons) {
1038             foreach ($lessons as $lessonid => $unused) {
1039                 if (!$cm = get_coursemodule_from_instance('lesson', $lessonid)) {
1040                     continue;
1041                 }
1042                 $context = context_module::instance($cm->id);
1043                 $fs->delete_area_files($context->id, 'mod_lesson', 'essay_responses');
1044             }
1045         }
1047         $DB->delete_records_select('lesson_timer', "lessonid IN ($lessonssql)", $params);
1048         $DB->delete_records_select('lesson_grades', "lessonid IN ($lessonssql)", $params);
1049         $DB->delete_records_select('lesson_attempts', "lessonid IN ($lessonssql)", $params);
1050         $DB->delete_records_select('lesson_branch', "lessonid IN ($lessonssql)", $params);
1052         // remove all grades from gradebook
1053         if (empty($data->reset_gradebook_grades)) {
1054             lesson_reset_gradebook($data->courseid);
1055         }
1057         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'lesson'), 'error'=>false);
1058     }
1060     // Remove user overrides.
1061     if (!empty($data->reset_lesson_user_overrides)) {
1062         $DB->delete_records_select('lesson_overrides',
1063                 'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid));
1064         $status[] = array(
1065         'component' => $componentstr,
1066         'item' => get_string('useroverridesdeleted', 'lesson'),
1067         'error' => false);
1068     }
1069     // Remove group overrides.
1070     if (!empty($data->reset_lesson_group_overrides)) {
1071         $DB->delete_records_select('lesson_overrides',
1072         'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid));
1073         $status[] = array(
1074         'component' => $componentstr,
1075         'item' => get_string('groupoverridesdeleted', 'lesson'),
1076         'error' => false);
1077     }
1078     /// updating dates - shift may be negative too
1079     if ($data->timeshift) {
1080         $DB->execute("UPDATE {lesson_overrides}
1081                          SET available = available + ?
1082                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
1083                          AND available <> 0", array($data->timeshift, $data->courseid));
1084         $DB->execute("UPDATE {lesson_overrides}
1085                          SET deadline = deadline + ?
1086                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
1087                          AND deadline <> 0", array($data->timeshift, $data->courseid));
1089         shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid);
1090         $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
1091     }
1093     return $status;
1096 /**
1097  * Returns all other caps used in module
1098  * @return array
1099  */
1100 function lesson_get_extra_capabilities() {
1101     return array('moodle/site:accessallgroups');
1104 /**
1105  * @uses FEATURE_GROUPS
1106  * @uses FEATURE_GROUPINGS
1107  * @uses FEATURE_MOD_INTRO
1108  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
1109  * @uses FEATURE_GRADE_HAS_GRADE
1110  * @uses FEATURE_GRADE_OUTCOMES
1111  * @param string $feature FEATURE_xx constant for requested feature
1112  * @return mixed True if module supports feature, false if not, null if doesn't know
1113  */
1114 function lesson_supports($feature) {
1115     switch($feature) {
1116         case FEATURE_GROUPS:
1117             return true;
1118         case FEATURE_GROUPINGS:
1119             return true;
1120         case FEATURE_MOD_INTRO:
1121             return true;
1122         case FEATURE_COMPLETION_TRACKS_VIEWS:
1123             return true;
1124         case FEATURE_GRADE_HAS_GRADE:
1125             return true;
1126         case FEATURE_COMPLETION_HAS_RULES:
1127             return true;
1128         case FEATURE_GRADE_OUTCOMES:
1129             return true;
1130         case FEATURE_BACKUP_MOODLE2:
1131             return true;
1132         case FEATURE_SHOW_DESCRIPTION:
1133             return true;
1134         default:
1135             return null;
1136     }
1139 /**
1140  * Obtains the automatic completion state for this lesson based on any conditions
1141  * in lesson settings.
1142  *
1143  * @param object $course Course
1144  * @param object $cm course-module
1145  * @param int $userid User ID
1146  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1147  * @return bool True if completed, false if not, $type if conditions not set.
1148  */
1149 function lesson_get_completion_state($course, $cm, $userid, $type) {
1150     global $CFG, $DB;
1152     // Get lesson details.
1153     $lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*',
1154             MUST_EXIST);
1156     $result = $type; // Default return value.
1157     // If completion option is enabled, evaluate it and return true/false.
1158     if ($lesson->completionendreached) {
1159         $value = $DB->record_exists('lesson_timer', array(
1160                 'lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1));
1161         if ($type == COMPLETION_AND) {
1162             $result = $result && $value;
1163         } else {
1164             $result = $result || $value;
1165         }
1166     }
1167     if ($lesson->completiontimespent != 0) {
1168         $duration = $DB->get_field_sql(
1169                         "SELECT SUM(lessontime - starttime)
1170                                FROM {lesson_timer}
1171                               WHERE lessonid = :lessonid
1172                                 AND userid = :userid",
1173                         array('userid' => $userid, 'lessonid' => $lesson->id));
1174         if (!$duration) {
1175             $duration = 0;
1176         }
1177         if ($type == COMPLETION_AND) {
1178             $result = $result && ($lesson->completiontimespent < $duration);
1179         } else {
1180             $result = $result || ($lesson->completiontimespent < $duration);
1181         }
1182     }
1183     return $result;
1185 /**
1186  * This function extends the settings navigation block for the site.
1187  *
1188  * It is safe to rely on PAGE here as we will only ever be within the module
1189  * context when this is called
1190  *
1191  * @param settings_navigation $settings
1192  * @param navigation_node $lessonnode
1193  */
1194 function lesson_extend_settings_navigation($settings, $lessonnode) {
1195     global $PAGE, $DB;
1197     // We want to add these new nodes after the Edit settings node, and before the
1198     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
1199     $keys = $lessonnode->get_children_key_list();
1200     $beforekey = null;
1201     $i = array_search('modedit', $keys);
1202     if ($i === false and array_key_exists(0, $keys)) {
1203         $beforekey = $keys[0];
1204     } else if (array_key_exists($i + 1, $keys)) {
1205         $beforekey = $keys[$i + 1];
1206     }
1208     if (has_capability('mod/lesson:manageoverrides', $PAGE->cm->context)) {
1209         $url = new moodle_url('/mod/lesson/overrides.php', array('cmid' => $PAGE->cm->id));
1210         $node = navigation_node::create(get_string('groupoverrides', 'lesson'),
1211                 new moodle_url($url, array('mode' => 'group')),
1212                 navigation_node::TYPE_SETTING, null, 'mod_lesson_groupoverrides');
1213         $lessonnode->add_node($node, $beforekey);
1215         $node = navigation_node::create(get_string('useroverrides', 'lesson'),
1216                 new moodle_url($url, array('mode' => 'user')),
1217                 navigation_node::TYPE_SETTING, null, 'mod_lesson_useroverrides');
1218         $lessonnode->add_node($node, $beforekey);
1219     }
1221     if (has_capability('mod/lesson:edit', $PAGE->cm->context)) {
1222         $url = new moodle_url('/mod/lesson/view.php', array('id' => $PAGE->cm->id));
1223         $lessonnode->add(get_string('preview', 'lesson'), $url);
1224         $editnode = $lessonnode->add(get_string('edit', 'lesson'));
1225         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'collapsed'));
1226         $editnode->add(get_string('collapsed', 'lesson'), $url);
1227         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'full'));
1228         $editnode->add(get_string('full', 'lesson'), $url);
1229     }
1231     if (has_capability('mod/lesson:viewreports', $PAGE->cm->context)) {
1232         $reportsnode = $lessonnode->add(get_string('reports', 'lesson'));
1233         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportoverview'));
1234         $reportsnode->add(get_string('overview', 'lesson'), $url);
1235         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportdetail'));
1236         $reportsnode->add(get_string('detailedstats', 'lesson'), $url);
1237     }
1239     if (has_capability('mod/lesson:grade', $PAGE->cm->context)) {
1240         $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$PAGE->cm->id));
1241         $lessonnode->add(get_string('manualgrading', 'lesson'), $url);
1242     }
1246 /**
1247  * Get list of available import or export formats
1248  *
1249  * Copied and modified from lib/questionlib.php
1250  *
1251  * @param string $type 'import' if import list, otherwise export list assumed
1252  * @return array sorted list of import/export formats available
1253  */
1254 function lesson_get_import_export_formats($type) {
1255     global $CFG;
1256     $fileformats = core_component::get_plugin_list("qformat");
1258     $fileformatname=array();
1259     foreach ($fileformats as $fileformat=>$fdir) {
1260         $format_file = "$fdir/format.php";
1261         if (file_exists($format_file) ) {
1262             require_once($format_file);
1263         } else {
1264             continue;
1265         }
1266         $classname = "qformat_$fileformat";
1267         $format_class = new $classname();
1268         if ($type=='import') {
1269             $provided = $format_class->provide_import();
1270         } else {
1271             $provided = $format_class->provide_export();
1272         }
1273         if ($provided) {
1274             $fileformatnames[$fileformat] = get_string('pluginname', 'qformat_'.$fileformat);
1275         }
1276     }
1277     natcasesort($fileformatnames);
1279     return $fileformatnames;
1282 /**
1283  * Serves the lesson attachments. Implements needed access control ;-)
1284  *
1285  * @package mod_lesson
1286  * @category files
1287  * @param stdClass $course course object
1288  * @param stdClass $cm course module object
1289  * @param stdClass $context context object
1290  * @param string $filearea file area
1291  * @param array $args extra arguments
1292  * @param bool $forcedownload whether or not force download
1293  * @param array $options additional options affecting the file serving
1294  * @return bool false if file not found, does not return if found - justsend the file
1295  */
1296 function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
1297     global $CFG, $DB;
1299     if ($context->contextlevel != CONTEXT_MODULE) {
1300         return false;
1301     }
1303     $fileareas = lesson_get_file_areas();
1304     if (!array_key_exists($filearea, $fileareas)) {
1305         return false;
1306     }
1308     if (!$lesson = $DB->get_record('lesson', array('id'=>$cm->instance))) {
1309         return false;
1310     }
1312     require_course_login($course, true, $cm);
1314     if ($filearea === 'page_contents') {
1315         $pageid = (int)array_shift($args);
1316         if (!$page = $DB->get_record('lesson_pages', array('id'=>$pageid))) {
1317             return false;
1318         }
1319         $fullpath = "/$context->id/mod_lesson/$filearea/$pageid/".implode('/', $args);
1321     } else if ($filearea === 'page_answers' || $filearea === 'page_responses') {
1322         $itemid = (int)array_shift($args);
1323         if (!$pageanswers = $DB->get_record('lesson_answers', array('id' => $itemid))) {
1324             return false;
1325         }
1326         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
1328     } else if ($filearea === 'essay_responses') {
1329         $itemid = (int)array_shift($args);
1330         if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $itemid))) {
1331             return false;
1332         }
1333         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
1335     } else if ($filearea === 'mediafile') {
1336         if (count($args) > 1) {
1337             // Remove the itemid when it appears to be part of the arguments. If there is only one argument
1338             // then it is surely the file name. The itemid is sometimes used to prevent browser caching.
1339             array_shift($args);
1340         }
1341         $fullpath = "/$context->id/mod_lesson/$filearea/0/".implode('/', $args);
1343     } else {
1344         return false;
1345     }
1347     $fs = get_file_storage();
1348     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1349         return false;
1350     }
1352     // finally send the file
1353     send_stored_file($file, 0, 0, $forcedownload, $options); // download MUST be forced - security!
1356 /**
1357  * Returns an array of file areas
1358  *
1359  * @package  mod_lesson
1360  * @category files
1361  * @return array a list of available file areas
1362  */
1363 function lesson_get_file_areas() {
1364     $areas = array();
1365     $areas['page_contents'] = get_string('pagecontents', 'mod_lesson');
1366     $areas['mediafile'] = get_string('mediafile', 'mod_lesson');
1367     $areas['page_answers'] = get_string('pageanswers', 'mod_lesson');
1368     $areas['page_responses'] = get_string('pageresponses', 'mod_lesson');
1369     $areas['essay_responses'] = get_string('essayresponses', 'mod_lesson');
1370     return $areas;
1373 /**
1374  * Returns a file_info_stored object for the file being requested here
1375  *
1376  * @package  mod_lesson
1377  * @category files
1378  * @global stdClass $CFG
1379  * @param file_browse $browser file browser instance
1380  * @param array $areas file areas
1381  * @param stdClass $course course object
1382  * @param stdClass $cm course module object
1383  * @param stdClass $context context object
1384  * @param string $filearea file area
1385  * @param int $itemid item ID
1386  * @param string $filepath file path
1387  * @param string $filename file name
1388  * @return file_info_stored
1389  */
1390 function lesson_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1391     global $CFG, $DB;
1393     if (!has_capability('moodle/course:managefiles', $context)) {
1394         // No peaking here for students!
1395         return null;
1396     }
1398     // Mediafile area does not have sub directories, so let's select the default itemid to prevent
1399     // the user from selecting a directory to access the mediafile content.
1400     if ($filearea == 'mediafile' && is_null($itemid)) {
1401         $itemid = 0;
1402     }
1404     if (is_null($itemid)) {
1405         return new mod_lesson_file_info($browser, $course, $cm, $context, $areas, $filearea);
1406     }
1408     $fs = get_file_storage();
1409     $filepath = is_null($filepath) ? '/' : $filepath;
1410     $filename = is_null($filename) ? '.' : $filename;
1411     if (!$storedfile = $fs->get_file($context->id, 'mod_lesson', $filearea, $itemid, $filepath, $filename)) {
1412         return null;
1413     }
1415     $itemname = $filearea;
1416     if ($filearea == 'page_contents') {
1417         $itemname = $DB->get_field('lesson_pages', 'title', array('lessonid' => $cm->instance, 'id' => $itemid));
1418         $itemname = format_string($itemname, true, array('context' => $context));
1419     } else {
1420         $areas = lesson_get_file_areas();
1421         if (isset($areas[$filearea])) {
1422             $itemname = $areas[$filearea];
1423         }
1424     }
1426     $urlbase = $CFG->wwwroot . '/pluginfile.php';
1427     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemname, $itemid, true, true, false);
1431 /**
1432  * Return a list of page types
1433  * @param string $pagetype current page type
1434  * @param stdClass $parentcontext Block's parent context
1435  * @param stdClass $currentcontext Current context of block
1436  */
1437 function lesson_page_type_list($pagetype, $parentcontext, $currentcontext) {
1438     $module_pagetype = array(
1439         'mod-lesson-*'=>get_string('page-mod-lesson-x', 'lesson'),
1440         'mod-lesson-view'=>get_string('page-mod-lesson-view', 'lesson'),
1441         'mod-lesson-edit'=>get_string('page-mod-lesson-edit', 'lesson'));
1442     return $module_pagetype;
1445 /**
1446  * Update the lesson activity to include any file
1447  * that was uploaded, or if there is none, set the
1448  * mediafile field to blank.
1449  *
1450  * @param int $lessonid the lesson id
1451  * @param stdClass $context the context
1452  * @param int $draftitemid the draft item
1453  */
1454 function lesson_update_media_file($lessonid, $context, $draftitemid) {
1455     global $DB;
1457     // Set the filestorage object.
1458     $fs = get_file_storage();
1459     // Save the file if it exists that is currently in the draft area.
1460     file_save_draft_area_files($draftitemid, $context->id, 'mod_lesson', 'mediafile', 0);
1461     // Get the file if it exists.
1462     $files = $fs->get_area_files($context->id, 'mod_lesson', 'mediafile', 0, 'itemid, filepath, filename', false);
1463     // Check that there is a file to process.
1464     if (count($files) == 1) {
1465         // Get the first (and only) file.
1466         $file = reset($files);
1467         // Set the mediafile column in the lessons table.
1468         $DB->set_field('lesson', 'mediafile', '/' . $file->get_filename(), array('id' => $lessonid));
1469     } else {
1470         // Set the mediafile column in the lessons table.
1471         $DB->set_field('lesson', 'mediafile', '', array('id' => $lessonid));
1472     }