From 4dee91aabfde0d4dbb929f0048d44dcfb7a72181 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Mon, 6 Aug 2018 16:35:45 +1000 Subject: [PATCH 1/4] MDL-62960 calendar: Added range check callback for course events Added core_calendar_get_valid_event_timestart_range callback for course events --- calendar/classes/local/api.php | 34 ++++++++++++++++++++++------------ calendar/upgrade.txt | 4 ++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/calendar/classes/local/api.php b/calendar/classes/local/api.php index 6da620e3bda..ac0f613b392 100644 --- a/calendar/classes/local/api.php +++ b/calendar/classes/local/api.php @@ -242,6 +242,7 @@ class api { $startdate->format('n'), $startdate->format('j') ); + $starttimestamp = $starttime->getTimestamp(); if ($hascoursemodule) { $moduleinstance = $DB->get_record( @@ -250,7 +251,6 @@ class api { '*', MUST_EXIST ); - $legacyevent->timestart = $starttime->getTimestamp(); // If there is a timestart range callback implemented then we can // use the values returned from the valid timestart range to apply @@ -262,20 +262,30 @@ class api { [$legacyevent, $moduleinstance], [false, false] ); + } else if ($legacyevent->courseid != 0 && $legacyevent->courseid != SITEID && $legacyevent->groupid == 0) { + // This is a course event. + list($min, $max) = component_callback( + 'core_course', + 'core_calendar_get_valid_event_timestart_range', + [$legacyevent, $event->get_course()->get_proxied_instance()], + [0, 0] + ); + } else { + $min = $max = 0; + } - // If the callback returns false for either value it means that - // there is no valid time start range. - if ($min === false || $max === false) { - throw new \moodle_exception('The start day of this event can not be modified'); - } + // If the callback returns false for either value it means that + // there is no valid time start range. + if ($min === false || $max === false) { + throw new \moodle_exception('The start day of this event can not be modified'); + } - if ($min && $legacyevent->timestart < $min[0]) { - throw new \moodle_exception($min[1]); - } + if ($min && $starttimestamp < $min[0]) { + throw new \moodle_exception($min[1]); + } - if ($max && $legacyevent->timestart > $max[0]) { - throw new \moodle_exception($max[1]); - } + if ($max && $starttimestamp > $max[0]) { + throw new \moodle_exception($max[1]); } // This function does our capability checks. diff --git a/calendar/upgrade.txt b/calendar/upgrade.txt index 46065bacffc..89050568e45 100644 --- a/calendar/upgrade.txt +++ b/calendar/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in /calendar/* , information provided here is intended especially for developers. +=== 3.5.3 === +* added core_calendar_get_valid_event_timestart_range callback for course events when the update_event_start_day function is used + in the local api. + === 3.5.2 === * calendar_get_default_courses() function now has optional $userid parameter. * calendar_set_filters() function now has optional $user parameter. From 81e11e98f7ba8006877ea16ba50c46b517f49856 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Mon, 6 Aug 2018 16:42:10 +1000 Subject: [PATCH 2/4] MDL-62960 calendar: Implementing course events validity check callback --- course/lib.php | 35 +++++++++++++++++++++++ course/tests/courselib_test.php | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/course/lib.php b/course/lib.php index cc6a75d0f58..f5f1a8c6bc5 100644 --- a/course/lib.php +++ b/course/lib.php @@ -3756,6 +3756,41 @@ function core_course_inplace_editable($itemtype, $itemid, $newvalue) { } } +/** + * 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 date must be after this date'], + * [1506741172, 'The date must be before this date'] + * ] + * + * @param calendar_event $event The calendar event to get the time range for + * @param stdClass $course The course object to get the range from + * @return array Returns an array with min and max date. + */ +function core_course_core_calendar_get_valid_event_timestart_range(\calendar_event $event, $course) { + $mindate = null; + $maxdate = null; + + if ($course->startdate) { + $mindate = [ + $course->startdate, + get_string('errorbeforecoursestart', 'calendar') + ]; + } + + return [$mindate, $maxdate]; +} + /** * Returns course modules tagged with a specified tag ready for output on tag/index.php page * diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index ce1850485f7..a2c01510cfe 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -4182,4 +4182,54 @@ class core_course_courselib_testcase extends advanced_testcase { assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context); $this->assertFalse(can_download_from_backup_filearea('testing', $context, $user)); } + + /** + * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has no end date. + */ + public function test_core_course_core_calendar_get_valid_event_timestart_range_no_enddate() { + global $CFG; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $now = time(); + $course = $generator->create_course(['startdate' => $now - 86400]); + + // Create a course event. + $event = new \calendar_event([ + 'name' => 'Test course event', + 'eventtype' => 'course', + 'courseid' => $course->id, + ]); + + list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course); + $this->assertEquals($course->startdate, $min[0]); + $this->assertNull($max); + } + + /** + * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has end date. + */ + public function test_core_course_core_calendar_get_valid_event_timestart_range_with_enddate() { + global $CFG; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $now = time(); + $course = $generator->create_course(['startdate' => $now - 86400, 'enddate' => $now + 86400]); + + // Create a course event. + $event = new \calendar_event([ + 'name' => 'Test course event', + 'eventtype' => 'course', + 'courseid' => $course->id, + ]); + + list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course); + $this->assertEquals($course->startdate, $min[0]); + $this->assertNull($max); + } } From 86eff202713e4ce1572fca38262fdf514d34189c Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Mon, 6 Aug 2018 17:35:04 +1000 Subject: [PATCH 3/4] MDL-62960 calendar: Rename get_module_timestamp_min/max_limit methods The methods get_module_timestamp_min_limit() and get_module_timestamp_max_limit() are doing generic stuff and can be used for other calendar event types as well. I have renamed them to get_timestamp_min_limit() and get_timestamp_max_limit() respectively. --- .../external/calendar_event_exporter.php | 40 +++++++++++++++---- .../tests/calendar_event_exporter_test.php | 22 +++++----- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/calendar/classes/external/calendar_event_exporter.php b/calendar/classes/external/calendar_event_exporter.php index bd1d7515be8..1764d12271d 100644 --- a/calendar/classes/external/calendar_event_exporter.php +++ b/calendar/classes/external/calendar_event_exporter.php @@ -240,11 +240,11 @@ class calendar_event_exporter extends event_exporter_base { } if ($min) { - $values = array_merge($values, $this->get_module_timestamp_min_limit($starttime, $min)); + $values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min)); } if ($max) { - $values = array_merge($values, $this->get_module_timestamp_max_limit($starttime, $max)); + $values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max)); } return $values; @@ -252,12 +252,13 @@ class calendar_event_exporter extends event_exporter_base { /** * Get the correct minimum midnight day limit based on the event start time - * and the module's minimum timestamp limit. + * and the minimum timestamp limit of what the event belongs to. * * @param DateTimeInterface $starttime The event start time * @param array $min The module's minimum limit for the event + * @return array Returns an array with mindaytimestamp and mindayerror keys. */ - protected function get_module_timestamp_min_limit(\DateTimeInterface $starttime, $min) { + protected function get_timestamp_min_limit(\DateTimeInterface $starttime, $min) { // We need to check that the minimum valid time is earlier in the // day than the current event time so that if the user drags and drops // the event to this day (which changes the date but not the time) it @@ -295,15 +296,16 @@ class calendar_event_exporter extends event_exporter_base { /** * Get the correct maximum midnight day limit based on the event start time - * and the module's maximum timestamp limit. + * and the maximum timestamp limit of what the event belongs to. * * @param DateTimeInterface $starttime The event start time * @param array $max The module's maximum limit for the event + * @return array Returns an array with maxdaytimestamp and maxdayerror keys. */ - protected function get_module_timestamp_max_limit(\DateTimeInterface $starttime, $max) { + protected function get_timestamp_max_limit(\DateTimeInterface $starttime, $max) { // We're doing a similar calculation here as we are for the minimum // day timestamp. See the explanation above. - $values; + $values = []; $timestamp = $max[0]; $errorstring = $max[1]; $maxdate = (new \DateTimeImmutable())->setTimestamp($timestamp); @@ -326,4 +328,28 @@ class calendar_event_exporter extends event_exporter_base { $values['maxdayerror'] = $errorstring; return $values; } + + /** + * Get the correct minimum midnight day limit based on the event start time + * and the module's minimum timestamp limit. + * + * @param DateTimeInterface $starttime The event start time + * @param array $min The module's minimum limit for the event + * @return array Returns an array with mindaytimestamp and mindayerror keys. + */ + protected function get_module_timestamp_min_limit(\DateTimeInterface $starttime, $min) { + return $this->get_timestamp_min_limit($starttime, $min); + } + + /** + * Get the correct maximum midnight day limit based on the event start time + * and the module's maximum timestamp limit. + * + * @param DateTimeInterface $starttime The event start time + * @param array $max The module's maximum limit for the event + * @return array Returns an array with maxdaytimestamp and maxdayerror keys. + */ + protected function get_module_timestamp_max_limit(\DateTimeInterface $starttime, $max) { + return $this->get_timestamp_max_limit($starttime, $max); + } } diff --git a/calendar/tests/calendar_event_exporter_test.php b/calendar/tests/calendar_event_exporter_test.php index f5a7b9b55e7..86c815d7f38 100644 --- a/calendar/tests/calendar_event_exporter_test.php +++ b/calendar/tests/calendar_event_exporter_test.php @@ -38,10 +38,10 @@ require_once(__DIR__ . '/helpers.php'); */ class core_calendar_event_exporter_testcase extends advanced_testcase { /** - * Data provider for the module timestamp min limit test case to confirm + * Data provider for the timestamp min limit test case to confirm * that the minimum time limit is set correctly on the boundary cases. */ - public function get_module_timestamp_min_limit_test_cases() { + public function get_timestamp_min_limit_test_cases() { $now = time(); $todaymidnight = usergetmidnight($now); $tomorrowmidnight = $todaymidnight + DAYSECS; @@ -77,16 +77,16 @@ class core_calendar_event_exporter_testcase extends advanced_testcase { } /** - * @dataProvider get_module_timestamp_min_limit_test_cases() + * @dataProvider get_timestamp_min_limit_test_cases() */ - public function test_get_module_timestamp_min_limit($starttime, $min, $expected) { + public function test_get_timestamp_min_limit($starttime, $min, $expected) { $class = \core_calendar\external\calendar_event_exporter::class; $mock = $this->getMockBuilder($class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); $reflector = new ReflectionClass($class); - $method = $reflector->getMethod('get_module_timestamp_min_limit'); + $method = $reflector->getMethod('get_timestamp_min_limit'); $method->setAccessible(true); $result = $method->invoke($mock, $starttime, $min); @@ -95,10 +95,10 @@ class core_calendar_event_exporter_testcase extends advanced_testcase { } /** - * Data provider for the module timestamp min limit test case to confirm - * that the minimum time limit is set correctly on the boundary cases. + * Data provider for the timestamp max limit test case to confirm + * that the maximum time limit is set correctly on the boundary cases. */ - public function get_module_timestamp_max_limit_test_cases() { + public function get_timestamp_max_limit_test_cases() { $now = time(); $todaymidnight = usergetmidnight($now); $yesterdaymidnight = $todaymidnight - DAYSECS; @@ -134,16 +134,16 @@ class core_calendar_event_exporter_testcase extends advanced_testcase { } /** - * @dataProvider get_module_timestamp_max_limit_test_cases() + * @dataProvider get_timestamp_max_limit_test_cases() */ - public function test_get_module_timestamp_max_limit($starttime, $max, $expected) { + public function test_get_timestamp_max_limit($starttime, $max, $expected) { $class = \core_calendar\external\calendar_event_exporter::class; $mock = $this->getMockBuilder($class) ->disableOriginalConstructor() ->setMethods(null) ->getMock(); $reflector = new ReflectionClass($class); - $method = $reflector->getMethod('get_module_timestamp_max_limit'); + $method = $reflector->getMethod('get_timestamp_max_limit'); $method->setAccessible(true); $result = $method->invoke($mock, $starttime, $max); From 8baf412ef7701caba68f7249d29a201d5e00bb0d Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Mon, 6 Aug 2018 17:36:36 +1000 Subject: [PATCH 4/4] MDL-62960 calendar: Visuall indicator for valid course event drop zones --- .../external/calendar_event_exporter.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/calendar/classes/external/calendar_event_exporter.php b/calendar/classes/external/calendar_event_exporter.php index 1764d12271d..1f79d199c79 100644 --- a/calendar/classes/external/calendar_event_exporter.php +++ b/calendar/classes/external/calendar_event_exporter.php @@ -176,6 +176,9 @@ class calendar_event_exporter extends event_exporter_base { if ($event->get_course_module()) { $values = array_merge($values, $this->get_module_timestamp_limits($event)); + } else if ($hascourse && $course->id != SITEID && empty($event->get_group())) { + // This is a course event. + $values = array_merge($values, $this->get_course_timestamp_limits($event)); } return $values; @@ -210,6 +213,44 @@ class calendar_event_exporter extends event_exporter_base { return $this->event->get_type(); } + /** + * Return the set of minimum and maximum date timestamp values + * for the given event. + * + * @param event_interface $event + * @return array + */ + protected function get_course_timestamp_limits($event) { + $values = []; + $mapper = container::get_event_mapper(); + $starttime = $event->get_times()->get_start_time(); + + list($min, $max) = component_callback( + 'core_course', + 'core_calendar_get_valid_event_timestart_range', + [$mapper->from_event_to_legacy_event($event), $event->get_course()->get_proxied_instance()], + [false, false] + ); + + // The callback will return false for either of the + // min or max cutoffs to indicate that there are no + // valid timestart values. In which case the event is + // not draggable. + if ($min === false || $max === false) { + return ['draggable' => false]; + } + + if ($min) { + $values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min)); + } + + if ($max) { + $values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max)); + } + + return $values; + } + /** * Return the set of minimum and maximum date timestamp values * for the given event.