Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Unit tests for mod/lesson/lib.php.
  19   *
  20   * @package    mod_lesson
  21   * @category   test
  22   * @copyright  2017 Jun Pataleta
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  require_once($CFG->dirroot . '/mod/lesson/lib.php');
  30  
  31  /**
  32   * Unit tests for mod/lesson/lib.php.
  33   *
  34   * @copyright  2017 Jun Pataleta
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
  36   */
  37  class mod_lesson_lib_testcase extends advanced_testcase {
  38      /**
  39       * Test for lesson_get_group_override_priorities().
  40       */
  41      public function test_lesson_get_group_override_priorities() {
  42          global $DB;
  43          $this->resetAfterTest();
  44          $this->setAdminUser();
  45  
  46          $dg = $this->getDataGenerator();
  47          $course = $dg->create_course();
  48          $lessonmodule = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
  49  
  50          $this->assertNull(lesson_get_group_override_priorities($lessonmodule->id));
  51  
  52          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
  53          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
  54  
  55          $now = 100;
  56          $override1 = (object)[
  57              'lessonid' => $lessonmodule->id,
  58              'groupid' => $group1->id,
  59              'available' => $now,
  60              'deadline' => $now + 20
  61          ];
  62          $DB->insert_record('lesson_overrides', $override1);
  63  
  64          $override2 = (object)[
  65              'lessonid' => $lessonmodule->id,
  66              'groupid' => $group2->id,
  67              'available' => $now - 10,
  68              'deadline' => $now + 10
  69          ];
  70          $DB->insert_record('lesson_overrides', $override2);
  71  
  72          $priorities = lesson_get_group_override_priorities($lessonmodule->id);
  73          $this->assertNotEmpty($priorities);
  74  
  75          $openpriorities = $priorities['open'];
  76          // Override 2's time open has higher priority since it is sooner than override 1's.
  77          $this->assertEquals(2, $openpriorities[$override1->available]);
  78          $this->assertEquals(1, $openpriorities[$override2->available]);
  79  
  80          $closepriorities = $priorities['close'];
  81          // Override 1's time close has higher priority since it is later than override 2's.
  82          $this->assertEquals(1, $closepriorities[$override1->deadline]);
  83          $this->assertEquals(2, $closepriorities[$override2->deadline]);
  84      }
  85  
  86      /**
  87       * Test check_updates_since callback.
  88       */
  89      public function test_check_updates_since() {
  90          global $DB;
  91  
  92          $this->resetAfterTest();
  93          $this->setAdminUser();
  94          $course = new stdClass();
  95          $course->groupmode = SEPARATEGROUPS;
  96          $course->groupmodeforce = true;
  97          $course = $this->getDataGenerator()->create_course($course);
  98  
  99          // Create user.
 100          $studentg1 = self::getDataGenerator()->create_user();
 101          $teacherg1 = self::getDataGenerator()->create_user();
 102          $studentg2 = self::getDataGenerator()->create_user();
 103  
 104          // User enrolment.
 105          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 106          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 107          $this->getDataGenerator()->enrol_user($studentg1->id, $course->id, $studentrole->id, 'manual');
 108          $this->getDataGenerator()->enrol_user($teacherg1->id, $course->id, $teacherrole->id, 'manual');
 109          $this->getDataGenerator()->enrol_user($studentg2->id, $course->id, $studentrole->id, 'manual');
 110  
 111          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 112          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 113          groups_add_member($group1, $studentg1);
 114          groups_add_member($group2, $studentg2);
 115  
 116          $this->setCurrentTimeStart();
 117          $record = array(
 118              'course' => $course->id,
 119              'custom' => 0,
 120              'feedback' => 1,
 121          );
 122          $lessonmodule = $this->getDataGenerator()->create_module('lesson', $record);
 123          // Convert to a lesson object.
 124          $lesson = new lesson($lessonmodule);
 125          $cm = $lesson->cm;
 126          $cm = cm_info::create($cm);
 127  
 128          // Check that upon creation, the updates are only about the new configuration created.
 129          $onehourago = time() - HOURSECS;
 130          $updates = lesson_check_updates_since($cm, $onehourago);
 131          foreach ($updates as $el => $val) {
 132              if ($el == 'configuration') {
 133                  $this->assertTrue($val->updated);
 134                  $this->assertTimeCurrent($val->timeupdated);
 135              } else {
 136                  $this->assertFalse($val->updated);
 137              }
 138          }
 139  
 140          // Set up a generator to create content.
 141          $generator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
 142          $tfrecord = $generator->create_question_truefalse($lesson);
 143  
 144          // Check now for pages and answers.
 145          $updates = lesson_check_updates_since($cm, $onehourago);
 146          $this->assertTrue($updates->pages->updated);
 147          $this->assertCount(1, $updates->pages->itemids);
 148  
 149          $this->assertTrue($updates->answers->updated);
 150          $this->assertCount(2, $updates->answers->itemids);
 151  
 152          // Now, do something in the lesson with the two users.
 153          $this->setUser($studentg1);
 154          mod_lesson_external::launch_attempt($lesson->id);
 155          $data = array(
 156              array(
 157                  'name' => 'answerid',
 158                  'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
 159              ),
 160              array(
 161                  'name' => '_qf__lesson_display_answer_form_truefalse',
 162                  'value' => 1,
 163              )
 164          );
 165          mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
 166          mod_lesson_external::finish_attempt($lesson->id);
 167  
 168          $this->setUser($studentg2);
 169          mod_lesson_external::launch_attempt($lesson->id);
 170          $data = array(
 171              array(
 172                  'name' => 'answerid',
 173                  'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
 174              ),
 175              array(
 176                  'name' => '_qf__lesson_display_answer_form_truefalse',
 177                  'value' => 1,
 178              )
 179          );
 180          mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
 181          mod_lesson_external::finish_attempt($lesson->id);
 182  
 183          $this->setUser($studentg1);
 184          $updates = lesson_check_updates_since($cm, $onehourago);
 185  
 186          // Check question attempts, timers and new grades.
 187          $this->assertTrue($updates->questionattempts->updated);
 188          $this->assertCount(1, $updates->questionattempts->itemids);
 189  
 190          $this->assertTrue($updates->grades->updated);
 191          $this->assertCount(1, $updates->grades->itemids);
 192  
 193          $this->assertTrue($updates->timers->updated);
 194          $this->assertCount(1, $updates->timers->itemids);
 195  
 196          // Now, as teacher, check that I can see the two users (even in separate groups).
 197          $this->setUser($teacherg1);
 198          $updates = lesson_check_updates_since($cm, $onehourago);
 199          $this->assertTrue($updates->userquestionattempts->updated);
 200          $this->assertCount(2, $updates->userquestionattempts->itemids);
 201  
 202          $this->assertTrue($updates->usergrades->updated);
 203          $this->assertCount(2, $updates->usergrades->itemids);
 204  
 205          $this->assertTrue($updates->usertimers->updated);
 206          $this->assertCount(2, $updates->usertimers->itemids);
 207  
 208          // Now, teacher can't access all groups.
 209          groups_add_member($group1, $teacherg1);
 210          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, context_module::instance($cm->id));
 211          accesslib_clear_all_caches_for_unit_testing();
 212          $updates = lesson_check_updates_since($cm, $onehourago);
 213          // I will see only the studentg1 updates.
 214          $this->assertTrue($updates->userquestionattempts->updated);
 215          $this->assertCount(1, $updates->userquestionattempts->itemids);
 216  
 217          $this->assertTrue($updates->usergrades->updated);
 218          $this->assertCount(1, $updates->usergrades->itemids);
 219  
 220          $this->assertTrue($updates->usertimers->updated);
 221          $this->assertCount(1, $updates->usertimers->itemids);
 222      }
 223  
 224      public function test_lesson_core_calendar_provide_event_action_open() {
 225          $this->resetAfterTest();
 226          $this->setAdminUser();
 227          // Create a course.
 228          $course = $this->getDataGenerator()->create_course();
 229          // Create a teacher and enrol into the course.
 230          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 231          // Create a lesson activity.
 232          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 233              'available' => time() - DAYSECS, 'deadline' => time() + DAYSECS));
 234          // Create a calendar event.
 235          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 236  
 237          // Log in as the teacher.
 238          $this->setUser($teacher);
 239          // Create an action factory.
 240          $factory = new \core_calendar\action_factory();
 241          // Decorate action event.
 242          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 243          // Confirm the event was decorated.
 244          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 245          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 246          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 247          $this->assertEquals(1, $actionevent->get_item_count());
 248          $this->assertTrue($actionevent->is_actionable());
 249      }
 250  
 251      public function test_lesson_core_calendar_provide_event_action_open_as_non_user() {
 252          global $CFG;
 253  
 254          $this->resetAfterTest();
 255          $this->setAdminUser();
 256  
 257          // Create a course.
 258          $course = $this->getDataGenerator()->create_course();
 259  
 260          // Create a lesson activity.
 261          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 262                  'available' => time() - DAYSECS, 'deadline' => time() + DAYSECS));
 263  
 264          // Create a calendar event.
 265          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 266  
 267          // Now, log out.
 268          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 269          $this->setUser();
 270  
 271          // Create an action factory.
 272          $factory = new \core_calendar\action_factory();
 273  
 274          // Decorate action event.
 275          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 276  
 277          // Confirm the event is not shown at all.
 278          $this->assertNull($actionevent);
 279      }
 280  
 281      public function test_lesson_core_calendar_provide_event_action_open_for_user() {
 282          global $CFG;
 283  
 284          $this->resetAfterTest();
 285          $this->setAdminUser();
 286  
 287          // Create a course.
 288          $course = $this->getDataGenerator()->create_course();
 289  
 290          // Create a student.
 291          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 292  
 293          // Create a lesson activity.
 294          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 295                  'available' => time() - DAYSECS, 'deadline' => time() + DAYSECS));
 296  
 297          // Create a calendar event.
 298          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 299  
 300          // Now, log out.
 301          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 302          $this->setUser();
 303  
 304          // Create an action factory.
 305          $factory = new \core_calendar\action_factory();
 306  
 307          // Decorate action event for the student.
 308          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 309  
 310          // Confirm the event was decorated.
 311          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 312          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 313          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 314          $this->assertEquals(1, $actionevent->get_item_count());
 315          $this->assertTrue($actionevent->is_actionable());
 316      }
 317  
 318      public function test_lesson_core_calendar_provide_event_action_open_in_hidden_section() {
 319          $this->resetAfterTest();
 320          $this->setAdminUser();
 321  
 322          // Create a course.
 323          $course = $this->getDataGenerator()->create_course();
 324  
 325          // Create a student.
 326          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 327  
 328          // Create a lesson activity.
 329          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 330                  'available' => time() - DAYSECS, 'deadline' => time() + DAYSECS));
 331  
 332          // Create a calendar event.
 333          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 334  
 335          // Set sections 0 as hidden.
 336          set_section_visible($course->id, 0, 0);
 337  
 338          // Create an action factory.
 339          $factory = new \core_calendar\action_factory();
 340  
 341          // Decorate action event for the student.
 342          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 343  
 344          // Confirm the event is not shown at all.
 345          $this->assertNull($actionevent);
 346      }
 347  
 348      public function test_lesson_core_calendar_provide_event_action_closed() {
 349          $this->resetAfterTest();
 350          $this->setAdminUser();
 351  
 352          // Create a course.
 353          $course = $this->getDataGenerator()->create_course();
 354          // Create a teacher and enrol.
 355          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 356  
 357          // Create a lesson activity.
 358          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 359              'deadline' => time() - DAYSECS));
 360  
 361          // Create a calendar event.
 362          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 363  
 364          // Now, log in as teacher.
 365          $this->setUser($teacher);
 366          // Create an action factory.
 367          $factory = new \core_calendar\action_factory();
 368  
 369          // Decorate action event.
 370          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 371  
 372          // Confirm the event was decorated.
 373          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 374          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 375          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 376          $this->assertEquals(1, $actionevent->get_item_count());
 377          $this->assertFalse($actionevent->is_actionable());
 378      }
 379  
 380      public function test_lesson_core_calendar_provide_event_action_closed_for_user() {
 381          global $CFG;
 382  
 383          $this->resetAfterTest();
 384          $this->setAdminUser();
 385  
 386          // Create a course.
 387          $course = $this->getDataGenerator()->create_course();
 388  
 389          // Create a student.
 390          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 391  
 392          // Create a lesson activity.
 393          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 394                  'deadline' => time() - DAYSECS));
 395  
 396          // Create a calendar event.
 397          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 398  
 399          // Now, log out.
 400          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 401          $this->setUser();
 402  
 403          // Create an action factory.
 404          $factory = new \core_calendar\action_factory();
 405  
 406          // Decorate action event.
 407          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 408  
 409          // Confirm the event was decorated.
 410          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 411          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 412          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 413          $this->assertEquals(1, $actionevent->get_item_count());
 414          $this->assertFalse($actionevent->is_actionable());
 415      }
 416  
 417      public function test_lesson_core_calendar_provide_event_action_open_in_future() {
 418          $this->resetAfterTest();
 419          $this->setAdminUser();
 420  
 421          // Create a course.
 422          $course = $this->getDataGenerator()->create_course();
 423          // Create a teacher and enrol into the course.
 424          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 425          // Create a lesson activity.
 426          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 427              'available' => time() + DAYSECS));
 428  
 429          // Create a calendar event.
 430          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 431  
 432          // Now, log in as teacher.
 433          $this->setUser($teacher);
 434          // Create an action factory.
 435          $factory = new \core_calendar\action_factory();
 436  
 437          // Decorate action event.
 438          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 439  
 440          // Confirm the event was decorated.
 441          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 442          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 443          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 444          $this->assertEquals(1, $actionevent->get_item_count());
 445          $this->assertFalse($actionevent->is_actionable());
 446      }
 447  
 448      public function test_lesson_core_calendar_provide_event_action_open_in_future_for_user() {
 449          global $CFG;
 450  
 451          $this->resetAfterTest();
 452          $this->setAdminUser();
 453  
 454          // Create a course.
 455          $course = $this->getDataGenerator()->create_course();
 456  
 457          // Create a student.
 458          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 459  
 460          // Create a lesson activity.
 461          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id,
 462                  'available' => time() + DAYSECS));
 463  
 464          // Create a calendar event.
 465          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 466  
 467          // Now, log out.
 468          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 469          $this->setUser();
 470  
 471          // Create an action factory.
 472          $factory = new \core_calendar\action_factory();
 473  
 474          // Decorate action event.
 475          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 476  
 477          // Confirm the event was decorated.
 478          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 479          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 480          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 481          $this->assertEquals(1, $actionevent->get_item_count());
 482          $this->assertFalse($actionevent->is_actionable());
 483      }
 484  
 485      public function test_lesson_core_calendar_provide_event_action_no_time_specified() {
 486          $this->resetAfterTest();
 487          $this->setAdminUser();
 488  
 489          // Create a course.
 490          $course = $this->getDataGenerator()->create_course();
 491          // Create a teacher and enrol into the course.
 492          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 493          // Create a lesson activity.
 494          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
 495  
 496          // Create a calendar event.
 497          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 498          // Now, log in as teacher.
 499          $this->setUser($teacher);
 500          // Create an action factory.
 501          $factory = new \core_calendar\action_factory();
 502  
 503          // Decorate action event.
 504          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 505  
 506          // Confirm the event was decorated.
 507          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 508          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 509          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 510          $this->assertEquals(1, $actionevent->get_item_count());
 511          $this->assertTrue($actionevent->is_actionable());
 512      }
 513  
 514      public function test_lesson_core_calendar_provide_event_action_no_time_specified_for_user() {
 515          global $CFG;
 516  
 517          $this->resetAfterTest();
 518          $this->setAdminUser();
 519  
 520          // Create a course.
 521          $course = $this->getDataGenerator()->create_course();
 522  
 523          // Create a student.
 524          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 525  
 526          // Create a lesson activity.
 527          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
 528  
 529          // Create a calendar event.
 530          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 531  
 532          // Now, log out.
 533          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 534          $this->setUser();
 535  
 536          // Create an action factory.
 537          $factory = new \core_calendar\action_factory();
 538  
 539          // Decorate action event.
 540          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 541  
 542          // Confirm the event was decorated.
 543          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 544          $this->assertEquals(get_string('startlesson', 'lesson'), $actionevent->get_name());
 545          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 546          $this->assertEquals(1, $actionevent->get_item_count());
 547          $this->assertTrue($actionevent->is_actionable());
 548      }
 549  
 550      public function test_lesson_core_calendar_provide_event_action_after_attempt() {
 551          global $DB;
 552  
 553          $this->resetAfterTest();
 554          $this->setAdminUser();
 555  
 556          // Create a course.
 557          $course = $this->getDataGenerator()->create_course();
 558  
 559          // Create user.
 560          $student = self::getDataGenerator()->create_user();
 561  
 562          // Create a lesson activity.
 563          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
 564  
 565          // Create a calendar event.
 566          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 567  
 568          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 569          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
 570  
 571          $generator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
 572          $tfrecord = $generator->create_question_truefalse($lesson);
 573  
 574          // Now, do something in the lesson.
 575          $this->setUser($student);
 576          mod_lesson_external::launch_attempt($lesson->id);
 577          $data = array(
 578              array(
 579                  'name' => 'answerid',
 580                  'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
 581              ),
 582              array(
 583                  'name' => '_qf__lesson_display_answer_form_truefalse',
 584                  'value' => 1,
 585              )
 586          );
 587          mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
 588          mod_lesson_external::finish_attempt($lesson->id);
 589  
 590          // Create an action factory.
 591          $factory = new \core_calendar\action_factory();
 592  
 593          // Decorate action event.
 594          $action = mod_lesson_core_calendar_provide_event_action($event, $factory);
 595  
 596          // Confirm there was no action for the user.
 597          $this->assertNull($action);
 598      }
 599  
 600      public function test_lesson_core_calendar_provide_event_action_after_attempt_for_user() {
 601          global $DB;
 602  
 603          $this->resetAfterTest();
 604          $this->setAdminUser();
 605  
 606          // Create a course.
 607          $course = $this->getDataGenerator()->create_course();
 608  
 609          // Create 2 students in the course.
 610          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 611          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 612  
 613          // Create a lesson activity.
 614          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
 615  
 616          // Create a calendar event.
 617          $event = $this->create_action_event($course->id, $lesson->id, LESSON_EVENT_TYPE_OPEN);
 618  
 619          $generator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
 620          $tfrecord = $generator->create_question_truefalse($lesson);
 621  
 622          // Now, do something in the lesson as student1.
 623          $this->setUser($student1);
 624          mod_lesson_external::launch_attempt($lesson->id);
 625          $data = array(
 626              array(
 627                  'name' => 'answerid',
 628                  'value' => $DB->get_field('lesson_answers', 'id', array('pageid' => $tfrecord->id, 'jumpto' => -1)),
 629              ),
 630              array(
 631                  'name' => '_qf__lesson_display_answer_form_truefalse',
 632                  'value' => 1,
 633              )
 634          );
 635          mod_lesson_external::process_page($lesson->id, $tfrecord->id, $data);
 636          mod_lesson_external::finish_attempt($lesson->id);
 637  
 638          // Now, log in as the other student.
 639          $this->setUser($student2);
 640  
 641          // Create an action factory.
 642          $factory = new \core_calendar\action_factory();
 643  
 644          // Decorate action event.
 645          $action = mod_lesson_core_calendar_provide_event_action($event, $factory, $student1->id);
 646  
 647          // Confirm there was no action for the user.
 648          $this->assertNull($action);
 649      }
 650  
 651      public function test_lesson_core_calendar_provide_event_action_already_completed() {
 652          $this->resetAfterTest();
 653          set_config('enablecompletion', 1);
 654          $this->setAdminUser();
 655  
 656          // Create the activity.
 657          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 658          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id),
 659              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 660  
 661          // Get some additional data.
 662          $cm = get_coursemodule_from_instance('lesson', $lesson->id);
 663  
 664          // Create a calendar event.
 665          $event = $this->create_action_event($course->id, $lesson->id,
 666              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 667  
 668          // Mark the activity as completed.
 669          $completion = new completion_info($course);
 670          $completion->set_module_viewed($cm);
 671  
 672          // Create an action factory.
 673          $factory = new \core_calendar\action_factory();
 674  
 675          // Decorate action event.
 676          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory);
 677  
 678          // Ensure result was null.
 679          $this->assertNull($actionevent);
 680      }
 681  
 682      public function test_lesson_core_calendar_provide_event_action_already_completed_for_user() {
 683          $this->resetAfterTest();
 684          set_config('enablecompletion', 1);
 685          $this->setAdminUser();
 686  
 687          // Create the activity.
 688          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 689          $lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id),
 690              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 691  
 692          // Enrol a student in the course.
 693          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 694  
 695          // Get some additional data.
 696          $cm = get_coursemodule_from_instance('lesson', $lesson->id);
 697  
 698          // Create a calendar event.
 699          $event = $this->create_action_event($course->id, $lesson->id,
 700              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 701  
 702          // Mark the activity as completed for the student.
 703          $completion = new completion_info($course);
 704          $completion->set_module_viewed($cm, $student->id);
 705  
 706          // Create an action factory.
 707          $factory = new \core_calendar\action_factory();
 708  
 709          // Decorate action event for the student.
 710          $actionevent = mod_lesson_core_calendar_provide_event_action($event, $factory, $student->id);
 711  
 712          // Ensure result was null.
 713          $this->assertNull($actionevent);
 714      }
 715  
 716      /**
 717       * Creates an action event.
 718       *
 719       * @param int $courseid
 720       * @param int $instanceid The lesson id.
 721       * @param string $eventtype The event type. eg. LESSON_EVENT_TYPE_OPEN.
 722       * @return bool|calendar_event
 723       */
 724      private function create_action_event($courseid, $instanceid, $eventtype) {
 725          $event = new stdClass();
 726          $event->name = 'Calendar event';
 727          $event->modulename  = 'lesson';
 728          $event->courseid = $courseid;
 729          $event->instance = $instanceid;
 730          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 731          $event->eventtype = $eventtype;
 732          $event->timestart = time();
 733          return calendar_event::create($event);
 734      }
 735  
 736      /**
 737       * Test the callback responsible for returning the completion rule descriptions.
 738       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 739       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 740       */
 741      public function test_mod_lesson_completion_get_active_rule_descriptions() {
 742          $this->resetAfterTest();
 743          $this->setAdminUser();
 744  
 745          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 746          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
 747          $lesson1 = $this->getDataGenerator()->create_module('lesson', [
 748              'course' => $course->id,
 749              'completion' => 2,
 750              'completionendreached' => 1,
 751              'completiontimespent' => 3600
 752          ]);
 753          $lesson2 = $this->getDataGenerator()->create_module('lesson', [
 754              'course' => $course->id,
 755              'completion' => 2,
 756              'completionendreached' => 0,
 757              'completiontimespent' => 0
 758          ]);
 759          $cm1 = cm_info::create(get_coursemodule_from_instance('lesson', $lesson1->id));
 760          $cm2 = cm_info::create(get_coursemodule_from_instance('lesson', $lesson2->id));
 761  
 762          // Data for the stdClass input type.
 763          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 764          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 765          $moddefaults = new stdClass();
 766          $moddefaults->customdata = ['customcompletionrules' => [
 767              'completionendreached' => 1,
 768              'completiontimespent' => 3600
 769          ]];
 770          $moddefaults->completion = 2;
 771  
 772          $activeruledescriptions = [
 773              get_string('completionendreached_desc', 'lesson'),
 774              get_string('completiontimespentdesc', 'lesson', format_time(3600)),
 775          ];
 776          $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 777          $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($cm2), []);
 778          $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 779          $this->assertEquals(mod_lesson_get_completion_active_rule_descriptions(new stdClass()), []);
 780      }
 781  
 782      /**
 783       * An unknown event type should not change the lesson instance.
 784       */
 785      public function test_mod_lesson_core_calendar_event_timestart_updated_unknown_event() {
 786          global $CFG, $DB;
 787          require_once($CFG->dirroot . "/calendar/lib.php");
 788  
 789          $this->resetAfterTest(true);
 790          $this->setAdminUser();
 791          $generator = $this->getDataGenerator();
 792          $course = $generator->create_course();
 793          $lessongenerator = $generator->get_plugin_generator('mod_lesson');
 794          $timeopen = time();
 795          $timeclose = $timeopen + DAYSECS;
 796          $lesson = $lessongenerator->create_instance(['course' => $course->id]);
 797          $lesson->available = $timeopen;
 798          $lesson->deadline = $timeclose;
 799          $DB->update_record('lesson', $lesson);
 800  
 801          // Create a valid event.
 802          $event = new \calendar_event([
 803              'name' => 'Test event',
 804              'description' => '',
 805              'format' => 1,
 806              'courseid' => $course->id,
 807              'groupid' => 0,
 808              'userid' => 2,
 809              'modulename' => 'lesson',
 810              'instance' => $lesson->id,
 811              'eventtype' => LESSON_EVENT_TYPE_OPEN . "SOMETHING ELSE",
 812              'timestart' => 1,
 813              'timeduration' => 86400,
 814              'visible' => 1
 815          ]);
 816  
 817          mod_lesson_core_calendar_event_timestart_updated($event, $lesson);
 818          $lesson = $DB->get_record('lesson', ['id' => $lesson->id]);
 819          $this->assertEquals($timeopen, $lesson->available);
 820          $this->assertEquals($timeclose, $lesson->deadline);
 821      }
 822  
 823      /**
 824       * A LESSON_EVENT_TYPE_OPEN event should update the available property of the lesson activity.
 825       */
 826      public function test_mod_lesson_core_calendar_event_timestart_updated_open_event() {
 827          global $CFG, $DB;
 828          require_once($CFG->dirroot . "/calendar/lib.php");
 829  
 830          $this->resetAfterTest(true);
 831          $this->setAdminUser();
 832          $generator = $this->getDataGenerator();
 833          $course = $generator->create_course();
 834          $lessongenerator = $generator->get_plugin_generator('mod_lesson');
 835          $timeopen = time();
 836          $timeclose = $timeopen + DAYSECS;
 837          $timemodified = 1;
 838          $newtimeopen = $timeopen - DAYSECS;
 839          $lesson = $lessongenerator->create_instance(['course' => $course->id]);
 840          $lesson->available = $timeopen;
 841          $lesson->deadline = $timeclose;
 842          $lesson->timemodified = $timemodified;
 843          $DB->update_record('lesson', $lesson);
 844  
 845          // Create a valid event.
 846          $event = new \calendar_event([
 847              'name' => 'Test event',
 848              'description' => '',
 849              'format' => 1,
 850              'courseid' => $course->id,
 851              'groupid' => 0,
 852              'userid' => 2,
 853              'modulename' => 'lesson',
 854              'instance' => $lesson->id,
 855              'eventtype' => LESSON_EVENT_TYPE_OPEN,
 856              'timestart' => $newtimeopen,
 857              'timeduration' => 86400,
 858              'visible' => 1
 859          ]);
 860  
 861          // Trigger and capture the event when adding a contact.
 862          $sink = $this->redirectEvents();
 863          mod_lesson_core_calendar_event_timestart_updated($event, $lesson);
 864          $triggeredevents = $sink->get_events();
 865          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 866              return is_a($e, 'core\event\course_module_updated');
 867          });
 868          $lesson = $DB->get_record('lesson', ['id' => $lesson->id]);
 869  
 870          // Ensure the available property matches the event timestart.
 871          $this->assertEquals($newtimeopen, $lesson->available);
 872  
 873          // Ensure the deadline isn't changed.
 874          $this->assertEquals($timeclose, $lesson->deadline);
 875  
 876          // Ensure the timemodified property has been changed.
 877          $this->assertNotEquals($timemodified, $lesson->timemodified);
 878  
 879          // Confirm that a module updated event is fired when the module is changed.
 880          $this->assertNotEmpty($moduleupdatedevents);
 881      }
 882  
 883      /**
 884       * A LESSON_EVENT_TYPE_CLOSE event should update the deadline property of the lesson activity.
 885       */
 886      public function test_mod_lesson_core_calendar_event_timestart_updated_close_event() {
 887          global $CFG, $DB;
 888          require_once($CFG->dirroot . "/calendar/lib.php");
 889          $this->resetAfterTest(true);
 890          $this->setAdminUser();
 891          $generator = $this->getDataGenerator();
 892          $course = $generator->create_course();
 893          $lessongenerator = $generator->get_plugin_generator('mod_lesson');
 894          $timeopen = time();
 895          $timeclose = $timeopen + DAYSECS;
 896          $timemodified = 1;
 897          $newtimeclose = $timeclose + DAYSECS;
 898          $lesson = $lessongenerator->create_instance(['course' => $course->id]);
 899          $lesson->available = $timeopen;
 900          $lesson->deadline = $timeclose;
 901          $lesson->timemodified = $timemodified;
 902          $DB->update_record('lesson', $lesson);
 903          // Create a valid event.
 904          $event = new \calendar_event([
 905              'name' => 'Test event',
 906              'description' => '',
 907              'format' => 1,
 908              'courseid' => $course->id,
 909              'groupid' => 0,
 910              'userid' => 2,
 911              'modulename' => 'lesson',
 912              'instance' => $lesson->id,
 913              'eventtype' => LESSON_EVENT_TYPE_CLOSE,
 914              'timestart' => $newtimeclose,
 915              'timeduration' => 86400,
 916              'visible' => 1
 917          ]);
 918          // Trigger and capture the event when adding a contact.
 919          $sink = $this->redirectEvents();
 920          mod_lesson_core_calendar_event_timestart_updated($event, $lesson);
 921          $triggeredevents = $sink->get_events();
 922          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 923              return is_a($e, 'core\event\course_module_updated');
 924          });
 925          $lesson = $DB->get_record('lesson', ['id' => $lesson->id]);
 926          // Ensure the deadline property matches the event timestart.
 927          $this->assertEquals($newtimeclose, $lesson->deadline);
 928          // Ensure the available isn't changed.
 929          $this->assertEquals($timeopen, $lesson->available);
 930          // Ensure the timemodified property has been changed.
 931          $this->assertNotEquals($timemodified, $lesson->timemodified);
 932          // Confirm that a module updated event is fired when the module is changed.
 933          $this->assertNotEmpty($moduleupdatedevents);
 934      }
 935  
 936      /**
 937       * An unknown event type should not have any limits.
 938       */
 939      public function test_mod_lesson_core_calendar_get_valid_event_timestart_range_unknown_event() {
 940          global $CFG;
 941          require_once($CFG->dirroot . "/calendar/lib.php");
 942  
 943          $this->resetAfterTest(true);
 944          $this->setAdminUser();
 945          $generator = $this->getDataGenerator();
 946          $course = $generator->create_course();
 947          $timeopen = time();
 948          $timeclose = $timeopen + DAYSECS;
 949          $lesson = new \stdClass();
 950          $lesson->available = $timeopen;
 951          $lesson->deadline = $timeclose;
 952  
 953          // Create a valid event.
 954          $event = new \calendar_event([
 955              'name' => 'Test event',
 956              'description' => '',
 957              'format' => 1,
 958              'courseid' => $course->id,
 959              'groupid' => 0,
 960              'userid' => 2,
 961              'modulename' => 'lesson',
 962              'instance' => 1,
 963              'eventtype' => LESSON_EVENT_TYPE_OPEN . "SOMETHING ELSE",
 964              'timestart' => 1,
 965              'timeduration' => 86400,
 966              'visible' => 1
 967          ]);
 968  
 969          list ($min, $max) = mod_lesson_core_calendar_get_valid_event_timestart_range($event, $lesson);
 970          $this->assertNull($min);
 971          $this->assertNull($max);
 972      }
 973  
 974      /**
 975       * The open event should be limited by the lesson's deadline property, if it's set.
 976       */
 977      public function test_mod_lesson_core_calendar_get_valid_event_timestart_range_open_event() {
 978          global $CFG;
 979          require_once($CFG->dirroot . "/calendar/lib.php");
 980  
 981          $this->resetAfterTest(true);
 982          $this->setAdminUser();
 983          $generator = $this->getDataGenerator();
 984          $course = $generator->create_course();
 985          $timeopen = time();
 986          $timeclose = $timeopen + DAYSECS;
 987          $lesson = new \stdClass();
 988          $lesson->available = $timeopen;
 989          $lesson->deadline = $timeclose;
 990  
 991          // Create a valid event.
 992          $event = new \calendar_event([
 993              'name' => 'Test event',
 994              'description' => '',
 995              'format' => 1,
 996              'courseid' => $course->id,
 997              'groupid' => 0,
 998              'userid' => 2,
 999              'modulename' => 'lesson',
1000              'instance' => 1,
1001              'eventtype' => LESSON_EVENT_TYPE_OPEN,
1002              'timestart' => 1,
1003              'timeduration' => 86400,
1004              'visible' => 1
1005          ]);
1006  
1007          // The max limit should be bounded by the timeclose value.
1008          list ($min, $max) = mod_lesson_core_calendar_get_valid_event_timestart_range($event, $lesson);
1009          $this->assertNull($min);
1010          $this->assertEquals($timeclose, $max[0]);
1011  
1012          // No timeclose value should result in no upper limit.
1013          $lesson->deadline = 0;
1014          list ($min, $max) = mod_lesson_core_calendar_get_valid_event_timestart_range($event, $lesson);
1015          $this->assertNull($min);
1016          $this->assertNull($max);
1017      }
1018  
1019      /**
1020       * The close event should be limited by the lesson's available property, if it's set.
1021       */
1022      public function test_mod_lesson_core_calendar_get_valid_event_timestart_range_close_event() {
1023          global $CFG;
1024          require_once($CFG->dirroot . "/calendar/lib.php");
1025  
1026          $this->resetAfterTest(true);
1027          $this->setAdminUser();
1028          $generator = $this->getDataGenerator();
1029          $course = $generator->create_course();
1030          $timeopen = time();
1031          $timeclose = $timeopen + DAYSECS;
1032          $lesson = new \stdClass();
1033          $lesson->available = $timeopen;
1034          $lesson->deadline = $timeclose;
1035  
1036          // Create a valid event.
1037          $event = new \calendar_event([
1038              'name' => 'Test event',
1039              'description' => '',
1040              'format' => 1,
1041              'courseid' => $course->id,
1042              'groupid' => 0,
1043              'userid' => 2,
1044              'modulename' => 'lesson',
1045              'instance' => 1,
1046              'eventtype' => LESSON_EVENT_TYPE_CLOSE,
1047              'timestart' => 1,
1048              'timeduration' => 86400,
1049              'visible' => 1
1050          ]);
1051  
1052          // The max limit should be bounded by the timeclose value.
1053          list ($min, $max) = mod_lesson_core_calendar_get_valid_event_timestart_range($event, $lesson);
1054          $this->assertEquals($timeopen, $min[0]);
1055          $this->assertNull($max);
1056  
1057          // No deadline value should result in no upper limit.
1058          $lesson->available = 0;
1059          list ($min, $max) = mod_lesson_core_calendar_get_valid_event_timestart_range($event, $lesson);
1060          $this->assertNull($min);
1061          $this->assertNull($max);
1062      }
1063  
1064      /**
1065       * A user who does not have capabilities to add events to the calendar should be able to create an lesson.
1066       */
1067      public function test_creation_with_no_calendar_capabilities() {
1068          $this->resetAfterTest();
1069          $course = self::getDataGenerator()->create_course();
1070          $context = context_course::instance($course->id);
1071          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
1072          $roleid = self::getDataGenerator()->create_role();
1073          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
1074          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1075          $generator = self::getDataGenerator()->get_plugin_generator('mod_lesson');
1076          // Create an instance as a user without the calendar capabilities.
1077          $this->setUser($user);
1078          $time = time();
1079          $params = array(
1080              'course' => $course->id,
1081              'available' => $time + 200,
1082              'deadline' => $time + 2000,
1083          );
1084          $generator->create_instance($params);
1085      }
1086  }