"Fossies" - the Fresh Open Source Software Archive

Member "moodle/mod/lesson/lib.php" (6 Sep 2019, 71943 Bytes) of package /linux/www/moodle-3.6.6.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "lib.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.6.5_vs_3.6.6.

    1 <?php
    2 
    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/>.
   17 
   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  **/
   25 
   26 defined('MOODLE_INTERNAL') || die();
   27 
   28 // Event types.
   29 define('LESSON_EVENT_TYPE_OPEN', 'open');
   30 define('LESSON_EVENT_TYPE_CLOSE', 'close');
   31 
   32 /* Do not include any libraries here! */
   33 
   34 /**
   35  * Given an object containing all the necessary data,
   36  * (defined by the form in mod_form.php) this function
   37  * will create a new instance and return the id number
   38  * of the new instance.
   39  *
   40  * @global object
   41  * @global object
   42  * @param object $lesson Lesson post data from the form
   43  * @return int
   44  **/
   45 function lesson_add_instance($data, $mform) {
   46     global $DB;
   47 
   48     $cmid = $data->coursemodule;
   49     $draftitemid = $data->mediafile;
   50     $context = context_module::instance($cmid);
   51 
   52     lesson_process_pre_save($data);
   53 
   54     unset($data->mediafile);
   55     $lessonid = $DB->insert_record("lesson", $data);
   56     $data->id = $lessonid;
   57 
   58     lesson_update_media_file($lessonid, $context, $draftitemid);
   59 
   60     lesson_process_post_save($data);
   61 
   62     lesson_grade_item_update($data);
   63 
   64     return $lessonid;
   65 }
   66 
   67 /**
   68  * Given an object containing all the necessary data,
   69  * (defined by the form in mod_form.php) this function
   70  * will update an existing instance with new data.
   71  *
   72  * @global object
   73  * @param object $lesson Lesson post data from the form
   74  * @return boolean
   75  **/
   76 function lesson_update_instance($data, $mform) {
   77     global $DB;
   78 
   79     $data->id = $data->instance;
   80     $cmid = $data->coursemodule;
   81     $draftitemid = $data->mediafile;
   82     $context = context_module::instance($cmid);
   83 
   84     lesson_process_pre_save($data);
   85 
   86     unset($data->mediafile);
   87     $DB->update_record("lesson", $data);
   88 
   89     lesson_update_media_file($data->id, $context, $draftitemid);
   90 
   91     lesson_process_post_save($data);
   92 
   93     // update grade item definition
   94     lesson_grade_item_update($data);
   95 
   96     // update grades - TODO: do it only when grading style changes
   97     lesson_update_grades($data, 0, false);
   98 
   99     return true;
  100 }
  101 
  102 /**
  103  * This function updates the events associated to the lesson.
  104  * If $override is non-zero, then it updates only the events
  105  * associated with the specified override.
  106  *
  107  * @uses LESSON_MAX_EVENT_LENGTH
  108  * @param object $lesson the lesson object.
  109  * @param object $override (optional) limit to a specific override
  110  */
  111 function lesson_update_events($lesson, $override = null) {
  112     global $CFG, $DB;
  113 
  114     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
  115     require_once($CFG->dirroot . '/calendar/lib.php');
  116 
  117     // Load the old events relating to this lesson.
  118     $conds = array('modulename' => 'lesson',
  119                    'instance' => $lesson->id);
  120     if (!empty($override)) {
  121         // Only load events for this override.
  122         if (isset($override->userid)) {
  123             $conds['userid'] = $override->userid;
  124         } else {
  125             $conds['groupid'] = $override->groupid;
  126         }
  127     }
  128     $oldevents = $DB->get_records('event', $conds, 'id ASC');
  129 
  130     // Now make a to-do list of all that needs to be updated.
  131     if (empty($override)) {
  132         // We are updating the primary settings for the lesson, so we need to add all the overrides.
  133         $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id), 'id ASC');
  134         // It is necessary to add an empty stdClass to the beginning of the array as the $oldevents
  135         // list contains the original (non-override) event for the module. If this is not included
  136         // the logic below will end up updating the wrong row when we try to reconcile this $overrides
  137         // list against the $oldevents list.
  138         array_unshift($overrides, new stdClass());
  139     } else {
  140         // Just do the one override.
  141         $overrides = array($override);
  142     }
  143 
  144     // Get group override priorities.
  145     $grouppriorities = lesson_get_group_override_priorities($lesson->id);
  146 
  147     foreach ($overrides as $current) {
  148         $groupid   = isset($current->groupid) ? $current->groupid : 0;
  149         $userid    = isset($current->userid) ? $current->userid : 0;
  150         $available  = isset($current->available) ? $current->available : $lesson->available;
  151         $deadline = isset($current->deadline) ? $current->deadline : $lesson->deadline;
  152 
  153         // Only add open/close events for an override if they differ from the lesson default.
  154         $addopen  = empty($current->id) || !empty($current->available);
  155         $addclose = empty($current->id) || !empty($current->deadline);
  156 
  157         if (!empty($lesson->coursemodule)) {
  158             $cmid = $lesson->coursemodule;
  159         } else {
  160             $cmid = get_coursemodule_from_instance('lesson', $lesson->id, $lesson->course)->id;
  161         }
  162 
  163         $event = new stdClass();
  164         $event->type = !$deadline ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
  165         $event->description = format_module_intro('lesson', $lesson, $cmid);
  166         // Events module won't show user events when the courseid is nonzero.
  167         $event->courseid    = ($userid) ? 0 : $lesson->course;
  168         $event->groupid     = $groupid;
  169         $event->userid      = $userid;
  170         $event->modulename  = 'lesson';
  171         $event->instance    = $lesson->id;
  172         $event->timestart   = $available;
  173         $event->timeduration = max($deadline - $available, 0);
  174         $event->timesort    = $available;
  175         $event->visible     = instance_is_visible('lesson', $lesson);
  176         $event->eventtype   = LESSON_EVENT_TYPE_OPEN;
  177         $event->priority    = null;
  178 
  179         // Determine the event name and priority.
  180         if ($groupid) {
  181             // Group override event.
  182             $params = new stdClass();
  183             $params->lesson = $lesson->name;
  184             $params->group = groups_get_group_name($groupid);
  185             if ($params->group === false) {
  186                 // Group doesn't exist, just skip it.
  187                 continue;
  188             }
  189             $eventname = get_string('overridegroupeventname', 'lesson', $params);
  190             // Set group override priority.
  191             if ($grouppriorities !== null) {
  192                 $openpriorities = $grouppriorities['open'];
  193                 if (isset($openpriorities[$available])) {
  194                     $event->priority = $openpriorities[$available];
  195                 }
  196             }
  197         } else if ($userid) {
  198             // User override event.
  199             $params = new stdClass();
  200             $params->lesson = $lesson->name;
  201             $eventname = get_string('overrideusereventname', 'lesson', $params);
  202             // Set user override priority.
  203             $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
  204         } else {
  205             // The parent event.
  206             $eventname = $lesson->name;
  207         }
  208 
  209         if ($addopen or $addclose) {
  210             // Separate start and end events.
  211             $event->timeduration  = 0;
  212             if ($available && $addopen) {
  213                 if ($oldevent = array_shift($oldevents)) {
  214                     $event->id = $oldevent->id;
  215                 } else {
  216                     unset($event->id);
  217                 }
  218                 $event->name = get_string('lessoneventopens', 'lesson', $eventname);
  219                 // The method calendar_event::create will reuse a db record if the id field is set.
  220                 calendar_event::create($event, false);
  221             }
  222             if ($deadline && $addclose) {
  223                 if ($oldevent = array_shift($oldevents)) {
  224                     $event->id = $oldevent->id;
  225                 } else {
  226                     unset($event->id);
  227                 }
  228                 $event->type      = CALENDAR_EVENT_TYPE_ACTION;
  229                 $event->name      = get_string('lessoneventcloses', 'lesson', $eventname);
  230                 $event->timestart = $deadline;
  231                 $event->timesort  = $deadline;
  232                 $event->eventtype = LESSON_EVENT_TYPE_CLOSE;
  233                 if ($groupid && $grouppriorities !== null) {
  234                     $closepriorities = $grouppriorities['close'];
  235                     if (isset($closepriorities[$deadline])) {
  236                         $event->priority = $closepriorities[$deadline];
  237                     }
  238                 }
  239                 calendar_event::create($event, false);
  240             }
  241         }
  242     }
  243 
  244     // Delete any leftover events.
  245     foreach ($oldevents as $badevent) {
  246         $badevent = calendar_event::load($badevent);
  247         $badevent->delete();
  248     }
  249 }
  250 
  251 /**
  252  * Calculates the priorities of timeopen and timeclose values for group overrides for a lesson.
  253  *
  254  * @param int $lessonid The lesson ID.
  255  * @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
  256  */
  257 function lesson_get_group_override_priorities($lessonid) {
  258     global $DB;
  259 
  260     // Fetch group overrides.
  261     $where = 'lessonid = :lessonid AND groupid IS NOT NULL';
  262     $params = ['lessonid' => $lessonid];
  263     $overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline');
  264     if (!$overrides) {
  265         return null;
  266     }
  267 
  268     $grouptimeopen = [];
  269     $grouptimeclose = [];
  270     foreach ($overrides as $override) {
  271         if ($override->available !== null && !in_array($override->available, $grouptimeopen)) {
  272             $grouptimeopen[] = $override->available;
  273         }
  274         if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) {
  275             $grouptimeclose[] = $override->deadline;
  276         }
  277     }
  278 
  279     // Sort open times in ascending manner. The earlier open time gets higher priority.
  280     sort($grouptimeopen);
  281     // Set priorities.
  282     $opengrouppriorities = [];
  283     $openpriority = 1;
  284     foreach ($grouptimeopen as $timeopen) {
  285         $opengrouppriorities[$timeopen] = $openpriority++;
  286     }
  287 
  288     // Sort close times in descending manner. The later close time gets higher priority.
  289     rsort($grouptimeclose);
  290     // Set priorities.
  291     $closegrouppriorities = [];
  292     $closepriority = 1;
  293     foreach ($grouptimeclose as $timeclose) {
  294         $closegrouppriorities[$timeclose] = $closepriority++;
  295     }
  296 
  297     return [
  298         'open' => $opengrouppriorities,
  299         'close' => $closegrouppriorities
  300     ];
  301 }
  302 
  303 /**
  304  * This standard function will check all instances of this module
  305  * and make sure there are up-to-date events created for each of them.
  306  * If courseid = 0, then every lesson event in the site is checked, else
  307  * only lesson events belonging to the course specified are checked.
  308  * This function is used, in its new format, by restore_refresh_events()
  309  *
  310  * @param int $courseid
  311  * @param int|stdClass $instance Lesson module instance or ID.
  312  * @param int|stdClass $cm Course module object or ID (not used in this module).
  313  * @return bool
  314  */
  315 function lesson_refresh_events($courseid = 0, $instance = null, $cm = null) {
  316     global $DB;
  317 
  318     // If we have instance information then we can just update the one event instead of updating all events.
  319     if (isset($instance)) {
  320         if (!is_object($instance)) {
  321             $instance = $DB->get_record('lesson', array('id' => $instance), '*', MUST_EXIST);
  322         }
  323         lesson_update_events($instance);
  324         return true;
  325     }
  326 
  327     if ($courseid == 0) {
  328         if (!$lessons = $DB->get_records('lesson')) {
  329             return true;
  330         }
  331     } else {
  332         if (!$lessons = $DB->get_records('lesson', array('course' => $courseid))) {
  333             return true;
  334         }
  335     }
  336 
  337     foreach ($lessons as $lesson) {
  338         lesson_update_events($lesson);
  339     }
  340 
  341     return true;
  342 }
  343 
  344 /**
  345  * Given an ID of an instance of this module,
  346  * this function will permanently delete the instance
  347  * and any data that depends on it.
  348  *
  349  * @global object
  350  * @param int $id
  351  * @return bool
  352  */
  353 function lesson_delete_instance($id) {
  354     global $DB, $CFG;
  355     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
  356 
  357     $lesson = $DB->get_record("lesson", array("id"=>$id), '*', MUST_EXIST);
  358     $lesson = new lesson($lesson);
  359     return $lesson->delete();
  360 }
  361 
  362 /**
  363  * Return a small object with summary information about what a
  364  * user has done with a given particular instance of this module
  365  * Used for user activity reports.
  366  * $return->time = the time they did it
  367  * $return->info = a short text description
  368  *
  369  * @global object
  370  * @param object $course
  371  * @param object $user
  372  * @param object $mod
  373  * @param object $lesson
  374  * @return object
  375  */
  376 function lesson_user_outline($course, $user, $mod, $lesson) {
  377     global $CFG, $DB;
  378 
  379     require_once("$CFG->libdir/gradelib.php");
  380     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
  381     $return = new stdClass();
  382 
  383     if (empty($grades->items[0]->grades)) {
  384         $return->info = get_string("nolessonattempts", "lesson");
  385     } else {
  386         $grade = reset($grades->items[0]->grades);
  387         if (empty($grade->grade)) {
  388 
  389             // Check to see if it an ungraded / incomplete attempt.
  390             $sql = "SELECT *
  391                       FROM {lesson_timer}
  392                      WHERE lessonid = :lessonid
  393                        AND userid = :userid
  394                   ORDER BY starttime DESC";
  395             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
  396 
  397             if ($attempts = $DB->get_records_sql($sql, $params, 0, 1)) {
  398                 $attempt = reset($attempts);
  399                 if ($attempt->completed) {
  400                     $return->info = get_string("completed", "lesson");
  401                 } else {
  402                     $return->info = get_string("notyetcompleted", "lesson");
  403                 }
  404                 $return->time = $attempt->lessontime;
  405             } else {
  406                 $return->info = get_string("nolessonattempts", "lesson");
  407             }
  408         } else {
  409             if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
  410                 $return->info = get_string('grade') . ': ' . $grade->str_long_grade;
  411             } else {
  412                 $return->info = get_string('grade') . ': ' . get_string('hidden', 'grades');
  413             }
  414 
  415             // Datesubmitted == time created. dategraded == time modified or time overridden.
  416             // If grade was last modified by the user themselves use date graded. Otherwise use date submitted.
  417             // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704.
  418             if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
  419                 $return->time = $grade->dategraded;
  420             } else {
  421                 $return->time = $grade->datesubmitted;
  422             }
  423         }
  424     }
  425     return $return;
  426 }
  427 
  428 /**
  429  * Print a detailed representation of what a  user has done with
  430  * a given particular instance of this module, for user activity reports.
  431  *
  432  * @global object
  433  * @param object $course
  434  * @param object $user
  435  * @param object $mod
  436  * @param object $lesson
  437  * @return bool
  438  */
  439 function lesson_user_complete($course, $user, $mod, $lesson) {
  440     global $DB, $OUTPUT, $CFG;
  441 
  442     require_once("$CFG->libdir/gradelib.php");
  443 
  444     $grades = grade_get_grades($course->id, 'mod', 'lesson', $lesson->id, $user->id);
  445 
  446     // Display the grade and feedback.
  447     if (empty($grades->items[0]->grades)) {
  448         echo $OUTPUT->container(get_string("nolessonattempts", "lesson"));
  449     } else {
  450         $grade = reset($grades->items[0]->grades);
  451         if (empty($grade->grade)) {
  452             // Check to see if it an ungraded / incomplete attempt.
  453             $sql = "SELECT *
  454                       FROM {lesson_timer}
  455                      WHERE lessonid = :lessonid
  456                        AND userid = :userid
  457                      ORDER by starttime desc";
  458             $params = array('lessonid' => $lesson->id, 'userid' => $user->id);
  459 
  460             if ($attempt = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) {
  461                 if ($attempt->completed) {
  462                     $status = get_string("completed", "lesson");
  463                 } else {
  464                     $status = get_string("notyetcompleted", "lesson");
  465                 }
  466             } else {
  467                 $status = get_string("nolessonattempts", "lesson");
  468             }
  469         } else {
  470             if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
  471                 $status = get_string("grade") . ': ' . $grade->str_long_grade;
  472             } else {
  473                 $status = get_string('grade') . ': ' . get_string('hidden', 'grades');
  474             }
  475         }
  476 
  477         // Display the grade or lesson status if there isn't one.
  478         echo $OUTPUT->container($status);
  479 
  480         if ($grade->str_feedback &&
  481             (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id)))) {
  482             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  483         }
  484     }
  485 
  486     // Display the lesson progress.
  487     // Attempt, pages viewed, questions answered, correct answers, time.
  488     $params = array ("lessonid" => $lesson->id, "userid" => $user->id);
  489     $attempts = $DB->get_records_select("lesson_attempts", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
  490     $branches = $DB->get_records_select("lesson_branch", "lessonid = :lessonid AND userid = :userid", $params, "retry, timeseen");
  491     if (!empty($attempts) or !empty($branches)) {
  492         echo $OUTPUT->box_start();
  493         $table = new html_table();
  494         // Table Headings.
  495         $table->head = array (get_string("attemptheader", "lesson"),
  496             get_string("totalpagesviewedheader", "lesson"),
  497             get_string("numberofpagesviewedheader", "lesson"),
  498             get_string("numberofcorrectanswersheader", "lesson"),
  499             get_string("time"));
  500         $table->width = "100%";
  501         $table->align = array ("center", "center", "center", "center", "center");
  502         $table->size = array ("*", "*", "*", "*", "*");
  503         $table->cellpadding = 2;
  504         $table->cellspacing = 0;
  505 
  506         $retry = 0;
  507         $nquestions = 0;
  508         $npages = 0;
  509         $ncorrect = 0;
  510 
  511         // Filter question pages (from lesson_attempts).
  512         foreach ($attempts as $attempt) {
  513             if ($attempt->retry == $retry) {
  514                 $npages++;
  515                 $nquestions++;
  516                 if ($attempt->correct) {
  517                     $ncorrect++;
  518                 }
  519                 $timeseen = $attempt->timeseen;
  520             } else {
  521                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
  522                 $retry++;
  523                 $nquestions = 1;
  524                 $npages = 1;
  525                 if ($attempt->correct) {
  526                     $ncorrect = 1;
  527                 } else {
  528                     $ncorrect = 0;
  529                 }
  530             }
  531         }
  532 
  533         // Filter content pages (from lesson_branch).
  534         foreach ($branches as $branch) {
  535             if ($branch->retry == $retry) {
  536                 $npages++;
  537 
  538                 $timeseen = $branch->timeseen;
  539             } else {
  540                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
  541                 $retry++;
  542                 $npages = 1;
  543             }
  544         }
  545         if ($npages > 0) {
  546                 $table->data[] = array($retry + 1, $npages, $nquestions, $ncorrect, userdate($timeseen));
  547         }
  548         echo html_writer::table($table);
  549         echo $OUTPUT->box_end();
  550     }
  551 
  552     return true;
  553 }
  554 
  555 /**
  556  * Prints lesson summaries on MyMoodle Page
  557  *
  558  * Prints lesson name, due date and attempt information on
  559  * lessons that have a deadline that has not already passed
  560  * and it is available for taking.
  561  *
  562  * @deprecated since 3.3
  563  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
  564  * @global object
  565  * @global stdClass
  566  * @global object
  567  * @uses CONTEXT_MODULE
  568  * @param array $courses An array of course objects to get lesson instances from
  569  * @param array $htmlarray Store overview output array( course ID => 'lesson' => HTML output )
  570  * @return void
  571  */
  572 function lesson_print_overview($courses, &$htmlarray) {
  573     global $USER, $CFG, $DB, $OUTPUT;
  574 
  575     debugging('The function lesson_print_overview() is now deprecated.', DEBUG_DEVELOPER);
  576 
  577     if (!$lessons = get_all_instances_in_courses('lesson', $courses)) {
  578         return;
  579     }
  580 
  581     // Get all of the current users attempts on all lessons.
  582     $params = array($USER->id);
  583     $sql = 'SELECT lessonid, userid, count(userid) as attempts
  584               FROM {lesson_grades}
  585              WHERE userid = ?
  586           GROUP BY lessonid, userid';
  587     $allattempts = $DB->get_records_sql($sql, $params);
  588     $completedattempts = array();
  589     foreach ($allattempts as $myattempt) {
  590         $completedattempts[$myattempt->lessonid] = $myattempt->attempts;
  591     }
  592 
  593     // Get the current course ID.
  594     $listoflessons = array();
  595     foreach ($lessons as $lesson) {
  596         $listoflessons[] = $lesson->id;
  597     }
  598     // Get the last page viewed by the current user for every lesson in this course.
  599     list($insql, $inparams) = $DB->get_in_or_equal($listoflessons, SQL_PARAMS_NAMED);
  600     $dbparams = array_merge($inparams, array('userid' => $USER->id));
  601 
  602     // Get the lesson attempts for the user that have the maximum 'timeseen' value.
  603     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.answerid as nextpageid, p.qtype ";
  604     $from = "FROM {lesson_attempts} l
  605              JOIN (
  606                    SELECT idselect.lessonid, idselect.userid, MAX(idselect.id) AS id
  607                      FROM {lesson_attempts} idselect
  608                      JOIN (
  609                            SELECT lessonid, userid, MAX(timeseen) AS timeseen
  610                              FROM {lesson_attempts}
  611                             WHERE userid = :userid
  612                               AND lessonid $insql
  613                          GROUP BY userid, lessonid
  614                            ) timeselect
  615                        ON timeselect.timeseen = idselect.timeseen
  616                       AND timeselect.userid = idselect.userid
  617                       AND timeselect.lessonid = idselect.lessonid
  618                  GROUP BY idselect.userid, idselect.lessonid
  619                    ) aid
  620                ON l.id = aid.id
  621              JOIN {lesson_pages} p
  622                ON l.pageid = p.id ";
  623     $lastattempts = $DB->get_records_sql($select . $from, $dbparams);
  624 
  625     // Now, get the lesson branches for the user that have the maximum 'timeseen' value.
  626     $select = "SELECT l.id, l.timeseen, l.lessonid, l.userid, l.retry, l.pageid, l.nextpageid, p.qtype ";
  627     $from = str_replace('{lesson_attempts}', '{lesson_branch}', $from);
  628     $lastbranches = $DB->get_records_sql($select . $from, $dbparams);
  629 
  630     $lastviewed = array();
  631     foreach ($lastattempts as $lastattempt) {
  632         $lastviewed[$lastattempt->lessonid] = $lastattempt;
  633     }
  634 
  635     // Go through the branch times and record the 'timeseen' value if it doesn't exist
  636     // for the lesson, or replace it if it exceeds the current recorded time.
  637     foreach ($lastbranches as $lastbranch) {
  638         if (!isset($lastviewed[$lastbranch->lessonid])) {
  639             $lastviewed[$lastbranch->lessonid] = $lastbranch;
  640         } else if ($lastviewed[$lastbranch->lessonid]->timeseen < $lastbranch->timeseen) {
  641             $lastviewed[$lastbranch->lessonid] = $lastbranch;
  642         }
  643     }
  644 
  645     // Since we have lessons in this course, now include the constants we need.
  646     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
  647 
  648     $now = time();
  649     foreach ($lessons as $lesson) {
  650         if ($lesson->deadline != 0                                         // The lesson has a deadline
  651             and $lesson->deadline >= $now                                  // And it is before the deadline has been met
  652             and ($lesson->available == 0 or $lesson->available <= $now)) { // And the lesson is available
  653 
  654             // Visibility.
  655             $class = (!$lesson->visible) ? 'dimmed' : '';
  656 
  657             // Context.
  658             $context = context_module::instance($lesson->coursemodule);
  659 
  660             // Link to activity.
  661             $url = new moodle_url('/mod/lesson/view.php', array('id' => $lesson->coursemodule));
  662             $url = html_writer::link($url, format_string($lesson->name, true, array('context' => $context)), array('class' => $class));
  663             $str = $OUTPUT->box(get_string('lessonname', 'lesson', $url), 'name');
  664 
  665             // Deadline.
  666             $str .= $OUTPUT->box(get_string('lessoncloseson', 'lesson', userdate($lesson->deadline)), 'info');
  667 
  668             // Attempt information.
  669             if (has_capability('mod/lesson:manage', $context)) {
  670                 // This is a teacher, Get the Number of user attempts.
  671                 $attempts = $DB->count_records('lesson_grades', array('lessonid' => $lesson->id));
  672                 $str     .= $OUTPUT->box(get_string('xattempts', 'lesson', $attempts), 'info');
  673                 $str      = $OUTPUT->box($str, 'lesson overview');
  674             } else {
  675                 // This is a student, See if the user has at least started the lesson.
  676                 if (isset($lastviewed[$lesson->id]->timeseen)) {
  677                     // See if the user has finished this attempt.
  678                     if (isset($completedattempts[$lesson->id]) &&
  679                              ($completedattempts[$lesson->id] == ($lastviewed[$lesson->id]->retry + 1))) {
  680                         // Are additional attempts allowed?
  681                         if ($lesson->retake) {
  682                             // User can retake the lesson.
  683                             $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
  684                             $str = $OUTPUT->box($str, 'lesson overview');
  685                         } else {
  686                             // User has completed the lesson and no retakes are allowed.
  687                             $str = '';
  688                         }
  689 
  690                     } else {
  691                         // The last attempt was not finished or the lesson does not contain questions.
  692                         // See if the last page viewed was a branchtable.
  693                         require_once($CFG->dirroot . '/mod/lesson/pagetypes/branchtable.php');
  694                         if ($lastviewed[$lesson->id]->qtype == LESSON_PAGE_BRANCHTABLE) {
  695                             // See if the next pageid is the end of lesson.
  696                             if ($lastviewed[$lesson->id]->nextpageid == LESSON_EOL) {
  697                                 // The last page viewed was the End of Lesson.
  698                                 if ($lesson->retake) {
  699                                     // User can retake the lesson.
  700                                     $str .= $OUTPUT->box(get_string('additionalattemptsremaining', 'lesson'), 'info');
  701                                     $str = $OUTPUT->box($str, 'lesson overview');
  702                                 } else {
  703                                     // User has completed the lesson and no retakes are allowed.
  704                                     $str = '';
  705                                 }
  706 
  707                             } else {
  708                                 // The last page viewed was NOT the end of lesson.
  709                                 $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
  710                                 $str = $OUTPUT->box($str, 'lesson overview');
  711                             }
  712 
  713                         } else {
  714                             // Last page was a question page, so the attempt is not completed yet.
  715                             $str .= $OUTPUT->box(get_string('notyetcompleted', 'lesson'), 'info');
  716                             $str = $OUTPUT->box($str, 'lesson overview');
  717                         }
  718                     }
  719 
  720                 } else {
  721                     // User has not yet started this lesson.
  722                     $str .= $OUTPUT->box(get_string('nolessonattempts', 'lesson'), 'info');
  723                     $str = $OUTPUT->box($str, 'lesson overview');
  724                 }
  725             }
  726             if (!empty($str)) {
  727                 if (empty($htmlarray[$lesson->course]['lesson'])) {
  728                     $htmlarray[$lesson->course]['lesson'] = $str;
  729                 } else {
  730                     $htmlarray[$lesson->course]['lesson'] .= $str;
  731                 }
  732             }
  733         }
  734     }
  735 }
  736 
  737 /**
  738  * Function to be run periodically according to the moodle cron
  739  * This function searches for things that need to be done, such
  740  * as sending out mail, toggling flags etc ...
  741  * @global stdClass
  742  * @return bool true
  743  */
  744 function lesson_cron () {
  745     global $CFG;
  746 
  747     return true;
  748 }
  749 
  750 /**
  751  * Return grade for given user or all users.
  752  *
  753  * @global stdClass
  754  * @global object
  755  * @param int $lessonid id of lesson
  756  * @param int $userid optional user id, 0 means all users
  757  * @return array array of grades, false if none
  758  */
  759 function lesson_get_user_grades($lesson, $userid=0) {
  760     global $CFG, $DB;
  761 
  762     $params = array("lessonid" => $lesson->id,"lessonid2" => $lesson->id);
  763 
  764     if (!empty($userid)) {
  765         $params["userid"] = $userid;
  766         $params["userid2"] = $userid;
  767         $user = "AND u.id = :userid";
  768         $fuser = "AND uu.id = :userid2";
  769     }
  770     else {
  771         $user="";
  772         $fuser="";
  773     }
  774 
  775     if ($lesson->retake) {
  776         if ($lesson->usemaxgrade) {
  777             $sql = "SELECT u.id, u.id AS userid, MAX(g.grade) AS rawgrade
  778                       FROM {user} u, {lesson_grades} g
  779                      WHERE u.id = g.userid AND g.lessonid = :lessonid
  780                            $user
  781                   GROUP BY u.id";
  782         } else {
  783             $sql = "SELECT u.id, u.id AS userid, AVG(g.grade) AS rawgrade
  784                       FROM {user} u, {lesson_grades} g
  785                      WHERE u.id = g.userid AND g.lessonid = :lessonid
  786                            $user
  787                   GROUP BY u.id";
  788         }
  789         unset($params['lessonid2']);
  790         unset($params['userid2']);
  791     } else {
  792         // use only first attempts (with lowest id in lesson_grades table)
  793         $firstonly = "SELECT uu.id AS userid, MIN(gg.id) AS firstcompleted
  794                         FROM {user} uu, {lesson_grades} gg
  795                        WHERE uu.id = gg.userid AND gg.lessonid = :lessonid2
  796                              $fuser
  797                        GROUP BY uu.id";
  798 
  799         $sql = "SELECT u.id, u.id AS userid, g.grade AS rawgrade
  800                   FROM {user} u, {lesson_grades} g, ($firstonly) f
  801                  WHERE u.id = g.userid AND g.lessonid = :lessonid
  802                        AND g.id = f.firstcompleted AND g.userid=f.userid
  803                        $user";
  804     }
  805 
  806     return $DB->get_records_sql($sql, $params);
  807 }
  808 
  809 /**
  810  * Update grades in central gradebook
  811  *
  812  * @category grade
  813  * @param object $lesson
  814  * @param int $userid specific user only, 0 means all
  815  * @param bool $nullifnone
  816  */
  817 function lesson_update_grades($lesson, $userid=0, $nullifnone=true) {
  818     global $CFG, $DB;
  819     require_once($CFG->libdir.'/gradelib.php');
  820 
  821     if ($lesson->grade == 0 || $lesson->practice) {
  822         lesson_grade_item_update($lesson);
  823 
  824     } else if ($grades = lesson_get_user_grades($lesson, $userid)) {
  825         lesson_grade_item_update($lesson, $grades);
  826 
  827     } else if ($userid and $nullifnone) {
  828         $grade = new stdClass();
  829         $grade->userid   = $userid;
  830         $grade->rawgrade = null;
  831         lesson_grade_item_update($lesson, $grade);
  832 
  833     } else {
  834         lesson_grade_item_update($lesson);
  835     }
  836 }
  837 
  838 /**
  839  * Create grade item for given lesson
  840  *
  841  * @category grade
  842  * @uses GRADE_TYPE_VALUE
  843  * @uses GRADE_TYPE_NONE
  844  * @param object $lesson object with extra cmidnumber
  845  * @param array|object $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
  846  * @return int 0 if ok, error code otherwise
  847  */
  848 function lesson_grade_item_update($lesson, $grades=null) {
  849     global $CFG;
  850     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
  851         require_once($CFG->libdir.'/gradelib.php');
  852     }
  853 
  854     if (array_key_exists('cmidnumber', $lesson)) { //it may not be always present
  855         $params = array('itemname'=>$lesson->name, 'idnumber'=>$lesson->cmidnumber);
  856     } else {
  857         $params = array('itemname'=>$lesson->name);
  858     }
  859 
  860     if (!$lesson->practice and $lesson->grade > 0) {
  861         $params['gradetype']  = GRADE_TYPE_VALUE;
  862         $params['grademax']   = $lesson->grade;
  863         $params['grademin']   = 0;
  864     } else if (!$lesson->practice and $lesson->grade < 0) {
  865         $params['gradetype']  = GRADE_TYPE_SCALE;
  866         $params['scaleid']   = -$lesson->grade;
  867 
  868         // Make sure current grade fetched correctly from $grades
  869         $currentgrade = null;
  870         if (!empty($grades)) {
  871             if (is_array($grades)) {
  872                 $currentgrade = reset($grades);
  873             } else {
  874                 $currentgrade = $grades;
  875             }
  876         }
  877 
  878         // When converting a score to a scale, use scale's grade maximum to calculate it.
  879         if (!empty($currentgrade) && $currentgrade->rawgrade !== null) {
  880             $grade = grade_get_grades($lesson->course, 'mod', 'lesson', $lesson->id, $currentgrade->userid);
  881             $params['grademax']   = reset($grade->items)->grademax;
  882         }
  883     } else {
  884         $params['gradetype']  = GRADE_TYPE_NONE;
  885     }
  886 
  887     if ($grades  === 'reset') {
  888         $params['reset'] = true;
  889         $grades = null;
  890     } else if (!empty($grades)) {
  891         // Need to calculate raw grade (Note: $grades has many forms)
  892         if (is_object($grades)) {
  893             $grades = array($grades->userid => $grades);
  894         } else if (array_key_exists('userid', $grades)) {
  895             $grades = array($grades['userid'] => $grades);
  896         }
  897         foreach ($grades as $key => $grade) {
  898             if (!is_array($grade)) {
  899                 $grades[$key] = $grade = (array) $grade;
  900             }
  901             //check raw grade isnt null otherwise we erroneously insert a grade of 0
  902             if ($grade['rawgrade'] !== null) {
  903                 $grades[$key]['rawgrade'] = ($grade['rawgrade'] * $params['grademax'] / 100);
  904             } else {
  905                 //setting rawgrade to null just in case user is deleting a grade
  906                 $grades[$key]['rawgrade'] = null;
  907             }
  908         }
  909     }
  910 
  911     return grade_update('mod/lesson', $lesson->course, 'mod', 'lesson', $lesson->id, 0, $grades, $params);
  912 }
  913 
  914 /**
  915  * List the actions that correspond to a view of this module.
  916  * This is used by the participation report.
  917  *
  918  * Note: This is not used by new logging system. Event with
  919  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
  920  *       be considered as view action.
  921  *
  922  * @return array
  923  */
  924 function lesson_get_view_actions() {
  925     return array('view','view all');
  926 }
  927 
  928 /**
  929  * List the actions that correspond to a post of this module.
  930  * This is used by the participation report.
  931  *
  932  * Note: This is not used by new logging system. Event with
  933  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
  934  *       will be considered as post action.
  935  *
  936  * @return array
  937  */
  938 function lesson_get_post_actions() {
  939     return array('end','start');
  940 }
  941 
  942 /**
  943  * Runs any processes that must run before
  944  * a lesson insert/update
  945  *
  946  * @global object
  947  * @param object $lesson Lesson form data
  948  * @return void
  949  **/
  950 function lesson_process_pre_save(&$lesson) {
  951     global $DB;
  952 
  953     $lesson->timemodified = time();
  954 
  955     if (empty($lesson->timelimit)) {
  956         $lesson->timelimit = 0;
  957     }
  958     if (empty($lesson->timespent) or !is_numeric($lesson->timespent) or $lesson->timespent < 0) {
  959         $lesson->timespent = 0;
  960     }
  961     if (!isset($lesson->completed)) {
  962         $lesson->completed = 0;
  963     }
  964     if (empty($lesson->gradebetterthan) or !is_numeric($lesson->gradebetterthan) or $lesson->gradebetterthan < 0) {
  965         $lesson->gradebetterthan = 0;
  966     } else if ($lesson->gradebetterthan > 100) {
  967         $lesson->gradebetterthan = 100;
  968     }
  969 
  970     if (empty($lesson->width)) {
  971         $lesson->width = 640;
  972     }
  973     if (empty($lesson->height)) {
  974         $lesson->height = 480;
  975     }
  976     if (empty($lesson->bgcolor)) {
  977         $lesson->bgcolor = '#FFFFFF';
  978     }
  979 
  980     // Conditions for dependency
  981     $conditions = new stdClass;
  982     $conditions->timespent = $lesson->timespent;
  983     $conditions->completed = $lesson->completed;
  984     $conditions->gradebetterthan = $lesson->gradebetterthan;
  985     $lesson->conditions = serialize($conditions);
  986     unset($lesson->timespent);
  987     unset($lesson->completed);
  988     unset($lesson->gradebetterthan);
  989 
  990     if (empty($lesson->password)) {
  991         unset($lesson->password);
  992     }
  993 }
  994 
  995 /**
  996  * Runs any processes that must be run
  997  * after a lesson insert/update
  998  *
  999  * @global object
 1000  * @param object $lesson Lesson form data
 1001  * @return void
 1002  **/
 1003 function lesson_process_post_save(&$lesson) {
 1004     // Update the events relating to this lesson.
 1005     lesson_update_events($lesson);
 1006     $completionexpected = (!empty($lesson->completionexpected)) ? $lesson->completionexpected : null;
 1007     \core_completion\api::update_completion_date_event($lesson->coursemodule, 'lesson', $lesson, $completionexpected);
 1008 }
 1009 
 1010 
 1011 /**
 1012  * Implementation of the function for printing the form elements that control
 1013  * whether the course reset functionality affects the lesson.
 1014  *
 1015  * @param $mform form passed by reference
 1016  */
 1017 function lesson_reset_course_form_definition(&$mform) {
 1018     $mform->addElement('header', 'lessonheader', get_string('modulenameplural', 'lesson'));
 1019     $mform->addElement('advcheckbox', 'reset_lesson', get_string('deleteallattempts','lesson'));
 1020     $mform->addElement('advcheckbox', 'reset_lesson_user_overrides',
 1021             get_string('removealluseroverrides', 'lesson'));
 1022     $mform->addElement('advcheckbox', 'reset_lesson_group_overrides',
 1023             get_string('removeallgroupoverrides', 'lesson'));
 1024 }
 1025 
 1026 /**
 1027  * Course reset form defaults.
 1028  * @param object $course
 1029  * @return array
 1030  */
 1031 function lesson_reset_course_form_defaults($course) {
 1032     return array('reset_lesson' => 1,
 1033             'reset_lesson_group_overrides' => 1,
 1034             'reset_lesson_user_overrides' => 1);
 1035 }
 1036 
 1037 /**
 1038  * Removes all grades from gradebook
 1039  *
 1040  * @global stdClass
 1041  * @global object
 1042  * @param int $courseid
 1043  * @param string optional type
 1044  */
 1045 function lesson_reset_gradebook($courseid, $type='') {
 1046     global $CFG, $DB;
 1047 
 1048     $sql = "SELECT l.*, cm.idnumber as cmidnumber, l.course as courseid
 1049               FROM {lesson} l, {course_modules} cm, {modules} m
 1050              WHERE m.name='lesson' AND m.id=cm.module AND cm.instance=l.id AND l.course=:course";
 1051     $params = array ("course" => $courseid);
 1052     if ($lessons = $DB->get_records_sql($sql,$params)) {
 1053         foreach ($lessons as $lesson) {
 1054             lesson_grade_item_update($lesson, 'reset');
 1055         }
 1056     }
 1057 }
 1058 
 1059 /**
 1060  * Actual implementation of the reset course functionality, delete all the
 1061  * lesson attempts for course $data->courseid.
 1062  *
 1063  * @global stdClass
 1064  * @global object
 1065  * @param object $data the data submitted from the reset course.
 1066  * @return array status array
 1067  */
 1068 function lesson_reset_userdata($data) {
 1069     global $CFG, $DB;
 1070 
 1071     $componentstr = get_string('modulenameplural', 'lesson');
 1072     $status = array();
 1073 
 1074     if (!empty($data->reset_lesson)) {
 1075         $lessonssql = "SELECT l.id
 1076                          FROM {lesson} l
 1077                         WHERE l.course=:course";
 1078 
 1079         $params = array ("course" => $data->courseid);
 1080         $lessons = $DB->get_records_sql($lessonssql, $params);
 1081 
 1082         // Get rid of attempts files.
 1083         $fs = get_file_storage();
 1084         if ($lessons) {
 1085             foreach ($lessons as $lessonid => $unused) {
 1086                 if (!$cm = get_coursemodule_from_instance('lesson', $lessonid)) {
 1087                     continue;
 1088                 }
 1089                 $context = context_module::instance($cm->id);
 1090                 $fs->delete_area_files($context->id, 'mod_lesson', 'essay_responses');
 1091             }
 1092         }
 1093 
 1094         $DB->delete_records_select('lesson_timer', "lessonid IN ($lessonssql)", $params);
 1095         $DB->delete_records_select('lesson_grades', "lessonid IN ($lessonssql)", $params);
 1096         $DB->delete_records_select('lesson_attempts', "lessonid IN ($lessonssql)", $params);
 1097         $DB->delete_records_select('lesson_branch', "lessonid IN ($lessonssql)", $params);
 1098 
 1099         // remove all grades from gradebook
 1100         if (empty($data->reset_gradebook_grades)) {
 1101             lesson_reset_gradebook($data->courseid);
 1102         }
 1103 
 1104         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'lesson'), 'error'=>false);
 1105     }
 1106 
 1107     // Remove user overrides.
 1108     if (!empty($data->reset_lesson_user_overrides)) {
 1109         $DB->delete_records_select('lesson_overrides',
 1110                 'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid));
 1111         $status[] = array(
 1112         'component' => $componentstr,
 1113         'item' => get_string('useroverridesdeleted', 'lesson'),
 1114         'error' => false);
 1115     }
 1116     // Remove group overrides.
 1117     if (!empty($data->reset_lesson_group_overrides)) {
 1118         $DB->delete_records_select('lesson_overrides',
 1119         'lessonid IN (SELECT id FROM {lesson} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid));
 1120         $status[] = array(
 1121         'component' => $componentstr,
 1122         'item' => get_string('groupoverridesdeleted', 'lesson'),
 1123         'error' => false);
 1124     }
 1125     /// updating dates - shift may be negative too
 1126     if ($data->timeshift) {
 1127         $DB->execute("UPDATE {lesson_overrides}
 1128                          SET available = available + ?
 1129                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
 1130                          AND available <> 0", array($data->timeshift, $data->courseid));
 1131         $DB->execute("UPDATE {lesson_overrides}
 1132                          SET deadline = deadline + ?
 1133                        WHERE lessonid IN (SELECT id FROM {lesson} WHERE course = ?)
 1134                          AND deadline <> 0", array($data->timeshift, $data->courseid));
 1135 
 1136         // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
 1137         // See MDL-9367.
 1138         shift_course_mod_dates('lesson', array('available', 'deadline'), $data->timeshift, $data->courseid);
 1139         $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
 1140     }
 1141 
 1142     return $status;
 1143 }
 1144 
 1145 /**
 1146  * @uses FEATURE_GROUPS
 1147  * @uses FEATURE_GROUPINGS
 1148  * @uses FEATURE_MOD_INTRO
 1149  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
 1150  * @uses FEATURE_GRADE_HAS_GRADE
 1151  * @uses FEATURE_GRADE_OUTCOMES
 1152  * @param string $feature FEATURE_xx constant for requested feature
 1153  * @return mixed True if module supports feature, false if not, null if doesn't know
 1154  */
 1155 function lesson_supports($feature) {
 1156     switch($feature) {
 1157         case FEATURE_GROUPS:
 1158             return true;
 1159         case FEATURE_GROUPINGS:
 1160             return true;
 1161         case FEATURE_MOD_INTRO:
 1162             return true;
 1163         case FEATURE_COMPLETION_TRACKS_VIEWS:
 1164             return true;
 1165         case FEATURE_GRADE_HAS_GRADE:
 1166             return true;
 1167         case FEATURE_COMPLETION_HAS_RULES:
 1168             return true;
 1169         case FEATURE_GRADE_OUTCOMES:
 1170             return true;
 1171         case FEATURE_BACKUP_MOODLE2:
 1172             return true;
 1173         case FEATURE_SHOW_DESCRIPTION:
 1174             return true;
 1175         default:
 1176             return null;
 1177     }
 1178 }
 1179 
 1180 /**
 1181  * Obtains the automatic completion state for this lesson based on any conditions
 1182  * in lesson settings.
 1183  *
 1184  * @param object $course Course
 1185  * @param object $cm course-module
 1186  * @param int $userid User ID
 1187  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
 1188  * @return bool True if completed, false if not, $type if conditions not set.
 1189  */
 1190 function lesson_get_completion_state($course, $cm, $userid, $type) {
 1191     global $CFG, $DB;
 1192 
 1193     // Get lesson details.
 1194     $lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*',
 1195             MUST_EXIST);
 1196 
 1197     $result = $type; // Default return value.
 1198     // If completion option is enabled, evaluate it and return true/false.
 1199     if ($lesson->completionendreached) {
 1200         $value = $DB->record_exists('lesson_timer', array(
 1201                 'lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1));
 1202         if ($type == COMPLETION_AND) {
 1203             $result = $result && $value;
 1204         } else {
 1205             $result = $result || $value;
 1206         }
 1207     }
 1208     if ($lesson->completiontimespent != 0) {
 1209         $duration = $DB->get_field_sql(
 1210                         "SELECT SUM(lessontime - starttime)
 1211                                FROM {lesson_timer}
 1212                               WHERE lessonid = :lessonid
 1213                                 AND userid = :userid",
 1214                         array('userid' => $userid, 'lessonid' => $lesson->id));
 1215         if (!$duration) {
 1216             $duration = 0;
 1217         }
 1218         if ($type == COMPLETION_AND) {
 1219             $result = $result && ($lesson->completiontimespent < $duration);
 1220         } else {
 1221             $result = $result || ($lesson->completiontimespent < $duration);
 1222         }
 1223     }
 1224     return $result;
 1225 }
 1226 /**
 1227  * This function extends the settings navigation block for the site.
 1228  *
 1229  * It is safe to rely on PAGE here as we will only ever be within the module
 1230  * context when this is called
 1231  *
 1232  * @param settings_navigation $settings
 1233  * @param navigation_node $lessonnode
 1234  */
 1235 function lesson_extend_settings_navigation($settings, $lessonnode) {
 1236     global $PAGE, $DB;
 1237 
 1238     // We want to add these new nodes after the Edit settings node, and before the
 1239     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
 1240     $keys = $lessonnode->get_children_key_list();
 1241     $beforekey = null;
 1242     $i = array_search('modedit', $keys);
 1243     if ($i === false and array_key_exists(0, $keys)) {
 1244         $beforekey = $keys[0];
 1245     } else if (array_key_exists($i + 1, $keys)) {
 1246         $beforekey = $keys[$i + 1];
 1247     }
 1248 
 1249     if (has_capability('mod/lesson:manageoverrides', $PAGE->cm->context)) {
 1250         $url = new moodle_url('/mod/lesson/overrides.php', array('cmid' => $PAGE->cm->id));
 1251         $node = navigation_node::create(get_string('groupoverrides', 'lesson'),
 1252                 new moodle_url($url, array('mode' => 'group')),
 1253                 navigation_node::TYPE_SETTING, null, 'mod_lesson_groupoverrides');
 1254         $lessonnode->add_node($node, $beforekey);
 1255 
 1256         $node = navigation_node::create(get_string('useroverrides', 'lesson'),
 1257                 new moodle_url($url, array('mode' => 'user')),
 1258                 navigation_node::TYPE_SETTING, null, 'mod_lesson_useroverrides');
 1259         $lessonnode->add_node($node, $beforekey);
 1260     }
 1261 
 1262     if (has_capability('mod/lesson:edit', $PAGE->cm->context)) {
 1263         $url = new moodle_url('/mod/lesson/view.php', array('id' => $PAGE->cm->id));
 1264         $lessonnode->add(get_string('preview', 'lesson'), $url);
 1265         $editnode = $lessonnode->add(get_string('edit', 'lesson'));
 1266         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'collapsed'));
 1267         $editnode->add(get_string('collapsed', 'lesson'), $url);
 1268         $url = new moodle_url('/mod/lesson/edit.php', array('id' => $PAGE->cm->id, 'mode' => 'full'));
 1269         $editnode->add(get_string('full', 'lesson'), $url);
 1270     }
 1271 
 1272     if (has_capability('mod/lesson:viewreports', $PAGE->cm->context)) {
 1273         $reportsnode = $lessonnode->add(get_string('reports', 'lesson'));
 1274         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportoverview'));
 1275         $reportsnode->add(get_string('overview', 'lesson'), $url);
 1276         $url = new moodle_url('/mod/lesson/report.php', array('id'=>$PAGE->cm->id, 'action'=>'reportdetail'));
 1277         $reportsnode->add(get_string('detailedstats', 'lesson'), $url);
 1278     }
 1279 
 1280     if (has_capability('mod/lesson:grade', $PAGE->cm->context)) {
 1281         $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$PAGE->cm->id));
 1282         $lessonnode->add(get_string('manualgrading', 'lesson'), $url);
 1283     }
 1284 
 1285 }
 1286 
 1287 /**
 1288  * Get list of available import or export formats
 1289  *
 1290  * Copied and modified from lib/questionlib.php
 1291  *
 1292  * @param string $type 'import' if import list, otherwise export list assumed
 1293  * @return array sorted list of import/export formats available
 1294  */
 1295 function lesson_get_import_export_formats($type) {
 1296     global $CFG;
 1297     $fileformats = core_component::get_plugin_list("qformat");
 1298 
 1299     $fileformatname=array();
 1300     foreach ($fileformats as $fileformat=>$fdir) {
 1301         $format_file = "$fdir/format.php";
 1302         if (file_exists($format_file) ) {
 1303             require_once($format_file);
 1304         } else {
 1305             continue;
 1306         }
 1307         $classname = "qformat_$fileformat";
 1308         $format_class = new $classname();
 1309         if ($type=='import') {
 1310             $provided = $format_class->provide_import();
 1311         } else {
 1312             $provided = $format_class->provide_export();
 1313         }
 1314         if ($provided) {
 1315             $fileformatnames[$fileformat] = get_string('pluginname', 'qformat_'.$fileformat);
 1316         }
 1317     }
 1318     natcasesort($fileformatnames);
 1319 
 1320     return $fileformatnames;
 1321 }
 1322 
 1323 /**
 1324  * Serves the lesson attachments. Implements needed access control ;-)
 1325  *
 1326  * @package mod_lesson
 1327  * @category files
 1328  * @param stdClass $course course object
 1329  * @param stdClass $cm course module object
 1330  * @param stdClass $context context object
 1331  * @param string $filearea file area
 1332  * @param array $args extra arguments
 1333  * @param bool $forcedownload whether or not force download
 1334  * @param array $options additional options affecting the file serving
 1335  * @return bool false if file not found, does not return if found - justsend the file
 1336  */
 1337 function lesson_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
 1338     global $CFG, $DB;
 1339 
 1340     if ($context->contextlevel != CONTEXT_MODULE) {
 1341         return false;
 1342     }
 1343 
 1344     $fileareas = lesson_get_file_areas();
 1345     if (!array_key_exists($filearea, $fileareas)) {
 1346         return false;
 1347     }
 1348 
 1349     if (!$lesson = $DB->get_record('lesson', array('id'=>$cm->instance))) {
 1350         return false;
 1351     }
 1352 
 1353     require_course_login($course, true, $cm);
 1354 
 1355     if ($filearea === 'page_contents') {
 1356         $pageid = (int)array_shift($args);
 1357         if (!$page = $DB->get_record('lesson_pages', array('id'=>$pageid))) {
 1358             return false;
 1359         }
 1360         $fullpath = "/$context->id/mod_lesson/$filearea/$pageid/".implode('/', $args);
 1361 
 1362     } else if ($filearea === 'page_answers' || $filearea === 'page_responses') {
 1363         $itemid = (int)array_shift($args);
 1364         if (!$pageanswers = $DB->get_record('lesson_answers', array('id' => $itemid))) {
 1365             return false;
 1366         }
 1367         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
 1368 
 1369     } else if ($filearea === 'essay_responses') {
 1370         $itemid = (int)array_shift($args);
 1371         if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $itemid))) {
 1372             return false;
 1373         }
 1374         $fullpath = "/$context->id/mod_lesson/$filearea/$itemid/".implode('/', $args);
 1375 
 1376     } else if ($filearea === 'mediafile') {
 1377         if (count($args) > 1) {
 1378             // Remove the itemid when it appears to be part of the arguments. If there is only one argument
 1379             // then it is surely the file name. The itemid is sometimes used to prevent browser caching.
 1380             array_shift($args);
 1381         }
 1382         $fullpath = "/$context->id/mod_lesson/$filearea/0/".implode('/', $args);
 1383 
 1384     } else {
 1385         return false;
 1386     }
 1387 
 1388     $fs = get_file_storage();
 1389     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
 1390         return false;
 1391     }
 1392 
 1393     // finally send the file
 1394     send_stored_file($file, 0, 0, $forcedownload, $options); // download MUST be forced - security!
 1395 }
 1396 
 1397 /**
 1398  * Returns an array of file areas
 1399  *
 1400  * @package  mod_lesson
 1401  * @category files
 1402  * @return array a list of available file areas
 1403  */
 1404 function lesson_get_file_areas() {
 1405     $areas = array();
 1406     $areas['page_contents'] = get_string('pagecontents', 'mod_lesson');
 1407     $areas['mediafile'] = get_string('mediafile', 'mod_lesson');
 1408     $areas['page_answers'] = get_string('pageanswers', 'mod_lesson');
 1409     $areas['page_responses'] = get_string('pageresponses', 'mod_lesson');
 1410     $areas['essay_responses'] = get_string('essayresponses', 'mod_lesson');
 1411     return $areas;
 1412 }
 1413 
 1414 /**
 1415  * Returns a file_info_stored object for the file being requested here
 1416  *
 1417  * @package  mod_lesson
 1418  * @category files
 1419  * @global stdClass $CFG
 1420  * @param file_browse $browser file browser instance
 1421  * @param array $areas file areas
 1422  * @param stdClass $course course object
 1423  * @param stdClass $cm course module object
 1424  * @param stdClass $context context object
 1425  * @param string $filearea file area
 1426  * @param int $itemid item ID
 1427  * @param string $filepath file path
 1428  * @param string $filename file name
 1429  * @return file_info_stored
 1430  */
 1431 function lesson_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
 1432     global $CFG, $DB;
 1433 
 1434     if (!has_capability('moodle/course:managefiles', $context)) {
 1435         // No peaking here for students!
 1436         return null;
 1437     }
 1438 
 1439     // Mediafile area does not have sub directories, so let's select the default itemid to prevent
 1440     // the user from selecting a directory to access the mediafile content.
 1441     if ($filearea == 'mediafile' && is_null($itemid)) {
 1442         $itemid = 0;
 1443     }
 1444 
 1445     if (is_null($itemid)) {
 1446         return new mod_lesson_file_info($browser, $course, $cm, $context, $areas, $filearea);
 1447     }
 1448 
 1449     $fs = get_file_storage();
 1450     $filepath = is_null($filepath) ? '/' : $filepath;
 1451     $filename = is_null($filename) ? '.' : $filename;
 1452     if (!$storedfile = $fs->get_file($context->id, 'mod_lesson', $filearea, $itemid, $filepath, $filename)) {
 1453         return null;
 1454     }
 1455 
 1456     $itemname = $filearea;
 1457     if ($filearea == 'page_contents') {
 1458         $itemname = $DB->get_field('lesson_pages', 'title', array('lessonid' => $cm->instance, 'id' => $itemid));
 1459         $itemname = format_string($itemname, true, array('context' => $context));
 1460     } else {
 1461         $areas = lesson_get_file_areas();
 1462         if (isset($areas[$filearea])) {
 1463             $itemname = $areas[$filearea];
 1464         }
 1465     }
 1466 
 1467     $urlbase = $CFG->wwwroot . '/pluginfile.php';
 1468     return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemname, $itemid, true, true, false);
 1469 }
 1470 
 1471 
 1472 /**
 1473  * Return a list of page types
 1474  * @param string $pagetype current page type
 1475  * @param stdClass $parentcontext Block's parent context
 1476  * @param stdClass $currentcontext Current context of block
 1477  */
 1478 function lesson_page_type_list($pagetype, $parentcontext, $currentcontext) {
 1479     $module_pagetype = array(
 1480         'mod-lesson-*'=>get_string('page-mod-lesson-x', 'lesson'),
 1481         'mod-lesson-view'=>get_string('page-mod-lesson-view', 'lesson'),
 1482         'mod-lesson-edit'=>get_string('page-mod-lesson-edit', 'lesson'));
 1483     return $module_pagetype;
 1484 }
 1485 
 1486 /**
 1487  * Update the lesson activity to include any file
 1488  * that was uploaded, or if there is none, set the
 1489  * mediafile field to blank.
 1490  *
 1491  * @param int $lessonid the lesson id
 1492  * @param stdClass $context the context
 1493  * @param int $draftitemid the draft item
 1494  */
 1495 function lesson_update_media_file($lessonid, $context, $draftitemid) {
 1496     global $DB;
 1497 
 1498     // Set the filestorage object.
 1499     $fs = get_file_storage();
 1500     // Save the file if it exists that is currently in the draft area.
 1501     file_save_draft_area_files($draftitemid, $context->id, 'mod_lesson', 'mediafile', 0);
 1502     // Get the file if it exists.
 1503     $files = $fs->get_area_files($context->id, 'mod_lesson', 'mediafile', 0, 'itemid, filepath, filename', false);
 1504     // Check that there is a file to process.
 1505     if (count($files) == 1) {
 1506         // Get the first (and only) file.
 1507         $file = reset($files);
 1508         // Set the mediafile column in the lessons table.
 1509         $DB->set_field('lesson', 'mediafile', '/' . $file->get_filename(), array('id' => $lessonid));
 1510     } else {
 1511         // Set the mediafile column in the lessons table.
 1512         $DB->set_field('lesson', 'mediafile', '', array('id' => $lessonid));
 1513     }
 1514 }
 1515 
 1516 /**
 1517  * Get icon mapping for font-awesome.
 1518  */
 1519 function mod_lesson_get_fontawesome_icon_map() {
 1520     return [
 1521         'mod_lesson:e/copy' => 'fa-clone',
 1522     ];
 1523 }
 1524 
 1525 /*
 1526  * Check if the module has any update that affects the current user since a given time.
 1527  *
 1528  * @param  cm_info $cm course module data
 1529  * @param  int $from the time to check updates from
 1530  * @param  array $filter  if we need to check only specific updates
 1531  * @return stdClass an object with the different type of areas indicating if they were updated or not
 1532  * @since Moodle 3.3
 1533  */
 1534 function lesson_check_updates_since(cm_info $cm, $from, $filter = array()) {
 1535     global $DB, $USER;
 1536 
 1537     $updates = course_check_module_updates_since($cm, $from, array(), $filter);
 1538 
 1539     // Check if there are new pages or answers in the lesson.
 1540     $updates->pages = (object) array('updated' => false);
 1541     $updates->answers = (object) array('updated' => false);
 1542     $select = 'lessonid = ? AND (timecreated > ? OR timemodified > ?)';
 1543     $params = array($cm->instance, $from, $from);
 1544 
 1545     $pages = $DB->get_records_select('lesson_pages', $select, $params, '', 'id');
 1546     if (!empty($pages)) {
 1547         $updates->pages->updated = true;
 1548         $updates->pages->itemids = array_keys($pages);
 1549     }
 1550     $answers = $DB->get_records_select('lesson_answers', $select, $params, '', 'id');
 1551     if (!empty($answers)) {
 1552         $updates->answers->updated = true;
 1553         $updates->answers->itemids = array_keys($answers);
 1554     }
 1555 
 1556     // Check for new question attempts, grades, pages viewed and timers.
 1557     $updates->questionattempts = (object) array('updated' => false);
 1558     $updates->grades = (object) array('updated' => false);
 1559     $updates->pagesviewed = (object) array('updated' => false);
 1560     $updates->timers = (object) array('updated' => false);
 1561 
 1562     $select = 'lessonid = ? AND userid = ? AND timeseen > ?';
 1563     $params = array($cm->instance, $USER->id, $from);
 1564 
 1565     $questionattempts = $DB->get_records_select('lesson_attempts', $select, $params, '', 'id');
 1566     if (!empty($questionattempts)) {
 1567         $updates->questionattempts->updated = true;
 1568         $updates->questionattempts->itemids = array_keys($questionattempts);
 1569     }
 1570     $pagesviewed = $DB->get_records_select('lesson_branch', $select, $params, '', 'id');
 1571     if (!empty($pagesviewed)) {
 1572         $updates->pagesviewed->updated = true;
 1573         $updates->pagesviewed->itemids = array_keys($pagesviewed);
 1574     }
 1575 
 1576     $select = 'lessonid = ? AND userid = ? AND completed > ?';
 1577     $grades = $DB->get_records_select('lesson_grades', $select, $params, '', 'id');
 1578     if (!empty($grades)) {
 1579         $updates->grades->updated = true;
 1580         $updates->grades->itemids = array_keys($grades);
 1581     }
 1582 
 1583     $select = 'lessonid = ? AND userid = ? AND (starttime > ? OR lessontime > ? OR timemodifiedoffline > ?)';
 1584     $params = array($cm->instance, $USER->id, $from, $from, $from);
 1585     $timers = $DB->get_records_select('lesson_timer', $select, $params, '', 'id');
 1586     if (!empty($timers)) {
 1587         $updates->timers->updated = true;
 1588         $updates->timers->itemids = array_keys($timers);
 1589     }
 1590 
 1591     // Now, teachers should see other students updates.
 1592     if (has_capability('mod/lesson:viewreports', $cm->context)) {
 1593         $select = 'lessonid = ? AND timeseen > ?';
 1594         $params = array($cm->instance, $from);
 1595 
 1596         $insql = '';
 1597         $inparams = [];
 1598         if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
 1599             $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
 1600             if (empty($groupusers)) {
 1601                 return $updates;
 1602             }
 1603             list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
 1604             $select .= ' AND userid ' . $insql;
 1605             $params = array_merge($params, $inparams);
 1606         }
 1607 
 1608         $updates->userquestionattempts = (object) array('updated' => false);
 1609         $updates->usergrades = (object) array('updated' => false);
 1610         $updates->userpagesviewed = (object) array('updated' => false);
 1611         $updates->usertimers = (object) array('updated' => false);
 1612 
 1613         $questionattempts = $DB->get_records_select('lesson_attempts', $select, $params, '', 'id');
 1614         if (!empty($questionattempts)) {
 1615             $updates->userquestionattempts->updated = true;
 1616             $updates->userquestionattempts->itemids = array_keys($questionattempts);
 1617         }
 1618         $pagesviewed = $DB->get_records_select('lesson_branch', $select, $params, '', 'id');
 1619         if (!empty($pagesviewed)) {
 1620             $updates->userpagesviewed->updated = true;
 1621             $updates->userpagesviewed->itemids = array_keys($pagesviewed);
 1622         }
 1623 
 1624         $select = 'lessonid = ? AND completed > ?';
 1625         if (!empty($insql)) {
 1626             $select .= ' AND userid ' . $insql;
 1627         }
 1628         $grades = $DB->get_records_select('lesson_grades', $select, $params, '', 'id');
 1629         if (!empty($grades)) {
 1630             $updates->usergrades->updated = true;
 1631             $updates->usergrades->itemids = array_keys($grades);
 1632         }
 1633 
 1634         $select = 'lessonid = ? AND (starttime > ? OR lessontime > ? OR timemodifiedoffline > ?)';
 1635         $params = array($cm->instance, $from, $from, $from);
 1636         if (!empty($insql)) {
 1637             $select .= ' AND userid ' . $insql;
 1638             $params = array_merge($params, $inparams);
 1639         }
 1640         $timers = $DB->get_records_select('lesson_timer', $select, $params, '', 'id');
 1641         if (!empty($timers)) {
 1642             $updates->usertimers->updated = true;
 1643             $updates->usertimers->itemids = array_keys($timers);
 1644         }
 1645     }
 1646     return $updates;
 1647 }
 1648 
 1649 /**
 1650  * This function receives a calendar event and returns the action associated with it, or null if there is none.
 1651  *
 1652  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 1653  * is not displayed on the block.
 1654  *
 1655  * @param calendar_event $event
 1656  * @param \core_calendar\action_factory $factory
 1657  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
 1658  * @return \core_calendar\local\event\entities\action_interface|null
 1659  */
 1660 function mod_lesson_core_calendar_provide_event_action(calendar_event $event,
 1661                                                        \core_calendar\action_factory $factory,
 1662                                                        int $userid = 0) {
 1663     global $DB, $CFG, $USER;
 1664     require_once($CFG->dirroot . '/mod/lesson/locallib.php');
 1665 
 1666     if (!$userid) {
 1667         $userid = $USER->id;
 1668     }
 1669 
 1670     $cm = get_fast_modinfo($event->courseid, $userid)->instances['lesson'][$event->instance];
 1671 
 1672     if (!$cm->uservisible) {
 1673         // The module is not visible to the user for any reason.
 1674         return null;
 1675     }
 1676 
 1677     $completion = new \completion_info($cm->get_course());
 1678 
 1679     $completiondata = $completion->get_data($cm, false, $userid);
 1680 
 1681     if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
 1682         return null;
 1683     }
 1684 
 1685     $lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST));
 1686 
 1687     if ($lesson->count_user_retries($userid)) {
 1688         // If the user has attempted the lesson then there is no further action for the user.
 1689         return null;
 1690     }
 1691 
 1692     // Apply overrides.
 1693     $lesson->update_effective_access($userid);
 1694 
 1695     if (!$lesson->is_participant($userid)) {
 1696         // If the user is not a participant then they have
 1697         // no action to take. This will filter out the events for teachers.
 1698         return null;
 1699     }
 1700 
 1701     return $factory->create_instance(
 1702         get_string('startlesson', 'lesson'),
 1703         new \moodle_url('/mod/lesson/view.php', ['id' => $cm->id]),
 1704         1,
 1705         $lesson->is_accessible()
 1706     );
 1707 }
 1708 
 1709 /**
 1710  * Add a get_coursemodule_info function in case any lesson type wants to add 'extra' information
 1711  * for the course (see resource).
 1712  *
 1713  * Given a course_module object, this function returns any "extra" information that may be needed
 1714  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
 1715  *
 1716  * @param stdClass $coursemodule The coursemodule object (record).
 1717  * @return cached_cm_info An object on information that the courses
 1718  *                        will know about (most noticeably, an icon).
 1719  */
 1720 function lesson_get_coursemodule_info($coursemodule) {
 1721     global $DB;
 1722 
 1723     $dbparams = ['id' => $coursemodule->instance];
 1724     $fields = 'id, name, intro, introformat, completionendreached, completiontimespent';
 1725     if (!$lesson = $DB->get_record('lesson', $dbparams, $fields)) {
 1726         return false;
 1727     }
 1728 
 1729     $result = new cached_cm_info();
 1730     $result->name = $lesson->name;
 1731 
 1732     if ($coursemodule->showdescription) {
 1733         // Convert intro to html. Do not filter cached version, filters run at display time.
 1734         $result->content = format_module_intro('lesson', $lesson, $coursemodule->id, false);
 1735     }
 1736 
 1737     // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
 1738     if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
 1739         $result->customdata['customcompletionrules']['completionendreached'] = $lesson->completionendreached;
 1740         $result->customdata['customcompletionrules']['completiontimespent'] = $lesson->completiontimespent;
 1741     }
 1742 
 1743     return $result;
 1744 }
 1745 
 1746 /**
 1747  * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
 1748  *
 1749  * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
 1750  * @return array $descriptions the array of descriptions for the custom rules.
 1751  */
 1752 function mod_lesson_get_completion_active_rule_descriptions($cm) {
 1753     // Values will be present in cm_info, and we assume these are up to date.
 1754     if (empty($cm->customdata['customcompletionrules'])
 1755         || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
 1756         return [];
 1757     }
 1758 
 1759     $descriptions = [];
 1760     foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
 1761         switch ($key) {
 1762             case 'completionendreached':
 1763                 if (!empty($val)) {
 1764                     $descriptions[] = get_string('completionendreached_desc', 'lesson', $val);
 1765                 }
 1766                 break;
 1767             case 'completiontimespent':
 1768                 if (!empty($val)) {
 1769                     $descriptions[] = get_string('completiontimespentdesc', 'lesson', format_time($val));
 1770                 }
 1771                 break;
 1772             default:
 1773                 break;
 1774         }
 1775     }
 1776     return $descriptions;
 1777 }
 1778 
 1779 /**
 1780  * This function calculates the minimum and maximum cutoff values for the timestart of
 1781  * the given event.
 1782  *
 1783  * It will return an array with two values, the first being the minimum cutoff value and
 1784  * the second being the maximum cutoff value. Either or both values can be null, which
 1785  * indicates there is no minimum or maximum, respectively.
 1786  *
 1787  * If a cutoff is required then the function must return an array containing the cutoff
 1788  * timestamp and error string to display to the user if the cutoff value is violated.
 1789  *
 1790  * A minimum and maximum cutoff return value will look like:
 1791  * [
 1792  *     [1505704373, 'The due date must be after the start date'],
 1793  *     [1506741172, 'The due date must be before the cutoff date']
 1794  * ]
 1795  *
 1796  * @param calendar_event $event The calendar event to get the time range for
 1797  * @param stdClass $instance The module instance to get the range from
 1798  * @return array
 1799  */
 1800 function mod_lesson_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
 1801     $mindate = null;
 1802     $maxdate = null;
 1803 
 1804     if ($event->eventtype == LESSON_EVENT_TYPE_OPEN) {
 1805         // The start time of the open event can't be equal to or after the
 1806         // close time of the lesson activity.
 1807         if (!empty($instance->deadline)) {
 1808             $maxdate = [
 1809                 $instance->deadline,
 1810                 get_string('openafterclose', 'lesson')
 1811             ];
 1812         }
 1813     } else if ($event->eventtype == LESSON_EVENT_TYPE_CLOSE) {
 1814         // The start time of the close event can't be equal to or earlier than the
 1815         // open time of the lesson activity.
 1816         if (!empty($instance->available)) {
 1817             $mindate = [
 1818                 $instance->available,
 1819                 get_string('closebeforeopen', 'lesson')
 1820             ];
 1821         }
 1822     }
 1823 
 1824     return [$mindate, $maxdate];
 1825 }
 1826 
 1827 /**
 1828  * This function will update the lesson module according to the
 1829  * event that has been modified.
 1830  *
 1831  * It will set the available or deadline value of the lesson instance
 1832  * according to the type of event provided.
 1833  *
 1834  * @throws \moodle_exception
 1835  * @param \calendar_event $event
 1836  * @param stdClass $lesson The module instance to get the range from
 1837  */
 1838 function mod_lesson_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $lesson) {
 1839     global $DB;
 1840 
 1841     if (empty($event->instance) || $event->modulename != 'lesson') {
 1842         return;
 1843     }
 1844 
 1845     if ($event->instance != $lesson->id) {
 1846         return;
 1847     }
 1848 
 1849     if (!in_array($event->eventtype, [LESSON_EVENT_TYPE_OPEN, LESSON_EVENT_TYPE_CLOSE])) {
 1850         return;
 1851     }
 1852 
 1853     $courseid = $event->courseid;
 1854     $modulename = $event->modulename;
 1855     $instanceid = $event->instance;
 1856     $modified = false;
 1857 
 1858     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
 1859     $context = context_module::instance($coursemodule->id);
 1860 
 1861     // The user does not have the capability to modify this activity.
 1862     if (!has_capability('moodle/course:manageactivities', $context)) {
 1863         return;
 1864     }
 1865 
 1866     if ($event->eventtype == LESSON_EVENT_TYPE_OPEN) {
 1867         // If the event is for the lesson activity opening then we should
 1868         // set the start time of the lesson activity to be the new start
 1869         // time of the event.
 1870         if ($lesson->available != $event->timestart) {
 1871             $lesson->available = $event->timestart;
 1872             $lesson->timemodified = time();
 1873             $modified = true;
 1874         }
 1875     } else if ($event->eventtype == LESSON_EVENT_TYPE_CLOSE) {
 1876         // If the event is for the lesson activity closing then we should
 1877         // set the end time of the lesson activity to be the new start
 1878         // time of the event.
 1879         if ($lesson->deadline != $event->timestart) {
 1880             $lesson->deadline = $event->timestart;
 1881             $modified = true;
 1882         }
 1883     }
 1884 
 1885     if ($modified) {
 1886         $lesson->timemodified = time();
 1887         $DB->update_record('lesson', $lesson);
 1888         $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
 1889         $event->trigger();
 1890     }
 1891 }