Proyectos de Subversion Moodle

Rev

Rev 1 | | 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
namespace mod_quiz;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once($CFG->dirroot . '/mod/quiz/lib.php');
23
 
24
/**
25
 * Unit tests for the calendar event modification callbacks used
26
 * for dragging and dropping quiz calendar events in the calendar
27
 * UI.
28
 *
29
 * @package    mod_quiz
30
 * @category   test
31
 * @copyright  2017 Ryan Wyllie <ryan@moodle.com>
32
 * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
33
 */
34
class calendar_event_modified_test extends \advanced_testcase {
35
 
36
    /**
37
     * Create an instance of the quiz activity.
38
     *
39
     * @param array $properties Properties to set on the activity
40
     * @return stdClass Quiz activity instance
41
     */
42
    protected function create_quiz_instance(array $properties) {
43
        global $DB;
44
 
45
        $generator = $this->getDataGenerator();
46
 
47
        if (empty($properties['course'])) {
48
            $course = $generator->create_course();
49
            $courseid = $course->id;
50
        } else {
51
            $courseid = $properties['course'];
52
        }
53
 
54
        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
55
        $quiz = $quizgenerator->create_instance(array_merge(['course' => $courseid], $properties));
56
 
57
        if (isset($properties['timemodified'])) {
58
            // The generator overrides the timemodified value to set it as
59
            // the current time even if a value is provided so we need to
60
            // make sure it's set back to the requested value.
61
            $quiz->timemodified = $properties['timemodified'];
62
            $DB->update_record('quiz', $quiz);
63
        }
64
 
65
        return $quiz;
66
    }
67
 
68
    /**
69
     * Create a calendar event for a quiz activity instance.
70
     *
71
     * @param \stdClass $quiz The activity instance
72
     * @param array $eventproperties Properties to set on the calendar event
73
     * @return calendar_event
74
     */
75
    protected function create_quiz_calendar_event(\stdClass $quiz, array $eventproperties) {
76
        $defaultproperties = [
77
            'name' => 'Test event',
78
            'description' => '',
79
            'format' => 1,
80
            'courseid' => $quiz->course,
81
            'groupid' => 0,
82
            'userid' => 2,
83
            'modulename' => 'quiz',
84
            'instance' => $quiz->id,
85
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
86
            'timestart' => time(),
87
            'timeduration' => 86400,
88
            'visible' => 1
89
        ];
90
 
91
        return new \calendar_event(array_merge($defaultproperties, $eventproperties));
92
    }
93
 
94
    /**
95
     * An unkown event type should not change the quiz instance.
96
     */
11 efrain 97
    public function test_mod_quiz_core_calendar_event_timestart_updated_unknown_event(): void {
1 efrain 98
        global $DB;
99
 
100
        $this->resetAfterTest(true);
101
        $this->setAdminUser();
102
        $timeopen = time();
103
        $timeclose = $timeopen + DAYSECS;
104
        $quiz = $this->create_quiz_instance(['timeopen' => $timeopen, 'timeclose' => $timeclose]);
105
        $event = $this->create_quiz_calendar_event($quiz, [
106
            'eventtype' => QUIZ_EVENT_TYPE_OPEN . "SOMETHING ELSE",
107
            'timestart' => 1
108
        ]);
109
 
110
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
111
 
112
        $quiz = $DB->get_record('quiz', ['id' => $quiz->id]);
113
        $this->assertEquals($timeopen, $quiz->timeopen);
114
        $this->assertEquals($timeclose, $quiz->timeclose);
115
    }
116
 
117
    /**
118
     * A QUIZ_EVENT_TYPE_OPEN event should update the timeopen property of
119
     * the quiz activity.
120
     */
11 efrain 121
    public function test_mod_quiz_core_calendar_event_timestart_updated_open_event(): void {
1 efrain 122
        global $DB;
123
 
124
        $this->resetAfterTest(true);
125
        $this->setAdminUser();
126
        $timeopen = time();
127
        $timeclose = $timeopen + DAYSECS;
128
        $timemodified = 1;
129
        $newtimeopen = $timeopen - DAYSECS;
130
        $quiz = $this->create_quiz_instance([
131
            'timeopen' => $timeopen,
132
            'timeclose' => $timeclose,
133
            'timemodified' => $timemodified
134
        ]);
135
        $event = $this->create_quiz_calendar_event($quiz, [
136
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
137
            'timestart' => $newtimeopen
138
        ]);
139
 
140
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
141
 
142
        $quiz = $DB->get_record('quiz', ['id' => $quiz->id]);
143
        // Ensure the timeopen property matches the event timestart.
144
        $this->assertEquals($newtimeopen, $quiz->timeopen);
145
        // Ensure the timeclose isn't changed.
146
        $this->assertEquals($timeclose, $quiz->timeclose);
147
        // Ensure the timemodified property has been changed.
148
        $this->assertNotEquals($timemodified, $quiz->timemodified);
149
    }
150
 
151
    /**
152
     * A QUIZ_EVENT_TYPE_CLOSE event should update the timeclose property of
153
     * the quiz activity.
154
     */
11 efrain 155
    public function test_mod_quiz_core_calendar_event_timestart_updated_close_event(): void {
1 efrain 156
        global $DB;
157
 
158
        $this->resetAfterTest(true);
159
        $this->setAdminUser();
160
        $timeopen = time();
161
        $timeclose = $timeopen + DAYSECS;
162
        $timemodified = 1;
163
        $newtimeclose = $timeclose + DAYSECS;
164
        $quiz = $this->create_quiz_instance([
165
            'timeopen' => $timeopen,
166
            'timeclose' => $timeclose,
167
            'timemodified' => $timemodified
168
        ]);
169
        $event = $this->create_quiz_calendar_event($quiz, [
170
            'eventtype' => QUIZ_EVENT_TYPE_CLOSE,
171
            'timestart' => $newtimeclose
172
        ]);
173
 
174
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
175
 
176
        $quiz = $DB->get_record('quiz', ['id' => $quiz->id]);
177
        // Ensure the timeclose property matches the event timestart.
178
        $this->assertEquals($newtimeclose, $quiz->timeclose);
179
        // Ensure the timeopen isn't changed.
180
        $this->assertEquals($timeopen, $quiz->timeopen);
181
        // Ensure the timemodified property has been changed.
182
        $this->assertNotEquals($timemodified, $quiz->timemodified);
183
    }
184
 
185
    /**
186
     * A QUIZ_EVENT_TYPE_OPEN event should not update the timeopen property of
187
     * the quiz activity if it's an override.
188
     */
11 efrain 189
    public function test_mod_quiz_core_calendar_event_timestart_updated_open_event_override(): void {
1 efrain 190
        global $DB;
191
 
192
        $this->resetAfterTest(true);
193
        $this->setAdminUser();
194
        $user = $this->getDataGenerator()->create_user();
195
        $timeopen = time();
196
        $timeclose = $timeopen + DAYSECS;
197
        $timemodified = 1;
198
        $newtimeopen = $timeopen - DAYSECS;
199
        $quiz = $this->create_quiz_instance([
200
            'timeopen' => $timeopen,
201
            'timeclose' => $timeclose,
202
            'timemodified' => $timemodified
203
        ]);
204
        $event = $this->create_quiz_calendar_event($quiz, [
205
            'userid' => $user->id,
206
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
207
            'timestart' => $newtimeopen
208
        ]);
209
        $record = (object) [
210
            'quiz' => $quiz->id,
211
            'userid' => $user->id
212
        ];
213
 
214
        $DB->insert_record('quiz_overrides', $record);
215
 
216
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
217
 
218
        $quiz = $DB->get_record('quiz', ['id' => $quiz->id]);
219
        // Ensure the timeopen property doesn't change.
220
        $this->assertEquals($timeopen, $quiz->timeopen);
221
        // Ensure the timeclose isn't changed.
222
        $this->assertEquals($timeclose, $quiz->timeclose);
223
        // Ensure the timemodified property has not been changed.
224
        $this->assertEquals($timemodified, $quiz->timemodified);
225
    }
226
 
227
    /**
228
     * If a student somehow finds a way to update the quiz calendar event
229
     * then the callback should not update the quiz activity otherwise that
230
     * would be a security issue.
231
     */
11 efrain 232
    public function test_student_role_cant_update_quiz_activity(): void {
1 efrain 233
        global $DB;
234
 
235
        $this->resetAfterTest();
236
        $this->setAdminUser();
237
 
238
        $generator = $this->getDataGenerator();
239
        $user = $generator->create_user();
240
        $course = $generator->create_course();
241
        $context = \context_course::instance($course->id);
242
        $roleid = $generator->create_role();
243
        $now = time();
244
        $timeopen = (new \DateTime())->setTimestamp($now);
245
        $newtimeopen = (new \DateTime())->setTimestamp($now)->modify('+1 day');
246
        $quiz = $this->create_quiz_instance([
247
            'course' => $course->id,
248
            'timeopen' => $timeopen->getTimestamp()
249
        ]);
250
 
251
        $generator->enrol_user($user->id, $course->id, 'student');
252
        $generator->role_assign($roleid, $user->id, $context->id);
253
 
254
        $event = $this->create_quiz_calendar_event($quiz, [
255
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
256
            'timestart' => $timeopen->getTimestamp()
257
        ]);
258
 
259
        assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
260
 
261
        $this->setUser($user);
262
 
263
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
264
 
265
        $newquiz = $DB->get_record('quiz', ['id' => $quiz->id]);
266
        // The time open shouldn't have changed even though we updated the calendar
267
        // event.
268
        $this->assertEquals($timeopen->getTimestamp(), $newquiz->timeopen);
269
    }
270
 
271
    /**
272
     * A teacher with the capability to modify a quiz module should be
273
     * able to update the quiz activity dates by changing the calendar
274
     * event.
275
     */
11 efrain 276
    public function test_teacher_role_can_update_quiz_activity(): void {
1 efrain 277
        global $DB;
278
 
279
        $this->resetAfterTest();
280
        $this->setAdminUser();
281
 
282
        $generator = $this->getDataGenerator();
283
        $user = $generator->create_user();
284
        $course = $generator->create_course();
285
        $context = \context_course::instance($course->id);
286
        $roleid = $generator->create_role();
287
        $now = time();
288
        $timeopen = (new \DateTime())->setTimestamp($now);
289
        $newtimeopen = (new \DateTime())->setTimestamp($now)->modify('+1 day');
290
        $quiz = $this->create_quiz_instance([
291
            'course' => $course->id,
292
            'timeopen' => $timeopen->getTimestamp()
293
        ]);
294
 
295
        $generator->enrol_user($user->id, $course->id, 'teacher');
296
        $generator->role_assign($roleid, $user->id, $context->id);
297
 
298
        $event = $this->create_quiz_calendar_event($quiz, [
299
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
300
            'timestart' => $newtimeopen->getTimestamp()
301
        ]);
302
 
303
        assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
304
 
305
        $this->setUser($user);
306
 
307
        // Trigger and capture the event.
308
        $sink = $this->redirectEvents();
309
 
310
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
311
 
312
        $triggeredevents = $sink->get_events();
313
        $moduleupdatedevents = array_filter($triggeredevents, function($e) {
314
            return is_a($e, 'core\event\course_module_updated');
315
        });
316
 
317
        $newquiz = $DB->get_record('quiz', ['id' => $quiz->id]);
318
        // The should be updated along with the event because the user has sufficient
319
        // capabilities.
320
        $this->assertEquals($newtimeopen->getTimestamp(), $newquiz->timeopen);
321
        // Confirm that a module updated event is fired when the module
322
        // is changed.
323
        $this->assertNotEmpty($moduleupdatedevents);
324
    }
325
 
326
 
327
    /**
328
     * An unkown event type should not have any limits
329
     */
11 efrain 330
    public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_unknown_event(): void {
1 efrain 331
        global $DB;
332
 
333
        $this->resetAfterTest(true);
334
        $this->setAdminUser();
335
        $timeopen = time();
336
        $timeclose = $timeopen + DAYSECS;
337
        $quiz = $this->create_quiz_instance([
338
            'timeopen' => $timeopen,
339
            'timeclose' => $timeclose
340
        ]);
341
        $event = $this->create_quiz_calendar_event($quiz, [
342
            'eventtype' => QUIZ_EVENT_TYPE_OPEN . "SOMETHING ELSE",
343
            'timestart' => 1
344
        ]);
345
 
346
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
347
        $this->assertNull($min);
348
        $this->assertNull($max);
349
    }
350
 
351
    /**
352
     * The open event should be limited by the quiz's timeclose property, if it's set.
353
     */
11 efrain 354
    public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_open_event(): void {
1 efrain 355
        global $DB;
356
 
357
        $this->resetAfterTest(true);
358
        $this->setAdminUser();
359
        $timeopen = time();
360
        $timeclose = $timeopen + DAYSECS;
361
        $quiz = $this->create_quiz_instance([
362
            'timeopen' => $timeopen,
363
            'timeclose' => $timeclose
364
        ]);
365
        $event = $this->create_quiz_calendar_event($quiz, [
366
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
367
            'timestart' => 1
368
        ]);
369
 
370
        // The max limit should be bounded by the timeclose value.
371
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
372
 
373
        $this->assertNull($min);
374
        $this->assertEquals($timeclose, $max[0]);
375
 
376
        // No timeclose value should result in no upper limit.
377
        $quiz->timeclose = 0;
378
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
379
 
380
        $this->assertNull($min);
381
        $this->assertNull($max);
382
    }
383
 
384
    /**
385
     * An override event should not have any limits.
386
     */
11 efrain 387
    public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_override_event(): void {
1 efrain 388
        global $DB;
389
 
390
        $this->resetAfterTest(true);
391
        $this->setAdminUser();
392
        $generator = $this->getDataGenerator();
393
        $user = $generator->create_user();
394
        $course = $generator->create_course();
395
        $timeopen = time();
396
        $timeclose = $timeopen + DAYSECS;
397
        $quiz = $this->create_quiz_instance([
398
            'course' => $course->id,
399
            'timeopen' => $timeopen,
400
            'timeclose' => $timeclose
401
        ]);
402
        $event = $this->create_quiz_calendar_event($quiz, [
403
            'userid' => $user->id,
404
            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
405
            'timestart' => 1
406
        ]);
407
        $record = (object) [
408
            'quiz' => $quiz->id,
409
            'userid' => $user->id
410
        ];
411
 
412
        $DB->insert_record('quiz_overrides', $record);
413
 
414
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
415
 
416
        $this->assertFalse($min);
417
        $this->assertFalse($max);
418
    }
419
 
420
    /**
421
     * The close event should be limited by the quiz's timeopen property, if it's set.
422
     */
11 efrain 423
    public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_close_event(): void {
1 efrain 424
        global $DB;
425
 
426
        $this->resetAfterTest(true);
427
        $this->setAdminUser();
428
        $timeopen = time();
429
        $timeclose = $timeopen + DAYSECS;
430
        $quiz = $this->create_quiz_instance([
431
            'timeopen' => $timeopen,
432
            'timeclose' => $timeclose
433
        ]);
434
        $event = $this->create_quiz_calendar_event($quiz, [
435
            'eventtype' => QUIZ_EVENT_TYPE_CLOSE,
436
            'timestart' => 1,
437
        ]);
438
 
439
        // The max limit should be bounded by the timeclose value.
440
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
441
 
442
        $this->assertEquals($timeopen, $min[0]);
443
        $this->assertNull($max);
444
 
445
        // No timeclose value should result in no upper limit.
446
        $quiz->timeopen = 0;
447
        list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
448
 
449
        $this->assertNull($min);
450
        $this->assertNull($max);
451
    }
452
 
453
    /**
454
     * When the close date event is changed and it results in the time close value of
455
     * the quiz being updated then the open quiz attempts should also be updated.
456
     */
11 efrain 457
    public function test_core_calendar_event_timestart_updated_update_quiz_attempt(): void {
1 efrain 458
        global $DB;
459
 
460
        $this->resetAfterTest();
461
        $this->setAdminUser();
462
 
463
        $generator = $this->getDataGenerator();
464
        $teacher = $generator->create_user();
465
        $student = $generator->create_user();
466
        $course = $generator->create_course();
467
        $context = \context_course::instance($course->id);
468
        $roleid = $generator->create_role();
469
        $now = time();
470
        $timelimit = 600;
471
        $timeopen = (new \DateTime())->setTimestamp($now);
472
        $timeclose = (new \DateTime())->setTimestamp($now)->modify('+1 day');
473
        // The new close time being earlier than the time open + time limit should
474
        // result in an update to the quiz attempts.
475
        $newtimeclose = $timeopen->getTimestamp() + $timelimit - 10;
476
        $quiz = $this->create_quiz_instance([
477
            'course' => $course->id,
478
            'timeopen' => $timeopen->getTimestamp(),
479
            'timeclose' => $timeclose->getTimestamp(),
480
            'timelimit' => $timelimit
481
        ]);
482
 
483
        $generator->enrol_user($student->id, $course->id, 'student');
484
        $generator->enrol_user($teacher->id, $course->id, 'teacher');
485
        $generator->role_assign($roleid, $teacher->id, $context->id);
486
 
487
        $event = $this->create_quiz_calendar_event($quiz, [
488
            'eventtype' => QUIZ_EVENT_TYPE_CLOSE,
489
            'timestart' => $newtimeclose
490
        ]);
491
 
492
        assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
493
 
494
        $attemptid = $DB->insert_record(
495
            'quiz_attempts',
496
            [
497
                'quiz' => $quiz->id,
498
                'userid' => $student->id,
499
                'state' => 'inprogress',
500
                'timestart' => $timeopen->getTimestamp(),
501
                'timecheckstate' => 0,
502
                'layout' => '',
503
                'uniqueid' => 1
504
            ]
505
        );
506
 
507
        $this->setUser($teacher);
508
 
509
        mod_quiz_core_calendar_event_timestart_updated($event, $quiz);
510
 
511
        $quiz = $DB->get_record('quiz', ['id' => $quiz->id]);
512
        $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid]);
513
        // When the close date is changed so that it's earlier than the time open
514
        // plus the time limit of the quiz then the attempt's timecheckstate should
515
        // be updated to the new time close date of the quiz.
516
        $this->assertEquals($newtimeclose, $attempt->timecheckstate);
517
        $this->assertEquals($newtimeclose, $quiz->timeclose);
518
    }
519
}