MDL-60062 calendar: prevent drag and drop of override events
authorRyan Wyllie <ryan@moodle.com>
Tue, 7 Nov 2017 05:47:18 +0000 (05:47 +0000)
committerRyan Wyllie <ryan@moodle.com>
Tue, 7 Nov 2017 05:47:18 +0000 (05:47 +0000)
calendar/classes/external/calendar_event_exporter.php
calendar/classes/local/api.php
calendar/templates/month_detailed.mustache
calendar/tests/local_api_test.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/tests/lib_test.php
mod/quiz/lib.php
mod/quiz/tests/calendar_event_modified_test.php

index f3dd9ff..ad97c96 100644 (file)
@@ -74,6 +74,10 @@ class calendar_event_exporter extends event_exporter_base {
             'type' => PARAM_TEXT,
             'optional' => true
         ];
+        $values['draggable'] = [
+            'type' => PARAM_BOOL,
+            'default' => false
+        ];
 
         return $values;
     }
@@ -90,6 +94,10 @@ class calendar_event_exporter extends event_exporter_base {
         $values = parent::get_other_values($output);
         $event = $this->event;
 
+        // By default all events that can be edited are
+        // draggable.
+        $values['draggable'] = $values['canedit'];
+
         if ($moduleproxy = $event->get_course_module()) {
             $modulename = $moduleproxy->get('modname');
             $moduleid = $moduleproxy->get('id');
@@ -226,9 +234,17 @@ class calendar_event_exporter extends event_exporter_base {
             'mod_' . $modname,
             'core_calendar_get_valid_event_timestart_range',
             [$mapper->from_event_to_legacy_event($event), $moduleinstance],
-            [null, null]
+            [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_module_timestamp_min_limit($starttime, $min));
         }
index 1b81231..6da620e 100644 (file)
@@ -260,9 +260,15 @@ class api {
                 'mod_' . $event->get_course_module()->get('modname'),
                 'core_calendar_get_valid_event_timestart_range',
                 [$legacyevent, $moduleinstance],
-                [null, null]
+                [false, false]
             );
 
+            // 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]);
             }
index 0cb0d8e..9abff8e 100644 (file)
@@ -87,7 +87,7 @@
                                         {{^underway}}
                                             <li data-region="event-item"
                                                 data-eventtype-{{calendareventtype}}="1"
-                                                {{#canedit}}
+                                                {{#draggable}}
                                                     draggable="true"
                                                     data-drag-type="move"
                                                     {{#mindaytimestamp}}
                                                     {{#maxdayerror}}
                                                         data-max-day-error="{{.}}"
                                                     {{/maxdayerror}}
-                                                {{/canedit}}>
+                                                {{/draggable}}>
 
                                                 <a data-action="view-event" data-event-id="{{id}}" href="{{url}}" title="{{name}}">
                                                     <span class="badge badge-circle calendar_event_{{calendareventtype}}">
index d97c08c..ff2e065 100644 (file)
@@ -1239,4 +1239,50 @@ class core_calendar_local_api_testcase extends advanced_testcase {
         $this->expectException('moodle_exception');
         $newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
     }
+
+    /**
+     * Updating the start day of an overridden event belonging to an activity
+     * should result in an exception. This is to prevent the drag and drop
+     * of override events.
+     *
+     * Note: This test uses the quiz activity because it requires
+     * module callbacks to be in place and override event support to test.
+     */
+    public function test_update_event_start_day_activity_event_override() {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . '/calendar/lib.php');
+        require_once($CFG->dirroot . '/mod/quiz/lib.php');
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+        $mapper = container::get_event_mapper();
+        $timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
+        $newstartdate = new DateTimeImmutable('2016-02-2T10:00:00+08:00');
+        $generator = $this->getDataGenerator();
+        $user = $generator->create_user();
+        $course = $generator->create_course();
+        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
+        $quiz = $quizgenerator->create_instance([
+            'course' => $course->id,
+            'timeopen' => $timeopen->getTimestamp(),
+        ]);
+        $event = create_event([
+            'courseid' => $course->id,
+            'userid' => $user->id,
+            'modulename' => 'quiz',
+            'instance' => $quiz->id,
+            'eventtype' => QUIZ_EVENT_TYPE_OPEN,
+            'timestart' => $timeopen->getTimestamp()
+        ]);
+        $event = $mapper->from_legacy_event_to_event($event);
+        $record = (object) [
+            'quiz' => $quiz->id,
+            'userid' => $user->id
+        ];
+
+        $DB->insert_record('quiz_overrides', $record);
+
+        $this->expectException('moodle_exception');
+        $newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
+    }
 }
index 214362b..5eeccf3 100644 (file)
@@ -1946,8 +1946,12 @@ function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $
  *     [1506741172, 'The due date must be before the cutoff date']
  * ]
  *
+ * If the event does not have a valid timestart range then [false, false] will
+ * be returned.
+ *
  * @param calendar_event $event The calendar event to get the time range for
  * @param stdClass $instance The module instance to get the range from
+ * @return array
  */
 function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
     global $CFG;
index 61c6d9b..ae24d0f 100644 (file)
@@ -1002,7 +1002,11 @@ class assign {
      *     [1506741172, 'The due date must be before the cutoff date']
      * ]
      *
+     * If the event does not have a valid timestart range then [false, false] will
+     * be returned.
+     *
      * @param calendar_event $event The calendar event to get the time range for
+     * @return array
      */
     function get_valid_calendar_event_timestart_range(\calendar_event $event) {
         $instance = $this->get_instance();
@@ -1018,8 +1022,9 @@ class assign {
             // the only events that can be overridden, so we can save a DB
             // query if we don't bother checking other events.
             if ($this->is_override_calendar_event($event)) {
-                // This is an override event so we should ignore it.
-                return [null, null];
+                // This is an override event so there is no valid timestart
+                // range to set it to.
+                return [false, false];
             }
 
             if ($submissionsfromdate) {
index 5e057ee..cadf766 100644 (file)
@@ -883,8 +883,8 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase {
         $DB->insert_record('assign_overrides', $record);
 
         list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
-        $this->assertNull($min);
-        $this->assertNull($max);
+        $this->assertFalse($min);
+        $this->assertFalse($max);
     }
 
     /**
index 49aef8d..9b2b227 100644 (file)
@@ -2264,6 +2264,9 @@ function mod_quiz_get_completion_active_rule_descriptions($cm) {
  * If either value isn't set then null will be returned instead to
  * indicate that there is no cutoff for that value.
  *
+ * If the vent has no valid timestart range then [false, false] will
+ * be returned. This is the case for overriden events.
+ *
  * A minimum and maximum cutoff return value will look like:
  * [
  *     [1505704373, 'The date must be after this date'],
@@ -2279,9 +2282,9 @@ function mod_quiz_core_calendar_get_valid_event_timestart_range(\calendar_event
     global $CFG, $DB;
     require_once($CFG->dirroot . '/mod/quiz/locallib.php');
 
-    // No restrictions on override events.
+    // Overrides do not have a valid timestart range.
     if (quiz_is_overriden_calendar_event($event)) {
-        return [null, null];
+        return [false, false];
     }
 
     $mindate = null;
index e514e04..4a4cfdf 100644 (file)
@@ -416,8 +416,8 @@ class mod_quiz_calendar_event_modified_testcase extends advanced_testcase {
 
         list ($min, $max) = mod_quiz_core_calendar_get_valid_event_timestart_range($event, $quiz);
 
-        $this->assertNull($min);
-        $this->assertNull($max);
+        $this->assertFalse($min);
+        $this->assertFalse($max);
     }
 
     /**