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 * Unit tests for (some of) mod/feedback/lib.php. 18 * 19 * @package mod_feedback 20 * @copyright 2016 Stephen Bourget 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23defined('MOODLE_INTERNAL') || die(); 24global $CFG; 25require_once($CFG->dirroot . '/mod/feedback/lib.php'); 26 27/** 28 * Unit tests for (some of) mod/feedback/lib.php. 29 * 30 * @copyright 2016 Stephen Bourget 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 */ 33class mod_feedback_lib_testcase extends advanced_testcase { 34 35 public function test_feedback_initialise() { 36 $this->resetAfterTest(); 37 $this->setAdminUser(); 38 39 $course = $this->getDataGenerator()->create_course(); 40 $params['course'] = $course->id; 41 $params['timeopen'] = time() - 5 * MINSECS; 42 $params['timeclose'] = time() + DAYSECS; 43 $params['anonymous'] = 1; 44 $params['intro'] = 'Some introduction text'; 45 $feedback = $this->getDataGenerator()->create_module('feedback', $params); 46 47 // Test different ways to construct the structure object. 48 $pseudocm = get_coursemodule_from_instance('feedback', $feedback->id); // Object similar to cm_info. 49 $cm = get_fast_modinfo($course)->instances['feedback'][$feedback->id]; // Instance of cm_info. 50 51 $constructorparams = [ 52 [$feedback, null], 53 [null, $pseudocm], 54 [null, $cm], 55 [$feedback, $pseudocm], 56 [$feedback, $cm], 57 ]; 58 59 foreach ($constructorparams as $params) { 60 $structure = new mod_feedback_completion($params[0], $params[1], 0); 61 $this->assertTrue($structure->is_open()); 62 $this->assertTrue($structure->get_cm() instanceof cm_info); 63 $this->assertEquals($feedback->cmid, $structure->get_cm()->id); 64 $this->assertEquals($feedback->intro, $structure->get_feedback()->intro); 65 } 66 } 67 68 /** 69 * Tests for mod_feedback_refresh_events. 70 */ 71 public function test_feedback_refresh_events() { 72 global $DB; 73 $this->resetAfterTest(); 74 $this->setAdminUser(); 75 76 $timeopen = time(); 77 $timeclose = time() + 86400; 78 79 $course = $this->getDataGenerator()->create_course(); 80 $generator = $this->getDataGenerator()->get_plugin_generator('mod_feedback'); 81 $params['course'] = $course->id; 82 $params['timeopen'] = $timeopen; 83 $params['timeclose'] = $timeclose; 84 $feedback = $generator->create_instance($params); 85 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 86 $context = context_module::instance($cm->id); 87 88 // Normal case, with existing course. 89 $this->assertTrue(feedback_refresh_events($course->id)); 90 $eventparams = array('modulename' => 'feedback', 'instance' => $feedback->id, 'eventtype' => 'open'); 91 $openevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST); 92 $this->assertEquals($openevent->timestart, $timeopen); 93 94 $eventparams = array('modulename' => 'feedback', 'instance' => $feedback->id, 'eventtype' => 'close'); 95 $closeevent = $DB->get_record('event', $eventparams, '*', MUST_EXIST); 96 $this->assertEquals($closeevent->timestart, $timeclose); 97 // In case the course ID is passed as a numeric string. 98 $this->assertTrue(feedback_refresh_events('' . $course->id)); 99 // Course ID not provided. 100 $this->assertTrue(feedback_refresh_events()); 101 $eventparams = array('modulename' => 'feedback'); 102 $events = $DB->get_records('event', $eventparams); 103 foreach ($events as $event) { 104 if ($event->modulename === 'feedback' && $event->instance === $feedback->id && $event->eventtype === 'open') { 105 $this->assertEquals($event->timestart, $timeopen); 106 } 107 if ($event->modulename === 'feedback' && $event->instance === $feedback->id && $event->eventtype === 'close') { 108 $this->assertEquals($event->timestart, $timeclose); 109 } 110 } 111 } 112 113 /** 114 * Test check_updates_since callback. 115 */ 116 public function test_check_updates_since() { 117 global $DB; 118 119 $this->resetAfterTest(); 120 $this->setAdminUser(); 121 $course = $this->getDataGenerator()->create_course(); 122 123 // Create user. 124 $student = self::getDataGenerator()->create_user(); 125 126 // User enrolment. 127 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 128 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 129 130 $this->setCurrentTimeStart(); 131 $record = array( 132 'course' => $course->id, 133 'custom' => 0, 134 'feedback' => 1, 135 ); 136 $feedback = $this->getDataGenerator()->create_module('feedback', $record); 137 $cm = get_coursemodule_from_instance('feedback', $feedback->id, $course->id); 138 $cm = cm_info::create($cm); 139 140 $this->setUser($student); 141 // Check that upon creation, the updates are only about the new configuration created. 142 $onehourago = time() - HOURSECS; 143 $updates = feedback_check_updates_since($cm, $onehourago); 144 foreach ($updates as $el => $val) { 145 if ($el == 'configuration') { 146 $this->assertTrue($val->updated); 147 $this->assertTimeCurrent($val->timeupdated); 148 } else { 149 $this->assertFalse($val->updated); 150 } 151 } 152 153 $record = [ 154 'feedback' => $feedback->id, 155 'userid' => $student->id, 156 'timemodified' => time(), 157 'random_response' => 0, 158 'anonymous_response' => FEEDBACK_ANONYMOUS_NO, 159 'courseid' => $course->id, 160 ]; 161 $DB->insert_record('feedback_completed', (object)$record); 162 $DB->insert_record('feedback_completedtmp', (object)$record); 163 164 // Check now for finished and unfinished attempts. 165 $updates = feedback_check_updates_since($cm, $onehourago); 166 $this->assertTrue($updates->attemptsunfinished->updated); 167 $this->assertCount(1, $updates->attemptsunfinished->itemids); 168 169 $this->assertTrue($updates->attemptsfinished->updated); 170 $this->assertCount(1, $updates->attemptsfinished->itemids); 171 } 172 173 /** 174 * Test calendar event provide action open. 175 */ 176 public function test_feedback_core_calendar_provide_event_action_open() { 177 $this->resetAfterTest(); 178 $this->setAdminUser(); 179 180 $now = time(); 181 $course = $this->getDataGenerator()->create_course(); 182 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id, 183 'timeopen' => $now - DAYSECS, 'timeclose' => $now + DAYSECS]); 184 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 185 186 $factory = new \core_calendar\action_factory(); 187 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory); 188 189 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 190 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 191 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 192 $this->assertEquals(1, $actionevent->get_item_count()); 193 $this->assertTrue($actionevent->is_actionable()); 194 } 195 196 /** 197 * Test calendar event provide action open, viewed by a different user. 198 */ 199 public function test_feedback_core_calendar_provide_event_action_open_for_user() { 200 global $DB; 201 202 $this->resetAfterTest(); 203 $this->setAdminUser(); 204 205 $now = time(); 206 $user = $this->getDataGenerator()->create_user(); 207 $user2 = $this->getDataGenerator()->create_user(); 208 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 209 $course = $this->getDataGenerator()->create_course(); 210 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 211 212 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id, 213 'timeopen' => $now - DAYSECS, 'timeclose' => $now + DAYSECS]); 214 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 215 $factory = new \core_calendar\action_factory(); 216 217 $this->setUser($user2); 218 219 // User2 checking their events. 220 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 221 $this->assertNull($actionevent); 222 223 // User2 checking $user's events. 224 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 225 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 226 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 227 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 228 $this->assertEquals(1, $actionevent->get_item_count()); 229 $this->assertTrue($actionevent->is_actionable()); 230 } 231 232 /** 233 * Test calendar event provide action closed. 234 */ 235 public function test_feedback_core_calendar_provide_event_action_closed() { 236 $this->resetAfterTest(); 237 $this->setAdminUser(); 238 239 $course = $this->getDataGenerator()->create_course(); 240 $feedback = $this->getDataGenerator()->create_module('feedback', array('course' => $course->id, 241 'timeclose' => time() - DAYSECS)); 242 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 243 244 $factory = new \core_calendar\action_factory(); 245 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory); 246 247 // No event on the dashboard if feedback is closed. 248 $this->assertNull($actionevent); 249 } 250 251 /** 252 * Test calendar event provide action closed, viewed by a different user. 253 */ 254 public function test_feedback_core_calendar_provide_event_action_closed_for_user() { 255 global $DB; 256 257 $this->resetAfterTest(); 258 $this->setAdminUser(); 259 260 $course = $this->getDataGenerator()->create_course(); 261 $user = $this->getDataGenerator()->create_user(); 262 $user2 = $this->getDataGenerator()->create_user(); 263 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 264 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 265 266 $feedback = $this->getDataGenerator()->create_module('feedback', array('course' => $course->id, 267 'timeclose' => time() - DAYSECS)); 268 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 269 $factory = new \core_calendar\action_factory(); 270 $this->setUser($user2); 271 272 // User2 checking their events. 273 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 274 $this->assertNull($actionevent); 275 276 // User2 checking $user's events. 277 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 278 279 // No event on the dashboard if feedback is closed. 280 $this->assertNull($actionevent); 281 } 282 283 /** 284 * Test calendar event action open in future. 285 * 286 * @throws coding_exception 287 */ 288 public function test_feedback_core_calendar_provide_event_action_open_in_future() { 289 $this->resetAfterTest(); 290 $this->setAdminUser(); 291 292 $course = $this->getDataGenerator()->create_course(); 293 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id, 294 'timeopen' => time() + DAYSECS]); 295 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 296 297 $factory = new \core_calendar\action_factory(); 298 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory); 299 300 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 301 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 302 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 303 $this->assertEquals(1, $actionevent->get_item_count()); 304 $this->assertFalse($actionevent->is_actionable()); 305 } 306 307 /** 308 * Test calendar event action open in future, viewed by a different user. 309 * 310 * @throws coding_exception 311 */ 312 public function test_feedback_core_calendar_provide_event_action_open_in_future_for_user() { 313 global $DB; 314 315 $this->resetAfterTest(); 316 $this->setAdminUser(); 317 318 $course = $this->getDataGenerator()->create_course(); 319 $user = $this->getDataGenerator()->create_user(); 320 $user2 = $this->getDataGenerator()->create_user(); 321 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 322 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 323 324 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id, 325 'timeopen' => time() + DAYSECS]); 326 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 327 328 $factory = new \core_calendar\action_factory(); 329 $this->setUser($user2); 330 331 // User2 checking their events. 332 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 333 $this->assertNull($actionevent); 334 335 // User2 checking $user's events. 336 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 337 338 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 339 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 340 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 341 $this->assertEquals(1, $actionevent->get_item_count()); 342 $this->assertFalse($actionevent->is_actionable()); 343 } 344 345 /** 346 * Test calendar event with no time specified. 347 * 348 * @throws coding_exception 349 */ 350 public function test_feedback_core_calendar_provide_event_action_no_time_specified() { 351 $this->resetAfterTest(); 352 $this->setAdminUser(); 353 354 $course = $this->getDataGenerator()->create_course(); 355 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 356 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 357 358 $factory = new \core_calendar\action_factory(); 359 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory); 360 361 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 362 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 363 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 364 $this->assertEquals(1, $actionevent->get_item_count()); 365 $this->assertTrue($actionevent->is_actionable()); 366 } 367 368 /** 369 * Test calendar event with no time specified, viewed by a different user. 370 * 371 * @throws coding_exception 372 */ 373 public function test_feedback_core_calendar_provide_event_action_no_time_specified_for_user() { 374 global $DB; 375 376 $this->resetAfterTest(); 377 $this->setAdminUser(); 378 379 $course = $this->getDataGenerator()->create_course(); 380 $user = $this->getDataGenerator()->create_user(); 381 $user2 = $this->getDataGenerator()->create_user(); 382 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 383 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 384 385 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 386 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 387 388 $factory = new \core_calendar\action_factory(); 389 $this->setUser($user2); 390 391 // User2 checking their events. 392 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 393 $this->assertNull($actionevent); 394 395 // User2 checking $user's events. 396 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 397 398 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 399 $this->assertEquals(get_string('answerquestions', 'feedback'), $actionevent->get_name()); 400 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 401 $this->assertEquals(1, $actionevent->get_item_count()); 402 $this->assertTrue($actionevent->is_actionable()); 403 } 404 405 /** 406 * A user that can not submit feedback should not have an action. 407 */ 408 public function test_feedback_core_calendar_provide_event_action_can_not_submit() { 409 global $DB; 410 411 $this->resetAfterTest(); 412 $this->setAdminUser(); 413 414 $user = $this->getDataGenerator()->create_user(); 415 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 416 $course = $this->getDataGenerator()->create_course(); 417 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 418 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 419 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 420 $context = context_module::instance($cm->id); 421 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 422 423 $this->setUser($user); 424 assign_capability('mod/feedback:complete', CAP_PROHIBIT, $studentrole->id, $context); 425 426 $factory = new \core_calendar\action_factory(); 427 $action = mod_feedback_core_calendar_provide_event_action($event, $factory); 428 429 $this->assertNull($action); 430 } 431 432 /** 433 * A user that can not submit feedback should not have an action, viewed by a different user. 434 */ 435 public function test_feedback_core_calendar_provide_event_action_can_not_submit_for_user() { 436 global $DB; 437 438 $this->resetAfterTest(); 439 $this->setAdminUser(); 440 441 $user = $this->getDataGenerator()->create_user(); 442 $user2 = $this->getDataGenerator()->create_user(); 443 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 444 $course = $this->getDataGenerator()->create_course(); 445 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 446 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 447 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 448 $context = context_module::instance($cm->id); 449 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 450 451 assign_capability('mod/feedback:complete', CAP_PROHIBIT, $studentrole->id, $context); 452 $factory = new \core_calendar\action_factory(); 453 $this->setUser($user2); 454 455 // User2 checking their events. 456 457 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 458 $this->assertNull($actionevent); 459 460 // User2 checking $user's events. 461 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 462 463 $this->assertNull($actionevent); 464 } 465 466 /** 467 * A user that has already submitted feedback should not have an action. 468 */ 469 public function test_feedback_core_calendar_provide_event_action_already_submitted() { 470 global $DB; 471 472 $this->resetAfterTest(); 473 $this->setAdminUser(); 474 475 $user = $this->getDataGenerator()->create_user(); 476 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 477 $course = $this->getDataGenerator()->create_course(); 478 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 479 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 480 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 481 $context = context_module::instance($cm->id); 482 483 $this->setUser($user); 484 485 $record = [ 486 'feedback' => $feedback->id, 487 'userid' => $user->id, 488 'timemodified' => time(), 489 'random_response' => 0, 490 'anonymous_response' => FEEDBACK_ANONYMOUS_NO, 491 'courseid' => 0, 492 ]; 493 $DB->insert_record('feedback_completed', (object) $record); 494 495 $factory = new \core_calendar\action_factory(); 496 $action = mod_feedback_core_calendar_provide_event_action($event, $factory); 497 498 $this->assertNull($action); 499 } 500 501 /** 502 * A user that has already submitted feedback should not have an action, viewed by a different user. 503 */ 504 public function test_feedback_core_calendar_provide_event_action_already_submitted_for_user() { 505 global $DB; 506 507 $this->resetAfterTest(); 508 $this->setAdminUser(); 509 510 $user = $this->getDataGenerator()->create_user(); 511 $user2 = $this->getDataGenerator()->create_user(); 512 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 513 $course = $this->getDataGenerator()->create_course(); 514 $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); 515 $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); 516 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 517 $context = context_module::instance($cm->id); 518 519 $this->setUser($user); 520 521 $record = [ 522 'feedback' => $feedback->id, 523 'userid' => $user->id, 524 'timemodified' => time(), 525 'random_response' => 0, 526 'anonymous_response' => FEEDBACK_ANONYMOUS_NO, 527 'courseid' => 0, 528 ]; 529 $DB->insert_record('feedback_completed', (object) $record); 530 531 $factory = new \core_calendar\action_factory(); 532 $this->setUser($user2); 533 534 // User2 checking their events. 535 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user2->id); 536 $this->assertNull($actionevent); 537 538 // User2 checking $user's events. 539 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $user->id); 540 541 $this->assertNull($actionevent); 542 } 543 544 public function test_feedback_core_calendar_provide_event_action_already_completed() { 545 $this->resetAfterTest(); 546 set_config('enablecompletion', 1); 547 $this->setAdminUser(); 548 549 // Create the activity. 550 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); 551 $feedback = $this->getDataGenerator()->create_module('feedback', array('course' => $course->id), 552 array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS)); 553 554 // Get some additional data. 555 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 556 557 // Create a calendar event. 558 $event = $this->create_action_event($course->id, $feedback->id, 559 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 560 561 // Mark the activity as completed. 562 $completion = new completion_info($course); 563 $completion->set_module_viewed($cm); 564 565 // Create an action factory. 566 $factory = new \core_calendar\action_factory(); 567 568 // Decorate action event. 569 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory); 570 571 // Ensure result was null. 572 $this->assertNull($actionevent); 573 } 574 575 public function test_feedback_core_calendar_provide_event_action_already_completed_for_user() { 576 $this->resetAfterTest(); 577 set_config('enablecompletion', 1); 578 $this->setAdminUser(); 579 580 // Create the activity. 581 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); 582 $feedback = $this->getDataGenerator()->create_module('feedback', array('course' => $course->id), 583 array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS)); 584 585 // Enrol a student in the course. 586 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 587 588 // Get some additional data. 589 $cm = get_coursemodule_from_instance('feedback', $feedback->id); 590 591 // Create a calendar event. 592 $event = $this->create_action_event($course->id, $feedback->id, 593 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 594 595 // Mark the activity as completed for the student. 596 $completion = new completion_info($course); 597 $completion->set_module_viewed($cm, $student->id); 598 599 // Create an action factory. 600 $factory = new \core_calendar\action_factory(); 601 602 // Decorate action event for the student. 603 $actionevent = mod_feedback_core_calendar_provide_event_action($event, $factory, $student->id); 604 605 // Ensure result was null. 606 $this->assertNull($actionevent); 607 } 608 609 /** 610 * Creates an action event. 611 * 612 * @param int $courseid The course id. 613 * @param int $instanceid The feedback id. 614 * @param string $eventtype The event type. eg. FEEDBACK_EVENT_TYPE_OPEN. 615 * @return bool|calendar_event 616 */ 617 private function create_action_event($courseid, $instanceid, $eventtype) { 618 $event = new stdClass(); 619 $event->name = 'Calendar event'; 620 $event->modulename = 'feedback'; 621 $event->courseid = $courseid; 622 $event->instance = $instanceid; 623 $event->type = CALENDAR_EVENT_TYPE_ACTION; 624 $event->eventtype = $eventtype; 625 $event->timestart = time(); 626 627 return calendar_event::create($event); 628 } 629 630 /** 631 * Test the callback responsible for returning the completion rule descriptions. 632 * This function should work given either an instance of the module (cm_info), such as when checking the active rules, 633 * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type. 634 */ 635 public function test_mod_feedback_completion_get_active_rule_descriptions() { 636 $this->resetAfterTest(); 637 $this->setAdminUser(); 638 639 // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't. 640 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]); 641 $feedback1 = $this->getDataGenerator()->create_module('feedback', [ 642 'course' => $course->id, 643 'completion' => 2, 644 'completionsubmit' => 1 645 ]); 646 $feedback2 = $this->getDataGenerator()->create_module('feedback', [ 647 'course' => $course->id, 648 'completion' => 2, 649 'completionsubmit' => 0 650 ]); 651 $cm1 = cm_info::create(get_coursemodule_from_instance('feedback', $feedback1->id)); 652 $cm2 = cm_info::create(get_coursemodule_from_instance('feedback', $feedback2->id)); 653 654 // Data for the stdClass input type. 655 // This type of input would occur when checking the default completion rules for an activity type, where we don't have 656 // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info. 657 $moddefaults = new stdClass(); 658 $moddefaults->customdata = ['customcompletionrules' => ['completionsubmit' => 1]]; 659 $moddefaults->completion = 2; 660 661 $activeruledescriptions = [get_string('completionsubmit', 'feedback')]; 662 $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions($cm1), $activeruledescriptions); 663 $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions($cm2), []); 664 $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); 665 $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions(new stdClass()), []); 666 } 667 668 /** 669 * An unknown event should not have min or max restrictions. 670 */ 671 public function test_get_valid_event_timestart_range_unknown_event() { 672 global $CFG, $DB; 673 require_once($CFG->dirroot . "/calendar/lib.php"); 674 675 $this->resetAfterTest(true); 676 $this->setAdminUser(); 677 $generator = $this->getDataGenerator(); 678 $course = $generator->create_course(); 679 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 680 $timeopen = time(); 681 $timeclose = $timeopen + DAYSECS; 682 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 683 $feedback->timeopen = $timeopen; 684 $feedback->timeclose = $timeclose; 685 $DB->update_record('feedback', $feedback); 686 687 $event = new \calendar_event([ 688 'name' => 'Test event', 689 'description' => '', 690 'format' => 1, 691 'courseid' => $course->id, 692 'groupid' => 0, 693 'userid' => 2, 694 'modulename' => 'feedback', 695 'instance' => $feedback->id, 696 'eventtype' => 'SOME UNKNOWN EVENT', 697 'timestart' => $timeopen, 698 'timeduration' => 86400, 699 'visible' => 1 700 ]); 701 702 list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $feedback); 703 $this->assertNull($min); 704 $this->assertNull($max); 705 } 706 707 /** 708 * A FEEDBACK_EVENT_TYPE_OPEN should have a max timestart equal to the activity 709 * close time. 710 */ 711 public function test_get_valid_event_timestart_range_event_type_open() { 712 global $CFG, $DB; 713 require_once($CFG->dirroot . "/calendar/lib.php"); 714 715 $this->resetAfterTest(true); 716 $this->setAdminUser(); 717 $generator = $this->getDataGenerator(); 718 $course = $generator->create_course(); 719 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 720 $timeopen = time(); 721 $timeclose = $timeopen + DAYSECS; 722 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 723 $feedback->timeopen = $timeopen; 724 $feedback->timeclose = $timeclose; 725 $DB->update_record('feedback', $feedback); 726 727 $event = new \calendar_event([ 728 'name' => 'Test event', 729 'description' => '', 730 'format' => 1, 731 'courseid' => $course->id, 732 'groupid' => 0, 733 'userid' => 2, 734 'modulename' => 'feedback', 735 'instance' => $feedback->id, 736 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, 737 'timestart' => $timeopen, 738 'timeduration' => 86400, 739 'visible' => 1 740 ]); 741 742 list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $feedback); 743 $this->assertNull($min); 744 $this->assertEquals($timeclose, $max[0]); 745 $this->assertNotEmpty($max[1]); 746 } 747 748 /** 749 * A FEEDBACK_EVENT_TYPE_OPEN should not have a max timestamp if the activity 750 * doesn't have a close date. 751 */ 752 public function test_get_valid_event_timestart_range_event_type_open_no_close() { 753 global $CFG, $DB; 754 require_once($CFG->dirroot . "/calendar/lib.php"); 755 756 $this->resetAfterTest(true); 757 $this->setAdminUser(); 758 $generator = $this->getDataGenerator(); 759 $course = $generator->create_course(); 760 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 761 $timeopen = time(); 762 $timeclose = $timeopen + DAYSECS; 763 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 764 $feedback->timeopen = $timeopen; 765 $feedback->timeclose = 0; 766 $DB->update_record('feedback', $feedback); 767 768 $event = new \calendar_event([ 769 'name' => 'Test event', 770 'description' => '', 771 'format' => 1, 772 'courseid' => $course->id, 773 'groupid' => 0, 774 'userid' => 2, 775 'modulename' => 'feedback', 776 'instance' => $feedback->id, 777 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, 778 'timestart' => $timeopen, 779 'timeduration' => 86400, 780 'visible' => 1 781 ]); 782 783 list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $feedback); 784 $this->assertNull($min); 785 $this->assertNull($max); 786 } 787 788 /** 789 * A FEEDBACK_EVENT_TYPE_CLOSE should have a min timestart equal to the activity 790 * open time. 791 */ 792 public function test_get_valid_event_timestart_range_event_type_close() { 793 global $CFG, $DB; 794 require_once($CFG->dirroot . "/calendar/lib.php"); 795 796 $this->resetAfterTest(true); 797 $this->setAdminUser(); 798 $generator = $this->getDataGenerator(); 799 $course = $generator->create_course(); 800 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 801 $timeopen = time(); 802 $timeclose = $timeopen + DAYSECS; 803 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 804 $feedback->timeopen = $timeopen; 805 $feedback->timeclose = $timeclose; 806 $DB->update_record('feedback', $feedback); 807 808 $event = new \calendar_event([ 809 'name' => 'Test event', 810 'description' => '', 811 'format' => 1, 812 'courseid' => $course->id, 813 'groupid' => 0, 814 'userid' => 2, 815 'modulename' => 'feedback', 816 'instance' => $feedback->id, 817 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, 818 'timestart' => $timeopen, 819 'timeduration' => 86400, 820 'visible' => 1 821 ]); 822 823 list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $feedback); 824 $this->assertEquals($timeopen, $min[0]); 825 $this->assertNotEmpty($min[1]); 826 $this->assertNull($max); 827 } 828 829 /** 830 * A FEEDBACK_EVENT_TYPE_CLOSE should not have a minimum timestamp if the activity 831 * doesn't have an open date. 832 */ 833 public function test_get_valid_event_timestart_range_event_type_close_no_open() { 834 global $CFG, $DB; 835 require_once($CFG->dirroot . "/calendar/lib.php"); 836 837 $this->resetAfterTest(true); 838 $this->setAdminUser(); 839 $generator = $this->getDataGenerator(); 840 $course = $generator->create_course(); 841 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 842 $timeopen = time(); 843 $timeclose = $timeopen + DAYSECS; 844 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 845 $feedback->timeopen = 0; 846 $feedback->timeclose = $timeclose; 847 $DB->update_record('feedback', $feedback); 848 849 $event = new \calendar_event([ 850 'name' => 'Test event', 851 'description' => '', 852 'format' => 1, 853 'courseid' => $course->id, 854 'groupid' => 0, 855 'userid' => 2, 856 'modulename' => 'feedback', 857 'instance' => $feedback->id, 858 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, 859 'timestart' => $timeopen, 860 'timeduration' => 86400, 861 'visible' => 1 862 ]); 863 864 list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $feedback); 865 $this->assertNull($min); 866 $this->assertNull($max); 867 } 868 869 /** 870 * An unkown event type should not change the feedback instance. 871 */ 872 public function test_mod_feedback_core_calendar_event_timestart_updated_unknown_event() { 873 global $CFG, $DB; 874 require_once($CFG->dirroot . "/calendar/lib.php"); 875 876 $this->resetAfterTest(true); 877 $this->setAdminUser(); 878 $generator = $this->getDataGenerator(); 879 $course = $generator->create_course(); 880 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 881 $timeopen = time(); 882 $timeclose = $timeopen + DAYSECS; 883 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 884 $feedback->timeopen = $timeopen; 885 $feedback->timeclose = $timeclose; 886 $DB->update_record('feedback', $feedback); 887 888 // Create a valid event. 889 $event = new \calendar_event([ 890 'name' => 'Test event', 891 'description' => '', 892 'format' => 1, 893 'courseid' => $course->id, 894 'groupid' => 0, 895 'userid' => 2, 896 'modulename' => 'feedback', 897 'instance' => $feedback->id, 898 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN . "SOMETHING ELSE", 899 'timestart' => 1, 900 'timeduration' => 86400, 901 'visible' => 1 902 ]); 903 904 mod_feedback_core_calendar_event_timestart_updated($event, $feedback); 905 906 $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); 907 $this->assertEquals($timeopen, $feedback->timeopen); 908 $this->assertEquals($timeclose, $feedback->timeclose); 909 } 910 911 /** 912 * A FEEDBACK_EVENT_TYPE_OPEN event should update the timeopen property of 913 * the feedback activity. 914 */ 915 public function test_mod_feedback_core_calendar_event_timestart_updated_open_event() { 916 global $CFG, $DB; 917 require_once($CFG->dirroot . "/calendar/lib.php"); 918 919 $this->resetAfterTest(true); 920 $this->setAdminUser(); 921 $generator = $this->getDataGenerator(); 922 $course = $generator->create_course(); 923 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 924 $timeopen = time(); 925 $timeclose = $timeopen + DAYSECS; 926 $timemodified = 1; 927 $newtimeopen = $timeopen - DAYSECS; 928 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 929 $feedback->timeopen = $timeopen; 930 $feedback->timeclose = $timeclose; 931 $feedback->timemodified = $timemodified; 932 $DB->update_record('feedback', $feedback); 933 934 // Create a valid event. 935 $event = new \calendar_event([ 936 'name' => 'Test event', 937 'description' => '', 938 'format' => 1, 939 'courseid' => $course->id, 940 'groupid' => 0, 941 'userid' => 2, 942 'modulename' => 'feedback', 943 'instance' => $feedback->id, 944 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, 945 'timestart' => $newtimeopen, 946 'timeduration' => 86400, 947 'visible' => 1 948 ]); 949 950 mod_feedback_core_calendar_event_timestart_updated($event, $feedback); 951 952 $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); 953 // Ensure the timeopen property matches the event timestart. 954 $this->assertEquals($newtimeopen, $feedback->timeopen); 955 // Ensure the timeclose isn't changed. 956 $this->assertEquals($timeclose, $feedback->timeclose); 957 // Ensure the timemodified property has been changed. 958 $this->assertNotEquals($timemodified, $feedback->timemodified); 959 } 960 961 /** 962 * A FEEDBACK_EVENT_TYPE_CLOSE event should update the timeclose property of 963 * the feedback activity. 964 */ 965 public function test_mod_feedback_core_calendar_event_timestart_updated_close_event() { 966 global $CFG, $DB; 967 require_once($CFG->dirroot . "/calendar/lib.php"); 968 969 $this->resetAfterTest(true); 970 $this->setAdminUser(); 971 $generator = $this->getDataGenerator(); 972 $course = $generator->create_course(); 973 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 974 $timeopen = time(); 975 $timeclose = $timeopen + DAYSECS; 976 $timemodified = 1; 977 $newtimeclose = $timeclose + DAYSECS; 978 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 979 $feedback->timeopen = $timeopen; 980 $feedback->timeclose = $timeclose; 981 $feedback->timemodified = $timemodified; 982 $DB->update_record('feedback', $feedback); 983 984 // Create a valid event. 985 $event = new \calendar_event([ 986 'name' => 'Test event', 987 'description' => '', 988 'format' => 1, 989 'courseid' => $course->id, 990 'groupid' => 0, 991 'userid' => 2, 992 'modulename' => 'feedback', 993 'instance' => $feedback->id, 994 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, 995 'timestart' => $newtimeclose, 996 'timeduration' => 86400, 997 'visible' => 1 998 ]); 999 1000 mod_feedback_core_calendar_event_timestart_updated($event, $feedback); 1001 1002 $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); 1003 // Ensure the timeclose property matches the event timestart. 1004 $this->assertEquals($newtimeclose, $feedback->timeclose); 1005 // Ensure the timeopen isn't changed. 1006 $this->assertEquals($timeopen, $feedback->timeopen); 1007 // Ensure the timemodified property has been changed. 1008 $this->assertNotEquals($timemodified, $feedback->timemodified); 1009 } 1010 1011 /** 1012 * If a student somehow finds a way to update the calendar event 1013 * then the callback should not be executed to update the activity 1014 * properties as well because that would be a security issue. 1015 */ 1016 public function test_student_role_cant_update_time_close_event() { 1017 global $CFG, $DB; 1018 require_once($CFG->dirroot . '/calendar/lib.php'); 1019 1020 $this->resetAfterTest(); 1021 $this->setAdminUser(); 1022 1023 $generator = $this->getDataGenerator(); 1024 $user = $generator->create_user(); 1025 $course = $generator->create_course(); 1026 $context = context_course::instance($course->id); 1027 $roleid = $generator->create_role(); 1028 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 1029 $timeopen = time(); 1030 $timeclose = $timeopen + DAYSECS; 1031 $timemodified = 1; 1032 $newtimeclose = $timeclose + DAYSECS; 1033 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 1034 $feedback->timeopen = $timeopen; 1035 $feedback->timeclose = $timeclose; 1036 $feedback->timemodified = $timemodified; 1037 $DB->update_record('feedback', $feedback); 1038 1039 $generator->enrol_user($user->id, $course->id, 'student'); 1040 $generator->role_assign($roleid, $user->id, $context->id); 1041 1042 // Create a valid event. 1043 $event = new \calendar_event([ 1044 'name' => 'Test event', 1045 'description' => '', 1046 'format' => 1, 1047 'courseid' => $course->id, 1048 'groupid' => 0, 1049 'userid' => $user->id, 1050 'modulename' => 'feedback', 1051 'instance' => $feedback->id, 1052 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, 1053 'timestart' => $newtimeclose, 1054 'timeduration' => 86400, 1055 'visible' => 1 1056 ]); 1057 1058 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); 1059 assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true); 1060 1061 $this->setUser($user); 1062 1063 mod_feedback_core_calendar_event_timestart_updated($event, $feedback); 1064 1065 $newfeedback = $DB->get_record('feedback', ['id' => $feedback->id]); 1066 // The activity shouldn't have been updated because the user 1067 // doesn't have permissions to do it. 1068 $this->assertEquals($timeclose, $newfeedback->timeclose); 1069 } 1070 1071 /** 1072 * The activity should update if a teacher modifies the calendar 1073 * event. 1074 */ 1075 public function test_teacher_role_can_update_time_close_event() { 1076 global $CFG, $DB; 1077 require_once($CFG->dirroot . '/calendar/lib.php'); 1078 1079 $this->resetAfterTest(); 1080 $this->setAdminUser(); 1081 1082 $generator = $this->getDataGenerator(); 1083 $user = $generator->create_user(); 1084 $course = $generator->create_course(); 1085 $context = context_course::instance($course->id); 1086 $roleid = $generator->create_role(); 1087 $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); 1088 $timeopen = time(); 1089 $timeclose = $timeopen + DAYSECS; 1090 $timemodified = 1; 1091 $newtimeclose = $timeclose + DAYSECS; 1092 $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); 1093 $feedback->timeopen = $timeopen; 1094 $feedback->timeclose = $timeclose; 1095 $feedback->timemodified = $timemodified; 1096 $DB->update_record('feedback', $feedback); 1097 1098 $generator->enrol_user($user->id, $course->id, 'teacher'); 1099 $generator->role_assign($roleid, $user->id, $context->id); 1100 1101 // Create a valid event. 1102 $event = new \calendar_event([ 1103 'name' => 'Test event', 1104 'description' => '', 1105 'format' => 1, 1106 'courseid' => $course->id, 1107 'groupid' => 0, 1108 'userid' => $user->id, 1109 'modulename' => 'feedback', 1110 'instance' => $feedback->id, 1111 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, 1112 'timestart' => $newtimeclose, 1113 'timeduration' => 86400, 1114 'visible' => 1 1115 ]); 1116 1117 assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); 1118 assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true); 1119 1120 $this->setUser($user); 1121 1122 $sink = $this->redirectEvents(); 1123 1124 mod_feedback_core_calendar_event_timestart_updated($event, $feedback); 1125 1126 $triggeredevents = $sink->get_events(); 1127 $moduleupdatedevents = array_filter($triggeredevents, function($e) { 1128 return is_a($e, 'core\event\course_module_updated'); 1129 }); 1130 1131 $newfeedback = $DB->get_record('feedback', ['id' => $feedback->id]); 1132 // The activity should have been updated because the user 1133 // has permissions to do it. 1134 $this->assertEquals($newtimeclose, $newfeedback->timeclose); 1135 // A course_module_updated event should be fired if the module 1136 // was successfully modified. 1137 $this->assertNotEmpty($moduleupdatedevents); 1138 } 1139 1140 /** 1141 * A user who does not have capabilities to add events to the calendar should be able to create an feedback. 1142 */ 1143 public function test_creation_with_no_calendar_capabilities() { 1144 $this->resetAfterTest(); 1145 $course = self::getDataGenerator()->create_course(); 1146 $context = context_course::instance($course->id); 1147 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1148 $roleid = self::getDataGenerator()->create_role(); 1149 self::getDataGenerator()->role_assign($roleid, $user->id, $context->id); 1150 assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); 1151 $generator = self::getDataGenerator()->get_plugin_generator('mod_feedback'); 1152 // Create an instance as a user without the calendar capabilities. 1153 $this->setUser($user); 1154 $time = time(); 1155 $params = array( 1156 'course' => $course->id, 1157 'timeopen' => $time + 200, 1158 'timeclose' => $time + 2000, 1159 ); 1160 $generator->create_instance($params); 1161 } 1162} 1163