MDL-59922 mod_choice: allow editing of open and close events
authorRyan Wyllie <ryan@moodle.com>
Tue, 5 Sep 2017 07:46:37 +0000 (07:46 +0000)
committerRyan Wyllie <ryan@moodle.com>
Tue, 19 Sep 2017 08:21:53 +0000 (08:21 +0000)
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/tests/lib_test.php
mod/choice/tests/restore_date_test.php

index 9b1cbfe..91db243 100644 (file)
@@ -100,6 +100,7 @@ $string['notenrolledchoose'] = 'Sorry, only enrolled users are allowed to make c
 $string['notopenyet'] = 'Sorry, this activity is not available until {$a}';
 $string['numberofuser'] = 'Number of responses';
 $string['numberofuserinpercentage'] = 'Percentage of responses';
+$string['openafterclose'] = 'You have specified an open date after the close date';
 $string['option'] = 'Option';
 $string['optionno'] = 'Option {no}';
 $string['options'] = 'Options';
index 2689910..85441c3 100644 (file)
@@ -1240,6 +1240,76 @@ function mod_choice_core_calendar_provide_event_action(calendar_event $event,
     );
 }
 
+/**
+ * This function will check that the given event is valid for it's
+ * corresponding choice module.
+ *
+ * An exception is thrown if the event fails validation.
+ *
+ * @throws \moodle_exception
+ * @param \calendar_event $event
+ * @return bool
+ */
+function mod_choice_core_calendar_validate_event_timestart(\calendar_event $event) {
+    global $DB;
+
+    $record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
+
+    if ($event->eventtype == CHOICE_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($record->timeclose) && $event->timestart > $record->timeclose) {
+            throw new \moodle_exception('openafterclose', 'choice');
+        }
+    } else if ($event->eventtype == CHOICE_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($record->timeopen) && $event->timestart < $record->timeopen) {
+            throw new \moodle_exception('closebeforeopen', 'choice');
+        }
+    }
+
+    return true;
+}
+
+/**
+ * This function will update the choice module according to the
+ * event that has been modified.
+ *
+ * It will set the timeopen or timeclose value of the choice instance
+ * according to the type of event provided.
+ *
+ * @throws \moodle_exception
+ * @param \calendar_event $event
+ */
+function mod_choice_core_calendar_event_timestart_updated(\calendar_event $event) {
+    global $DB;
+
+    if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
+        // If the event is for the choice activity opening then we should
+        // set the start time of the choice activity to be the new start
+        // time of the event.
+        $record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
+
+        if ($record->timeopen != $event->timestart) {
+            $record->timeopen = $event->timestart;
+            $record->timemodified = time();
+            $DB->update_record('choice', $record);
+        }
+    } else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
+        // If the event is for the choice activity closing then we should
+        // set the end time of the choice activity to be the new start
+        // time of the event.
+        $record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
+
+        if ($record->timeclose != $event->timestart) {
+            $record->timeclose = $event->timestart;
+            $record->timemodified = time();
+            $DB->update_record('choice', $record);
+        }
+    }
+}
+
 /**
  * Get icon mapping for font-awesome.
  */
index 118a7d7..5d1c19c 100644 (file)
@@ -346,12 +346,13 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
         // Create a course.
         $course = $this->getDataGenerator()->create_course();
 
+        $timeclose = time() - DAYSECS;
         // Create a choice.
         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
-            'timeclose' => time() - DAYSECS));
+            'timeclose' => $timeclose));
 
         // Create a calendar event.
-        $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
+        $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeclose - 1);
 
         // Create an action factory.
         $factory = new \core_calendar\action_factory();
@@ -371,12 +372,15 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
         // Create a course.
         $course = $this->getDataGenerator()->create_course();
 
+        $timeopen = time() + DAYSECS;
+        $timeclose = $timeopen + DAYSECS;
+
         // Create a choice.
         $choice = $this->getDataGenerator()->create_module('choice', array('course' => $course->id,
-            'timeopen' => time() + DAYSECS));
+            'timeopen' => $timeopen, 'timeclose' => $timeclose));
 
         // Create a calendar event.
-        $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN);
+        $event = $this->create_action_event($course->id, $choice->id, CHOICE_EVENT_TYPE_OPEN, $timeopen);
 
         // Create an action factory.
         $factory = new \core_calendar\action_factory();
@@ -426,9 +430,10 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
      * @param int $courseid
      * @param int $instanceid The choice id.
      * @param string $eventtype The event type. eg. CHOICE_EVENT_TYPE_OPEN.
+     * @param int|null $timestart The start timestamp for the event
      * @return bool|calendar_event
      */
-    private function create_action_event($courseid, $instanceid, $eventtype) {
+    private function create_action_event($courseid, $instanceid, $eventtype, $timestart = null) {
         $event = new stdClass();
         $event->name = 'Calendar event';
         $event->modulename = 'choice';
@@ -436,7 +441,12 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
         $event->instance = $instanceid;
         $event->type = CALENDAR_EVENT_TYPE_ACTION;
         $event->eventtype = $eventtype;
-        $event->timestart = time();
+
+        if ($timestart) {
+            $event->timestart = $timestart;
+        } else {
+            $event->timestart = time();
+        }
 
         return calendar_event::create($event);
     }
@@ -478,4 +488,330 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
         $this->assertEquals(mod_choice_get_completion_active_rule_descriptions(new stdClass()), []);
     }
+
+    /**
+     * You can't create a choice module event when the module doesn't exist.
+     */
+    public function test_mod_choice_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' => 'choice',
+            'instance' => 1234,
+            'eventtype' => CHOICE_EVENT_TYPE_OPEN,
+            'timestart' => time(),
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        $this->expectException('moodle_exception');
+        mod_choice_core_calendar_validate_event_timestart($event);
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_OPEN must be before the close time of the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $DB->update_record('choice', $choice);
+
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_OPEN,
+            'timestart' => $timeopen,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        $this->assertTrue(mod_choice_core_calendar_validate_event_timestart($event));
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_OPEN can not have a start time set after the close time
+     * of the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $DB->update_record('choice', $choice);
+
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_OPEN,
+            'timestart' => $timeclose + 1,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        $this->expectException('moodle_exception');
+        mod_choice_core_calendar_validate_event_timestart($event);
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_CLOSE must be after the open time of the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $DB->update_record('choice', $choice);
+
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
+            'timestart' => $timeclose,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        $this->assertTrue(mod_choice_core_calendar_validate_event_timestart($event));
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_CLOSE can not have a start time set before the open time
+     * of the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $DB->update_record('choice', $choice);
+
+        // Create a valid event.
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
+            'timestart' => $timeopen - 1,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        $this->expectException('moodle_exception');
+        mod_choice_core_calendar_validate_event_timestart($event);
+    }
+
+    /**
+     * An unkown event type should not change the choice instance.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $DB->update_record('choice', $choice);
+
+        // Create a valid event.
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_OPEN . "SOMETHING ELSE",
+            'timestart' => 1,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        mod_choice_core_calendar_event_timestart_updated($event);
+
+        $choice = $DB->get_record('choice', ['id' => $choice->id]);
+        $this->assertEquals($timeopen, $choice->timeopen);
+        $this->assertEquals($timeclose, $choice->timeclose);
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_OPEN event should update the timeopen property of
+     * the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $timemodified = 1;
+        $newtimeopen = $timeopen - DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $choice->timemodified = $timemodified;
+        $DB->update_record('choice', $choice);
+
+        // Create a valid event.
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_OPEN,
+            'timestart' => $newtimeopen,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        mod_choice_core_calendar_event_timestart_updated($event);
+
+        $choice = $DB->get_record('choice', ['id' => $choice->id]);
+        // Ensure the timeopen property matches the event timestart.
+        $this->assertEquals($newtimeopen, $choice->timeopen);
+        // Ensure the timeclose isn't changed.
+        $this->assertEquals($timeclose, $choice->timeclose);
+        // Ensure the timemodified property has been changed.
+        $this->assertNotEquals($timemodified, $choice->timemodified);
+    }
+
+    /**
+     * A CHOICE_EVENT_TYPE_CLOSE event should update the timeclose property of
+     * the choice activity.
+     */
+    public function test_mod_choice_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();
+        $choicegenerator = $generator->get_plugin_generator('mod_choice');
+        $timeopen = time();
+        $timeclose = $timeopen + DAYSECS;
+        $timemodified = 1;
+        $newtimeclose = $timeclose + DAYSECS;
+        $choice = $choicegenerator->create_instance(['course' => $course->id]);
+        $choice->timeopen = $timeopen;
+        $choice->timeclose = $timeclose;
+        $choice->timemodified = $timemodified;
+        $DB->update_record('choice', $choice);
+
+        // Create a valid event.
+        $event = new \calendar_event([
+            'name' => 'Test event',
+            'description' => '',
+            'format' => 1,
+            'courseid' => $course->id,
+            'groupid' => 0,
+            'userid' => 2,
+            'modulename' => 'choice',
+            'instance' => $choice->id,
+            'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
+            'timestart' => $newtimeclose,
+            'timeduration' => 86400,
+            'visible' => 1
+        ]);
+
+        mod_choice_core_calendar_event_timestart_updated($event);
+
+        $choice = $DB->get_record('choice', ['id' => $choice->id]);
+        // Ensure the timeclose property matches the event timestart.
+        $this->assertEquals($newtimeclose, $choice->timeclose);
+        // Ensure the timeopen isn't changed.
+        $this->assertEquals($timeopen, $choice->timeopen);
+        // Ensure the timemodified property has been changed.
+        $this->assertNotEquals($timemodified, $choice->timemodified);
+    }
 }
index 8c49f47..2241ebc 100644 (file)
@@ -40,7 +40,7 @@ class mod_choice_restore_date_testcase extends restore_date_testcase {
         global $DB, $USER;
 
         $time = 100000;
-        $record = ['timeopen' => $time, 'timeclose' => $time];
+        $record = ['timeopen' => $time, 'timeclose' => $time + 1];
         list($course, $choice) = $this->create_course_and_module('choice', $record);
 
         $options = $DB->get_records('choice_options', ['choiceid' => $choice->id]);