diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index c3ead10b95b..5b26096097b 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -405,7 +405,7 @@ $string['gradingdetailszeropenalty'] = 'You were not penalized for this submissi $string['gradingmethod'] = 'Grading method: {$a}'; $string['groupoverrides'] = 'Group overrides'; $string['groupoverridesdeleted'] = 'Group overrides deleted'; -$string['groupsnone'] = 'There are no groups in this course'; +$string['groupsnone'] = 'No groups you can access.'; $string['guestsno'] = 'Sorry, guests cannot see or attempt quizzes'; $string['hidebreaks'] = 'Hide page breaks'; $string['hidereordertool'] = 'Hide the reordering tool'; diff --git a/mod/quiz/override_form.php b/mod/quiz/override_form.php index 695f82c668f..7561c136358 100644 --- a/mod/quiz/override_form.php +++ b/mod/quiz/override_form.php @@ -78,13 +78,16 @@ class quiz_override_form extends moodleform { } protected function definition() { - global $CFG, $DB; + global $DB; $cm = $this->cm; $mform = $this->_form; $mform->addElement('header', 'override', get_string('override', 'quiz')); + $quizgroupmode = groups_get_activity_groupmode($cm); + $accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $this->context); + if ($this->groupmode) { // Group override. if ($this->groupid) { @@ -96,7 +99,8 @@ class quiz_override_form extends moodleform { $mform->freeze('groupid'); } else { // Prepare the list of groups. - $groups = groups_get_all_groups($cm->course); + // Only include the groups the current can access. + $groups = $accessallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm); if (empty($groups)) { // Generate an error. $link = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id)); @@ -136,9 +140,17 @@ class quiz_override_form extends moodleform { 'This is unexpected, and a problem because there is no way to pass these ' . 'parameters to get_users_by_capability. See MDL-34657.'); } - $users = get_users_by_capability($this->context, 'mod/quiz:attempt', - 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'), - $sort, '', '', '', '', false, true); + + // Get the list of appropriate users, depending on whether and how groups are used. + if ($accessallgroups) { + $users = get_users_by_capability($this->context, 'mod/quiz:attempt', + 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'), + $sort); + } else if ($groups = groups_get_activity_allowed_groups($cm)) { + $users = get_users_by_capability($this->context, 'mod/quiz:attempt', + 'u.id, u.email, ' . get_all_user_name_fields(true, 'u'), + $sort, '', '', array_keys($groups)); + } // Filter users based on any fixed restrictions (groups, profile). $info = new \core_availability\info_module($cm); @@ -220,7 +232,6 @@ class quiz_override_form extends moodleform { } public function validation($data, $files) { - global $COURSE, $DB; $errors = parent::validation($data, $files); $mform =& $this->_form; diff --git a/mod/quiz/overrides.php b/mod/quiz/overrides.php index 2b7d7416a9b..a42c3d6f96f 100644 --- a/mod/quiz/overrides.php +++ b/mod/quiz/overrides.php @@ -35,11 +35,18 @@ $mode = optional_param('mode', '', PARAM_ALPHA); // One of 'user' or 'group', de list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'quiz'); $quiz = $DB->get_record('quiz', array('id' => $cm->instance), '*', MUST_EXIST); -// Get the course groups. -$groups = groups_get_all_groups($cm->course); -if ($groups === false) { - $groups = array(); -} +require_login($course, false, $cm); + +$context = context_module::instance($cm->id); + +// Check the user has the required capabilities to list overrides. +require_capability('mod/quiz:manageoverrides', $context); + +$quizgroupmode = groups_get_activity_groupmode($cm); +$accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $context); + +// Get the course groups that the current user can access. +$groups = $accessallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm); // Default mode is "group", unless there are no groups. if ($mode != "user" and $mode != "group") { @@ -55,13 +62,6 @@ $url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id, '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/quiz:manageoverrides', $context); - // Display a list of overrides. $PAGE->set_pagelayout('admin'); $PAGE->set_title(get_string('overrides', 'quiz')); @@ -71,38 +71,63 @@ echo $OUTPUT->heading(format_string($quiz->name, true, array('context' => $conte // Delete orphaned group overrides. $sql = 'SELECT o.id - FROM {quiz_overrides} o LEFT JOIN {groups} g - ON o.groupid = g.id - WHERE o.groupid IS NOT NULL - AND g.id IS NULL - AND o.quiz = ?'; + FROM {quiz_overrides} o + LEFT JOIN {groups} g ON o.groupid = g.id + WHERE o.groupid IS NOT NULL + AND g.id IS NULL + AND o.quiz = ?'; $params = array($quiz->id); $orphaned = $DB->get_records_sql($sql, $params); if (!empty($orphaned)) { $DB->delete_records_list('quiz_overrides', 'id', array_keys($orphaned)); } +$overrides = []; + // Fetch all overrides. if ($groupmode) { $colname = get_string('group'); - $sql = 'SELECT o.*, g.name - FROM {quiz_overrides} o - JOIN {groups} g ON o.groupid = g.id - WHERE o.quiz = :quizid - ORDER BY g.name'; - $params = array('quizid' => $quiz->id); + // To filter the result by the list of groups that the current user has access to. + if ($groups) { + $params = ['quizid' => $quiz->id]; + list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); + $params += $inparams; + + $sql = "SELECT o.*, g.name + FROM {quiz_overrides} o + JOIN {groups} g ON o.groupid = g.id + WHERE o.quiz = :quizid AND g.id $insql + ORDER BY g.name"; + + $overrides = $DB->get_records_sql($sql, $params); + } } else { $colname = get_string('user'); list($sort, $params) = users_order_by_sql('u'); - $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' - FROM {quiz_overrides} o - JOIN {user} u ON o.userid = u.id - WHERE o.quiz = :quizid - ORDER BY ' . $sort; $params['quizid'] = $quiz->id; -} -$overrides = $DB->get_records_sql($sql, $params); + if ($accessallgroups) { + $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + FROM {quiz_overrides} o + JOIN {user} u ON o.userid = u.id + WHERE o.quiz = :quizid + ORDER BY ' . $sort; + + $overrides = $DB->get_records_sql($sql, $params); + } else if ($groups) { + list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED); + $params += $inparams; + + $sql = 'SELECT o.*, ' . get_all_user_name_fields(true, 'u') . ' + FROM {quiz_overrides} o + JOIN {user} u ON o.userid = u.id + JOIN {groups_members} gm ON u.id = gm.userid + WHERE o.quiz = :quizid AND gm.groupid ' . $insql . ' + ORDER BY ' . $sort; + + $overrides = $DB->get_records_sql($sql, $params); + } +} // Initialise table. $table = new html_table(); @@ -256,13 +281,21 @@ if ($groupmode) { } else { $users = array(); // See if there are any students in the quiz. - $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id'); + if ($accessallgroups) { + $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id'); + $nousermessage = get_string('usersnone', 'quiz'); + } else if ($groups) { + $users = get_users_by_capability($context, 'mod/quiz:attempt', 'u.id', '', '', '', array_keys($groups)); + $nousermessage = get_string('usersnone', 'quiz'); + } else { + $nousermessage = get_string('groupsnone', 'quiz'); + } $info = new \core_availability\info_module($cm); $users = $info->filter_user_list($users); if (empty($users)) { // There are no students. - echo $OUTPUT->notification(get_string('usersnone', 'quiz'), 'error'); + echo $OUTPUT->notification($nousermessage, 'error'); $options['disabled'] = true; } echo $OUTPUT->single_button($overrideediturl->out(true, diff --git a/mod/quiz/tests/behat/quiz_group_override.feature b/mod/quiz/tests/behat/quiz_group_override.feature new file mode 100644 index 00000000000..b90b775a726 --- /dev/null +++ b/mod/quiz/tests/behat/quiz_group_override.feature @@ -0,0 +1,113 @@ +@mod @mod_quiz +Feature: Quiz group override + In order to grant a group special access to a quiz + As a teacher + I need to create an override for thta group. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Terry 1 | Teacher 1 | teacher1@example.com | + | student1 | Sam 1 | Student 1 | student1@example.com | + | teacher2 | Terry 2 | Teacher 2 | teacher2@example.com | + | student2 | Sam 2 | Student 2 | student2@example.com | + | teacher3 | Terry 3 | Teacher 3 | teacher3@example.com | + | student3 | Sam 3 | Student 3 | 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 | + | teacher2 | C1 | editingteacher | + | student2 | C1 | student | + | teacher3 | C1 | editingteacher | + | student3 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + | Group 2 | C1 | G2 | + | Group 3 | C1 | G3 | + And the following "group members" exist: + | user | group | + | student1 | G1 | + | teacher1 | G1 | + | teacher1 | G3 | + | student2 | G2 | + | teacher2 | G2 | + | teacher2 | G3 | + | student3 | G3 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | groupmode | + | quiz | Test quiz | Test quiz description | C1 | quiz1 | 1 | + + Scenario: Override Group 1 as teacher of Group 1 + Given the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | + When I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + And I press "Add group override" + Then the "Override group" select box should contain "Group 1" + And the "Override group" select box should not contain "Group 2" + + Scenario: Override Group 1 as teacher in no group + Given the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | + When I log in as "teacher3" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + Then I should see "No groups you can access." + And the "Add group override" "button" should be disabled + + Scenario: A teacher with accessallgroups permission should see all group overrides + Given I log in as "admin" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | Attempts allowed | 2 | + And I press "Save and enter another override" + And I set the following fields to these values: + | Override group | Group 2 | + | Attempts allowed | 2 | + And I press "Save" + And I log out + When I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + Then I should see "Group 1" in the ".generaltable" "css_element" + And I should see "Group 2" in the ".generaltable" "css_element" + + Scenario: A teacher without accessallgroups permission should only see the group overrides within his/her groups, when the activity's group mode is "separate groups" + Given the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | + And I log in as "admin" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + And I press "Add group override" + And I set the following fields to these values: + | Override group | Group 1 | + | Attempts allowed | 2 | + And I press "Save and enter another override" + And I set the following fields to these values: + | Override group | Group 2 | + | Attempts allowed | 2 | + And I press "Save" + And I log out + When I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test quiz" + And I navigate to "Group overrides" in current page administration + Then I should see "Group 1" in the ".generaltable" "css_element" + And I should not see "Group 2" in the ".generaltable" "css_element" diff --git a/mod/quiz/tests/behat/quiz_user_override.feature b/mod/quiz/tests/behat/quiz_user_override.feature index 4b7a5bfe210..344488fa7e1 100644 --- a/mod/quiz/tests/behat/quiz_user_override.feature +++ b/mod/quiz/tests/behat/quiz_user_override.feature @@ -1,4 +1,4 @@ -@mod @mod_quiz @javascript +@mod @mod_quiz Feature: Quiz user override In order to grant a student special access to a quiz As a teacher @@ -18,24 +18,14 @@ Feature: Quiz user override | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | - And the following "question categories" exist: - | contextlevel | reference | name | - | Course | C1 | Test questions | And the following "activities" exist: | activity | name | intro | course | idnumber | | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | - And the following "questions" exist: - | questioncategory | qtype | name | questiontext | - | Test questions | truefalse | TF1 | First question | - | Test questions | truefalse | TF2 | Second question | - And quiz "Quiz 1" contains the following questions: - | question | page | maxmark | - | TF1 | 1 | | - | TF2 | 1 | 3.0 | - And I log in as "teacher1" - And I am on "Course 1" course homepage + @javascript Scenario: Add, modify then delete a user override + Given I log in as "teacher1" + And I am on "Course 1" course homepage When I follow "Quiz 1" And I navigate to "User overrides" in current page administration And I press "Add user override" @@ -58,8 +48,11 @@ Feature: Quiz user override And I press "Continue" And I should not see "Student One" + @javascript Scenario: Being able to modify a user override when the quiz is not available to the student - Given I follow "Quiz 1" + Given I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Quiz 1" And I navigate to "Edit settings" in current page administration And I expand all fieldsets And I set the field "Availability" to "Hide from students" @@ -71,3 +64,48 @@ Feature: Quiz user override | Attempts allowed | 1 | And I press "Save" Then "Edit" "icon" should exist in the "Student One" "table_row" + + Scenario: A teacher without accessallgroups permission should only be able to add user override for users that he/she shares groups with, + when the activity's group mode is to "separate groups" + Given the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + | Group 2 | C1 | G2 | + And the following "group members" exist: + | user | group | + | student1 | G1 | + | teacher1 | G1 | + | student2 | G2 | + And the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | groupmode | + | quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 | 1 | + When I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Quiz 2" + And I navigate to "User overrides" in current page administration + And I press "Add user override" + Then the "Override user" select box should contain "Student One, student1@example.com" + And the "Override user" select box should not contain "Student Two, student2@example.com" + + Scenario: Override user in an activity with group mode set to "separate groups" as a teacher who is not a member in any group, and does not have accessallgroups permission + Given the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + And the following "group members" exist: + | user | group | + | student1 | G1 | + And the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | moodle/site:accessallgroups | Prevent | editingteacher | Course | C1 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | groupmode | + | quiz | Quiz 2 | Quiz 2 description | C1 | quiz2 | 1 | + When I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Quiz 2" + And I navigate to "User overrides" in current page administration + Then I should see "No groups you can access." + And the "Add user override" "button" should be disabled