Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

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