Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 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_lti lib
  19   *
  20   * @package    mod_lti
  21   * @category   external
  22   * @copyright  2015 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.0
  25   */
  26  namespace mod_lti;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * Unit tests for mod_lti lib
  32   *
  33   * @package    mod_lti
  34   * @category   external
  35   * @copyright  2015 Juan Leyva <juan@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   * @since      Moodle 3.0
  38   */
  39  class lib_test extends \advanced_testcase {
  40  
  41      /**
  42       * Prepares things before this test case is initialised
  43       * @return void
  44       */
  45      public static function setUpBeforeClass(): void {
  46          global $CFG;
  47          require_once($CFG->dirroot . '/mod/lti/lib.php');
  48      }
  49  
  50      /**
  51       * Test lti_view
  52       * @return void
  53       */
  54      public function test_lti_view() {
  55          global $CFG;
  56  
  57          $CFG->enablecompletion = 1;
  58          $this->resetAfterTest();
  59  
  60          $this->setAdminUser();
  61          // Setup test data.
  62          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  63          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
  64                                                              array('completion' => 2, 'completionview' => 1));
  65          $context = \context_module::instance($lti->cmid);
  66          $cm = get_coursemodule_from_instance('lti', $lti->id);
  67  
  68          // Trigger and capture the event.
  69          $sink = $this->redirectEvents();
  70  
  71          lti_view($lti, $course, $cm, $context);
  72  
  73          $events = $sink->get_events();
  74          // 2 additional events thanks to completion.
  75          $this->assertCount(3, $events);
  76          $event = array_shift($events);
  77  
  78          // Checking that the event contains the expected values.
  79          $this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
  80          $this->assertEquals($context, $event->get_context());
  81          $moodleurl = new \moodle_url('/mod/lti/view.php', array('id' => $cm->id));
  82          $this->assertEquals($moodleurl, $event->get_url());
  83          $this->assertEventContextNotUsed($event);
  84          $this->assertNotEmpty($event->get_name());
  85  
  86          // Check completion status.
  87          $completion = new \completion_info($course);
  88          $completiondata = $completion->get_data($cm);
  89          $this->assertEquals(1, $completiondata->completionstate);
  90  
  91      }
  92  
  93      /**
  94       * Test deleting LTI instance.
  95       */
  96      public function test_lti_delete_instance() {
  97          $this->resetAfterTest();
  98  
  99          $this->setAdminUser();
 100          $course = $this->getDataGenerator()->create_course(array());
 101          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
 102          $cm = get_coursemodule_from_instance('lti', $lti->id);
 103  
 104          // Must not throw notices.
 105          course_delete_module($cm->id);
 106      }
 107  
 108      public function test_lti_core_calendar_provide_event_action() {
 109          $this->resetAfterTest();
 110          $this->setAdminUser();
 111  
 112          // Create the activity.
 113          $course = $this->getDataGenerator()->create_course();
 114          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
 115  
 116          // Create a calendar event.
 117          $event = $this->create_action_event($course->id, $lti->id,
 118              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 119  
 120          // Create an action factory.
 121          $factory = new \core_calendar\action_factory();
 122  
 123          // Decorate action event.
 124          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
 125  
 126          // Confirm the event was decorated.
 127          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 128          $this->assertEquals(get_string('view'), $actionevent->get_name());
 129          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 130          $this->assertEquals(1, $actionevent->get_item_count());
 131          $this->assertTrue($actionevent->is_actionable());
 132      }
 133  
 134      public function test_lti_core_calendar_provide_event_action_as_non_user() {
 135          global $CFG;
 136  
 137          $this->resetAfterTest();
 138          $this->setAdminUser();
 139  
 140          // Create the activity.
 141          $course = $this->getDataGenerator()->create_course();
 142          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
 143  
 144          // Create a calendar event.
 145          $event = $this->create_action_event($course->id, $lti->id,
 146              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 147  
 148          // Now, log out.
 149          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 150          $this->setUser();
 151  
 152          // Create an action factory.
 153          $factory = new \core_calendar\action_factory();
 154  
 155          // Decorate action event.
 156          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
 157  
 158          // Confirm the event is not shown at all.
 159          $this->assertNull($actionevent);
 160      }
 161  
 162      public function test_lti_core_calendar_provide_event_action_for_user() {
 163          global $CFG;
 164  
 165          $this->resetAfterTest();
 166          $this->setAdminUser();
 167  
 168          // Create the activity.
 169          $course = $this->getDataGenerator()->create_course();
 170          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
 171  
 172          // Enrol a student in the course.
 173          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 174  
 175          // Create a calendar event.
 176          $event = $this->create_action_event($course->id, $lti->id,
 177              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 178  
 179          // Now, log out.
 180          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 181          $this->setUser();
 182  
 183          // Create an action factory.
 184          $factory = new \core_calendar\action_factory();
 185  
 186          // Decorate action event for the student.
 187          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory, $student->id);
 188  
 189          // Confirm the event was decorated.
 190          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 191          $this->assertEquals(get_string('view'), $actionevent->get_name());
 192          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 193          $this->assertEquals(1, $actionevent->get_item_count());
 194          $this->assertTrue($actionevent->is_actionable());
 195      }
 196  
 197      public function test_lti_core_calendar_provide_event_action_already_completed() {
 198          global $CFG;
 199  
 200          $this->resetAfterTest();
 201          $this->setAdminUser();
 202  
 203          $CFG->enablecompletion = 1;
 204  
 205          // Create the activity.
 206          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 207          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
 208              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 209  
 210          // Get some additional data.
 211          $cm = get_coursemodule_from_instance('lti', $lti->id);
 212  
 213          // Create a calendar event.
 214          $event = $this->create_action_event($course->id, $lti->id,
 215              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 216  
 217          // Mark the activity as completed.
 218          $completion = new \completion_info($course);
 219          $completion->set_module_viewed($cm);
 220  
 221          // Create an action factory.
 222          $factory = new \core_calendar\action_factory();
 223  
 224          // Decorate action event.
 225          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
 226  
 227          // Ensure result was null.
 228          $this->assertNull($actionevent);
 229      }
 230  
 231      public function test_lti_core_calendar_provide_event_action_already_completed_as_non_user() {
 232          global $CFG;
 233  
 234          $this->resetAfterTest();
 235          $this->setAdminUser();
 236  
 237          $CFG->enablecompletion = 1;
 238  
 239          // Create the activity.
 240          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 241          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
 242              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 243  
 244          // Get some additional data.
 245          $cm = get_coursemodule_from_instance('lti', $lti->id);
 246  
 247          // Create a calendar event.
 248          $event = $this->create_action_event($course->id, $lti->id,
 249              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 250  
 251          // Mark the activity as completed.
 252          $completion = new \completion_info($course);
 253          $completion->set_module_viewed($cm);
 254  
 255          // Now, log out.
 256          $CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
 257          $this->setUser();
 258  
 259          // Create an action factory.
 260          $factory = new \core_calendar\action_factory();
 261  
 262          // Decorate action event.
 263          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
 264  
 265          // Ensure result was null.
 266          $this->assertNull($actionevent);
 267      }
 268  
 269      public function test_lti_core_calendar_provide_event_action_already_completed_for_user() {
 270          global $CFG;
 271  
 272          $this->resetAfterTest();
 273          $this->setAdminUser();
 274  
 275          $CFG->enablecompletion = 1;
 276  
 277          // Create the activity.
 278          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 279          $lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
 280              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 281  
 282          // Enrol 2 students in the course.
 283          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 284          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 285  
 286          // Get some additional data.
 287          $cm = get_coursemodule_from_instance('lti', $lti->id);
 288  
 289          // Create a calendar event.
 290          $event = $this->create_action_event($course->id, $lti->id,
 291              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 292  
 293          // Mark the activity as completed for $student1.
 294          $completion = new \completion_info($course);
 295          $completion->set_module_viewed($cm, $student1->id);
 296  
 297          // Now, log in as $student2.
 298          $this->setUser($student2);
 299  
 300          // Create an action factory.
 301          $factory = new \core_calendar\action_factory();
 302  
 303          // Decorate action event for $student1.
 304          $actionevent = mod_lti_core_calendar_provide_event_action($event, $factory, $student1->id);
 305  
 306          // Ensure result was null.
 307          $this->assertNull($actionevent);
 308      }
 309  
 310      /**
 311       * Creates an action event.
 312       *
 313       * @param int $courseid The course id.
 314       * @param int $instanceid The instance id.
 315       * @param string $eventtype The event type.
 316       * @return bool|calendar_event
 317       */
 318      private function create_action_event($courseid, $instanceid, $eventtype) {
 319          $event = new \stdClass();
 320          $event->name = 'Calendar event';
 321          $event->modulename  = 'lti';
 322          $event->courseid = $courseid;
 323          $event->instance = $instanceid;
 324          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 325          $event->eventtype = $eventtype;
 326          $event->timestart = time();
 327  
 328          return \calendar_event::create($event);
 329      }
 330  
 331      /**
 332       * Test verifying the output of the lti_get_course_content_items and lti_get_all_content_items callbacks.
 333       */
 334      public function test_content_item_callbacks() {
 335          $this->resetAfterTest();
 336          global $DB, $CFG;
 337          require_once($CFG->dirroot . '/mod/lti/locallib.php');
 338  
 339          $admin = get_admin();
 340          $time = time();
 341          $course = $this->getDataGenerator()->create_course();
 342          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 343          $course2 = $this->getDataGenerator()->create_course();
 344          $teacher2 = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
 345  
 346          // Create some preconfigured tools.
 347          $sitetoolrecord = (object) [
 348              'name' => 'Site level tool which is available in the activity chooser',
 349              'baseurl' => 'http://example.com',
 350              'createdby' => $admin->id,
 351              'course' => SITEID,
 352              'ltiversion' => 'LTI-1p0',
 353              'timecreated' => $time,
 354              'timemodified' => $time,
 355              'state' => LTI_TOOL_STATE_CONFIGURED,
 356              'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
 357          ];
 358          $sitetoolrecordnonchooser = (object) [
 359              'name' => 'Site level tool which is NOT available in the course activity chooser',
 360              'baseurl' => 'http://example2.com',
 361              'createdby' => $admin->id,
 362              'course' => SITEID,
 363              'ltiversion' => 'LTI-1p0',
 364              'timecreated' => $time,
 365              'timemodified' => $time,
 366              'state' => LTI_TOOL_STATE_CONFIGURED,
 367              'coursevisible' => LTI_COURSEVISIBLE_PRECONFIGURED
 368          ];
 369          $course1toolrecord = (object) [
 370              'name' => 'Course created tool which is available in the activity chooser',
 371              'baseurl' => 'http://example3.com',
 372              'createdby' => $teacher->id,
 373              'course' => $course->id,
 374              'ltiversion' => 'LTI-1p0',
 375              'timecreated' => $time,
 376              'timemodified' => $time,
 377              'state' => LTI_TOOL_STATE_CONFIGURED,
 378              'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
 379          ];
 380          $course2toolrecord = (object) [
 381              'name' => 'Course created tool which is available in the activity chooser',
 382              'baseurl' => 'http://example4.com',
 383              'createdby' => $teacher2->id,
 384              'course' => $course2->id,
 385              'ltiversion' => 'LTI-1p0',
 386              'timecreated' => $time,
 387              'timemodified' => $time,
 388              'state' => LTI_TOOL_STATE_CONFIGURED,
 389              'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
 390          ];
 391          $tool1id = $DB->insert_record('lti_types', $sitetoolrecord);
 392          $tool2id = $DB->insert_record('lti_types', $sitetoolrecordnonchooser);
 393          $tool3id = $DB->insert_record('lti_types', $course1toolrecord);
 394          $tool4id = $DB->insert_record('lti_types', $course2toolrecord);
 395          $sitetoolrecord->id = $tool1id;
 396          $sitetoolrecordnonchooser->id = $tool2id;
 397          $course1toolrecord->id = $tool3id;
 398          $course2toolrecord->id = $tool4id;
 399  
 400          $defaultmodulecontentitem = new \core_course\local\entity\content_item(
 401              '1',
 402              'default module content item',
 403              new \core_course\local\entity\string_title('Content item title'),
 404              new \moodle_url(''),
 405              'icon',
 406              'Description of the module',
 407              MOD_ARCHETYPE_OTHER,
 408              'mod_lti',
 409              MOD_PURPOSE_CONTENT
 410          );
 411  
 412          // The lti_get_lti_types_by_course method (used by the callbacks) assumes the global user.
 413          $this->setUser($teacher);
 414  
 415          // Teacher in course1 should be able to see the default module item ('external tool'),
 416          // the site preconfigured tool and the tool created in course1.
 417          $courseitems = lti_get_course_content_items($defaultmodulecontentitem, $teacher, $course);
 418          $this->assertCount(3, $courseitems);
 419          $ids = [];
 420          foreach ($courseitems as $item) {
 421              $ids[] = $item->get_id();
 422          }
 423          $this->assertContains(1, $ids);
 424          $this->assertContains($sitetoolrecord->id + 1, $ids);
 425          $this->assertContains($course1toolrecord->id + 1, $ids);
 426          $this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
 427  
 428          // The content items for teacher2 in course2 include the default module content item ('external tool'),
 429          // the site preconfigured tool and the tool created in course2.
 430          $this->setUser($teacher2);
 431          $course2items = lti_get_course_content_items($defaultmodulecontentitem, $teacher2, $course2);
 432          $this->assertCount(3, $course2items);
 433          $ids = [];
 434          foreach ($course2items as $item) {
 435              $ids[] = $item->get_id();
 436          }
 437          $this->assertContains(1, $ids);
 438          $this->assertContains($sitetoolrecord->id + 1, $ids);
 439          $this->assertContains($course2toolrecord->id + 1, $ids);
 440          $this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
 441  
 442          // When fetching all content items, we expect to see all items available in activity choosers (in any course),
 443          // plus the default module content item ('external tool').
 444          $this->setAdminUser();
 445          $allitems = mod_lti_get_all_content_items($defaultmodulecontentitem);
 446          $this->assertCount(4, $allitems);
 447          $ids = [];
 448          foreach ($allitems as $item) {
 449              $ids[] = $item->get_id();
 450          }
 451          $this->assertContains(1, $ids);
 452          $this->assertContains($sitetoolrecord->id + 1, $ids);
 453          $this->assertContains($course1toolrecord->id + 1, $ids);
 454          $this->assertContains($course2toolrecord->id + 1, $ids);
 455          $this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
 456      }
 457  }