From d685b959355da9c24a1950b1d5340a31859d8278 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Mon, 18 Jul 2016 19:08:50 +1000 Subject: [PATCH 01/10] MDL-29795 mod_assign: Add user/group override assignment module --- .../backup/moodle2/backup_assign_stepslib.php | 15 + .../moodle2/restore_assign_stepslib.php | 39 ++ .../classes/event/group_override_created.php | 118 ++++++ .../classes/event/group_override_deleted.php | 117 ++++++ .../classes/event/group_override_updated.php | 117 ++++++ .../classes/event/user_override_created.php | 115 ++++++ .../classes/event/user_override_deleted.php | 115 ++++++ .../classes/event/user_override_updated.php | 116 ++++++ mod/assign/classes/group_observers.php | 85 +++++ mod/assign/db/access.php | 10 + mod/assign/db/events.php | 43 +++ mod/assign/db/install.xml | 18 + mod/assign/db/upgrade.php | 30 ++ mod/assign/externallib.php | 12 + mod/assign/gradingtable.php | 134 ++++++- mod/assign/lang/en/assign.php | 34 +- mod/assign/lib.php | 164 +++++++- mod/assign/locallib.php | 354 +++++++++++++++++- mod/assign/override_form.php | 293 +++++++++++++++ mod/assign/overridedelete.php | 96 +++++ mod/assign/overrideedit.php | 240 ++++++++++++ mod/assign/overrides.php | 300 +++++++++++++++ .../templates/grading_navigation.mustache | 3 - .../grading_navigation_user_summary.mustache | 2 +- .../tests/behat/assign_course_reset.feature | 110 ++++++ .../tests/behat/assign_group_override.feature | 248 ++++++++++++ .../tests/behat/assign_user_override.feature | 183 +++++++++ mod/assign/tests/events_test.php | 190 ++++++++++ mod/assign/tests/locallib_test.php | 20 +- mod/assign/view.php | 3 + 30 files changed, 3307 insertions(+), 17 deletions(-) create mode 100644 mod/assign/classes/event/group_override_created.php create mode 100644 mod/assign/classes/event/group_override_deleted.php create mode 100644 mod/assign/classes/event/group_override_updated.php create mode 100644 mod/assign/classes/event/user_override_created.php create mode 100644 mod/assign/classes/event/user_override_deleted.php create mode 100644 mod/assign/classes/event/user_override_updated.php create mode 100644 mod/assign/classes/group_observers.php create mode 100644 mod/assign/db/events.php create mode 100644 mod/assign/override_form.php create mode 100644 mod/assign/overridedelete.php create mode 100644 mod/assign/overrideedit.php create mode 100644 mod/assign/overrides.php create mode 100644 mod/assign/tests/behat/assign_course_reset.feature create mode 100644 mod/assign/tests/behat/assign_group_override.feature create mode 100644 mod/assign/tests/behat/assign_user_override.feature diff --git a/mod/assign/backup/moodle2/backup_assign_stepslib.php b/mod/assign/backup/moodle2/backup_assign_stepslib.php index d28589168ab6d..38c87f851d29a 100644 --- a/mod/assign/backup/moodle2/backup_assign_stepslib.php +++ b/mod/assign/backup/moodle2/backup_assign_stepslib.php @@ -130,6 +130,10 @@ protected function define_structure() { 'name', 'value')); + $overrides = new backup_nested_element('overrides'); + $override = new backup_nested_element('override', array('id'), array( + 'groupid', 'userid', 'allowsubmissionsfromdate', 'duedate', 'cutoffdate')); + // Build the tree. $assign->add_child($userflags); $userflags->add_child($userflag); @@ -139,12 +143,17 @@ protected function define_structure() { $grades->add_child($grade); $assign->add_child($pluginconfigs); $pluginconfigs->add_child($pluginconfig); + $assign->add_child($overrides); + $overrides->add_child($override); // Define sources. $assign->set_source_table('assign', array('id' => backup::VAR_ACTIVITYID)); $pluginconfig->set_source_table('assign_plugin_config', array('assignment' => backup::VAR_PARENTID)); + // Assign overrides to backup are different depending of user info. + $overrideparams = array('assignid' => backup::VAR_PARENTID); + if ($userinfo) { $userflag->set_source_table('assign_user_flags', array('assignment' => backup::VAR_PARENTID)); @@ -158,8 +167,12 @@ protected function define_structure() { // Support 2 types of subplugins. $this->add_subplugin_structure('assignsubmission', $submission, true); $this->add_subplugin_structure('assignfeedback', $grade, true); + } else { + $overrideparams['userid'] = backup_helper::is_sqlparam(null); // Without userinfo, skip user overrides. } + $override->set_source_table('assign_overrides', $overrideparams); + // Define id annotations. $userflag->annotate_ids('user', 'userid'); $userflag->annotate_ids('user', 'allocatedmarker'); @@ -168,6 +181,8 @@ protected function define_structure() { $grade->annotate_ids('user', 'userid'); $grade->annotate_ids('user', 'grader'); $assign->annotate_ids('grouping', 'teamsubmissiongroupingid'); + $override->annotate_ids('user', 'userid'); + $override->annotate_ids('group', 'groupid'); // Define file annotations. // These file areas don't have an itemid. diff --git a/mod/assign/backup/moodle2/restore_assign_stepslib.php b/mod/assign/backup/moodle2/restore_assign_stepslib.php index 69fa58e3e6888..38e6c42e0eff0 100644 --- a/mod/assign/backup/moodle2/restore_assign_stepslib.php +++ b/mod/assign/backup/moodle2/restore_assign_stepslib.php @@ -65,6 +65,7 @@ protected function define_structure() { $userflag = new restore_path_element('assign_userflag', '/activity/assign/userflags/userflag'); $paths[] = $userflag; + $paths[] = new restore_path_element('assign_override', '/activity/assign/overrides/override'); } $paths[] = new restore_path_element('assign_plugin_config', '/activity/assign/plugin_configs/plugin_config'); @@ -353,6 +354,44 @@ protected function add_plugin_config_files($subtype) { } } + /** + * Process a assign override restore + * @param object $data The data in object form + * @return void + */ + protected function process_assign_override($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + + // Based on userinfo, we'll restore user overides or no. + $userinfo = $this->get_setting_value('userinfo'); + + // Skip user overrides if we are not restoring userinfo. + if (!$userinfo && !is_null($data->userid)) { + return; + } + + $data->assignid = $this->get_new_parentid('assign'); + + if (!is_null($data->userid)) { + $data->userid = $this->get_mappingid('user', $data->userid); + } + if (!is_null($data->groupid)) { + $data->groupid = $this->get_mappingid('group', $data->groupid); + } + + $data->allowsubmissionsfromdate = $this->apply_date_offset($data->allowsubmissionsfromdate); + $data->duedate = $this->apply_date_offset($data->duedate); + $data->cutoffdate = $this->apply_date_offset($data->cutoffdate); + + $newitemid = $DB->insert_record('assign_overrides', $data); + + // Add mapping, restore of logs needs it. + $this->set_mapping('assign_override', $oldid, $newitemid); + } + /** * Once the database tables have been fully restored, restore the files * @return void diff --git a/mod/assign/classes/event/group_override_created.php b/mod/assign/classes/event/group_override_created.php new file mode 100644 index 0000000000000..52c379e2bd0d5 --- /dev/null +++ b/mod/assign/classes/event/group_override_created.php @@ -0,0 +1,118 @@ +. + +/** + * The mod_assign group override created event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign group override created event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * - int groupid: the id of the group. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class group_override_created extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverridecreated', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' created the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the group with id '{$this->other['groupid']}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrideedit.php', array('id' => $this->objectid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + + if (!isset($this->other['groupid'])) { + throw new \coding_exception('The \'groupid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/event/group_override_deleted.php b/mod/assign/classes/event/group_override_deleted.php new file mode 100644 index 0000000000000..6c603a46d841d --- /dev/null +++ b/mod/assign/classes/event/group_override_deleted.php @@ -0,0 +1,117 @@ +. + +/** + * The mod_assign group override deleted event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign group override deleted event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * - int groupid: the id of the group. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class group_override_deleted extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverridedeleted', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' deleted the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the group with id '{$this->other['groupid']}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrides.php', array('cmid' => $this->contextinstanceid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + + if (!isset($this->other['groupid'])) { + throw new \coding_exception('The \'groupid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/event/group_override_updated.php b/mod/assign/classes/event/group_override_updated.php new file mode 100644 index 0000000000000..f1c075420c20f --- /dev/null +++ b/mod/assign/classes/event/group_override_updated.php @@ -0,0 +1,117 @@ +. + +/** + * The mod_assign group override updated event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign group override updated event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * - int groupid: the id of the group. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class group_override_updated extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverrideupdated', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' updated the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the group with id '{$this->other['groupid']}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrideedit.php', array('id' => $this->objectid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + + if (!isset($this->other['groupid'])) { + throw new \coding_exception('The \'groupid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/event/user_override_created.php b/mod/assign/classes/event/user_override_created.php new file mode 100644 index 0000000000000..f2eaf33b52f27 --- /dev/null +++ b/mod/assign/classes/event/user_override_created.php @@ -0,0 +1,115 @@ +. + +/** + * The mod_assign user override created event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign user override created event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_override_created extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverridecreated', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' created the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the user with id '{$this->relateduserid}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrideedit.php', array('id' => $this->objectid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/event/user_override_deleted.php b/mod/assign/classes/event/user_override_deleted.php new file mode 100644 index 0000000000000..fbd82ed979843 --- /dev/null +++ b/mod/assign/classes/event/user_override_deleted.php @@ -0,0 +1,115 @@ +. + +/** + * The mod_assign user override deleted event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign user override deleted event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_override_deleted extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverridedeleted', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' deleted the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the user with id '{$this->relateduserid}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrides.php', array('cmid' => $this->contextinstanceid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/event/user_override_updated.php b/mod/assign/classes/event/user_override_updated.php new file mode 100644 index 0000000000000..e181b23701cfb --- /dev/null +++ b/mod/assign/classes/event/user_override_updated.php @@ -0,0 +1,116 @@ +. + +/** + * The mod_assign user override updated event. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_assign\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_assign user override updated event class. + * + * @property-read array $other { + * Extra information about event. + * + * - int assignid: the id of the assign. + * } + * + * @package mod_assign + * @since Moodle 3.2 + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_override_updated extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['objecttable'] = 'assign_overrides'; + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_TEACHING; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventoverrideupdated', 'mod_assign'); + } + + /** + * Returns description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' updated the override with id '$this->objectid' for the assign with " . + "course module id '$this->contextinstanceid' for the user with id '{$this->relateduserid}'."; + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/mod/assign/overrideedit.php', array('id' => $this->objectid)); + } + + /** + * Custom validation. + * + * @throws \coding_exception + * @return void + */ + protected function validate_data() { + parent::validate_data(); + + if (!isset($this->relateduserid)) { + throw new \coding_exception('The \'relateduserid\' must be set.'); + } + + if (!isset($this->other['assignid'])) { + throw new \coding_exception('The \'assignid\' value must be set in other.'); + } + } + + /** + * Get objectid mapping + */ + public static function get_objectid_mapping() { + return array('db' => 'assign_overrides', 'restore' => 'assign_override'); + } + + /** + * Get other mapping + */ + public static function get_other_mapping() { + $othermapped = array(); + $othermapped['assignid'] = array('db' => 'assign', 'restore' => 'assign'); + + return $othermapped; + } +} diff --git a/mod/assign/classes/group_observers.php b/mod/assign/classes/group_observers.php new file mode 100644 index 0000000000000..ef850d6ca278d --- /dev/null +++ b/mod/assign/classes/group_observers.php @@ -0,0 +1,85 @@ +. + +/** + * Group observers. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_assign; +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/assign/locallib.php'); + +/** + * Group observers class. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class group_observers { + + /** + * Flag whether a course reset is in progress or not. + * + * @var int The course ID. + */ + protected static $resetinprogress = false; + + /** + * A course reset has started. + * + * @param \core\event\base $event The event. + * @return void + */ + public static function course_reset_started($event) { + self::$resetinprogress = $event->courseid; + } + + /** + * A course reset has ended. + * + * @param \core\event\base $event The event. + * @return void + */ + public static function course_reset_ended($event) { + if (!empty(self::$resetinprogress)) { + if (!empty($event->other['reset_options']['reset_groups_remove'])) { + assign_process_group_deleted_in_course($event->courseid); + } + } + + self::$resetinprogress = null; + } + + /** + * A group was deleted. + * + * @param \core\event\base $event The event. + * @return void + */ + public static function group_deleted($event) { + if (!empty(self::$resetinprogress)) { + // We will take care of that once the course reset ends. + return; + } + assign_process_group_deleted_in_course($event->courseid, $event->objectid); + } +} diff --git a/mod/assign/db/access.php b/mod/assign/db/access.php index e58da85cf571f..d946dd2b8ff87 100644 --- a/mod/assign/db/access.php +++ b/mod/assign/db/access.php @@ -178,5 +178,15 @@ 'manager' => CAP_ALLOW ) ), + + // Edit the assign overrides. + 'mod/assign:manageoverrides' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ) + ), ); diff --git a/mod/assign/db/events.php b/mod/assign/db/events.php new file mode 100644 index 0000000000000..28683c4b9faf2 --- /dev/null +++ b/mod/assign/db/events.php @@ -0,0 +1,43 @@ +. + +/** + * Add event handlers for the assign + * + * @package mod_assign + * @category event + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +$observers = array( + + array( + 'eventname' => '\core\event\course_reset_started', + 'callback' => '\mod_assign\group_observers::course_reset_started', + ), + array( + 'eventname' => '\core\event\course_reset_ended', + 'callback' => '\mod_assign\group_observers::course_reset_ended', + ), + array( + 'eventname' => '\core\event\group_deleted', + 'callback' => '\mod_assign\group_observers::group_deleted' + ), +); diff --git a/mod/assign/db/install.xml b/mod/assign/db/install.xml index 335ce3f5cca8b..9cba667e03a09 100644 --- a/mod/assign/db/install.xml +++ b/mod/assign/db/install.xml @@ -138,5 +138,23 @@ + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/mod/assign/db/upgrade.php b/mod/assign/db/upgrade.php index c0935c5a21d6f..6b2797cfd8692 100644 --- a/mod/assign/db/upgrade.php +++ b/mod/assign/db/upgrade.php @@ -200,5 +200,35 @@ function xmldb_assign_upgrade($oldversion) { // Moodle v3.1.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2016070402) { + + // Define table assign_overrides to be created. + $table = new xmldb_table('assign_overrides'); + + // Adding fields to table assign_overrides. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('assignid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('groupid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('allowsubmissionsfromdate', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('duedate', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('cutoffdate', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + + // Adding keys to table assign_overrides. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('assignid', XMLDB_KEY_FOREIGN, array('assignid'), 'assign', array('id')); + $table->add_key('groupid', XMLDB_KEY_FOREIGN, array('groupid'), 'groups', array('id')); + $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); + + // Conditionally launch create table for assign_overrides. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Assign savepoint reached. + upgrade_mod_savepoint(true, 2016070402, 'assign'); + } + return true; } diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index bb9598380a1f4..fa283a237e951 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -2720,6 +2720,10 @@ public static function get_participant($assignid, $userid, $embeduser) { $assign->require_view_grades(); $participant = $assign->get_participant($params['userid']); + + // Update assign with override information. + $assign->update_effective_access($params['userid']); + if (!$participant) { // No participant found so we can return early. throw new moodle_exception('usernotincourse'); @@ -2731,6 +2735,10 @@ public static function get_participant($assignid, $userid, $embeduser) { 'submitted' => $participant->submitted, 'requiregrading' => $participant->requiregrading, 'blindmarking' => $assign->is_blind_marking(), + 'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate, + 'duedate' => $assign->get_instance()->duedate, + 'cutoffdate' => $assign->get_instance()->cutoffdate, + 'duedatestr' => userdate($assign->get_instance()->duedate), ); if (!empty($participant->groupid)) { @@ -2766,6 +2774,10 @@ public static function get_participant_returns() { 'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'), 'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'), 'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'), + 'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allowsubmissionsfromdate for the user'), + 'duedate' => new external_value(PARAM_INT, 'duedate for the user'), + 'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user'), + 'duedatestr' => new external_value(PARAM_TEXT, 'duedate for the user'), 'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL), 'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL), 'user' => $userdescription, diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index e787658fbb11e..68e66586c7653 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -131,6 +131,12 @@ public function __construct(assign $assignment, $params['assignmentid2'] = (int)$this->assignment->get_instance()->id; $params['assignmentid3'] = (int)$this->assignment->get_instance()->id; + $params['assignmentid5'] = (int)$this->assignment->get_instance()->id; + $params['assignmentid6'] = (int)$this->assignment->get_instance()->id; + $params['assignmentid7'] = (int)$this->assignment->get_instance()->id; + $params['assignmentid8'] = (int)$this->assignment->get_instance()->id; + $params['assignmentid9'] = (int)$this->assignment->get_instance()->id; + $extrauserfields = get_extra_user_fields($this->assignment->get_context()); $fields = user_picture::fields('u', $extrauserfields) . ', '; @@ -148,7 +154,11 @@ public function __construct(assign $assignment, $fields .= 'uf.locked as locked, '; $fields .= 'uf.extensionduedate as extensionduedate, '; $fields .= 'uf.workflowstate as workflowstate, '; - $fields .= 'uf.allocatedmarker as allocatedmarker '; + $fields .= 'uf.allocatedmarker as allocatedmarker, '; + $fields .= 'priority.priority, '; + $fields .= 'effective.allowsubmissionsfromdate, '; + $fields .= 'effective.duedate, '; + $fields .= 'effective.cutoffdate '; $from = '{user} u LEFT JOIN {assign_submission} s @@ -178,6 +188,62 @@ public function __construct(assign $assignment, ON u.id = uf.userid AND uf.assignment = :assignmentid3 '; + $from .= ' LEFT JOIN ( + SELECT merged.userid, min(merged.priority) priority FROM ( + ( SELECT u.id as userid, 9999999 AS priority + FROM {user} u + ) + UNION + ( SELECT uo.userid, 0 AS priority + FROM {assign_overrides} uo + WHERE uo.assignid = :assignmentid5 + ) + UNION + ( SELECT gm.userid, go.sortorder AS priority + FROM {assign_overrides} go + JOIN {groups} g ON g.id = go.groupid + JOIN {groups_members} gm ON gm.groupid = g.id + WHERE go.assignid = :assignmentid6 + ) + ) AS merged + GROUP BY merged.userid + ) priority ON priority.userid = u.id + + JOIN ( + (SELECT 9999999 AS priority, + u.id AS userid, + + a.allowsubmissionsfromdate, + a.duedate, + a.cutoffdate + FROM {user} u + JOIN {assign} a ON a.id = :assignmentid7 + ) + UNION + (SELECT 0 AS priority, + uo.userid, + + uo.allowsubmissionsfromdate, + uo.duedate, + uo.cutoffdate + FROM {assign_overrides} uo + WHERE uo.assignid = :assignmentid8 + ) + UNION + (SELECT go.sortorder AS priority, + gm.userid, + + go.allowsubmissionsfromdate, + go.duedate, + go.cutoffdate + FROM {assign_overrides} go + JOIN {groups} g ON g.id = go.groupid + JOIN {groups_members} gm ON gm.groupid = g.id + WHERE go.assignid = :assignmentid9 + ) + + ) AS effective ON effective.priority = priority.priority AND effective.userid = priority.userid '; + if (!empty($this->assignment->get_instance()->blindmarking)) { $from .= 'LEFT JOIN {assign_user_mapping} um ON u.id = um.userid @@ -303,6 +369,18 @@ public function __construct(assign $assignment, $columns[] = 'status'; $headers[] = get_string('status', 'assign'); + // Allowsubmissionsfromdate. + $columns[] = 'allowsubmissionsfromdate'; + $headers[] = get_string('allowsubmissionsfromdate', 'assign'); + + // Duedate. + $columns[] = 'duedate'; + $headers[] = get_string('duedate', 'assign'); + + // Cutoffdate. + $columns[] = 'cutoffdate'; + $headers[] = get_string('cutoffdate', 'assign'); + // Team submission columns. if ($assignment->get_instance()->teamsubmission) { $columns[] = 'team'; @@ -1012,6 +1090,60 @@ public function col_status(stdClass $row) { return $o; } + /** + * Format a column of data for display. + * + * @param stdClass $row + * @return string + */ + public function col_allowsubmissionsfromdate(stdClass $row) { + $o = ''; + + if ($row->allowsubmissionsfromdate) { + $userdate = userdate($row->allowsubmissionsfromdate); + $o = $this->output->container($userdate, 'allowsubmissionsfromdate'); + } + + return $o; + + } + + /** + * Format a column of data for display. + * + * @param stdClass $row + * @return string + */ + public function col_duedate(stdClass $row) { + $o = ''; + + if ($row->duedate) { + $userdate = userdate($row->duedate); + $o = $this->output->container($userdate, 'duedate'); + } + + return $o; + + } + + /** + * Format a column of data for display. + * + * @param stdClass $row + * @return string + */ + public function col_cutoffdate(stdClass $row) { + $o = ''; + + if ($row->cutoffdate) { + $userdate = userdate($row->cutoffdate); + $o = $this->output->container($userdate, 'cutoffdate'); + } + + return $o; + + } + /** * Format a column of data for display. * diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index 700b4a3fdea38..ec3d966bee765 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -29,6 +29,8 @@ $string['addnewattempt_help'] = 'This will create a new blank submission for you to work on.'; $string['addnewattemptfromprevious'] = 'Add a new attempt based on previous submission'; $string['addnewattemptfromprevious_help'] = 'This will copy the contents of your previous submission to a new submission for you to work on.'; +$string['addnewgroupoverride'] = 'Add group override'; +$string['addnewuseroverride'] = 'Add user override'; $string['allocatedmarker'] = 'Allocated Marker'; $string['allocatedmarker_help'] = 'Marker allocated to this submission'; $string['allowsubmissions'] = 'Allow the user to continue making submissions to this assignment.'; @@ -47,6 +49,7 @@ $string['assign:grantextension'] = 'Grant extension'; $string['assign:manageallocations'] = 'Manage markers allocated to submissions'; $string['assign:managegrades'] = 'Review and release grades'; +$string['assign:manageoverrides'] = 'Manage assign overrides'; $string['assign:receivegradernotifications'] = 'Receive grader submission notifications'; $string['assign:releasegrades'] = 'Release grades'; $string['assign:revealidentities'] = 'Reveal student identities'; @@ -135,6 +138,7 @@ $string['defaultteam'] = 'Default group'; $string['deleteallsubmissions'] = 'Delete all submissions'; $string['description'] = 'Description'; +$string['disabled'] = 'Disabled'; $string['downloadall'] = 'Download all submissions'; $string['download all submissions'] = 'Download all submissions in a zip file.'; $string['downloadasfolders'] = 'Download as separate folders'; @@ -151,11 +155,13 @@ $string['duedatevalidation'] = 'Due date must be after the allow submissions from date.'; $string['editattemptfeedback'] = 'Edit the grade and feedback for attempt number {$a}.'; $string['editingpreviousfeedbackwarning'] = 'You are editing the feedback for a previous attempt. This is attempt {$a->attemptnumber} out of {$a->totalattempts}.'; +$string['editoverride'] = 'Edit override'; $string['editsubmission'] = 'Edit submission'; $string['editsubmissionother'] = 'Edit submission for {$a}'; $string['editsubmission_help'] = 'Make changes to your submission'; $string['editingstatus'] = 'Editing status'; $string['editaction'] = 'Actions...'; +$string['enabled'] = 'Enabled'; $string['eventallsubmissionsdownloaded'] = 'All the submissions are being downloaded.'; $string['eventassessablesubmitted'] = 'A submission has been submitted.'; $string['eventbatchsetmarkerallocationviewed'] = 'Batch set marker allocation viewed'; @@ -167,6 +173,9 @@ $string['eventgradingtableviewed'] = 'Grading table viewed'; $string['eventidentitiesrevealed'] = 'The identities have been revealed.'; $string['eventmarkerupdated'] = 'The allocated marker has been updated.'; +$string['eventoverridecreated'] = 'Assign override created'; +$string['eventoverridedeleted'] = 'Assign override deleted'; +$string['eventoverrideupdated'] = 'Assign override updated'; $string['eventrevealidentitiesconfirmationpageviewed'] = 'Reveal identities confirmation page viewed.'; $string['eventstatementaccepted'] = 'The user has accepted the statement of the submission.'; $string['eventsubmissionconfirmationformviewed'] = 'Submission confirmation form viewed.'; @@ -200,7 +209,6 @@ $string['grantextension'] = 'Grant extension'; $string['grantextensionforusers'] = 'Grant extension for {$a} students'; $string['groupsubmissionsettings'] = 'Group submission settings'; -$string['enabled'] = 'Enabled'; $string['errornosubmissions'] = 'There are no submissions to download'; $string['errorquickgradingvsadvancedgrading'] = 'The grades were not saved because this assignment is currently using advanced grading'; $string['errorrecordmodified'] = 'The grades were not saved because someone has modified one or more records more recently than when you loaded the page.'; @@ -245,6 +253,9 @@ $string['gradingstatus'] = 'Grading status'; $string['gradingstudent'] = 'Grading student'; $string['gradingsummary'] = 'Grading summary'; +$string['groupoverrides'] = 'Group overrides'; +$string['groupoverridesdeleted'] = 'Group overrides deleted'; +$string['groupsnone'] = 'There are no groups in this course'; $string['hideshow'] = 'Hide/Show'; $string['hiddenuser'] = 'Participant '; $string['instructionfiles'] = 'Instruction files'; @@ -252,6 +263,7 @@ $string['introattachments_help'] = 'Additional files for use in the assignment, such as answer templates, may be added. Download links for the files will then be displayed on the assignment page under the description.'; $string['invalidgradeforscale'] = 'The grade supplied was not valid for the current scale'; $string['invalidfloatforgrade'] = 'The grade provided could not be understood: {$a}'; +$string['invalidoverrideid'] = 'Invalid override id'; $string['lastmodifiedsubmission'] = 'Last modified (submission)'; $string['lastmodifiedgrade'] = 'Last modified (grade)'; $string['latesubmissions'] = 'Late submissions'; @@ -304,12 +316,16 @@ $string['mysubmission'] = 'My submission: '; $string['newsubmissions'] = 'Assignments submitted'; $string['noattempt'] = 'No attempt'; +$string['noclose'] = 'No close date'; $string['nofilters'] = 'No filters'; $string['nofiles'] = 'No files. '; $string['nograde'] = 'No grade. '; $string['nolatesubmissions'] = 'No late submissions accepted. '; $string['nomoresubmissionsaccepted'] = 'Only allowed for participants who have been granted an extension'; +$string['none'] = 'None'; $string['noonlinesubmissions'] = 'This assignment does not require you to submit anything online'; +$string['noopen'] = 'No open date'; +$string['nooverridedata'] = 'You must override at least one of the assign settings.'; $string['nosavebutnext'] = 'Next'; $string['nosubmission'] = 'Nothing has been submitted for this assignment'; $string['nosubmissionsacceptedafter'] = 'No submissions accepted after '; @@ -330,6 +346,14 @@ $string['open'] = 'Open'; $string['outof'] = '{$a->current} out of {$a->total}'; $string['overdue'] = 'Assignment is overdue by: {$a}'; +$string['override'] = 'Override'; +$string['overridedeletegroupsure'] = 'Are you sure you want to delete the override for group {$a}?'; +$string['overridedeleteusersure'] = 'Are you sure you want to delete the override for user {$a}?'; +$string['overridegroup'] = 'Override group'; +$string['overridegroupeventname'] = '{$a->assign} - {$a->group}'; +$string['overrides'] = 'Overrides'; +$string['overrideuser'] = 'Override user'; +$string['overrideusereventname'] = '{$a->assign} - Override'; $string['outlinegrade'] = 'Grade: {$a}'; $string['page-mod-assign-x'] = 'Any assignment module page'; $string['page-mod-assign-view'] = 'Assignment module main and submission page'; @@ -346,6 +370,8 @@ $string['quickgradingresult'] = 'Quick grading'; $string['quickgradingchangessaved'] = 'The grade changes were saved'; $string['quickgrading_help'] = 'Quick grading allows you to assign grades (and outcomes) directly in the submissions table. Quick grading is not compatible with advanced grading and is not recommended when there are multiple markers.'; +$string['removeallgroupoverrides'] = 'Delete all group overrides'; +$string['removealluseroverrides'] = 'Delete all user overrides'; $string['reopenuntilpassincompatiblewithblindmarking'] = 'Reopen until pass option is incompatible with blind marking, because the grades are not released to the gradebook until the student identities are revealed.'; $string['requiresubmissionstatement'] = 'Require that students accept the submission statement'; $string['requiresubmissionstatement_help'] = 'Require that students accept the submission statement for all submissions to this assignment.'; @@ -354,16 +380,19 @@ $string['recordid'] = 'Identifier'; $string['revealidentities'] = 'Reveal student identities'; $string['revealidentitiesconfirm'] = 'Are you sure you want to reveal student identities for this assignment? This operation cannot be undone. Once the student identities have been revealed, the marks will be released to the gradebook.'; +$string['reverttodefaults'] = 'Revert to assign defaults'; $string['reverttodraftforstudent'] = 'Revert submission to draft for student: (id={$a->id}, fullname={$a->fullname}).'; $string['reverttodraft'] = 'Revert the submission to draft status.'; $string['reverttodraftshort'] = 'Revert the submission to draft'; $string['reviewed'] = 'Reviewed'; +$string['save'] = 'Save'; $string['saveallquickgradingchanges'] = 'Save all quick grading changes'; $string['saveandcontinue'] = 'Save and continue'; $string['savechanges'] = 'Save changes'; $string['savegradingresult'] = 'Grade'; $string['savenext'] = 'Save and show next'; $string['savingchanges'] = 'Saving changes...'; +$string['saveoverrideandstay'] = 'Save and enter another override'; $string['scale'] = 'Scale'; $string['search:activity'] = 'Assignment - activity information'; $string['sendstudentnotificationsdefault'] = 'Default setting for "Notify students"'; @@ -482,6 +511,9 @@ $string['upgradenotimplemented'] = 'Upgrade not implemented in plugin ({$a->type} {$a->subtype})'; $string['userextensiondate'] = 'Extension granted until: {$a}'; $string['useridlistnotcached'] = 'The grade changes were NOT saved, as it was not possible to determine which submission they were for.'; +$string['useroverrides'] = 'User overrides'; +$string['useroverridesdeleted'] = 'User overrides deleted'; +$string['usersnone'] = 'No students have access to this assign'; $string['userswhoneedtosubmit'] = 'Users who need to submit: {$a}'; $string['usergrade'] = 'User grade'; $string['validmarkingworkflowstates'] = 'Valid marking workflow states'; diff --git a/mod/assign/lib.php b/mod/assign/lib.php index 750404cd1012a..a847e62c498ee 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -166,6 +166,10 @@ function assign_reset_course_form_definition(&$mform) { $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign')); $name = get_string('deleteallsubmissions', 'assign'); $mform->addElement('advcheckbox', 'reset_assign_submissions', $name); + $mform->addElement('advcheckbox', 'reset_assign_user_overrides', + get_string('removealluseroverrides', 'assign')); + $mform->addElement('advcheckbox', 'reset_assign_group_overrides', + get_string('removeallgroupoverrides', 'assign')); } /** @@ -174,7 +178,9 @@ function assign_reset_course_form_definition(&$mform) { * @return array */ function assign_reset_course_form_defaults($course) { - return array('reset_assign_submissions'=>1); + return array('reset_assign_submissions' => 1, + 'reset_assign_group_overrides' => 1, + 'reset_assign_user_overrides' => 1); } /** @@ -193,6 +199,138 @@ function assign_update_instance(stdClass $data, $form) { return $assignment->update_instance($data); } +/** + * This function updates the events associated to the assign. + * If $override is non-zero, then it updates only the events + * associated with the specified override. + * + * @uses ASSIGN_MAX_EVENT_LENGTH + * @param object $assign the assign object. + * @param object $override (optional) limit to a specific override + */ +function assign_update_events($assign, $override = null) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/calendar/lib.php'); + + // Load the old events relating to this assign. + $conds = array('modulename' => 'assign', + 'instance' => $assign->get_context()->id); + if (!empty($override)) { + // Only load events for this override. + if (isset($override->userid)) { + $conds['userid'] = $override->userid; + } else { + $conds['groupid'] = $override->groupid; + } + } + $oldevents = $DB->get_records('event', $conds); + + // Now make a todo list of all that needs to be updated. + if (empty($override)) { + // We are updating the primary settings for the assign, so we + // need to add all the overrides. + $overrides = $DB->get_records('assign_overrides', array('assignid' => $assign->id)); + // As well as the original assign (empty override). + $overrides[] = new stdClass(); + } else { + // Just do the one override. + $overrides = array($override); + } + + foreach ($overrides as $current) { + $groupid = isset($current->groupid) ? $current->groupid : 0; + $userid = isset($current->userid) ? $current->userid : 0; + $allowsubmissionsfromdate = isset($current->allowsubmissionsfromdate + ) ? $current->allowsubmissionsfromdate : $assign->get_context()->allowsubmissionsfromdate; + $duedate = isset($current->duedate) ? $current->duedate : $assign->get_context()->duedate; + + // Only add open/close events for an override if they differ from the assign default. + $addopen = empty($current->id) || !empty($current->allowsubmissionsfromdate); + $addclose = empty($current->id) || !empty($current->duedate); + + if (!empty($assign->coursemodule)) { + $cmid = $assign->coursemodule; + } else { + $cmid = get_coursemodule_from_instance('assign', $assign->get_context()->id, $assign->get_context()->course)->id; + } + + $event = new stdClass(); + $event->description = format_module_intro('assign', $assign->get_context(), $cmid); + // Events module won't show user events when the courseid is nonzero. + $event->courseid = ($userid) ? 0 : $assign->get_context()->course; + $event->groupid = $groupid; + $event->userid = $userid; + $event->modulename = 'assign'; + $event->instance = $assign->get_context()->id; + $event->timestart = $allowsubmissionsfromdate; + $event->timeduration = max($duedate - $allowsubmissionsfromdate, 0); + $event->visible = instance_is_visible('assign', $assign); + $event->eventtype = 'open'; + + // Determine the event name. + if ($groupid) { + $params = new stdClass(); + $params->assign = $assign->get_context()->name; + $params->group = groups_get_group_name($groupid); + if ($params->group === false) { + // Group doesn't exist, just skip it. + continue; + } + $eventname = get_string('overridegroupeventname', 'assign', $params); + } else if ($userid) { + $params = new stdClass(); + $params->assign = $assign->get_context()->name; + $eventname = get_string('overrideusereventname', 'assign', $params); + } else { + $eventname = $assign->name; + } + if ($addopen or $addclose) { + if ($duedate and $allowsubmissionsfromdate and $event->timeduration <= ASSIGN_MAX_EVENT_LENGTH) { + // Single event for the whole assign. + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } else { + unset($event->id); + } + $event->name = $eventname; + // The method calendar_event::create will reuse a db record if the id field is set. + calendar_event::create($event); + } else { + // Separate start and end events. + $event->timeduration = 0; + if ($allowsubmissionsfromdate && $addopen) { + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } else { + unset($event->id); + } + $event->name = $eventname.' ('.get_string('open', 'assign').')'; + // The method calendar_event::create will reuse a db record if the id field is set. + calendar_event::create($event); + } + if ($duedate && $addclose) { + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } else { + unset($event->id); + } + $event->name = $eventname.' ('.get_string('duedate', 'assign').')'; + $event->timestart = $duedate; + $event->eventtype = 'close'; + calendar_event::create($event); + } + } + } + } + + // Delete any leftover events. + foreach ($oldevents as $badevent) { + $badevent = calendar_event::load($badevent); + $badevent->delete(); + } +} + /** * Return the list if Moodle features this module supports * @@ -249,6 +387,17 @@ function assign_grading_areas_list() { function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) { global $PAGE, $DB; + // We want to add these new nodes after the Edit settings node, and before the + // Locally assigned roles node. Of course, both of those are controlled by capabilities. + $keys = $navref->get_children_key_list(); + $beforekey = null; + $i = array_search('modedit', $keys); + if ($i === false and array_key_exists(0, $keys)) { + $beforekey = $keys[0]; + } else if (array_key_exists($i + 1, $keys)) { + $beforekey = $keys[$i + 1]; + } + $cm = $PAGE->cm; if (!$cm) { return; @@ -261,6 +410,19 @@ function assign_extend_settings_navigation(settings_navigation $settings, naviga return; } + if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) { + $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id)); + $node = navigation_node::create(get_string('groupoverrides', 'assign'), + new moodle_url($url, array('mode' => 'group')), + navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides'); + $navref->add_node($node, $beforekey); + + $node = navigation_node::create(get_string('useroverrides', 'assign'), + new moodle_url($url, array('mode' => 'user')), + navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides'); + $navref->add_node($node, $beforekey); + } + // Link to gradebook. if (has_capability('gradereport/grader:view', $cm->context) && has_capability('moodle/grade:viewall', $cm->context)) { diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index d8e49ab7ed52b..7666a8bdaf1cf 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -61,6 +61,9 @@ define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease'); define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released'); +/** ASSIGN_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */ +define("ASSIGN_MAX_EVENT_LENGTH", "432000"); + // Name of file area for intro attachments. define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment'); @@ -740,6 +743,8 @@ public function delete_instance() { $result = false; } + $this->delete_all_overrides(); + // Delete_records will throw an exception if it fails - so no need for error checking here. $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id)); $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id)); @@ -758,6 +763,170 @@ public function delete_instance() { return $result; } + /** + * Deletes a assign override from the database and clears any corresponding calendar events + * + * @param int $overrideid The id of the override being deleted + * @return bool true on success + */ + public function delete_override($overrideid) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/calendar/lib.php'); + + $cm = get_coursemodule_from_instance('assign', $this->get_context()->id, $this->get_context()->course); + + $override = $DB->get_record('assign_overrides', array('id' => $overrideid), '*', MUST_EXIST); + + // Delete the events. + $conds = array('modulename' => 'assign', + 'instance' => $this->get_context()->id); + if (isset($override->userid)) { + $conds['userid'] = $override->userid; + } else { + $conds['groupid'] = $override->groupid; + } + $events = $DB->get_records('event', $conds); + foreach ($events as $event) { + $eventold = calendar_event::load($event); + $eventold->delete(); + } + + $DB->delete_records('assign_overrides', array('id' => $overrideid)); + + // Set the common parameters for one of the events we will be triggering. + $params = array( + 'objectid' => $override->id, + 'context' => context_module::instance($cm->id), + 'other' => array( + 'assignid' => $override->assignid + ) + ); + // Determine which override deleted event to fire. + if (!empty($override->userid)) { + $params['relateduserid'] = $override->userid; + $event = \mod_assign\event\user_override_deleted::create($params); + } else { + $params['other']['groupid'] = $override->groupid; + $event = \mod_assign\event\group_override_deleted::create($params); + } + + // Trigger the override deleted event. + $event->add_record_snapshot('assign_overrides', $override); + $event->trigger(); + + return true; + } + + /** + * Deletes all assign overrides from the database and clears any corresponding calendar events + */ + public function delete_all_overrides() { + global $DB; + + $overrides = $DB->get_records('assign_overrides', array('assignid' => $this->get_context()->id), 'id'); + foreach ($overrides as $override) { + $this->delete_override($override->id); + } + } + + /** + * Updates the assign properties with override information for a user. + * + * Algorithm: For each assign setting, if there is a matching user-specific override, + * then use that otherwise, if there are group-specific overrides, return the most + * lenient combination of them. If neither applies, leave the assign setting unchanged. + * + * @param int $userid The userid. + */ + public function update_effective_access($userid) { + + $override = $this->override_exists($userid); + + // Merge with assign defaults. + $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); + foreach ($keys as $key) { + if (isset($override->{$key})) { + $this->get_instance()->{$key} = $override->{$key}; + } + } + + } + + /** + * Returns user override + * + * Algorithm: For each assign setting, if there is a matching user-specific override, + * then use that otherwise, if there are group-specific overrides, return the most + * lenient combination of them. If neither applies, leave the assign setting unchanged. + * + * @param int $userid The userid. + * @return override if exist + */ + public function override_exists($userid) { + global $DB; + + // Check for user override. + $override = $DB->get_record('assign_overrides', array('assignid' => $this->get_instance()->id, 'userid' => $userid)); + + if (!$override) { + $override = new stdClass(); + $override->duedate = null; + $override->cutoffdate = null; + $override->allowsubmissionsfromdate = null; + } + + // Check for group overrides. + $groupings = groups_get_user_groups($this->get_instance()->course, $userid); + + if (!empty($groupings[0])) { + // Select all overrides that apply to the User's groups. + list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[0])); + $sql = "SELECT * FROM {assign_overrides} + WHERE groupid $extra AND assignid = ?"; + $params[] = $this->get_instance()->id; + $records = $DB->get_records_sql($sql, $params); + + // Combine the overrides. + $duedates = array(); + $cutoffdates = array(); + $allowsubmissionsfromdates = array(); + + foreach ($records as $gpoverride) { + if (isset($gpoverride->duedate)) { + $duedates[] = $gpoverride->duedate; + } + if (isset($gpoverride->cutoffdate)) { + $cutoffdates[] = $gpoverride->cutoffdate; + } + if (isset($gpoverride->allowsubmissionsfromdate)) { + $allowsubmissionsfromdates[] = $gpoverride->allowsubmissionsfromdate; + } + } + // If there is a user override for a setting, ignore the group override. + if (is_null($override->allowsubmissionsfromdate) && count($allowsubmissionsfromdates)) { + $override->allowsubmissionsfromdate = min($allowsubmissionsfromdates); + } + if (is_null($override->cutoffdate) && count($cutoffdates)) { + if (in_array(0, $cutoffdates)) { + $override->cutoffdate = 0; + } else { + $override->cutoffdate = max($cutoffdates); + } + } + if (is_null($override->duedate) && count($duedates)) { + if (in_array(0, $duedates)) { + $override->duedate = 0; + } else { + $override->duedate = max($duedates); + } + } + + } + + return $override; + } + /** * Actual implementation of the reset course functionality, delete all the * assignment submissions for course $data->courseid. @@ -826,8 +995,41 @@ public function reset_userdata($data) { } } } + + // Remove user overrides. + if (!empty($data->reset_assign_user_overrides)) { + $DB->delete_records_select('assign_overrides', + 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND userid IS NOT NULL', array($data->courseid)); + $status[] = array( + 'component' => $componentstr, + 'item' => get_string('useroverridesdeleted', 'assign'), + 'error' => false); + } + // Remove group overrides. + if (!empty($data->reset_assign_group_overrides)) { + $DB->delete_records_select('assign_overrides', + 'assignid IN (SELECT id FROM {assign} WHERE course = ?) AND groupid IS NOT NULL', array($data->courseid)); + $status[] = array( + 'component' => $componentstr, + 'item' => get_string('groupoverridesdeleted', 'assign'), + 'error' => false); + } + // Updating dates - shift may be negative too. if ($data->timeshift) { + $DB->execute("UPDATE {assign_overrides} + SET allowsubmissionsfromdate = allowsubmissionsfromdate + ? + WHERE assignid = ? AND allowsubmissionsfromdate <> 0", + array($data->timeshift, $this->get_instance()->id)); + $DB->execute("UPDATE {assign_overrides} + SET duedate = duedate + ? + WHERE assignid = ? AND duedate <> 0", + array($data->timeshift, $this->get_instance()->id)); + $DB->execute("UPDATE {assign_overrides} + SET cutoffdate = cutoffdate + ? + WHERE assignid =? AND cutoffdate <> 0", + array($data->timeshift, $this->get_instance()->id)); + shift_course_mod_dates('assign', array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'), $data->timeshift, @@ -2246,6 +2448,27 @@ protected function view_grant_extension($mform) { } $userlist = explode(',', $users); + $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); + $maxoverride = array('allowsubmissionsfromdate' => 0, 'duedate' => 0, 'cutoffdate' => 0); + foreach ($userlist as $userid) { + // To validate extension date with users overrides. + $override = $this->override_exists($userid); + foreach ($keys as $key) { + if ($override->{$key}) { + if ($maxoverride[$key] < $override->{$key}) { + $maxoverride[$key] = $override->{$key}; + } + } else if ($maxoverride[$key] < $this->get_instance()->{$key}) { + $maxoverride[$key] = $this->get_instance()->{$key}; + } + } + } + foreach ($keys as $key) { + if ($maxoverride[$key]) { + $this->get_instance()->{$key} = $maxoverride[$key]; + } + } + $formparams['userlist'] = $userlist; $data->selectedusers = $users; @@ -2456,6 +2679,10 @@ private function view_course_index() { $assignment = new assign($context, $cm, $course); + // Apply overrides. + $assignment->update_effective_access($USER->id); + $timedue = $assignment->get_instance()->duedate; + if (has_capability('mod/assign:grade', $context)) { $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED); @@ -3257,6 +3484,9 @@ protected function view_single_grading_panel($args) { $userid = $args['userid']; $attemptnumber = $args['attemptnumber']; + // Apply overrides. + $this->update_effective_access($userid); + $rownum = 0; $useridlist = array($userid); @@ -3454,6 +3684,7 @@ protected function view_single_grade_page($mform) { $user = $DB->get_record('user', array('id' => $userid)); if ($user) { + $this->update_effective_access($userid); $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context()); $usersummary = new assign_user_summary($user, $this->get_course()->id, @@ -3865,6 +4096,8 @@ protected function view_grader() { $currentgroup = groups_get_activity_group($this->get_course_module(), true); $framegrader = new grading_app($userid, $currentgroup, $this); + $this->update_effective_access($userid); + $o .= $this->get_renderer()->render($framegrader); $o .= $this->view_footer(); @@ -5825,6 +6058,27 @@ protected function process_save_extension(& $mform) { } $userlist = explode(',', $users); + $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); + $maxoverride = array('allowsubmissionsfromdate' => 0, 'duedate' => 0, 'cutoffdate' => 0); + foreach ($userlist as $userid) { + // To validate extension date with users overrides. + $override = $this->override_exists($userid); + foreach ($keys as $key) { + if ($override->{$key}) { + if ($maxoverride[$key] < $override->{$key}) { + $maxoverride[$key] = $override->{$key}; + } + } else if ($maxoverride[$key] < $this->get_instance()->{$key}) { + $maxoverride[$key] = $this->get_instance()->{$key}; + } + } + } + foreach ($keys as $key) { + if ($maxoverride[$key]) { + $this->get_instance()->{$key} = $maxoverride[$key]; + } + } + $formparams = array( 'instance' => $this->get_instance(), 'assign' => $this, @@ -5856,7 +6110,6 @@ protected function process_save_extension(& $mform) { return false; } - /** * Save quick grades. * @@ -8406,3 +8659,102 @@ public static function base_supported_formats() { return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A); } } + +/** + * Logic to happen when a/some group(s) has/have been deleted in a course. + * + * @param int $courseid The course ID. + * @param int $groupid The group id if it is known + * @return void + */ +function assign_process_group_deleted_in_course($courseid, $groupid = null) { + global $DB; + + $params = array('courseid' => $courseid); + if ($groupid) { + $params['groupid'] = $groupid; + // We just update the group that was deleted. + $sql = "SELECT o.id, o.assignid + FROM {assign_overrides} o + JOIN {assign} assign ON assign.id = o.assignid + WHERE assign.course = :courseid + AND o.groupid = :groupid"; + } else { + // No groupid, we update all orphaned group overrides for all assign in course. + $sql = "SELECT o.id, o.assignid + FROM {assign_overrides} o + JOIN {assign} assign ON assign.id = o.assignid + LEFT JOIN {groups} grp ON grp.id = o.groupid + WHERE assign.course = :courseid + AND o.groupid IS NOT NULL + AND grp.id IS NULL"; + } + $records = $DB->get_records_sql_menu($sql, $params); + if (!$records) { + return; // Nothing to do. + } + $DB->delete_records_list('assign_overrides', 'id', array_keys($records)); +} + +/** + * Change the sort order of an override + * + * @param int $id of the override + * @param string $move direction of move + * @param int $assignid of the assignment + * @return bool success of operation + */ +function move_group_override($id, $move, $assignid) { + global $DB; + + // Get the override object. + if (!$override = $DB->get_record('assign_overrides', array('id' => $id), 'id, sortorder')) { + return false; + } + // Count the number of group overrides. + $overridecountgroup = $DB->count_records('assign_overrides', array('userid' => null, 'assignid' => $assignid)); + + // Calculate the new sortorder. + if ( ($move == 'up') and ($override->sortorder > 1)) { + $neworder = $override->sortorder - 1; + } else if (($move == 'down') and ($override->sortorder < $overridecountgroup)) { + $neworder = $override->sortorder + 1; + } else { + return false; + } + + // Retrieve the override object that is currently residing in the new position. + $params = array('sortorder' => $neworder, 'assignid' => $assignid); + if ($swapoverride = $DB->get_record('assign_overrides', $params, 'id, sortorder')) { + + // Swap the sortorders. + $swapoverride->sortorder = $override->sortorder; + $override->sortorder = $neworder; + + // Update the override records. + $DB->update_record('assign_overrides', $override); + $DB->update_record('assign_overrides', $swapoverride); + } + + reorder_group_overrides($assignid); + return true; +} + +/** + * Reorder the overrides starting at the override at the given startorder. + * + * @param int $assignid of the assigment + */ +function reorder_group_overrides($assignid) { + global $DB; + + $i = 1; + if ($overrides = $DB->get_records('assign_overrides', array('userid' => null, 'assignid' => $assignid), 'sortorder ASC')) { + foreach ($overrides as $override) { + $f = new stdClass(); + $f->id = $override->id; + $f->sortorder = $i++; + $DB->update_record('assign_overrides', $f); + } + } +} diff --git a/mod/assign/override_form.php b/mod/assign/override_form.php new file mode 100644 index 0000000000000..ef0944d63108d --- /dev/null +++ b/mod/assign/override_form.php @@ -0,0 +1,293 @@ +. + +/** + * Settings form for overrides in the assign module. + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); +require_once($CFG->dirroot . '/mod/assign/mod_form.php'); + + +/** + * Form for editing settings overrides. + * + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class assign_override_form extends moodleform { + + /** @var object course module object. */ + protected $cm; + + /** @var object the assign settings object. */ + protected $assign; + + /** @var context the assign context. */ + protected $context; + + /** @var bool editing group override (true) or user override (false). */ + protected $groupmode; + + /** @var int groupid, if provided. */ + protected $groupid; + + /** @var int userid, if provided. */ + protected $userid; + + /** + * Constructor. + * @param moodle_url $submiturl the form action URL. + * @param object $cm course module object. + * @param object $assign the assign settings object. + * @param object $context the assign context. + * @param bool $groupmode editing group override (true) or user override (false). + * @param object $override the override being edited, if it already exists. + */ + public function __construct($submiturl, $cm, $assign, $context, $groupmode, $override) { + + $this->cm = $cm; + $this->assign = $assign; + $this->context = $context; + $this->groupmode = $groupmode; + $this->groupid = empty($override->groupid) ? 0 : $override->groupid; + $this->userid = empty($override->userid) ? 0 : $override->userid; + + parent::__construct($submiturl, null, 'post'); + + } + + /** + * Define this form - called by the parent constructor + */ + protected function definition() { + global $CFG, $DB; + + $cm = $this->cm; + $mform = $this->_form; + + $mform->addElement('header', 'override', get_string('override', 'assign')); + + if ($this->groupmode) { + // Group override. + if ($this->groupid) { + // There is already a groupid, so freeze the selector. + $groupchoices = array(); + $groupchoices[$this->groupid] = groups_get_group_name($this->groupid); + $mform->addElement('select', 'groupid', + get_string('overridegroup', 'assign'), $groupchoices); + $mform->freeze('groupid'); + } else { + // Prepare the list of groups. + $groups = groups_get_all_groups($cm->course); + if (empty($groups)) { + // Generate an error. + $link = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id)); + print_error('groupsnone', 'assign', $link); + } + + $groupchoices = array(); + foreach ($groups as $group) { + $groupchoices[$group->id] = $group->name; + } + unset($groups); + + if (count($groupchoices) == 0) { + $groupchoices[0] = get_string('none'); + } + + $mform->addElement('select', 'groupid', + get_string('overridegroup', 'assign'), $groupchoices); + $mform->addRule('groupid', get_string('required'), 'required', null, 'client'); + } + } else { + // User override. + if ($this->userid) { + // There is already a userid, so freeze the selector. + $user = $DB->get_record('user', array('id' => $this->userid)); + $userchoices = array(); + $userchoices[$this->userid] = fullname($user); + $mform->addElement('select', 'userid', + get_string('overrideuser', 'assign'), $userchoices); + $mform->freeze('userid'); + } else { + // Prepare the list of users. + $users = get_enrolled_users($this->context, '', 0, + 'u.id, u.email, ' . get_all_user_name_fields(true, 'u')); + + // Filter users based on any fixed restrictions (groups, profile). + $info = new \core_availability\info_module($cm); + $users = $info->filter_user_list($users); + + if (empty($users)) { + // Generate an error. + $link = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id)); + print_error('usersnone', 'assign', $link); + } + + $userchoices = array(); + $canviewemail = in_array('email', get_extra_user_fields($this->context)); + foreach ($users as $id => $user) { + if (empty($invalidusers[$id]) || (!empty($override) && + $id == $override->userid)) { + if ($canviewemail) { + $userchoices[$id] = fullname($user) . ', ' . $user->email; + } else { + $userchoices[$id] = fullname($user); + } + } + } + unset($users); + + if (count($userchoices) == 0) { + $userchoices[0] = get_string('none'); + } + $mform->addElement('searchableselector', 'userid', + get_string('overrideuser', 'assign'), $userchoices); + $mform->addRule('userid', get_string('required'), 'required', null, 'client'); + } + } + + $users = $DB->get_fieldset_select('groups_members', 'userid', 'groupid = ?', array($this->groupid)); + array_push($users, $this->userid); + $extensionmax = 0; + foreach ($users as $value) { + $extension = $DB->get_record('assign_user_flags', array('assignment' => $this->assign->get_context()->id, + 'userid' => $value)); + if ($extension) { + if ($extensionmax < $extension->extensionduedate) { + $extensionmax = $extension->extensionduedate; + } + } + } + + if ($extensionmax) { + $this->assign->get_context()->extensionduedate = $extensionmax; + } + + // Open and close dates. + $mform->addElement('date_time_selector', 'allowsubmissionsfromdate', + get_string('allowsubmissionsfromdate', 'assign'), array('optional' => true)); + $mform->setDefault('allowsubmissionsfromdate', $this->assign->get_context()->allowsubmissionsfromdate); + + $mform->addElement('date_time_selector', 'duedate', get_string('duedate', 'assign'), array('optional' => true)); + $mform->setDefault('duedate', $this->assign->get_context()->duedate); + + $mform->addElement('date_time_selector', 'cutoffdate', get_string('cutoffdate', 'assign'), array('optional' => true)); + $mform->setDefault('cutoffdate', $this->assign->get_context()->cutoffdate); + + if (isset($this->assign->get_context()->extensionduedate)) { + $mform->addElement('static', 'extensionduedate', get_string('extensionduedate', 'assign'), + userdate($this->assign->get_context()->extensionduedate)); + } + + // Submit buttons. + $mform->addElement('submit', 'resetbutton', + get_string('reverttodefaults', 'assign')); + + $buttonarray = array(); + $buttonarray[] = $mform->createElement('submit', 'submitbutton', + get_string('save', 'assign')); + $buttonarray[] = $mform->createElement('submit', 'againbutton', + get_string('saveoverrideandstay', 'assign')); + $buttonarray[] = $mform->createElement('cancel'); + + $mform->addGroup($buttonarray, 'buttonbar', '', array(' '), false); + $mform->closeHeaderBefore('buttonbar'); + + } + + /** + * Validate the submitted form data. + * + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $files array of uploaded files "element_name"=>tmp_file_path + * @return array of "element_name"=>"error_description" if there are errors + */ + public function validation($data, $files) { + global $COURSE, $DB; + $errors = parent::validation($data, $files); + + $mform =& $this->_form; + $assign = $this->assign; + + if ($mform->elementExists('userid')) { + if (empty($data['userid'])) { + $errors['userid'] = get_string('required'); + } + } + + if ($mform->elementExists('groupid')) { + if (empty($data['groupid'])) { + $errors['groupid'] = get_string('required'); + } + } + + // Ensure that the dates make sense. + if (!empty($data['allowsubmissionsfromdate']) && !empty($data['cutoffdate'])) { + if ($data['cutoffdate'] < $data['allowsubmissionsfromdate']) { + $errors['cutoffdate'] = get_string('cutoffdatefromdatevalidation', 'assign'); + } + } + + if (!empty($data['allowsubmissionsfromdate']) && !empty($data['duedate'])) { + if ($data['duedate'] < $data['allowsubmissionsfromdate']) { + $errors['duedate'] = get_string('duedatevalidation', 'assign'); + } + } + + if (!empty($data['cutoffdate']) && !empty($data['duedate'])) { + if ($data['cutoffdate'] < $data['duedate'] ) { + $errors['cutoffdate'] = get_string('cutoffdatevalidation', 'assign'); + } + } + + // Ensure that override duedate/allowsubmissionsfromdate are before extension date if exist. + if (!empty($assign->get_context()->extensionduedate) && !empty($data['duedate'])) { + if ($assign->get_context()->extensionduedate < $data['duedate']) { + $errors['duedate'] = get_string('extensionnotafterduedate', 'assign'); + } + } + if (!empty($assign->get_context()->extensionduedate) && !empty($data['allowsubmissionsfromdate'])) { + if ($assign->get_context()->extensionduedate < $data['allowsubmissionsfromdate']) { + $errors['allowsubmissionsfromdate'] = get_string('extensionnotafterfromdate', 'assign'); + } + } + + // Ensure that at least one assign setting was changed. + $changed = false; + $keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); + foreach ($keys as $key) { + if ($data[$key] != $assign->get_context()->{$key}) { + $changed = true; + break; + } + } + + if (!$changed) { + $errors['allowsubmissionsfromdate'] = get_string('nooverridedata', 'assign'); + } + + return $errors; + } +} diff --git a/mod/assign/overridedelete.php b/mod/assign/overridedelete.php new file mode 100644 index 0000000000000..ba37c60dd69ec --- /dev/null +++ b/mod/assign/overridedelete.php @@ -0,0 +1,96 @@ +. + +/** + * This page handles deleting assigment overrides + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/assign/lib.php'); +require_once($CFG->dirroot.'/mod/assign/locallib.php'); +require_once($CFG->dirroot.'/mod/assign/override_form.php'); + +$overrideid = required_param('id', PARAM_INT); +$confirm = optional_param('confirm', false, PARAM_BOOL); + +if (! $override = $DB->get_record('assign_overrides', array('id' => $overrideid))) { + print_error('invalidoverrideid', 'assign'); +} + +$assign = new assign($DB->get_record('assign', array('id' => $override->assignid), '*', MUST_EXIST), null, null); + +if (! $cm = get_coursemodule_from_instance("assign", $assign->get_context()->id, $assign->get_context()->course)) { + print_error('invalidcoursemodule'); +} +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +$context = context_module::instance($cm->id); + +require_login($course, false, $cm); + +// Check the user has the required capabilities to modify an override. +require_capability('mod/assign:manageoverrides', $context); + +$url = new moodle_url('/mod/assign/overridedelete.php', array('id' => $override->id)); +$confirmurl = new moodle_url($url, array('id' => $override->id, 'confirm' => 1)); +$cancelurl = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id)); + +if (!empty($override->userid)) { + $cancelurl->param('mode', 'user'); +} + +// If confirm is set (PARAM_BOOL) then we have confirmation of intention to delete. +if ($confirm) { + require_sesskey(); + + $assign->delete_override($override->id); + + reorder_group_overrides($assign->get_context()->id); + + redirect($cancelurl); +} + +// Prepare the page to show the confirmation form. +$stroverride = get_string('override', 'assign'); +$title = get_string('deletecheck', null, $stroverride); + +$PAGE->set_url($url); +$PAGE->set_pagelayout('admin'); +$PAGE->navbar->add($title); +$PAGE->set_title($title); +$PAGE->set_heading($course->fullname); + +echo $OUTPUT->header(); +echo $OUTPUT->heading(format_string($assign->get_context()->name, true, array('context' => $context))); + +if ($override->groupid) { + $group = $DB->get_record('groups', array('id' => $override->groupid), 'id, name'); + $confirmstr = get_string("overridedeletegroupsure", "assign", $group->name); +} else { + $namefields = get_all_user_name_fields(true); + $user = $DB->get_record('user', array('id' => $override->userid), + 'id, ' . $namefields); + $confirmstr = get_string("overridedeleteusersure", "assign", fullname($user)); +} + +echo $OUTPUT->confirm($confirmstr, $confirmurl, $cancelurl); + +echo $OUTPUT->footer(); diff --git a/mod/assign/overrideedit.php b/mod/assign/overrideedit.php new file mode 100644 index 0000000000000..6a7f6666b1b10 --- /dev/null +++ b/mod/assign/overrideedit.php @@ -0,0 +1,240 @@ +. + +/** + * This page handles editing and creation of assign overrides + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/assign/lib.php'); +require_once($CFG->dirroot.'/mod/assign/locallib.php'); +require_once($CFG->dirroot.'/mod/assign/override_form.php'); + + +$cmid = optional_param('cmid', 0, PARAM_INT); +$overrideid = optional_param('id', 0, PARAM_INT); +$action = optional_param('action', null, PARAM_ALPHA); +$reset = optional_param('reset', false, PARAM_BOOL); + +$override = null; +if ($overrideid) { + + if (! $override = $DB->get_record('assign_overrides', array('id' => $overrideid))) { + print_error('invalidoverrideid', 'assign'); + } + + $assign = new assign($DB->get_record('assign', array('id' => $override->assignid), '*', MUST_EXIST), null, null); + + list($course, $cm) = get_course_and_cm_from_instance($assign->get_context(), 'assign'); + +} else if ($cmid) { + list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'assign'); + $assign = new assign($DB->get_record('assign', array('id' => $cm->instance), '*', MUST_EXIST), null, null); + +} else { + print_error('invalidcoursemodule'); +} +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +$url = new moodle_url('/mod/assign/overrideedit.php'); +if ($action) { + $url->param('action', $action); +} +if ($overrideid) { + $url->param('id', $overrideid); +} else { + $url->param('cmid', $cmid); +} + +$PAGE->set_url($url); + +require_login($course, false, $cm); + +$context = context_module::instance($cm->id); + +// Add or edit an override. +require_capability('mod/assign:manageoverrides', $context); + +if ($overrideid) { + // Editing an override. + $data = clone $override; +} else { + // Creating a new override. + $data = new stdClass(); +} + +// Merge assign defaults with data. +$keys = array('duedate', 'cutoffdate', 'allowsubmissionsfromdate'); +foreach ($keys as $key) { + if (!isset($data->{$key}) || $reset) { + $data->{$key} = $assign->get_context()->{$key}; + } +} + +// True if group-based override. +$groupmode = !empty($data->groupid) || ($action === 'addgroup' && empty($overrideid)); + +// If we are duplicating an override, then clear the user/group and override id +// since they will change. +if ($action === 'duplicate') { + $override->id = $data->id = null; + $override->userid = $data->userid = null; + $override->groupid = $data->groupid = null; +} + +$overridelisturl = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id)); +if (!$groupmode) { + $overridelisturl->param('mode', 'user'); +} + +// Setup the form. +$mform = new assign_override_form($url, $cm, $assign, $context, $groupmode, $override); +$mform->set_data($data); + +if ($mform->is_cancelled()) { + redirect($overridelisturl); + +} else if (optional_param('resetbutton', 0, PARAM_ALPHA)) { + $url->param('reset', true); + redirect($url); + +} else if ($fromform = $mform->get_data()) { + // Process the data. + $fromform->assignid = $assign->get_context()->id; + + // Replace unchanged values with null. + foreach ($keys as $key) { + if (($fromform->{$key} == $assign->get_context()->{$key})) { + $fromform->{$key} = null; + } + } + + // See if we are replacing an existing override. + $userorgroupchanged = false; + if (empty($override->id)) { + $userorgroupchanged = true; + } else if (!empty($fromform->userid)) { + $userorgroupchanged = $fromform->userid !== $override->userid; + } else { + $userorgroupchanged = $fromform->groupid !== $override->groupid; + } + + if ($userorgroupchanged) { + $conditions = array( + 'assignid' => $assign->get_context()->id, + 'userid' => empty($fromform->userid) ? null : $fromform->userid, + 'groupid' => empty($fromform->groupid) ? null : $fromform->groupid); + if ($oldoverride = $DB->get_record('assign_overrides', $conditions)) { + // There is an old override, so we merge any new settings on top of + // the older override. + foreach ($keys as $key) { + if (is_null($fromform->{$key})) { + $fromform->{$key} = $oldoverride->{$key}; + } + } + + $assign->delete_override($oldoverride->id); + } + } + + // Set the common parameters for one of the events we may be triggering. + $params = array( + 'context' => $context, + 'other' => array( + 'assignid' => $assign->get_context()->id + ) + ); + if (!empty($override->id)) { + $fromform->id = $override->id; + $DB->update_record('assign_overrides', $fromform); + + // Determine which override updated event to fire. + $params['objectid'] = $override->id; + if (!$groupmode) { + $params['relateduserid'] = $fromform->userid; + $event = \mod_assign\event\user_override_updated::create($params); + } else { + $params['other']['groupid'] = $fromform->groupid; + $event = \mod_assign\event\group_override_updated::create($params); + } + + // Trigger the override updated event. + $event->trigger(); + } else { + unset($fromform->id); + $fromform->id = $DB->insert_record('assign_overrides', $fromform); + if ($groupmode) { + $fromform->sortorder = $fromform->id; + + $overridecountgroup = $DB->count_records('assign_overrides', + array('userid' => null, 'assignid' => $assign->get_context()->id)); + $overridecountall = $DB->count_records('assign_overrides', array('assignid' => $assign->get_context()->id)); + if ((!$overridecountgroup) && ($overridecountall)) { // No group overrides and there are user overrides. + $fromform->sortorder = 1; + } else { + $fromform->sortorder = $overridecountgroup; + + } + + $DB->update_record('assign_overrides', $fromform); + + } + + // Determine which override created event to fire. + $params['objectid'] = $fromform->id; + if (!$groupmode) { + $params['relateduserid'] = $fromform->userid; + $event = \mod_assign\event\user_override_created::create($params); + } else { + $params['other']['groupid'] = $fromform->groupid; + $event = \mod_assign\event\group_override_created::create($params); + } + + // Trigger the override created event. + $event->trigger(); + } + + assign_update_events($assign, $fromform); + + if (!empty($fromform->submitbutton)) { + redirect($overridelisturl); + } + + // The user pressed the 'again' button, so redirect back to this page. + $url->remove_params('cmid'); + $url->param('action', 'duplicate'); + $url->param('id', $fromform->id); + redirect($url); + +} + +// Print the form. +$pagetitle = get_string('editoverride', 'assign'); +$PAGE->navbar->add($pagetitle); +$PAGE->set_pagelayout('admin'); +$PAGE->set_title($pagetitle); +$PAGE->set_heading($course->fullname); +echo $OUTPUT->header(); +echo $OUTPUT->heading(format_string($assign->get_context()->name, true, array('context' => $context))); + +$mform->display(); + +echo $OUTPUT->footer(); diff --git a/mod/assign/overrides.php b/mod/assign/overrides.php new file mode 100644 index 0000000000000..457e2c5dc8d87 --- /dev/null +++ b/mod/assign/overrides.php @@ -0,0 +1,300 @@ +. + +/** + * This page handles listing of assign overrides + * + * @package mod_assign + * @copyright 2016 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/assign/lib.php'); +require_once($CFG->dirroot.'/mod/assign/locallib.php'); +require_once($CFG->dirroot.'/mod/assign/override_form.php'); + + +$cmid = required_param('cmid', PARAM_INT); +$mode = optional_param('mode', '', PARAM_ALPHA); // One of 'user' or 'group', default is 'group'. + +$action = optional_param('action', '', PARAM_ALPHA); +$redirect = $CFG->wwwroot.'/mod/assign/overrides.php?cmid=' . $cmid . '&mode=group'; + +list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'assign'); +$assign = $DB->get_record('assign', array('id' => $cm->instance), '*', MUST_EXIST); + +$overridecountgroup = $DB->count_records('assign_overrides', array('userid' => null, 'assignid' => $assign->id)); + +// Get the course groups. +$groups = groups_get_all_groups($cm->course); +if ($groups === false) { + $groups = array(); +} + +// Default mode is "group", unless there are no groups. +if ($mode != "user" and $mode != "group") { + if (!empty($groups)) { + $mode = "group"; + } else { + $mode = "user"; + } +} +$groupmode = ($mode == "group"); + +$url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id, 'mode' => $mode)); + +$PAGE->set_url($url); + +require_login($course, false, $cm); + +$context = context_module::instance($cm->id); + +// Check the user has the required capabilities to list overrides. +require_capability('mod/assign:manageoverrides', $context); + +if ($action == 'movegroupoverride') { + $id = required_param('id', PARAM_INT); + $dir = required_param('dir', PARAM_ALPHA); + + if (confirm_sesskey()) { + move_group_override($id, $dir, $assign->id); + } + redirect($redirect); +} + +// Display a list of overrides. +$PAGE->set_pagelayout('admin'); +$PAGE->set_title(get_string('overrides', 'assign')); +$PAGE->set_heading($course->fullname); +echo $OUTPUT->header(); +echo $OUTPUT->heading(format_string($assign->name, true, array('context' => $context))); + +// Delete orphaned group overrides. +$sql = 'SELECT o.id + FROM {assign_overrides} o LEFT JOIN {groups} g + ON o.groupid = g.id + WHERE o.groupid IS NOT NULL + AND g.id IS NULL + AND o.assignid = ?'; +$params = array($assign->id); +$orphaned = $DB->get_records_sql($sql, $params); +if (!empty($orphaned)) { + $DB->delete_records_list('assign_overrides', 'id', array_keys($orphaned)); +} + +// Fetch all overrides. +if ($groupmode) { + $colname = get_string('group'); + $sql = 'SELECT o.*, g.name + FROM {assign_overrides} o + JOIN {groups} g ON o.groupid = g.id + WHERE o.assignid = :assignid + ORDER BY o.sortorder'; + $params = array('assignid' => $assign->id); +} else { + $colname = get_string('user'); + list($sort, $params) = users_order_by_sql('u'); + $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + FROM {assign_overrides} o + JOIN {user} u ON o.userid = u.id + WHERE o.assignid = :assignid + ORDER BY ' . $sort; + $params['assignid'] = $assign->id; +} + +$overrides = $DB->get_records_sql($sql, $params); + +// Initialise table. +$table = new html_table(); +$table->headspan = array(1, 2, 1); +$table->colclasses = array('colname', 'colsetting', 'colvalue', 'colaction'); +$table->head = array( + $colname, + get_string('overrides', 'assign'), + get_string('action'), +); + +$userurl = new moodle_url('/user/view.php', array()); +$groupurl = new moodle_url('/group/overview.php', array('id' => $cm->course)); + +$overridedeleteurl = new moodle_url('/mod/assign/overridedelete.php'); +$overrideediturl = new moodle_url('/mod/assign/overrideedit.php'); + +$hasinactive = false; // Whether there are any inactive overrides. + +foreach ($overrides as $override) { + + $fields = array(); + $values = array(); + $active = true; + + // Check for inactive overrides. + if (!$groupmode) { + if (!is_enrolled($context, $override->userid)) { + // User not enrolled. + $active = false; + } else if (!\core_availability\info_module::is_user_visible($cm, $override->userid)) { + // User cannot access the module. + $active = false; + } + } + + // Format allowsubmissionsfromdate. + if (isset($override->allowsubmissionsfromdate)) { + $fields[] = get_string('open', 'assign'); + $values[] = $override->allowsubmissionsfromdate > 0 ? userdate($override->allowsubmissionsfromdate) : get_string('noopen', + 'assign'); + } + + // Format duedate. + if (isset($override->duedate)) { + $fields[] = get_string('duedate', 'assign'); + $values[] = $override->duedate > 0 ? userdate($override->duedate) : get_string('noclose', 'assign'); + } + + // Format cutoffdate. + if (isset($override->cutoffdate)) { + $fields[] = get_string('cutoffdate', 'assign'); + $values[] = $override->cutoffdate > 0 ? userdate($override->cutoffdate) : get_string('noclose', 'assign'); + } + + // Icons. + $iconstr = ''; + + if ($active) { + // Edit. + $editurlstr = $overrideediturl->out(true, array('id' => $override->id)); + $iconstr = '' . + '' .
+                get_string('edit') . ' '; + // Duplicate. + $copyurlstr = $overrideediturl->out(true, + array('id' => $override->id, 'action' => 'duplicate')); + $iconstr .= '' . + '' .
+                get_string('copy') . ' '; + } + // Delete. + $deleteurlstr = $overridedeleteurl->out(true, + array('id' => $override->id, 'sesskey' => sesskey())); + $iconstr .= '' . + '' .
+            get_string('delete') . ' '; + + if ($groupmode) { + $usergroupstr = '' . $override->name . ''; + + // Move up. + if ($override->sortorder > 1) { + $iconstr .= ' + '.get_string('moveup').' '; + } else { + $iconstr .= ' '; + } + + // Move down. + if ($override->sortorder < $overridecountgroup) { + $iconstr .= ' + '.get_string('movedown').' '; + } else { + $iconstr .= ' '; + } + + + } else { + $usergroupstr = '' . fullname($override) . ''; + } + + $class = ''; + if (!$active) { + $class = "dimmed_text"; + $usergroupstr .= '*'; + $hasinactive = true; + } + + $usergroupcell = new html_table_cell(); + $usergroupcell->rowspan = count($fields); + $usergroupcell->text = $usergroupstr; + $actioncell = new html_table_cell(); + $actioncell->rowspan = count($fields); + $actioncell->text = $iconstr; + + for ($i = 0; $i < count($fields); ++$i) { + $row = new html_table_row(); + $row->attributes['class'] = $class; + if ($i == 0) { + $row->cells[] = $usergroupcell; + } + $cell1 = new html_table_cell(); + $cell1->text = $fields[$i]; + $row->cells[] = $cell1; + $cell2 = new html_table_cell(); + $cell2->text = $values[$i]; + $row->cells[] = $cell2; + if ($i == 0) { + $row->cells[] = $actioncell; + } + $table->data[] = $row; + } +} + +// Output the table and button. +echo html_writer::start_tag('div', array('id' => 'assignoverrides')); +if (count($table->data)) { + echo html_writer::table($table); +} +if ($hasinactive) { + echo $OUTPUT->notification(get_string('inactiveoverridehelp', 'assign'), 'dimmed_text'); +} + +echo html_writer::start_tag('div', array('class' => 'buttons')); +$options = array(); +if ($groupmode) { + if (empty($groups)) { + // There are no groups. + echo $OUTPUT->notification(get_string('groupsnone', 'assign'), 'error'); + $options['disabled'] = true; + } + echo $OUTPUT->single_button($overrideediturl->out(true, + array('action' => 'addgroup', 'cmid' => $cm->id)), + get_string('addnewgroupoverride', 'assign'), 'post', $options); +} else { + $users = array(); + // See if there are any users in the assign. + $users = get_enrolled_users($context); + $info = new \core_availability\info_module($cm); + $users = $info->filter_user_list($users); + + if (empty($users)) { + // There are no users. + echo $OUTPUT->notification(get_string('usersnone', 'assign'), 'error'); + $options['disabled'] = true; + } + echo $OUTPUT->single_button($overrideediturl->out(true, + array('action' => 'adduser', 'cmid' => $cm->id)), + get_string('addnewuseroverride', 'assign'), 'get', $options); +} +echo html_writer::end_tag('div'); +echo html_writer::end_tag('div'); + +// Finish the page. +echo $OUTPUT->footer(); diff --git a/mod/assign/templates/grading_navigation.mustache b/mod/assign/templates/grading_navigation.mustache index 4b107deec3fc5..5109fbb3d3e5c 100644 --- a/mod/assign/templates/grading_navigation.mustache +++ b/mod/assign/templates/grading_navigation.mustache @@ -72,9 +72,6 @@ {{#caneditsettings}} {{#pix}}t/edit, core,{{#str}}editsettings{{/str}}{{/pix}} {{/caneditsettings}} -{{#duedate}} -{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}} -{{/duedate}} diff --git a/mod/assign/templates/grading_navigation_user_summary.mustache b/mod/assign/templates/grading_navigation_user_summary.mustache index 49ee55c3ff76d..e4c49c77f8cab 100644 --- a/mod/assign/templates/grading_navigation_user_summary.mustache +++ b/mod/assign/templates/grading_navigation_user_summary.mustache @@ -37,4 +37,4 @@ "profileimageurl": "https://moodle.org/pix/u/f3.png" } }} -

{{^blindmarking}}{{/blindmarking}}{{#profileimageurl}}{{/profileimageurl}} {{fullname}}{{#hasidentity}} {{identity}} {{/hasidentity}}{{^blindmarking}}{{/blindmarking}}

+

{{^blindmarking}}{{/blindmarking}}{{#profileimageurl}}{{/profileimageurl}} {{fullname}}{{#hasidentity}} {{identity}} {{/hasidentity}}{{#duedate}}{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}}{{/duedate}}{{^blindmarking}}{{/blindmarking}}

diff --git a/mod/assign/tests/behat/assign_course_reset.feature b/mod/assign/tests/behat/assign_course_reset.feature new file mode 100644 index 0000000000000..287220b2215d9 --- /dev/null +++ b/mod/assign/tests/behat/assign_course_reset.feature @@ -0,0 +1,110 @@ +@mod @mod_assign +Feature: Assign reset + In order to reuse past Assignss + As a teacher + I need to remove all previous data. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Tina | Teacher1 | teacher1@example.com | + | student1 | Sam1 | Student1 | student1@example.com | + | student2 | Sam2 | Student2 | student2@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + | Group 2 | C1 | G2 | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment name | + | Description | Submit your online text | + | assignsubmission_onlinetext_enabled | 1 | + | assignsubmission_onlinetext_wordlimit_enabled | 1 | + | assignsubmission_onlinetext_wordlimit | 10 | + | assignsubmission_file_enabled | 0 | + + Scenario: Use course reset to clear all attempt data + When I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + When I press "Add submission" + And I set the following fields to these values: + | Online text | I'm the student first submission | + And I press "Save changes" + Then I should see "Submitted for grading" + And I should see "I'm the student first submission" + And I should see "Not graded" + And I log out + And I log in as "teacher1" + And I follow "Course 1" + And I follow "Test assignment name" + And I navigate to "View all submissions" node in "Assignment administration" + And I should see "Submitted for grading" + And I navigate to "Reset" node in "Course administration" + And I set the following fields to these values: + | Delete all submissions | 1 | + And I press "Reset course" + And I press "Continue" + And I follow "Course 1" + And I follow "Test assignment name" + And I navigate to "View all submissions" node in "Assignment administration" + Then I should not see "Submitted for grading" + + Scenario: Use course reset to remove user overrides. + When I follow "Test assignment name" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Sam1 Student1" + And I navigate to "Reset" node in "Course administration" + And I set the following fields to these values: + | Delete all user overrides | 1 | + And I press "Reset course" + And I press "Continue" + And I follow "Course 1" + And I follow "Test assignment name" + And I navigate to "User overrides" node in "Assignment administration" + Then I should not see "Sam1 Student1" + + Scenario: Use course reset to remove group overrides. + When I follow "Test assignment name" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Group 1" + And I navigate to "Reset" node in "Course administration" + And I set the following fields to these values: + | Delete all group overrides | 1 | + And I press "Reset course" + And I press "Continue" + And I follow "Course 1" + And I follow "Test assignment name" + And I navigate to "Group overrides" node in "Assignment administration" + Then I should not see "Group 1" diff --git a/mod/assign/tests/behat/assign_group_override.feature b/mod/assign/tests/behat/assign_group_override.feature new file mode 100644 index 0000000000000..393b13d730b02 --- /dev/null +++ b/mod/assign/tests/behat/assign_group_override.feature @@ -0,0 +1,248 @@ +@mod @mod_assign +Feature: Assign group override + In order to grant a group special access to an assignment + As a teacher + I need to create an override for that group. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Tina | Teacher1 | teacher1@example.com | + | student1 | Sam1 | Student1 | student1@example.com | + | student2 | Sam2 | Student2 | student2@example.com | + | student3 | Sam3 | Student3 | student3@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + | Group 2 | C1 | G2 | + Given the following "group members" exist: + | user | group | + | student1 | G1 | + | student2 | G2 | + | student3 | G1 | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment name | + | Description | Submit your online text | + | assignsubmission_onlinetext_enabled | 1 | + | assignsubmission_onlinetext_wordlimit_enabled | 1 | + | assignsubmission_onlinetext_wordlimit | 10 | + | assignsubmission_file_enabled | 0 | + + Scenario: Add, modify then delete a group override + When I follow "Test assignment name" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + Then I click on "Edit" "link" + And I set the following fields to these values: + | duedate[year] | 2030 | + And I press "Save" + And I should see "Tuesday, 1 January 2030, 8:00" + And I click on "Delete" "link" + And I press "Continue" + And I should not see "Group 1" + + Scenario: Duplicate a user override + When I follow "Test assignment name" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + Then I click on "copy" "link" + And I set the following fields to these values: + | Override group | Group 2 | + | duedate[year] | 2030 | + And I press "Save" + And I should see "Tuesday, 1 January 2030, 8:00" + And I should see "Group 2" + + Scenario: Allow a group to have a different due date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 1 | + | id_allowsubmissionsfromdate_enabled | 0 | + | id_cutoffdate_enabled | 0 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2000 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save and display" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should see "Saturday, 1 January 2000, 8:00" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "Wednesday, 1 January 2020, 8:00" + + Scenario: Allow a group to have a different cut off date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 0 | + | id_allowsubmissionsfromdate_enabled | 0 | + | id_cutoffdate_enabled | 1 | + | cutoffdate[day] | 1 | + | cutoffdate[month] | January | + | cutoffdate[year] | 2000 | + | cutoffdate[hour] | 08 | + | cutoffdate[minute] | 00 | + And I press "Save and display" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_cutoffdate_enabled | 1 | + | cutoffdate[day] | 1 | + | cutoffdate[month] | January | + | cutoffdate[year] | 2020 | + | cutoffdate[hour] | 08 | + | cutoffdate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should not see "Make changes to your submission" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "Make changes to your submission" + + Scenario: Allow a group to have a different start date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 0 | + | id_allowsubmissionsfromdate_enabled | 1 | + | id_cutoffdate_enabled | 0 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2020 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save and display" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_allowsubmissionsfromdate_enabled | 1 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2015 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save" + And I should see "Thursday, 1 January 2015, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00" + And I should not see "Add submission" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should not see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00" + + Scenario: Add both a user and group override and verify that both are applied correctly + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 0 | + | id_allowsubmissionsfromdate_enabled | 1 | + | id_cutoffdate_enabled | 0 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2030 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save and display" + And I navigate to "Group overrides" node in "Assignment administration" + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | id_allowsubmissionsfromdate_enabled | 1 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2020 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_allowsubmissionsfromdate_enabled | 1 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2021 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save" + And I should see "Friday, 1 January 2021, 8:00" + And I log out + Then I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "This assignment will accept submissions from Friday, 1 January 2021, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00" + And I log out + And I log in as "student3" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00" diff --git a/mod/assign/tests/behat/assign_user_override.feature b/mod/assign/tests/behat/assign_user_override.feature new file mode 100644 index 0000000000000..c9071bd4c15dc --- /dev/null +++ b/mod/assign/tests/behat/assign_user_override.feature @@ -0,0 +1,183 @@ +@mod @mod_assign +Feature: Assign user override + In order to grant a student special access to an assignment + As a teacher + I need to create an override for that user. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Tina | Teacher1 | teacher1@example.com | + | student1 | Sam1 | Student1 | student1@example.com | + | student2 | Sam2 | Student2 | student2@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Assignment" to section "1" and I fill the form with: + | Assignment name | Test assignment name | + | Description | Submit your online text | + | assignsubmission_onlinetext_enabled | 1 | + | assignsubmission_onlinetext_wordlimit_enabled | 1 | + | assignsubmission_onlinetext_wordlimit | 10 | + | assignsubmission_file_enabled | 0 | + + Scenario: Add, modify then delete a user override + When I follow "Test assignment name" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + Then I click on "Edit" "link" + And I set the following fields to these values: + | duedate[year] | 2030 | + And I press "Save" + And I should see "Tuesday, 1 January 2030, 8:00" + And I click on "Delete" "link" + And I press "Continue" + And I should not see "Sam1 Student1" + + Scenario: Duplicate a user override + When I follow "Test assignment name" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + Then I click on "copy" "link" + And I set the following fields to these values: + | Override user | Student2 | + | duedate[year] | 2030 | + And I press "Save" + And I should see "Tuesday, 1 January 2030, 8:00" + And I should see "Sam2 Student2" + + Scenario: Allow a user to have a different due date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 1 | + | id_allowsubmissionsfromdate_enabled | 0 | + | id_cutoffdate_enabled | 0 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2000 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save and display" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_duedate_enabled | 1 | + | duedate[day] | 1 | + | duedate[month] | January | + | duedate[year] | 2020 | + | duedate[hour] | 08 | + | duedate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should see "Saturday, 1 January 2000, 8:00" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "Wednesday, 1 January 2020, 8:00" + + Scenario: Allow a user to have a different cut off date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 0 | + | id_allowsubmissionsfromdate_enabled | 0 | + | id_cutoffdate_enabled | 1 | + | cutoffdate[day] | 1 | + | cutoffdate[month] | January | + | cutoffdate[year] | 2000 | + | cutoffdate[hour] | 08 | + | cutoffdate[minute] | 00 | + And I press "Save and display" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_cutoffdate_enabled | 1 | + | cutoffdate[day] | 1 | + | cutoffdate[month] | January | + | cutoffdate[year] | 2020 | + | cutoffdate[hour] | 08 | + | cutoffdate[minute] | 00 | + And I press "Save" + And I should see "Wednesday, 1 January 2020, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should not see "Make changes to your submission" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should see "Make changes to your submission" + + Scenario: Allow a user to have a different start date + When I follow "Test assignment name" + And I navigate to "Edit settings" node in "Assignment administration" + And I set the following fields to these values: + | id_duedate_enabled | 0 | + | id_allowsubmissionsfromdate_enabled | 1 | + | id_cutoffdate_enabled | 0 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2020 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save and display" + And I navigate to "User overrides" node in "Assignment administration" + And I press "Add user override" + And I set the following fields to these values: + | Override user | Student1 | + | id_allowsubmissionsfromdate_enabled | 1 | + | allowsubmissionsfromdate[day] | 1 | + | allowsubmissionsfromdate[month] | January | + | allowsubmissionsfromdate[year] | 2015 | + | allowsubmissionsfromdate[hour] | 08 | + | allowsubmissionsfromdate[minute] | 00 | + And I press "Save" + And I should see "Thursday, 1 January 2015, 8:00" + And I log out + And I log in as "student2" + And I follow "Course 1" + And I follow "Test assignment name" + Then I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00" + And I log out + And I log in as "student1" + And I follow "Course 1" + And I follow "Test assignment name" + And I should not see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00" diff --git a/mod/assign/tests/events_test.php b/mod/assign/tests/events_test.php index 787350b941cba..946f4bdedf67c 100644 --- a/mod/assign/tests/events_test.php +++ b/mod/assign/tests/events_test.php @@ -27,6 +27,7 @@ global $CFG; require_once($CFG->dirroot . '/mod/assign/tests/base_test.php'); require_once($CFG->dirroot . '/mod/assign/tests/fixtures/event_mod_assign_fixtures.php'); +require_once($CFG->dirroot . '/mod/assign/locallib.php'); /** * Contains the event tests for the module assign. @@ -1023,4 +1024,193 @@ public function test_batch_set_marker_allocation_viewed() { $this->assertEventLegacyLogData($expected, $event); $this->assertEventContextNotUsed($event); } + + /** + * Test the user override created event. + * + * There is no external API for creating a user override, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_user_override_created() { + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + + $params = array( + 'objectid' => 1, + 'relateduserid' => 2, + 'context' => context_module::instance($assign->cmid), + 'other' => array( + 'assignid' => $assign->id + ) + ); + $event = \mod_assign\event\user_override_created::create($params); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\user_override_created', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the group override created event. + * + * There is no external API for creating a group override, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_group_override_created() { + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + + $params = array( + 'objectid' => 1, + 'context' => context_module::instance($assign->cmid), + 'other' => array( + 'assignid' => $assign->id, + 'groupid' => 2 + ) + ); + $event = \mod_assign\event\group_override_created::create($params); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\group_override_created', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the user override updated event. + * + * There is no external API for updating a user override, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_user_override_updated() { + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + + $params = array( + 'objectid' => 1, + 'relateduserid' => 2, + 'context' => context_module::instance($assign->cmid), + 'other' => array( + 'assignid' => $assign->id + ) + ); + $event = \mod_assign\event\user_override_updated::create($params); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\user_override_updated', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the group override updated event. + * + * There is no external API for updating a group override, so the unit test will simply + * create and trigger the event and ensure the event data is returned as expected. + */ + public function test_group_override_updated() { + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + + $params = array( + 'objectid' => 1, + 'context' => context_module::instance($assign->cmid), + 'other' => array( + 'assignid' => $assign->id, + 'groupid' => 2 + ) + ); + $event = \mod_assign\event\group_override_updated::create($params); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $event->trigger(); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\group_override_updated', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the user override deleted event. + */ + public function test_user_override_deleted() { + global $DB; + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + $this->assign = new assign($assign, null, null); + + // Create an override. + $override = new stdClass(); + $override->assign = $this->assign->get_context()->id; + $override->userid = 2; + $override->id = $DB->insert_record('assign_overrides', $override); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $this->assign->delete_override($override->id); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\user_override_deleted', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } + + /** + * Test the group override deleted event. + */ + public function test_group_override_deleted() { + global $DB; + + $course = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); + $this->assign = new assign($assign, null, null); + + // Create an override. + $override = new stdClass(); + $override->assign = $this->assign->get_context()->id; + $override->groupid = 2; + $override->id = $DB->insert_record('assign_overrides', $override); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + $this->assign->delete_override($override->id); + $events = $sink->get_events(); + $event = reset($events); + + // Check that the event data is valid. + $this->assertInstanceOf('\mod_assign\event\group_override_deleted', $event); + $this->assertEquals(context_module::instance($assign->cmid), $event->get_context()); + $this->assertEventContextNotUsed($event); + } } + diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index 4eba37a8a49e3..cc5c5a5f358fb 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -317,7 +317,7 @@ public function test_gradingtable_status_rendering() { $document = new DOMDocument(); @$document->loadHTML($output); $xpath = new DOMXPath($document); - $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')); + $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c11"])')); } /** @@ -367,20 +367,20 @@ public function test_gradingtable_group_submissions_rendering() { $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])')); // Check submission last modified date - $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'))); - $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])'))); + $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c11"])'))); + $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c11"])'))); // Check group. - $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])')); - $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])')); + $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')); + $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])')); // Check submission text. - $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)')); - $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)')); + $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c12"]/div/div)')); + $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c12"]/div/div)')); // Check comments can be made. - $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)')); - $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)')); + $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c13"]//textarea)')); + $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c13"]//textarea)')); } public function test_show_intro() { @@ -513,6 +513,8 @@ public function test_reset_userdata() { $data = new stdClass(); $data->reset_assign_submissions = 1; $data->reset_gradebook_grades = 1; + $data->reset_assign_user_overrides = 1; + $data->reset_assign_group_overrides = 1; $data->courseid = $this->course->id; $data->timeshift = 24*60*60; $this->setUser($this->editingteachers[0]); diff --git a/mod/assign/view.php b/mod/assign/view.php index 8c3ddfe64debf..571df31df7475 100644 --- a/mod/assign/view.php +++ b/mod/assign/view.php @@ -47,6 +47,9 @@ // Update module completion status. $assign->set_module_viewed(); +// Apply overrides. +$assign->update_effective_access($USER->id); + // Get the assign class to // render the page. echo $assign->view(optional_param('action', '', PARAM_TEXT)); From 6b8e798db1e3d7696655fbb2ffe9f10cb5de6915 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Sun, 23 Oct 2016 21:19:15 +0800 Subject: [PATCH 02/10] MDL-29795 assign: Apply overrides before saving a submission From the webservice. --- mod/assign/externallib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index fa283a237e951..a11e6cc0658c7 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -1788,6 +1788,7 @@ public static function save_submission($assignmentid, $plugindata) { $notices = array(); + $assign->update_effective_access($USER->id); if (!$assignment->submissions_open($USER->id)) { $notices[] = get_string('duedatereached', 'assign'); } else { From a18cb9d84f798830831d24bf8b57cd245fc14275 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 24 Oct 2016 11:14:50 +0100 Subject: [PATCH 03/10] MDL-29795 assign: Apply overrides to get_assignments This commit also fix a problem in the previous commit --- mod/assign/db/upgrade.php | 4 ++-- mod/assign/externallib.php | 10 ++++++---- mod/assign/version.php | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mod/assign/db/upgrade.php b/mod/assign/db/upgrade.php index 6b2797cfd8692..53302a8a19706 100644 --- a/mod/assign/db/upgrade.php +++ b/mod/assign/db/upgrade.php @@ -200,7 +200,7 @@ function xmldb_assign_upgrade($oldversion) { // Moodle v3.1.0 release upgrade line. // Put any upgrade step following this. - if ($oldversion < 2016070402) { + if ($oldversion < 2016100301) { // Define table assign_overrides to be created. $table = new xmldb_table('assign_overrides'); @@ -227,7 +227,7 @@ function xmldb_assign_upgrade($oldversion) { } // Assign savepoint reached. - upgrade_mod_savepoint(true, 2016070402, 'assign'); + upgrade_mod_savepoint(true, 2016100301, 'assign'); } return true; diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index a11e6cc0658c7..2515ea2d9a87f 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -403,6 +403,8 @@ public static function get_assignments($courseids = array(), $capabilities = arr } $assign = new assign($context, null, null); + // Update assign with override information. + $assign->update_effective_access($USER->id); // Get configurations for only enabled plugins. $plugins = $assign->get_submission_plugins(); @@ -433,12 +435,12 @@ public static function get_assignments($courseids = array(), $capabilities = arr 'sendnotifications' => $module->sendnotifications, 'sendlatenotifications' => $module->sendlatenotifications, 'sendstudentnotifications' => $module->sendstudentnotifications, - 'duedate' => $module->duedate, - 'allowsubmissionsfromdate' => $module->allowsubmissionsfromdate, + 'duedate' => $assign->get_instance()->duedate, + 'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate, 'grade' => $module->grade, 'timemodified' => $module->timemodified, 'completionsubmit' => $module->completionsubmit, - 'cutoffdate' => $module->cutoffdate, + 'cutoffdate' => $assign->get_instance()->cutoffdate, 'teamsubmission' => $module->teamsubmission, 'requireallteammemberssubmit' => $module->requireallteammemberssubmit, 'teamsubmissiongroupingid' => $module->teamsubmissiongroupingid, @@ -1788,7 +1790,7 @@ public static function save_submission($assignmentid, $plugindata) { $notices = array(); - $assign->update_effective_access($USER->id); + $assignment->update_effective_access($USER->id); if (!$assignment->submissions_open($USER->id)) { $notices[] = get_string('duedatereached', 'assign'); } else { diff --git a/mod/assign/version.php b/mod/assign/version.php index 6f0ee67a7b7f6..a9b4b152d1bfb 100644 --- a/mod/assign/version.php +++ b/mod/assign/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics). -$plugin->version = 2016100300; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2016100301; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2016051900; // Requires this Moodle version. $plugin->cron = 60; From f73c5b519c508392a741777a5590516ea5e59df3 Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 10:39:49 +1100 Subject: [PATCH 04/10] MDL-29795 assign: Add missing inactiveoverridehelp lang string --- mod/assign/lang/en/assign.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index ec3d966bee765..ff032237fc47c 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -258,6 +258,7 @@ $string['groupsnone'] = 'There are no groups in this course'; $string['hideshow'] = 'Hide/Show'; $string['hiddenuser'] = 'Participant '; +$string['inactiveoverridehelp'] = '* Student does not have the correct group or role to attempt the assignment'; $string['instructionfiles'] = 'Instruction files'; $string['introattachments'] = 'Additional files'; $string['introattachments_help'] = 'Additional files for use in the assignment, such as answer templates, may be added. Download links for the files will then be displayed on the assignment page under the description.'; From 34fac159fbcf20e04ab23a53df9541e73d778740 Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 11:38:40 +1100 Subject: [PATCH 05/10] MDL-29795 assign: Add course to user profile link --- mod/assign/overrides.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mod/assign/overrides.php b/mod/assign/overrides.php index 457e2c5dc8d87..e8472f86790f9 100644 --- a/mod/assign/overrides.php +++ b/mod/assign/overrides.php @@ -220,8 +220,9 @@ } else { - $usergroupstr = '' . fullname($override) . ''; + $usergroupstr = html_writer::link($userurl->out(false, + array('id' => $override->userid, 'course' => $course->id)), + fullname($override)); } $class = ''; From a13f1f4996dce1e23083e8e3bc55d1a1a86d34ec Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 12:37:53 +1100 Subject: [PATCH 06/10] MDL-29795 assign: Only display date columns if overrides --- mod/assign/gradingtable.php | 135 +++++++++++++++-------------- mod/assign/locallib.php | 17 ++++ mod/assign/tests/locallib_test.php | 18 ++-- 3 files changed, 97 insertions(+), 73 deletions(-) diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index 68e66586c7653..7648f69be4e07 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -154,11 +154,7 @@ public function __construct(assign $assignment, $fields .= 'uf.locked as locked, '; $fields .= 'uf.extensionduedate as extensionduedate, '; $fields .= 'uf.workflowstate as workflowstate, '; - $fields .= 'uf.allocatedmarker as allocatedmarker, '; - $fields .= 'priority.priority, '; - $fields .= 'effective.allowsubmissionsfromdate, '; - $fields .= 'effective.duedate, '; - $fields .= 'effective.cutoffdate '; + $fields .= 'uf.allocatedmarker as allocatedmarker'; $from = '{user} u LEFT JOIN {assign_submission} s @@ -188,61 +184,70 @@ public function __construct(assign $assignment, ON u.id = uf.userid AND uf.assignment = :assignmentid3 '; - $from .= ' LEFT JOIN ( - SELECT merged.userid, min(merged.priority) priority FROM ( - ( SELECT u.id as userid, 9999999 AS priority - FROM {user} u + $hasoverrides = $this->assignment->has_overrides(); + + if ($hasoverrides) { + $fields .= ', priority.priority, '; + $fields .= 'effective.allowsubmissionsfromdate, '; + $fields .= 'effective.duedate, '; + $fields .= 'effective.cutoffdate '; + + $from .= ' LEFT JOIN ( + SELECT merged.userid, min(merged.priority) priority FROM ( + ( SELECT u.id as userid, 9999999 AS priority + FROM {user} u + ) + UNION + ( SELECT uo.userid, 0 AS priority + FROM {assign_overrides} uo + WHERE uo.assignid = :assignmentid5 + ) + UNION + ( SELECT gm.userid, go.sortorder AS priority + FROM {assign_overrides} go + JOIN {groups} g ON g.id = go.groupid + JOIN {groups_members} gm ON gm.groupid = g.id + WHERE go.assignid = :assignmentid6 + ) + ) AS merged + GROUP BY merged.userid + ) priority ON priority.userid = u.id + + JOIN ( + (SELECT 9999999 AS priority, + u.id AS userid, + + a.allowsubmissionsfromdate, + a.duedate, + a.cutoffdate + FROM {user} u + JOIN {assign} a ON a.id = :assignmentid7 ) UNION - ( SELECT uo.userid, 0 AS priority - FROM {assign_overrides} uo - WHERE uo.assignid = :assignmentid5 + (SELECT 0 AS priority, + uo.userid, + + uo.allowsubmissionsfromdate, + uo.duedate, + uo.cutoffdate + FROM {assign_overrides} uo + WHERE uo.assignid = :assignmentid8 ) UNION - ( SELECT gm.userid, go.sortorder AS priority - FROM {assign_overrides} go - JOIN {groups} g ON g.id = go.groupid - JOIN {groups_members} gm ON gm.groupid = g.id - WHERE go.assignid = :assignmentid6 + (SELECT go.sortorder AS priority, + gm.userid, + + go.allowsubmissionsfromdate, + go.duedate, + go.cutoffdate + FROM {assign_overrides} go + JOIN {groups} g ON g.id = go.groupid + JOIN {groups_members} gm ON gm.groupid = g.id + WHERE go.assignid = :assignmentid9 ) - ) AS merged - GROUP BY merged.userid - ) priority ON priority.userid = u.id - - JOIN ( - (SELECT 9999999 AS priority, - u.id AS userid, - - a.allowsubmissionsfromdate, - a.duedate, - a.cutoffdate - FROM {user} u - JOIN {assign} a ON a.id = :assignmentid7 - ) - UNION - (SELECT 0 AS priority, - uo.userid, - - uo.allowsubmissionsfromdate, - uo.duedate, - uo.cutoffdate - FROM {assign_overrides} uo - WHERE uo.assignid = :assignmentid8 - ) - UNION - (SELECT go.sortorder AS priority, - gm.userid, - - go.allowsubmissionsfromdate, - go.duedate, - go.cutoffdate - FROM {assign_overrides} go - JOIN {groups} g ON g.id = go.groupid - JOIN {groups_members} gm ON gm.groupid = g.id - WHERE go.assignid = :assignmentid9 - ) - - ) AS effective ON effective.priority = priority.priority AND effective.userid = priority.userid '; + + ) AS effective ON effective.priority = priority.priority AND effective.userid = priority.userid '; + } if (!empty($this->assignment->get_instance()->blindmarking)) { $from .= 'LEFT JOIN {assign_user_mapping} um @@ -369,17 +374,19 @@ public function __construct(assign $assignment, $columns[] = 'status'; $headers[] = get_string('status', 'assign'); - // Allowsubmissionsfromdate. - $columns[] = 'allowsubmissionsfromdate'; - $headers[] = get_string('allowsubmissionsfromdate', 'assign'); + if ($hasoverrides) { + // Allowsubmissionsfromdate. + $columns[] = 'allowsubmissionsfromdate'; + $headers[] = get_string('allowsubmissionsfromdate', 'assign'); - // Duedate. - $columns[] = 'duedate'; - $headers[] = get_string('duedate', 'assign'); + // Duedate. + $columns[] = 'duedate'; + $headers[] = get_string('duedate', 'assign'); - // Cutoffdate. - $columns[] = 'cutoffdate'; - $headers[] = get_string('cutoffdate', 'assign'); + // Cutoffdate. + $columns[] = 'cutoffdate'; + $headers[] = get_string('cutoffdate', 'assign'); + } // Team submission columns. if ($assignment->get_instance()->teamsubmission) { diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 7666a8bdaf1cf..4b79af983248f 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -853,6 +853,23 @@ public function update_effective_access($userid) { } + /** + * Returns whether an assign has any overrides. + * + * @return true if any, false if not + */ + public function has_overrides() { + global $DB; + + $override = $DB->record_exists('assign_overrides', array('assignid' => $this->get_instance()->id)); + + if ($override) { + return true; + } + + return false; + } + /** * Returns user override * diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index cc5c5a5f358fb..d345fd074fd6b 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -317,7 +317,7 @@ public function test_gradingtable_status_rendering() { $document = new DOMDocument(); @$document->loadHTML($output); $xpath = new DOMXPath($document); - $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c11"])')); + $this->assertEquals('', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')); } /** @@ -367,20 +367,20 @@ public function test_gradingtable_group_submissions_rendering() { $this->assertSame(get_string('submissionstatus_submitted', 'assign'), $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c4"]/div[@class="submissionstatussubmitted"])')); // Check submission last modified date - $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c11"])'))); - $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c11"])'))); + $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])'))); + $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])'))); // Check group. - $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c8"])')); - $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c8"])')); + $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c5"])')); + $this->assertSame($this->groups[0]->name, $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c5"])')); // Check submission text. - $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c12"]/div/div)')); - $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c12"]/div/div)')); + $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r0_c9"]/div/div)')); + $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="mod_assign_grading_r3_c9"]/div/div)')); // Check comments can be made. - $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c13"]//textarea)')); - $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c13"]//textarea)')); + $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r0_c10"]//textarea)')); + $this->assertSame(1, (int)$xpath->evaluate('count(//td[@id="mod_assign_grading_r3_c10"]//textarea)')); } public function test_show_intro() { From eeaefbb850f7c8b64a3f923a071975e1f985eade Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 13:05:37 +1100 Subject: [PATCH 07/10] MDL-29795 assign: Add 'Duplicate override' breadcrumb --- mod/assign/lang/en/assign.php | 1 + mod/assign/overrideedit.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php index ff032237fc47c..2184e307e34ff 100644 --- a/mod/assign/lang/en/assign.php +++ b/mod/assign/lang/en/assign.php @@ -148,6 +148,7 @@ $string['duedatecolon'] = 'Due date: {$a}'; $string['duedate_help'] = 'This is when the assignment is due. Submissions will still be allowed after this date but any assignments submitted after this date are marked as late. To prevent submissions after a certain date - set the assignment cut off date.'; $string['duedateno'] = 'No due date'; +$string['duplicateoverride'] = 'Duplicate override'; $string['submissionempty'] = 'Nothing was submitted'; $string['submissionmodified'] = 'You have existing submission data. Please leave this page and try again.'; $string['submissionmodifiedgroup'] = 'The submission has been modified by somebody else. Please leave this page and try again.'; diff --git a/mod/assign/overrideedit.php b/mod/assign/overrideedit.php index 6a7f6666b1b10..227b363915f52 100644 --- a/mod/assign/overrideedit.php +++ b/mod/assign/overrideedit.php @@ -34,6 +34,8 @@ $action = optional_param('action', null, PARAM_ALPHA); $reset = optional_param('reset', false, PARAM_BOOL); +$pagetitle = get_string('editoverride', 'assign'); + $override = null; if ($overrideid) { @@ -98,6 +100,7 @@ $override->id = $data->id = null; $override->userid = $data->userid = null; $override->groupid = $data->groupid = null; + $pagetitle = get_string('duplicateoverride', 'assign'); } $overridelisturl = new moodle_url('/mod/assign/overrides.php', array('cmid' => $cm->id)); @@ -227,7 +230,6 @@ } // Print the form. -$pagetitle = get_string('editoverride', 'assign'); $PAGE->navbar->add($pagetitle); $PAGE->set_pagelayout('admin'); $PAGE->set_title($pagetitle); From 84a303b5d25f05fb36d116111ff0002f20fd4567 Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 14:42:32 +1100 Subject: [PATCH 08/10] MDL-29795 assign: Fix mustache template --- mod/assign/templates/grading_navigation_user_summary.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/assign/templates/grading_navigation_user_summary.mustache b/mod/assign/templates/grading_navigation_user_summary.mustache index e4c49c77f8cab..c48d1d871aca2 100644 --- a/mod/assign/templates/grading_navigation_user_summary.mustache +++ b/mod/assign/templates/grading_navigation_user_summary.mustache @@ -33,7 +33,7 @@ "id": "5", "fullname": "Mr T", "hasidentity": true, - "identity": "t@example.org, T" + "identity": "t@example.org, T", "profileimageurl": "https://moodle.org/pix/u/f3.png" } }} From e47c002c7567799e4ce396be0c86b8c273415cb8 Mon Sep 17 00:00:00 2001 From: Kenneth Hendricks Date: Thu, 27 Oct 2016 16:56:52 +1100 Subject: [PATCH 09/10] MDL-29795 assign: Tweak user-info section in grader view - also mirror due date removal in boost template --- mod/assign/externallib.php | 2 +- mod/assign/styles.css | 6 ++++++ .../templates/grading_navigation_user_summary.mustache | 2 +- .../boost/templates/mod_assign/grading_navigation.mustache | 3 --- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 2515ea2d9a87f..cf5169d825ec2 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -2741,7 +2741,7 @@ public static function get_participant($assignid, $userid, $embeduser) { 'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate, 'duedate' => $assign->get_instance()->duedate, 'cutoffdate' => $assign->get_instance()->cutoffdate, - 'duedatestr' => userdate($assign->get_instance()->duedate), + 'duedatestr' => userdate($assign->get_instance()->duedate, get_string('strftimedatetime', 'langconfig')), ); if (!empty($participant->groupid)) { diff --git a/mod/assign/styles.css b/mod/assign/styles.css index d3fbb47f9974f..1ecba03144425 100644 --- a/mod/assign/styles.css +++ b/mod/assign/styles.css @@ -436,6 +436,12 @@ font-style: normal; } +.path-mod-assign [data-region="user-info"] em.nowrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .path-mod-assign [data-region="grading-actions-form"] label { display: inline-block; } diff --git a/mod/assign/templates/grading_navigation_user_summary.mustache b/mod/assign/templates/grading_navigation_user_summary.mustache index c48d1d871aca2..0a3ad80932eb4 100644 --- a/mod/assign/templates/grading_navigation_user_summary.mustache +++ b/mod/assign/templates/grading_navigation_user_summary.mustache @@ -37,4 +37,4 @@ "profileimageurl": "https://moodle.org/pix/u/f3.png" } }} -

{{^blindmarking}}{{/blindmarking}}{{#profileimageurl}}{{/profileimageurl}} {{fullname}}{{#hasidentity}} {{identity}} {{/hasidentity}}{{#duedate}}{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}}{{/duedate}}{{^blindmarking}}{{/blindmarking}}

+

{{^blindmarking}}{{/blindmarking}}{{#profileimageurl}}{{/profileimageurl}} {{fullname}}{{#hasidentity}} {{identity}} {{/hasidentity}}{{#duedate}}{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}}{{/duedate}}{{^blindmarking}}{{/blindmarking}}

diff --git a/theme/boost/templates/mod_assign/grading_navigation.mustache b/theme/boost/templates/mod_assign/grading_navigation.mustache index bc769ab879126..9fb489a82004d 100644 --- a/theme/boost/templates/mod_assign/grading_navigation.mustache +++ b/theme/boost/templates/mod_assign/grading_navigation.mustache @@ -72,9 +72,6 @@ {{#caneditsettings}} {{#pix}}t/edit, core,{{#str}}editsettings{{/str}}{{/pix}} {{/caneditsettings}} -{{#duedate}} -{{#str}}duedatecolon, mod_assign, {{duedatestr}}{{/str}} -{{/duedate}} From 325ac748b2b693d56a37358852ab989db1eb4356 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Mon, 7 Nov 2016 09:18:07 +0800 Subject: [PATCH 10/10] MDL-29795 behat: Narrow Edit link scope --- mod/assign/tests/behat/assign_group_override.feature | 2 +- mod/assign/tests/behat/assign_user_override.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/assign/tests/behat/assign_group_override.feature b/mod/assign/tests/behat/assign_group_override.feature index 393b13d730b02..8e42ce10813dc 100644 --- a/mod/assign/tests/behat/assign_group_override.feature +++ b/mod/assign/tests/behat/assign_group_override.feature @@ -54,7 +54,7 @@ Feature: Assign group override | duedate[minute] | 00 | And I press "Save" And I should see "Wednesday, 1 January 2020, 8:00" - Then I click on "Edit" "link" + Then I click on "Edit" "link" in the "Group 1" "table_row" And I set the following fields to these values: | duedate[year] | 2030 | And I press "Save" diff --git a/mod/assign/tests/behat/assign_user_override.feature b/mod/assign/tests/behat/assign_user_override.feature index c9071bd4c15dc..184899f1459e1 100644 --- a/mod/assign/tests/behat/assign_user_override.feature +++ b/mod/assign/tests/behat/assign_user_override.feature @@ -43,7 +43,7 @@ Feature: Assign user override | duedate[minute] | 00 | And I press "Save" And I should see "Wednesday, 1 January 2020, 8:00" - Then I click on "Edit" "link" + Then I click on "Edit" "link" in the "Sam1 Student1" "table_row" And I set the following fields to these values: | duedate[year] | 2030 | And I press "Save"