From f2c3818b3943888db1c8fb0995612726e24fe057 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Sun, 24 Sep 2017 03:31:04 +0000 Subject: [PATCH 1/2] MDL-60063 feedback: show feedback events on calendar for teacher --- mod/feedback/lib.php | 21 +++++------------- mod/feedback/tests/lib_test.php | 38 +++++---------------------------- 2 files changed, 10 insertions(+), 49 deletions(-) diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index 0946089fbd3..639cf9222a2 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -3440,22 +3440,6 @@ function feedback_check_updates_since(cm_info $cm, $from, $filter = array()) { return $updates; } -/** - * The event is only visible anywhere if the user can submit feedback. - * - * @param calendar_event $event - * @return bool Returns true if the event is visible to the current user, false otherwise. - */ -function mod_feedback_core_calendar_is_event_visible(calendar_event $event) { - global $DB; - - $cm = get_fast_modinfo($event->courseid)->instances['feedback'][$event->instance]; - $feedbackcompletion = new mod_feedback_completion(null, $cm, 0); - - // The event is only visible if the user can submit it. - return $feedbackcompletion->can_complete(); -} - /** * This function receives a calendar event and returns the action associated with it, or null if there is none. * @@ -3477,6 +3461,11 @@ function mod_feedback_core_calendar_provide_event_action(calendar_event $event, return null; } + if (!$feedbackcompletion->can_complete()) { + // The user can't complete the feedback so there is no action for them. + return null; + } + // The feedback is actionable if it does not have timeopen or timeopen is in the past. $actionable = $feedbackcompletion->is_open(); diff --git a/mod/feedback/tests/lib_test.php b/mod/feedback/tests/lib_test.php index 99e024f61e6..22c01e3a0b5 100644 --- a/mod/feedback/tests/lib_test.php +++ b/mod/feedback/tests/lib_test.php @@ -260,9 +260,9 @@ class mod_feedback_lib_testcase extends advanced_testcase { } /** - * A user that cannot submit the feedback should not see the event. + * A user that can not submit feedback should not have an action. */ - public function test_feedback_core_calendar_is_event_visible_can_not_submit() { + public function test_feedback_core_calendar_provide_event_action_can_not_submit() { global $DB; $this->resetAfterTest(); @@ -278,41 +278,13 @@ class mod_feedback_lib_testcase extends advanced_testcase { $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); $this->setUser($user); - assign_capability('mod/feedback:complete', CAP_PROHIBIT, $studentrole->id, $context); $context->mark_dirty(); - $visible = mod_feedback_core_calendar_is_event_visible($event); + $factory = new \core_calendar\action_factory(); + $action = mod_feedback_core_calendar_provide_event_action($event, $factory); - $this->assertFalse($visible); - } - - /** - * A user that can submit the feedback should see the event. - */ - public function test_feedback_core_calendar_is_event_visible_can_submit() { - global $DB; - - $this->resetAfterTest(); - $this->setAdminUser(); - - $user = $this->getDataGenerator()->create_user(); - $studentrole = $DB->get_record('role', array('shortname' => 'student')); - $course = $this->getDataGenerator()->create_course(); - $feedback = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); - $event = $this->create_action_event($course->id, $feedback->id, FEEDBACK_EVENT_TYPE_OPEN); - $cm = get_coursemodule_from_instance('feedback', $feedback->id); - $context = context_module::instance($cm->id); - $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); - - $this->setUser($user); - - assign_capability('mod/feedback:complete', CAP_ALLOW, $studentrole->id, $context->id); - $context->mark_dirty(); - - $visible = mod_feedback_core_calendar_is_event_visible($event); - - $this->assertTrue($visible); + $this->assertNull($action); } /** From 83d4635d99448e7f07c37885b33b4905a992ad34 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Sun, 24 Sep 2017 04:26:33 +0000 Subject: [PATCH 2/2] MDL-60063 feedback: add drag and drop of open close events --- mod/feedback/lang/en/feedback.php | 1 + mod/feedback/lib.php | 150 +++++++ mod/feedback/tests/lib_test.php | 660 ++++++++++++++++++++++++++++++ 3 files changed, 811 insertions(+) diff --git a/mod/feedback/lang/en/feedback.php b/mod/feedback/lang/en/feedback.php index cd6de5afbc7..34bfabd72dd 100644 --- a/mod/feedback/lang/en/feedback.php +++ b/mod/feedback/lang/en/feedback.php @@ -207,6 +207,7 @@ $string['of'] = 'of'; $string['oldvaluespreserved'] = 'All old questions and the assigned values will be preserved'; $string['oldvalueswillbedeleted'] = 'Current questions and all responses will be deleted.'; $string['only_one_captcha_allowed'] = 'Only one captcha is allowed in a feedback'; +$string['openafterclose'] = 'You have specified an open date after the close date'; $string['overview'] = 'Overview'; $string['page'] = 'Page'; $string['page-mod-feedback-x'] = 'Any feedback module page'; diff --git a/mod/feedback/lib.php b/mod/feedback/lib.php index 639cf9222a2..c87bbca0fb9 100644 --- a/mod/feedback/lib.php +++ b/mod/feedback/lib.php @@ -3556,3 +3556,153 @@ function mod_feedback_get_completion_active_rule_descriptions($cm) { } return $descriptions; } + +/** + * This function calculates the minimum and maximum cutoff values for the timestart of + * the given event. + * + * It will return an array with two values, the first being the minimum cutoff value and + * the second being the maximum cutoff value. Either or both values can be null, which + * indicates there is no minimum or maximum, respectively. + * + * If a cutoff is required then the function must return an array containing the cutoff + * timestamp and error string to display to the user if the cutoff value is violated. + * + * A minimum and maximum cutoff return value will look like: + * [ + * [1505704373, 'The due date must be after the sbumission start date'], + * [1506741172, 'The due date must be before the cutoff date'] + * ] + * + * @param calendar_event $event The calendar event to get the time range for + * @param stdClass|null $instance The module instance to get the range from + * @return array + */ +function mod_feedback_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance = null) { + global $DB; + + if (!$instance) { + $instance = $DB->get_record('feedback', ['id' => $event->instance], '*', MUST_EXIST); + } + + $mindate = null; + $maxdate = null; + + if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) { + // The start time of the open event can't be equal to or after the + // close time of the choice activity. + if (!empty($instance->timeclose)) { + $maxdate = [ + $instance->timeclose, + get_string('openafterclose', 'feedback') + ]; + } + } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) { + // The start time of the close event can't be equal to or earlier than the + // open time of the choice activity. + if (!empty($instance->timeopen)) { + $mindate = [ + $instance->timeopen, + get_string('closebeforeopen', 'feedback') + ]; + } + } + + return [$mindate, $maxdate]; +} + +/** + * This function will check that the given event is valid for it's + * corresponding feedback module. + * + * An exception is thrown if the event fails validation. + * + * @throws \moodle_exception + * @param \calendar_event $event + */ +function mod_feedback_core_calendar_validate_event_timestart(\calendar_event $event) { + global $DB; + + $record = $DB->get_record('feedback', ['id' => $event->instance], '*', MUST_EXIST); + $timestart = $event->timestart; + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event, $record); + + if ($min && $timestart < $min[0]) { + throw new \moodle_exception($min[1]); + } + + if ($max && $timestart > $max[0]) { + throw new \moodle_exception($max[1]); + } +} + +/** + * This function will update the feedback module according to the + * event that has been modified. + * + * It will set the timeopen or timeclose value of the feedback instance + * according to the type of event provided. + * + * @throws \moodle_exception + * @param \calendar_event $event + */ +function mod_feedback_core_calendar_event_timestart_updated(\calendar_event $event) { + global $CFG, $DB; + + if (empty($event->instance) || $event->modulename != 'feedback') { + return; + } + + $coursemodule = get_coursemodule_from_instance('feedback', + $event->instance, + $event->courseid, + false, + MUST_EXIST); + + if (empty($coursemodule)) { + // If we don't have a course module yet then it likely means + // the activity is still being set up. In this case there is + // nothing for us to do anyway. + return; + } + + $context = context_module::instance($coursemodule->id); + + // The user does not have the capability to modify this activity. + if (!has_capability('moodle/course:manageactivities', $context)) { + return; + } + + $modified = false; + + if ($event->eventtype == FEEDBACK_EVENT_TYPE_OPEN) { + // If the event is for the feedback activity opening then we should + // set the start time of the feedback activity to be the new start + // time of the event. + $record = $DB->get_record('feedback', ['id' => $event->instance], '*', MUST_EXIST); + + if ($record->timeopen != $event->timestart) { + $record->timeopen = $event->timestart; + $record->timemodified = time(); + $modified = true; + } + } else if ($event->eventtype == FEEDBACK_EVENT_TYPE_CLOSE) { + // If the event is for the feedback activity closing then we should + // set the end time of the feedback activity to be the new start + // time of the event. + $record = $DB->get_record('feedback', ['id' => $event->instance], '*', MUST_EXIST); + + if ($record->timeclose != $event->timestart) { + $record->timeclose = $event->timestart; + $record->timemodified = time(); + $modified = true; + } + } + + if ($modified) { + $DB->update_record('feedback', $record); + $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); + $event->trigger(); + } +} diff --git a/mod/feedback/tests/lib_test.php b/mod/feedback/tests/lib_test.php index 22c01e3a0b5..b0b7c84a75c 100644 --- a/mod/feedback/tests/lib_test.php +++ b/mod/feedback/tests/lib_test.php @@ -380,4 +380,664 @@ class mod_feedback_lib_testcase extends advanced_testcase { $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); $this->assertEquals(mod_feedback_get_completion_active_rule_descriptions(new stdClass()), []); } + + /** + * An unknown event should not have min or max restrictions. + */ + public function test_get_valid_event_timestart_range_unknown_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => 'SOME UNKNOWN EVENT', + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event); + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * A FEEDBACK_EVENT_TYPE_OPEN should have a max timestart equal to the activity + * close time. + */ + public function test_get_valid_event_timestart_range_event_type_open() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event); + $this->assertNull($min); + $this->assertEquals($timeclose, $max[0]); + $this->assertNotEmpty($max[1]); + } + + /** + * A FEEDBACK_EVENT_TYPE_OPEN should not have a max timestamp if the activity + * doesn't have a close date. + */ + public function test_get_valid_event_timestart_range_event_type_open_no_close() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = 0; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event); + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * A FEEDBACK_EVENT_TYPE_CLOSE should have a min timestart equal to the activity + * open time. + */ + public function test_get_valid_event_timestart_range_event_type_close() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event); + $this->assertEquals($timeopen, $min[0]); + $this->assertNotEmpty($min[1]); + $this->assertNull($max); + } + + /** + * A FEEDBACK_EVENT_TYPE_CLOSE should not have a minimum timestamp if the activity + * doesn't have an open date. + */ + public function test_get_valid_event_timestart_range_event_type_close_no_open() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = 0; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + list($min, $max) = mod_feedback_core_calendar_get_valid_event_timestart_range($event); + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * You can't create a feedback module event when the module doesn't exist. + */ + public function test_mod_feedback_core_calendar_validate_event_timestart_no_activity() { + global $CFG; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => 1234, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => time(), + 'timeduration' => 86400, + 'visible' => 1 + ]); + + $this->expectException('moodle_exception'); + mod_feedback_core_calendar_validate_event_timestart($event); + } + + /** + * A FEEDBACK_EVENT_TYPE_OPEN must be before the close time of the feedback activity. + */ + public function test_mod_feedback_core_calendar_validate_event_timestart_valid_open_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => $timeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // This will throw an exception if the event is invald. + mod_feedback_core_calendar_validate_event_timestart($event); + $this->assertTrue(true); + } + + /** + * A FEEDBACK_EVENT_TYPE_OPEN can not have a start time set after the close time + * of the feedback activity. + */ + public function test_mod_feedback_core_calendar_validate_event_timestart_invalid_open_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => $timeclose + 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + $this->expectException('moodle_exception'); + mod_feedback_core_calendar_validate_event_timestart($event); + } + + /** + * A FEEDBACK_EVENT_TYPE_CLOSE must be after the open time of the feedback activity. + */ + public function test_mod_feedback_core_calendar_validate_event_timestart_valid_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $timeclose, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + // This will throw an exception if the event is invald. + mod_feedback_core_calendar_validate_event_timestart($event); + $this->assertTrue(true); + } + + /** + * A FEEDBACK_EVENT_TYPE_CLOSE can not have a start time set before the open time + * of the feedback activity. + */ + public function test_mod_feedback_core_calendar_validate_event_timestart_invalid_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $timeopen - 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + $this->expectException('moodle_exception'); + mod_feedback_core_calendar_validate_event_timestart($event); + } + + /** + * An unkown event type should not change the feedback instance. + */ + public function test_mod_feedback_core_calendar_event_timestart_updated_unknown_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $DB->update_record('feedback', $feedback); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN . "SOMETHING ELSE", + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + mod_feedback_core_calendar_event_timestart_updated($event); + + $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); + $this->assertEquals($timeopen, $feedback->timeopen); + $this->assertEquals($timeclose, $feedback->timeclose); + } + + /** + * A FEEDBACK_EVENT_TYPE_OPEN event should update the timeopen property of + * the feedback activity. + */ + public function test_mod_feedback_core_calendar_event_timestart_updated_open_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeopen = $timeopen - DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $feedback->timemodified = $timemodified; + $DB->update_record('feedback', $feedback); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_OPEN, + 'timestart' => $newtimeopen, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + mod_feedback_core_calendar_event_timestart_updated($event); + + $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); + // Ensure the timeopen property matches the event timestart. + $this->assertEquals($newtimeopen, $feedback->timeopen); + // Ensure the timeclose isn't changed. + $this->assertEquals($timeclose, $feedback->timeclose); + // Ensure the timemodified property has been changed. + $this->assertNotEquals($timemodified, $feedback->timemodified); + } + + /** + * A FEEDBACK_EVENT_TYPE_CLOSE event should update the timeclose property of + * the feedback activity. + */ + public function test_mod_feedback_core_calendar_event_timestart_updated_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeclose = $timeclose + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $feedback->timemodified = $timemodified; + $DB->update_record('feedback', $feedback); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $newtimeclose, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + mod_feedback_core_calendar_event_timestart_updated($event); + + $feedback = $DB->get_record('feedback', ['id' => $feedback->id]); + // Ensure the timeclose property matches the event timestart. + $this->assertEquals($newtimeclose, $feedback->timeclose); + // Ensure the timeopen isn't changed. + $this->assertEquals($timeopen, $feedback->timeopen); + // Ensure the timemodified property has been changed. + $this->assertNotEquals($timemodified, $feedback->timemodified); + } + + /** + * If a student somehow finds a way to update the calendar event + * then the callback should not be executed to update the activity + * properties as well because that would be a security issue. + */ + public function test_student_role_cant_update_time_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . '/calendar/lib.php'); + + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $roleid = $generator->create_role(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeclose = $timeclose + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $feedback->timemodified = $timemodified; + $DB->update_record('feedback', $feedback); + + $generator->enrol_user($user->id, $course->id, 'student'); + $generator->role_assign($roleid, $user->id, $context->id); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => $user->id, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $newtimeclose, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true); + + $this->setUser($user); + + mod_feedback_core_calendar_event_timestart_updated($event); + + $newfeedback = $DB->get_record('feedback', ['id' => $feedback->id]); + // The activity shouldn't have been updated because the user + // doesn't have permissions to do it. + $this->assertEquals($timeclose, $newfeedback->timeclose); + } + + /** + * The activity should update if a teacher modifies the calendar + * event. + */ + public function test_teacher_role_can_update_time_close_event() { + global $CFG, $DB; + require_once($CFG->dirroot . '/calendar/lib.php'); + + $this->resetAfterTest(); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $roleid = $generator->create_role(); + $feedbackgenerator = $generator->get_plugin_generator('mod_feedback'); + $timeopen = time(); + $timeclose = $timeopen + DAYSECS; + $timemodified = 1; + $newtimeclose = $timeclose + DAYSECS; + $feedback = $feedbackgenerator->create_instance(['course' => $course->id]); + $feedback->timeopen = $timeopen; + $feedback->timeclose = $timeclose; + $feedback->timemodified = $timemodified; + $DB->update_record('feedback', $feedback); + + $generator->enrol_user($user->id, $course->id, 'teacher'); + $generator->role_assign($roleid, $user->id, $context->id); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => $user->id, + 'modulename' => 'feedback', + 'instance' => $feedback->id, + 'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE, + 'timestart' => $newtimeclose, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true); + + $this->setUser($user); + + $sink = $this->redirectEvents(); + + mod_feedback_core_calendar_event_timestart_updated($event); + + $triggeredevents = $sink->get_events(); + $moduleupdatedevents = array_filter($triggeredevents, function($e) { + return is_a($e, 'core\event\course_module_updated'); + }); + + $newfeedback = $DB->get_record('feedback', ['id' => $feedback->id]); + // The activity should have been updated because the user + // has permissions to do it. + $this->assertEquals($newtimeclose, $newfeedback->timeclose); + // A course_module_updated event should be fired if the module + // was successfully modified. + $this->assertNotEmpty($moduleupdatedevents); + } }