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