From 7fb86321091633deabe38554d8733e8d26974285 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Tue, 12 Feb 2019 16:27:20 +1100 Subject: [PATCH] MDL-60059 mod_workshop: Add drag and drop support to workshop events --- mod/workshop/lib.php | 173 ++++++++++++++++++++ mod/workshop/tests/lib_test.php | 276 ++++++++++++++++++++++++++++++++ 2 files changed, 449 insertions(+) diff --git a/mod/workshop/lib.php b/mod/workshop/lib.php index 8cdf52b3c05..029b9a034c2 100644 --- a/mod/workshop/lib.php +++ b/mod/workshop/lib.php @@ -1853,6 +1853,179 @@ function mod_workshop_core_calendar_provide_event_action(calendar_event $event, ); } +/** + * 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 $workshop The module instance to get the range from + * @return array Returns an array with min and max date. + */ +function mod_workshop_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $workshop) : array { + $mindate = null; + $maxdate = null; + + $phasesubmissionend = max($workshop->submissionstart, $workshop->submissionend); + $phaseassessmentstart = min($workshop->assessmentstart, $workshop->assessmentend); + if ($phaseassessmentstart == 0) { + $phaseassessmentstart = max($workshop->assessmentstart, $workshop->assessmentend); + } + + switch ($event->eventtype) { + case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN: + if (!empty($workshop->submissionend)) { + $maxdate = [ + $workshop->submissionend - 1, // The submissionstart and submissionend cannot be exactly the same. + get_string('submissionendbeforestart', 'mod_workshop') + ]; + } else if ($phaseassessmentstart) { + $maxdate = [ + $phaseassessmentstart, + get_string('phasesoverlap', 'mod_workshop') + ]; + } + break; + case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE: + if (!empty($workshop->submissionstart)) { + $mindate = [ + $workshop->submissionstart + 1, // The submissionstart and submissionend cannot be exactly the same. + get_string('submissionendbeforestart', 'mod_workshop') + ]; + } + if ($phaseassessmentstart) { + $maxdate = [ + $phaseassessmentstart, + get_string('phasesoverlap', 'mod_workshop') + ]; + } + break; + case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN: + if ($phasesubmissionend) { + $mindate = [ + $phasesubmissionend, + get_string('phasesoverlap', 'mod_workshop') + ]; + } + if (!empty($workshop->assessmentend)) { + $maxdate = [ + $workshop->assessmentend - 1, // The assessmentstart and assessmentend cannot be exactly the same. + get_string('assessmentendbeforestart', 'mod_workshop') + ]; + } + break; + case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE: + if (!empty($workshop->assessmentstart)) { + $mindate = [ + $workshop->assessmentstart + 1, // The assessmentstart and assessmentend cannot be exactly the same. + get_string('assessmentendbeforestart', 'mod_workshop') + ]; + } else if ($phasesubmissionend) { + $mindate = [ + $phasesubmissionend, + get_string('phasesoverlap', 'mod_workshop') + ]; + } + break; + } + + return [$mindate, $maxdate]; +} + +/** + * This function will update the workshop module according to the + * event that has been modified. + * + * @param \calendar_event $event + * @param stdClass $workshop The module instance to get the range from + */ +function mod_workshop_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $workshop) : void { + global $DB; + + $courseid = $event->courseid; + $modulename = $event->modulename; + $instanceid = $event->instance; + + // Something weird going on. The event is for a different module so + // we should ignore it. + if ($modulename != 'workshop') { + return; + } + + if ($workshop->id != $instanceid) { + return; + } + + if (!in_array( + $event->eventtype, + [ + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE + ] + )) { + return; + } + + $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; + $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; + + switch ($event->eventtype) { + case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN: + if ($event->timestart != $workshop->submissionstart) { + $workshop->submissionstart = $event->timestart; + $modified = true; + } + break; + case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE: + if ($event->timestart != $workshop->submissionend) { + $workshop->submissionend = $event->timestart; + $modified = true; + } + break; + case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN: + if ($event->timestart != $workshop->assessmentstart) { + $workshop->assessmentstart = $event->timestart; + $modified = true; + } + break; + case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE: + if ($event->timestart != $workshop->assessmentend) { + $workshop->assessmentend = $event->timestart; + $modified = true; + } + break; + } + + if ($modified) { + $workshop->timemodified = time(); + // Persist the assign instance changes. + $DB->update_record('workshop', $workshop); + $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); + $event->trigger(); + } +} + //////////////////////////////////////////////////////////////////////////////// // Course reset API // //////////////////////////////////////////////////////////////////////////////// diff --git a/mod/workshop/tests/lib_test.php b/mod/workshop/tests/lib_test.php index b52404d84b8..1bf8960932a 100644 --- a/mod/workshop/tests/lib_test.php +++ b/mod/workshop/tests/lib_test.php @@ -241,4 +241,280 @@ class mod_workshop_lib_testcase extends advanced_testcase { $this->assertFalse($updates->submissions->updated); $this->assertFalse($updates->assessments->updated); } + + /** + * An unknown event type should not have any limits + */ + public function test_mod_workshop_core_calendar_get_valid_event_timestart_range_unknown_event() { + global $CFG; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $timestart = time(); + $timeend = $timestart + DAYSECS; + $workshop = new \stdClass(); + $workshop->submissionstart = $timestart; + $workshop->submissionend = $timeend; + $workshop->assessmentstart = 0; + $workshop->assessmentend = 0; + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'workshop', + 'instance' => 1, + 'eventtype' => WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE . "SOMETHING ELSE", + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + list ($min, $max) = mod_workshop_core_calendar_get_valid_event_timestart_range($event, $workshop); + $this->assertNull($min); + $this->assertNull($max); + } + + /** + * Provider for test_mod_workshop_core_calendar_get_valid_event_timestart_range. + * + * @return array of (submissionstart, submissionend, assessmentstart, assessmentend, eventtype, expectedmin, expectedmax) + */ + public function mod_workshop_core_calendar_get_valid_event_timestart_range_due_no_limit_provider() { + $submissionstart = time() + DAYSECS; + $submissionend = $submissionstart + DAYSECS; + $assessmentstart = $submissionend + DAYSECS; + $assessmentend = $assessmentstart + DAYSECS; + + return [ + 'Only with submissionstart' => [$submissionstart, 0, 0, 0, WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, null, null], + 'Only with submissionend' => [0, $submissionend, 0, 0, WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, null, null], + 'Only with assessmentstart' => [0, 0, $assessmentstart, 0, WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, null, null], + 'Only with assessmentend' => [0, 0, 0, $assessmentend, WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, null, null], + + 'Move submissionstart when with submissionend' => [$submissionstart, $submissionend, 0, 0, + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, null, $submissionend - 1], + 'Move submissionend when with submissionstart' => [$submissionstart, $submissionend, 0, 0, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, $submissionstart + 1, null], + 'Move assessmentstart when with assessmentend' => [0, 0, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, null, $assessmentend - 1], + 'Move assessmentend when with assessmentstart' => [0, 0, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, $assessmentstart + 1, null], + + 'Move submissionstart when with assessmentstart' => [$submissionstart, 0, $assessmentstart, 0, + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, null, $assessmentstart], + 'Move submissionstart when with assessmentend' => [$submissionstart, 0, 0, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, null, $assessmentend], + 'Move submissionend when with assessmentstart' => [0, $submissionend, $assessmentstart, 0, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, null, $assessmentstart], + 'Move submissionend when with assessmentend' => [0, $submissionend, 0, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, null, $assessmentend], + + 'Move assessmentstart when with submissionstart' => [$submissionstart, 0, $assessmentstart, 0, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, $submissionstart, null], + 'Move assessmentstart when with submissionend' => [0, $submissionend, $assessmentstart, 0, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, $submissionend, null], + 'Move assessmentend when with submissionstart' => [$submissionstart, 0, 0, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, $submissionstart, null], + 'Move assessmentend when with submissionend' => [0, $submissionend, 0, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, $submissionend, null], + + 'Move submissionstart when with others' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, null, $submissionend - 1], + 'Move submissionend when with others' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, $submissionstart + 1, $assessmentstart], + 'Move assessmentstart when with others' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, $submissionend, $assessmentend - 1], + 'Move assessmentend when with others' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, $assessmentstart + 1, null], + ]; + } + + /** + * Tests mod_workshop_core_calendar_get_valid_event_timestart_range in various settings. + * + * @dataProvider mod_workshop_core_calendar_get_valid_event_timestart_range_due_no_limit_provider + * + * @param int $submissionstart The start of the submission phase + * @param int $submissionend The end of the submission phase + * @param int $assessmentstart The start of the assessment phase + * @param int $assessmentend The end of the assessment phase + * @param string $eventtype The type if the event + * @param int|null $expectedmin The expected value for min of the valid event range + * @param int|null $expectedmax The expected value for max of the valid event range + */ + public function test_mod_workshop_core_calendar_get_valid_event_timestart_range($submissionstart, $submissionend, + $assessmentstart, $assessmentend, $eventtype, $expectedmin, $expectedmax) { + + global $CFG; + require_once($CFG->dirroot . '/calendar/lib.php'); + + $this->resetAfterTest(true); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $workshop = new \stdClass(); + $workshop->submissionstart = $submissionstart; + $workshop->submissionend = $submissionend; + $workshop->assessmentstart = $assessmentstart; + $workshop->assessmentend = $assessmentend; + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'workshop', + 'instance' => 1, + 'eventtype' => $eventtype, + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + list($min, $max) = mod_workshop_core_calendar_get_valid_event_timestart_range($event, $workshop); + + $this->assertSame($expectedmin, is_array($min) ? $min[0] : $min); + $this->assertSame($expectedmax, is_array($max) ? $max[0] : $max); + } + + /** + * An unknown event type should not change the workshop instance. + */ + public function test_mod_workshop_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(); + + $workshopgenerator = $generator->get_plugin_generator('mod_workshop'); + $submissionstart = time() + DAYSECS; + $submissionend = $submissionstart + DAYSECS; + $assessmentstart = $submissionend + DAYSECS; + $assessmentend = $assessmentstart + DAYSECS; + $workshop = $workshopgenerator->create_instance(['course' => $course->id]); + $workshop->submissionstart = $submissionstart; + $workshop->submissionend = $submissionend; + $workshop->assessmentstart = $assessmentstart; + $workshop->assessmentend = $assessmentend; + $DB->update_record('workshop', $workshop); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'workshop', + 'instance' => $workshop->id, + 'eventtype' => WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE . "SOMETHING ELSE", + 'timestart' => 1, + 'timeduration' => 86400, + 'visible' => 1 + ]); + + mod_workshop_core_calendar_event_timestart_updated($event, $workshop); + + $workshop = $DB->get_record('workshop', ['id' => $workshop->id]); + $this->assertEquals($submissionstart, $workshop->submissionstart); + $this->assertEquals($submissionend, $workshop->submissionend); + $this->assertEquals($assessmentstart, $workshop->assessmentstart); + $this->assertEquals($assessmentend, $workshop->assessmentend); + } + + /** + * Provider for test_mod_workshop_core_calendar_event_timestart_updated. + * + * @return array of (submissionstart, submissionend, assessmentstart, assessmentend, eventtype, fieldtoupdate, newtime) + */ + public function mod_workshop_core_calendar_event_timestart_updated_provider() { + $submissionstart = time() + DAYSECS; + $submissionend = $submissionstart + DAYSECS; + $assessmentstart = $submissionend + DAYSECS; + $assessmentend = $assessmentstart + DAYSECS; + + return [ + 'Move submissionstart' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN, 'submissionstart', $submissionstart + 50], + 'Move submissionend' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE, 'submissionend', $submissionend + 50], + 'Move assessmentstart' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN, 'assessmentstart', $assessmentstart + 50], + 'Move assessmentend' => [$submissionstart, $submissionend, $assessmentstart, $assessmentend, + WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE, 'assessmentend', $assessmentend + 50], + ]; + } + + /** + * Due date events should update the workshop due date. + * + * @dataProvider mod_workshop_core_calendar_event_timestart_updated_provider + * + * @param int $submissionstart The start of the submission phase + * @param int $submissionend The end of the submission phase + * @param int $assessmentstart The start of the assessment phase + * @param int $assessmentend The end of the assessment phase + * @param string $eventtype The type if the event + * @param string $fieldtoupdate The field that is supposed to be updated. + * Either of 'submissionstart', 'submissionend', 'assessmentstart' or 'assessmentend'. + * @param int $newtime The new value for the $fieldtoupdate + */ + public function test_mod_workshop_core_calendar_event_timestart_updated($submissionstart, $submissionend, $assessmentstart, + $assessmentend, $eventtype, $fieldtoupdate, $newtime) { + global $CFG, $DB; + require_once($CFG->dirroot . "/calendar/lib.php"); + + $this->resetAfterTest(true); + $this->setAdminUser(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + + $workshopgenerator = $generator->get_plugin_generator('mod_workshop'); + $workshop = $workshopgenerator->create_instance(['course' => $course->id]); + $workshop->submissionstart = $submissionstart; + $workshop->submissionend = $submissionend; + $workshop->assessmentstart = $assessmentstart; + $workshop->assessmentend = $assessmentend; + $DB->update_record('workshop', $workshop); + + // Create a valid event. + $event = new \calendar_event([ + 'name' => 'Test event', + 'description' => '', + 'format' => 1, + 'courseid' => $course->id, + 'groupid' => 0, + 'userid' => 2, + 'modulename' => 'workshop', + 'instance' => $workshop->id, + 'eventtype' => $eventtype, + 'timestart' => $newtime, + 'timeduration' => 86400, + 'visible' => 1 + ]); + mod_workshop_core_calendar_event_timestart_updated($event, $workshop); + + $$fieldtoupdate = $newtime; + + $workshop = $DB->get_record('workshop', ['id' => $workshop->id]); + $this->assertEquals($submissionstart, $workshop->submissionstart); + $this->assertEquals($submissionend, $workshop->submissionend); + $this->assertEquals($assessmentstart, $workshop->assessmentstart); + $this->assertEquals($assessmentend, $workshop->assessmentend); + } }