Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * SCORM module library functions tests
19
 *
20
 * @package    mod_scorm
21
 * @category   test
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_scorm;
27
 
28
use mod_scorm_get_completion_active_rule_descriptions;
29
 
30
defined('MOODLE_INTERNAL') || die();
31
 
32
global $CFG;
33
 
34
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
35
require_once($CFG->dirroot . '/mod/scorm/lib.php');
36
 
37
/**
38
 * SCORM module library functions tests
39
 *
40
 * @package    mod_scorm
41
 * @category   test
42
 * @copyright  2015 Juan Leyva <juan@moodle.com>
43
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44
 * @since      Moodle 3.0
45
 */
1441 ariadna 46
final class lib_test extends \advanced_testcase {
1 efrain 47
 
48
    /** @var \stdClass course record. */
49
    protected \stdClass $course;
50
 
51
    /** @var \stdClass activity record. */
52
    protected \stdClass $scorm;
53
 
54
    /** @var \core\context\module context instance. */
55
    protected \core\context\module $context;
56
 
57
    /** @var \stdClass */
58
    protected \stdClass $cm;
59
 
60
    /** @var \stdClass user record. */
61
    protected \stdClass $student;
62
 
63
    /** @var \stdClass user record. */
64
    protected \stdClass $teacher;
65
 
66
    /** @var \stdClass a fieldset object, false or exception if error not found. */
67
    protected \stdClass $studentrole;
68
 
69
    /** @var \stdClass a fieldset object, false or exception if error not found. */
70
    protected \stdClass $teacherrole;
71
 
72
    /**
73
     * Set up for every test
74
     */
75
    public function setUp(): void {
76
        global $DB;
1441 ariadna 77
        parent::setUp();
1 efrain 78
        $this->resetAfterTest();
79
        $this->setAdminUser();
80
 
81
        // Setup test data.
82
        $this->course = $this->getDataGenerator()->create_course();
83
        $this->scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $this->course->id));
84
        $this->context = \context_module::instance($this->scorm->cmid);
85
        $this->cm = get_coursemodule_from_instance('scorm', $this->scorm->id);
86
 
87
        // Create users.
88
        $this->student = self::getDataGenerator()->create_user();
89
        $this->teacher = self::getDataGenerator()->create_user();
90
 
91
        // Users enrolments.
92
        $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
93
        $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
94
        $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
95
        $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
96
    }
97
 
98
    /** Test scorm_check_mode
99
     *
100
     * @return void
101
     */
11 efrain 102
    public function test_scorm_check_mode(): void {
1 efrain 103
        global $CFG;
104
 
105
        $newattempt = 'on';
106
        $attempt = 1;
107
        $mode = 'normal';
108
        scorm_check_mode($this->scorm, $newattempt, $attempt, $this->student->id, $mode);
109
        $this->assertEquals('off', $newattempt);
110
 
111
        $scoes = scorm_get_scoes($this->scorm->id);
112
        $sco = array_pop($scoes);
113
        scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
114
        $newattempt = 'on';
115
        scorm_check_mode($this->scorm, $newattempt, $attempt, $this->student->id, $mode);
116
        $this->assertEquals('on', $newattempt);
117
 
118
        // Now do the same with a SCORM 2004 package.
119
        $record = new \stdClass();
120
        $record->course = $this->course->id;
121
        $record->packagefilepath = $CFG->dirroot.'/mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip';
122
        $scorm13 = $this->getDataGenerator()->create_module('scorm', $record);
123
        $newattempt = 'on';
124
        $attempt = 1;
125
        $mode = 'normal';
126
        scorm_check_mode($scorm13, $newattempt, $attempt, $this->student->id, $mode);
127
        $this->assertEquals('off', $newattempt);
128
 
129
        $scoes = scorm_get_scoes($scorm13->id);
130
        $sco = array_pop($scoes);
131
        scorm_insert_track($this->student->id, $scorm13->id, $sco->id, 1, 'cmi.completion_status', 'completed');
132
 
133
        $newattempt = 'on';
134
        $attempt = 1;
135
        $mode = 'normal';
136
        scorm_check_mode($scorm13, $newattempt, $attempt, $this->student->id, $mode);
137
        $this->assertEquals('on', $newattempt);
138
    }
139
 
140
    /**
141
     * Test scorm_view
142
     * @return void
143
     */
11 efrain 144
    public function test_scorm_view(): void {
1 efrain 145
        global $CFG;
146
 
147
        // Trigger and capture the event.
148
        $sink = $this->redirectEvents();
149
 
150
        scorm_view($this->scorm, $this->course, $this->cm, $this->context);
151
 
152
        $events = $sink->get_events();
153
        $this->assertCount(1, $events);
154
        $event = array_shift($events);
155
 
156
        // Checking that the event contains the expected values.
157
        $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
158
        $this->assertEquals($this->context, $event->get_context());
159
        $url = new \moodle_url('/mod/scorm/view.php', array('id' => $this->cm->id));
160
        $this->assertEquals($url, $event->get_url());
161
        $this->assertEventContextNotUsed($event);
162
        $this->assertNotEmpty($event->get_name());
163
    }
164
 
165
    /**
166
     * Test scorm_get_availability_status and scorm_require_available
167
     * @return void
168
     */
11 efrain 169
    public function test_scorm_check_and_require_available(): void {
1 efrain 170
        global $DB;
171
 
172
        $this->setAdminUser();
173
 
174
        // User override case.
175
        $this->scorm->timeopen = time() + DAYSECS;
176
        $this->scorm->timeclose = time() - DAYSECS;
177
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
178
        $this->assertEquals(true, $status);
179
        $this->assertCount(0, $warnings);
180
 
181
        // Now check with a student.
182
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context, $this->student->id);
183
        $this->assertEquals(false, $status);
184
        $this->assertCount(2, $warnings);
185
        $this->assertArrayHasKey('notopenyet', $warnings);
186
        $this->assertArrayHasKey('expired', $warnings);
187
        $this->assertEquals(userdate($this->scorm->timeopen), $warnings['notopenyet']);
188
        $this->assertEquals(userdate($this->scorm->timeclose), $warnings['expired']);
189
 
190
        // Reset the scorm's times.
191
        $this->scorm->timeopen = $this->scorm->timeclose = 0;
192
 
193
        // Set to the student user.
194
        self::setUser($this->student);
195
 
196
        // Usual case.
197
        list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
198
        $this->assertEquals(true, $status);
199
        $this->assertCount(0, $warnings);
200
 
201
        // SCORM not open.
202
        $this->scorm->timeopen = time() + DAYSECS;
203
        list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
204
        $this->assertEquals(false, $status);
205
        $this->assertCount(1, $warnings);
206
 
207
        // SCORM closed.
208
        $this->scorm->timeopen = 0;
209
        $this->scorm->timeclose = time() - DAYSECS;
210
        list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
211
        $this->assertEquals(false, $status);
212
        $this->assertCount(1, $warnings);
213
 
214
        // SCORM not open and closed.
215
        $this->scorm->timeopen = time() + DAYSECS;
216
        list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
217
        $this->assertEquals(false, $status);
218
        $this->assertCount(2, $warnings);
219
 
220
        // Now additional checkings with different parameters values.
221
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
222
        $this->assertEquals(false, $status);
223
        $this->assertCount(2, $warnings);
224
 
225
        // SCORM not open.
226
        $this->scorm->timeopen = time() + DAYSECS;
227
        $this->scorm->timeclose = 0;
228
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
229
        $this->assertEquals(false, $status);
230
        $this->assertCount(1, $warnings);
231
 
232
        // SCORM closed.
233
        $this->scorm->timeopen = 0;
234
        $this->scorm->timeclose = time() - DAYSECS;
235
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
236
        $this->assertEquals(false, $status);
237
        $this->assertCount(1, $warnings);
238
 
239
        // SCORM not open and closed.
240
        $this->scorm->timeopen = time() + DAYSECS;
241
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
242
        $this->assertEquals(false, $status);
243
        $this->assertCount(2, $warnings);
244
 
245
        // As teacher now.
246
        self::setUser($this->teacher);
247
 
248
        // SCORM not open and closed.
249
        $this->scorm->timeopen = time() + DAYSECS;
250
        list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
251
        $this->assertEquals(false, $status);
252
        $this->assertCount(2, $warnings);
253
 
254
        // Now, we use the special capability.
255
        // SCORM not open and closed.
256
        $this->scorm->timeopen = time() + DAYSECS;
257
        list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
258
        $this->assertEquals(true, $status);
259
        $this->assertCount(0, $warnings);
260
 
261
        // Check exceptions does not broke anything.
262
        scorm_require_available($this->scorm, true, $this->context);
263
        // Now, expect exceptions.
264
        $this->expectException('moodle_exception');
265
        $this->expectExceptionMessage(get_string("notopenyet", "scorm", userdate($this->scorm->timeopen)));
266
 
267
        // Now as student other condition.
268
        self::setUser($this->student);
269
        $this->scorm->timeopen = 0;
270
        $this->scorm->timeclose = time() - DAYSECS;
271
 
272
        $this->expectException('moodle_exception');
273
        $this->expectExceptionMessage(get_string("expired", "scorm", userdate($this->scorm->timeclose)));
274
        scorm_require_available($this->scorm, false);
275
    }
276
 
277
    /**
278
     * Test scorm_get_last_completed_attempt
279
     *
280
     * @return void
281
     */
11 efrain 282
    public function test_scorm_get_last_completed_attempt(): void {
1 efrain 283
        $this->assertEquals(1, scorm_get_last_completed_attempt($this->scorm->id, $this->student->id));
284
    }
285
 
11 efrain 286
    public function test_scorm_core_calendar_provide_event_action_open(): void {
1 efrain 287
        $this->resetAfterTest();
288
 
289
        $this->setAdminUser();
290
 
291
        // Create a course.
292
        $course = $this->getDataGenerator()->create_course();
293
 
294
        // Create a scorm activity.
295
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
296
            'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
297
 
298
        // Create a calendar event.
299
        $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
300
 
301
        // Only students see scorm events.
302
        $this->setUser($this->student);
303
 
304
        // Create an action factory.
305
        $factory = new \core_calendar\action_factory();
306
 
307
        // Decorate action event.
308
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
309
 
310
        // Confirm the event was decorated.
311
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
312
        $this->assertEquals(get_string('enter', 'scorm'), $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
 
11 efrain 318
    public function test_scorm_core_calendar_provide_event_action_closed(): void {
1 efrain 319
        $this->resetAfterTest();
320
 
321
        $this->setAdminUser();
322
 
323
        // Create a course.
324
        $course = $this->getDataGenerator()->create_course();
325
 
326
        // Create a scorm activity.
327
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
328
            'timeclose' => time() - DAYSECS));
329
 
330
        // Create a calendar event.
331
        $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
332
 
333
        // Create an action factory.
334
        $factory = new \core_calendar\action_factory();
335
 
336
        // Decorate action event.
337
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
338
 
339
        // No event on the dashboard if module is closed.
340
        $this->assertNull($actionevent);
341
    }
342
 
11 efrain 343
    public function test_scorm_core_calendar_provide_event_action_open_in_future(): void {
1 efrain 344
        $this->resetAfterTest();
345
 
346
        $this->setAdminUser();
347
 
348
        // Create a course.
349
        $course = $this->getDataGenerator()->create_course();
350
 
351
        // Create a scorm activity.
352
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
353
            'timeopen' => time() + DAYSECS));
354
 
355
        // Create a calendar event.
356
        $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
357
 
358
        // Only students see scorm events.
359
        $this->setUser($this->student);
360
 
361
        // Create an action factory.
362
        $factory = new \core_calendar\action_factory();
363
 
364
        // Decorate action event.
365
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
366
 
367
        // Confirm the event was decorated.
368
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
369
        $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
370
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
371
        $this->assertEquals(1, $actionevent->get_item_count());
372
        $this->assertFalse($actionevent->is_actionable());
373
    }
374
 
11 efrain 375
    public function test_scorm_core_calendar_provide_event_action_with_different_user_as_admin(): void {
1 efrain 376
        $this->resetAfterTest();
377
 
378
        $this->setAdminUser();
379
 
380
        // Create a course.
381
        $course = $this->getDataGenerator()->create_course();
382
 
383
        // Create a scorm activity.
384
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
385
            'timeopen' => time() + DAYSECS));
386
 
387
        // Create a calendar event.
388
        $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
389
 
390
        // Create an action factory.
391
        $factory = new \core_calendar\action_factory();
392
 
393
        // Decorate action event override with a passed in user.
394
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory, $this->student->id);
395
        $actionevent2 = mod_scorm_core_calendar_provide_event_action($event, $factory);
396
 
397
        // Only students see scorm events.
398
        $this->assertNull($actionevent2);
399
 
400
        // Confirm the event was decorated.
401
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
402
        $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
403
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
404
        $this->assertEquals(1, $actionevent->get_item_count());
405
        $this->assertFalse($actionevent->is_actionable());
406
    }
407
 
11 efrain 408
    public function test_scorm_core_calendar_provide_event_action_no_time_specified(): void {
1 efrain 409
        $this->resetAfterTest();
410
 
411
        $this->setAdminUser();
412
 
413
        // Create a course.
414
        $course = $this->getDataGenerator()->create_course();
415
 
416
        // Create a scorm activity.
417
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id));
418
 
419
        // Create a calendar event.
420
        $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
421
 
422
        // Only students see scorm events.
423
        $this->setUser($this->student);
424
 
425
        // Create an action factory.
426
        $factory = new \core_calendar\action_factory();
427
 
428
        // Decorate action event.
429
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
430
 
431
        // Confirm the event was decorated.
432
        $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
433
        $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
434
        $this->assertInstanceOf('moodle_url', $actionevent->get_url());
435
        $this->assertEquals(1, $actionevent->get_item_count());
436
        $this->assertTrue($actionevent->is_actionable());
437
    }
438
 
11 efrain 439
    public function test_scorm_core_calendar_provide_event_action_already_completed(): void {
1 efrain 440
        $this->resetAfterTest();
441
        set_config('enablecompletion', 1);
442
        $this->setAdminUser();
443
 
444
        // Create the activity.
445
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
446
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id),
447
            array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
448
 
449
        // Get some additional data.
450
        $cm = get_coursemodule_from_instance('scorm', $scorm->id);
451
 
452
        // Create a calendar event.
453
        $event = $this->create_action_event($course->id, $scorm->id,
454
            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
455
 
456
        // Mark the activity as completed.
457
        $completion = new \completion_info($course);
458
        $completion->set_module_viewed($cm);
459
 
460
        // Create an action factory.
461
        $factory = new \core_calendar\action_factory();
462
 
463
        // Decorate action event.
464
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
465
 
466
        // Ensure result was null.
467
        $this->assertNull($actionevent);
468
    }
469
 
11 efrain 470
    public function test_scorm_core_calendar_provide_event_action_already_completed_for_user(): void {
1 efrain 471
        $this->resetAfterTest();
472
        set_config('enablecompletion', 1);
473
        $this->setAdminUser();
474
 
475
        // Create the activity.
476
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
477
        $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id),
478
            array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
479
 
480
        // Enrol a student in the course.
481
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
482
 
483
        // Get some additional data.
484
        $cm = get_coursemodule_from_instance('scorm', $scorm->id);
485
 
486
        // Create a calendar event.
487
        $event = $this->create_action_event($course->id, $scorm->id,
488
            \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
489
 
490
        // Mark the activity as completed for the student.
491
        $completion = new \completion_info($course);
492
        $completion->set_module_viewed($cm, $student->id);
493
 
494
        // Create an action factory.
495
        $factory = new \core_calendar\action_factory();
496
 
497
        // Decorate action event for the student.
498
        $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory, $student->id);
499
 
500
        // Ensure result was null.
501
        $this->assertNull($actionevent);
502
    }
503
 
504
    /**
505
     * Creates an action event.
506
     *
507
     * @param int $courseid
508
     * @param int $instanceid The data id.
509
     * @param string $eventtype The event type. eg. DATA_EVENT_TYPE_OPEN.
510
     * @param int|null $timestart The start timestamp for the event
511
     * @return bool|calendar_event
512
     */
513
    private function create_action_event($courseid, $instanceid, $eventtype, $timestart = null) {
514
        $event = new \stdClass();
515
        $event->name = 'Calendar event';
516
        $event->modulename = 'scorm';
517
        $event->courseid = $courseid;
518
        $event->instance = $instanceid;
519
        $event->type = CALENDAR_EVENT_TYPE_ACTION;
520
        $event->eventtype = $eventtype;
521
        $event->eventtype = $eventtype;
522
 
523
        if ($timestart) {
524
            $event->timestart = $timestart;
525
        } else {
526
            $event->timestart = time();
527
        }
528
 
529
        return \calendar_event::create($event);
530
    }
531
 
532
    /**
533
     * Test the callback responsible for returning the completion rule descriptions.
534
     * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
535
     * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
536
     */
11 efrain 537
    public function test_mod_scorm_completion_get_active_rule_descriptions(): void {
1 efrain 538
        $this->resetAfterTest();
539
        $this->setAdminUser();
540
 
541
        // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
542
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
543
        $scorm1 = $this->getDataGenerator()->create_module('scorm', [
544
            'course' => $course->id,
545
            'completion' => 2,
546
            'completionstatusrequired' => 6,
547
            'completionscorerequired' => 5,
548
            'completionstatusallscos' => 1
549
        ]);
550
        $scorm2 = $this->getDataGenerator()->create_module('scorm', [
551
            'course' => $course->id,
552
            'completion' => 2,
553
            'completionstatusrequired' => null,
554
            'completionscorerequired' => null,
555
            'completionstatusallscos' => null
556
        ]);
557
        $cm1 = \cm_info::create(get_coursemodule_from_instance('scorm', $scorm1->id));
558
        $cm2 = \cm_info::create(get_coursemodule_from_instance('scorm', $scorm2->id));
559
 
560
        // Data for the stdClass input type.
561
        // This type of input would occur when checking the default completion rules for an activity type, where we don't have
562
        // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
563
        $moddefaults = new \stdClass();
564
        $moddefaults->customdata = ['customcompletionrules' => [
565
            'completionstatusrequired' => 6,
566
            'completionscorerequired' => 5,
567
            'completionstatusallscos' => 1
568
        ]];
569
        $moddefaults->completion = 2;
570
 
571
        // Determine the selected statuses using a bitwise operation.
572
        $cvalues = array();
573
        foreach (scorm_status_options(true) as $key => $value) {
574
            if (($scorm1->completionstatusrequired & $key) == $key) {
575
                $cvalues[] = $value;
576
            }
577
        }
578
        $statusstring = implode(', ', $cvalues);
579
 
580
        $activeruledescriptions = [
581
            get_string('completionstatusrequireddesc', 'scorm', $statusstring),
582
            get_string('completionscorerequireddesc', 'scorm', $scorm1->completionscorerequired),
583
            get_string('completionstatusallscos', 'scorm'),
584
        ];
585
        $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
586
        $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($cm2), []);
587
        $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
588
        $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions(new \stdClass()), []);
589
    }
590
 
591
    /**
592
     * An unkown event type should not change the scorm instance.
593
     */
11 efrain 594
    public function test_mod_scorm_core_calendar_event_timestart_updated_unknown_event(): void {
1 efrain 595
        global $CFG, $DB;
596
        require_once($CFG->dirroot . "/calendar/lib.php");
597
 
598
        $this->resetAfterTest(true);
599
        $this->setAdminUser();
600
        $generator = $this->getDataGenerator();
601
        $course = $generator->create_course();
602
        $scormgenerator = $generator->get_plugin_generator('mod_scorm');
603
        $timeopen = time();
604
        $timeclose = $timeopen + DAYSECS;
605
        $scorm = $scormgenerator->create_instance(['course' => $course->id]);
606
        $scorm->timeopen = $timeopen;
607
        $scorm->timeclose = $timeclose;
608
        $DB->update_record('scorm', $scorm);
609
 
610
        // Create a valid event.
611
        $event = new \calendar_event([
612
            'name' => 'Test event',
613
            'description' => '',
614
            'format' => 1,
615
            'courseid' => $course->id,
616
            'groupid' => 0,
617
            'userid' => 2,
618
            'modulename' => 'scorm',
619
            'instance' => $scorm->id,
620
            'eventtype' => SCORM_EVENT_TYPE_OPEN . "SOMETHING ELSE",
621
            'timestart' => 1,
622
            'timeduration' => 86400,
623
            'visible' => 1
624
        ]);
625
 
626
        mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
627
 
628
        $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
629
        $this->assertEquals($timeopen, $scorm->timeopen);
630
        $this->assertEquals($timeclose, $scorm->timeclose);
631
    }
632
 
633
    /**
634
     * A SCORM_EVENT_TYPE_OPEN event should update the timeopen property of
635
     * the scorm activity.
636
     */
11 efrain 637
    public function test_mod_scorm_core_calendar_event_timestart_updated_open_event(): void {
1 efrain 638
        global $CFG, $DB;
639
        require_once($CFG->dirroot . "/calendar/lib.php");
640
 
641
        $this->resetAfterTest(true);
642
        $this->setAdminUser();
643
        $generator = $this->getDataGenerator();
644
        $course = $generator->create_course();
645
        $scormgenerator = $generator->get_plugin_generator('mod_scorm');
646
        $timeopen = time();
647
        $timeclose = $timeopen + DAYSECS;
648
        $timemodified = 1;
649
        $newtimeopen = $timeopen - DAYSECS;
650
        $scorm = $scormgenerator->create_instance(['course' => $course->id]);
651
        $scorm->timeopen = $timeopen;
652
        $scorm->timeclose = $timeclose;
653
        $scorm->timemodified = $timemodified;
654
        $DB->update_record('scorm', $scorm);
655
 
656
        // Create a valid event.
657
        $event = new \calendar_event([
658
            'name' => 'Test event',
659
            'description' => '',
660
            'format' => 1,
661
            'courseid' => $course->id,
662
            'groupid' => 0,
663
            'userid' => 2,
664
            'modulename' => 'scorm',
665
            'instance' => $scorm->id,
666
            'eventtype' => SCORM_EVENT_TYPE_OPEN,
667
            'timestart' => $newtimeopen,
668
            'timeduration' => 86400,
669
            'visible' => 1
670
        ]);
671
 
672
        // Trigger and capture the event when adding a contact.
673
        $sink = $this->redirectEvents();
674
 
675
        mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
676
 
677
        $triggeredevents = $sink->get_events();
678
        $moduleupdatedevents = array_filter($triggeredevents, function($e) {
679
            return is_a($e, 'core\event\course_module_updated');
680
        });
681
 
682
        $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
683
        // Ensure the timeopen property matches the event timestart.
684
        $this->assertEquals($newtimeopen, $scorm->timeopen);
685
        // Ensure the timeclose isn't changed.
686
        $this->assertEquals($timeclose, $scorm->timeclose);
687
        // Ensure the timemodified property has been changed.
688
        $this->assertNotEquals($timemodified, $scorm->timemodified);
689
        // Confirm that a module updated event is fired when the module
690
        // is changed.
691
        $this->assertNotEmpty($moduleupdatedevents);
692
    }
693
 
694
    /**
695
     * A SCORM_EVENT_TYPE_CLOSE event should update the timeclose property of
696
     * the scorm activity.
697
     */
11 efrain 698
    public function test_mod_scorm_core_calendar_event_timestart_updated_close_event(): void {
1 efrain 699
        global $CFG, $DB;
700
        require_once($CFG->dirroot . "/calendar/lib.php");
701
 
702
        $this->resetAfterTest(true);
703
        $this->setAdminUser();
704
        $generator = $this->getDataGenerator();
705
        $course = $generator->create_course();
706
        $scormgenerator = $generator->get_plugin_generator('mod_scorm');
707
        $timeopen = time();
708
        $timeclose = $timeopen + DAYSECS;
709
        $timemodified = 1;
710
        $newtimeclose = $timeclose + DAYSECS;
711
        $scorm = $scormgenerator->create_instance(['course' => $course->id]);
712
        $scorm->timeopen = $timeopen;
713
        $scorm->timeclose = $timeclose;
714
        $scorm->timemodified = $timemodified;
715
        $DB->update_record('scorm', $scorm);
716
 
717
        // Create a valid event.
718
        $event = new \calendar_event([
719
            'name' => 'Test event',
720
            'description' => '',
721
            'format' => 1,
722
            'courseid' => $course->id,
723
            'groupid' => 0,
724
            'userid' => 2,
725
            'modulename' => 'scorm',
726
            'instance' => $scorm->id,
727
            'eventtype' => SCORM_EVENT_TYPE_CLOSE,
728
            'timestart' => $newtimeclose,
729
            'timeduration' => 86400,
730
            'visible' => 1
731
        ]);
732
 
733
        // Trigger and capture the event when adding a contact.
734
        $sink = $this->redirectEvents();
735
 
736
        mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
737
 
738
        $triggeredevents = $sink->get_events();
739
        $moduleupdatedevents = array_filter($triggeredevents, function($e) {
740
            return is_a($e, 'core\event\course_module_updated');
741
        });
742
 
743
        $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
744
        // Ensure the timeclose property matches the event timestart.
745
        $this->assertEquals($newtimeclose, $scorm->timeclose);
746
        // Ensure the timeopen isn't changed.
747
        $this->assertEquals($timeopen, $scorm->timeopen);
748
        // Ensure the timemodified property has been changed.
749
        $this->assertNotEquals($timemodified, $scorm->timemodified);
750
        // Confirm that a module updated event is fired when the module
751
        // is changed.
752
        $this->assertNotEmpty($moduleupdatedevents);
753
    }
754
 
755
    /**
756
     * An unkown event type should not have any limits
757
     */
11 efrain 758
    public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_unknown_event(): void {
1 efrain 759
        global $CFG, $DB;
760
        require_once($CFG->dirroot . "/calendar/lib.php");
761
 
762
        $this->resetAfterTest(true);
763
        $this->setAdminUser();
764
        $generator = $this->getDataGenerator();
765
        $course = $generator->create_course();
766
        $timeopen = time();
767
        $timeclose = $timeopen + DAYSECS;
768
        $scorm = new \stdClass();
769
        $scorm->timeopen = $timeopen;
770
        $scorm->timeclose = $timeclose;
771
 
772
        // Create a valid event.
773
        $event = new \calendar_event([
774
            'name' => 'Test event',
775
            'description' => '',
776
            'format' => 1,
777
            'courseid' => $course->id,
778
            'groupid' => 0,
779
            'userid' => 2,
780
            'modulename' => 'scorm',
781
            'instance' => 1,
782
            'eventtype' => SCORM_EVENT_TYPE_OPEN . "SOMETHING ELSE",
783
            'timestart' => 1,
784
            'timeduration' => 86400,
785
            'visible' => 1
786
        ]);
787
 
788
        list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
789
        $this->assertNull($min);
790
        $this->assertNull($max);
791
    }
792
 
793
    /**
794
     * The open event should be limited by the scorm's timeclose property, if it's set.
795
     */
11 efrain 796
    public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_open_event(): void {
1 efrain 797
        global $CFG, $DB;
798
        require_once($CFG->dirroot . "/calendar/lib.php");
799
 
800
        $this->resetAfterTest(true);
801
        $this->setAdminUser();
802
        $generator = $this->getDataGenerator();
803
        $course = $generator->create_course();
804
        $timeopen = time();
805
        $timeclose = $timeopen + DAYSECS;
806
        $scorm = new \stdClass();
807
        $scorm->timeopen = $timeopen;
808
        $scorm->timeclose = $timeclose;
809
 
810
        // Create a valid event.
811
        $event = new \calendar_event([
812
            'name' => 'Test event',
813
            'description' => '',
814
            'format' => 1,
815
            'courseid' => $course->id,
816
            'groupid' => 0,
817
            'userid' => 2,
818
            'modulename' => 'scorm',
819
            'instance' => 1,
820
            'eventtype' => SCORM_EVENT_TYPE_OPEN,
821
            'timestart' => 1,
822
            'timeduration' => 86400,
823
            'visible' => 1
824
        ]);
825
 
826
        // The max limit should be bounded by the timeclose value.
827
        list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
828
 
829
        $this->assertNull($min);
830
        $this->assertEquals($timeclose, $max[0]);
831
 
832
        // No timeclose value should result in no upper limit.
833
        $scorm->timeclose = 0;
834
        list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
835
 
836
        $this->assertNull($min);
837
        $this->assertNull($max);
838
    }
839
 
840
    /**
841
     * The close event should be limited by the scorm's timeopen property, if it's set.
842
     */
11 efrain 843
    public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_close_event(): void {
1 efrain 844
        global $CFG, $DB;
845
        require_once($CFG->dirroot . "/calendar/lib.php");
846
 
847
        $this->resetAfterTest(true);
848
        $this->setAdminUser();
849
        $generator = $this->getDataGenerator();
850
        $course = $generator->create_course();
851
        $timeopen = time();
852
        $timeclose = $timeopen + DAYSECS;
853
        $scorm = new \stdClass();
854
        $scorm->timeopen = $timeopen;
855
        $scorm->timeclose = $timeclose;
856
 
857
        // Create a valid event.
858
        $event = new \calendar_event([
859
            'name' => 'Test event',
860
            'description' => '',
861
            'format' => 1,
862
            'courseid' => $course->id,
863
            'groupid' => 0,
864
            'userid' => 2,
865
            'modulename' => 'scorm',
866
            'instance' => 1,
867
            'eventtype' => SCORM_EVENT_TYPE_CLOSE,
868
            'timestart' => 1,
869
            'timeduration' => 86400,
870
            'visible' => 1
871
        ]);
872
 
873
        // The max limit should be bounded by the timeclose value.
874
        list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
875
 
876
        $this->assertEquals($timeopen, $min[0]);
877
        $this->assertNull($max);
878
 
879
        // No timeclose value should result in no upper limit.
880
        $scorm->timeopen = 0;
881
        list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
882
 
883
        $this->assertNull($min);
884
        $this->assertNull($max);
885
    }
886
 
887
    /**
888
     * A user who does not have capabilities to add events to the calendar should be able to create a SCORM.
889
     */
11 efrain 890
    public function test_creation_with_no_calendar_capabilities(): void {
1 efrain 891
        $this->resetAfterTest();
892
        $course = self::getDataGenerator()->create_course();
893
        $context = \context_course::instance($course->id);
894
        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
895
        $roleid = self::getDataGenerator()->create_role();
896
        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
897
        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
898
        $generator = self::getDataGenerator()->get_plugin_generator('mod_scorm');
899
        // Create an instance as a user without the calendar capabilities.
900
        $this->setUser($user);
901
        $time = time();
902
        $params = array(
903
            'course' => $course->id,
904
            'timeopen' => $time + 200,
905
            'timeclose' => $time + 2000,
906
        );
907
        $generator->create_instance($params);
908
    }
909
}