See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 */ 97 public function test_mod_quiz_core_calendar_event_timestart_updated_unknown_event() { 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 */ 121 public function test_mod_quiz_core_calendar_event_timestart_updated_open_event() { 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 */ 155 public function test_mod_quiz_core_calendar_event_timestart_updated_close_event() { 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 */ 189 public function test_mod_quiz_core_calendar_event_timestart_updated_open_event_override() { 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 */ 232 public function test_student_role_cant_update_quiz_activity() { 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 */ 276 public function test_teacher_role_can_update_quiz_activity() { 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 */ 330 public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_unknown_event() { 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 */ 354 public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_open_event() { 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 */ 387 public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_override_event() { 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 */ 423 public function test_mod_quiz_core_calendar_get_valid_event_timestart_range_close_event() { 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 */ 457 public function test_core_calendar_event_timestart_updated_update_quiz_attempt() { 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body