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