diff --git a/group/overview.php b/group/overview.php index 173ab922f6d..c72477e7365 100644 --- a/group/overview.php +++ b/group/overview.php @@ -26,6 +26,10 @@ require_once('../config.php'); require_once($CFG->libdir . '/filelib.php'); +define('OVERVIEW_NO_GROUP', -1); // The fake group for users not in a group. +define('OVERVIEW_GROUPING_GROUP_NO_GROUPING', -1); // The fake grouping for groups that have no grouping. +define('OVERVIEW_GROUPING_NO_GROUP', -2); // The fake grouping for users with no group. + $courseid = required_param('id', PARAM_INT); $groupid = optional_param('group', 0, PARAM_INT); $groupingid = optional_param('grouping', 0, PARAM_INT); @@ -61,6 +65,9 @@ $strnotingrouping = get_string('notingrouping', 'group'); $strfiltergroups = get_string('filtergroups', 'group'); $strnogroups = get_string('nogroups', 'group'); $strdescription = get_string('description'); +$strnotingroup = get_string('notingrouplist', 'group'); +$strnogroup = get_string('nogroup', 'group'); +$strnogrouping = get_string('nogrouping', 'group'); // Get all groupings and sort them by formatted name. $groupings = $DB->get_records('groupings', array('courseid'=>$courseid), 'name'); @@ -72,7 +79,7 @@ $members = array(); foreach ($groupings as $grouping) { $members[$grouping->id] = array(); } -$members[-1] = array(); //groups not in a grouping +$members[OVERVIEW_GROUPING_GROUP_NO_GROUPING] = array(); //groups not in a grouping // Get all groups $groups = $DB->get_records('groups', array('courseid'=>$courseid), 'name'); @@ -86,8 +93,12 @@ if ($groupid) { } if ($groupingid) { - $groupingwhere = "AND gg.groupingid = :groupingid"; - $params['groupingid'] = $groupingid; + if ($groupingid < 0) { // No grouping filter. + $groupingwhere = "AND gg.groupingid IS NULL"; + } else { + $groupingwhere = "AND gg.groupingid = :groupingid"; + $params['groupingid'] = $groupingid; + } } else { $groupingwhere = ""; } @@ -108,7 +119,7 @@ foreach ($rs as $row) { $user = new stdClass(); $user = username_load_fields_from_object($user, $row, null, array('id' => 'userid', 'username', 'idnumber')); if (!$row->groupingid) { - $row->groupingid = -1; + $row->groupingid = OVERVIEW_GROUPING_GROUP_NO_GROUPING; } if (!array_key_exists($row->groupid, $members[$row->groupingid])) { $members[$row->groupingid][$row->groupid] = array(); @@ -119,6 +130,47 @@ foreach ($rs as $row) { } $rs->close(); +// Add 'no groupings' / 'no groups' selectors. +$groupings[OVERVIEW_GROUPING_GROUP_NO_GROUPING] = (object)array( + 'id' => OVERVIEW_GROUPING_GROUP_NO_GROUPING, + 'formattedname' => $strnogrouping, +); +$groups[OVERVIEW_NO_GROUP] = (object)array( + 'id' => OVERVIEW_NO_GROUP, + 'courseid' => $courseid, + 'idnumber' => '', + 'name' => $strnogroup, + 'description' => '', + 'descriptionformat' => FORMAT_HTML, + 'enrolmentkey' => '', + 'picture' => 0, + 'hidepicture' => 0, + 'timecreated' => 0, + 'timemodified' => 0, +); + +// Add users who are not in a group. +if ($groupid <= 0 && $groupingid <= 0) { + list($esql, $params) = get_enrolled_sql($context, null, 0, true); + $sql = "SELECT u.id, $allnames, u.idnumber, u.username + FROM {user} u + JOIN ($esql) e ON e.id = u.id + LEFT JOIN ( + SELECT gm.userid + FROM {groups_members} gm + JOIN {groups} g ON g.id = gm.groupid + WHERE g.courseid = :courseid + ) grouped ON grouped.userid = u.id + WHERE grouped.userid IS NULL"; + $params['courseid'] = $courseid; + + $nogroupusers = $DB->get_records_sql($sql, $params); + + if ($nogroupusers) { + $members[OVERVIEW_GROUPING_NO_GROUP][OVERVIEW_NO_GROUP] = $nogroupusers; + } +} + navigation_node::override_active_url(new moodle_url('/group/index.php', array('id'=>$courseid))); $PAGE->navbar->add(get_string('overview', 'group')); @@ -164,7 +216,9 @@ $printed = false; $hoverevents = array(); foreach ($members as $gpgid=>$groupdata) { if ($groupingid and $groupingid != $gpgid) { - continue; // do not show + if ($groupingid > 0 || $gpgid > 0) { // Still show 'not in group' when 'no grouping' selected. + continue; // Do not show. + } } $table = new html_table(); $table->head = array(get_string('groupscount', 'group', count($groupdata)), get_string('groupmembers', 'group'), get_string('usercount', 'group')); @@ -201,7 +255,12 @@ foreach ($members as $gpgid=>$groupdata) { continue; } if ($gpgid < 0) { - echo $OUTPUT->heading($strnotingrouping, 3); + // Display 'not in group' for grouping id == OVERVIEW_GROUPING_NO_GROUP. + if ($gpgid == OVERVIEW_GROUPING_NO_GROUP) { + echo $OUTPUT->heading($strnotingroup, 3); + } else { + echo $OUTPUT->heading($strnotingrouping, 3); + } } else { echo $OUTPUT->heading($groupings[$gpgid]->formattedname, 3); $description = file_rewrite_pluginfile_urls($groupings[$gpgid]->description, 'pluginfile.php', $context->id, 'grouping', 'description', $gpgid); diff --git a/group/tests/behat/behat_groups.php b/group/tests/behat/behat_groups.php index 8efae99a409..a5ef28ef779 100644 --- a/group/tests/behat/behat_groups.php +++ b/group/tests/behat/behat_groups.php @@ -27,6 +27,7 @@ require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); +use Behat\Behat\Context\Step\Then; use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** @@ -84,4 +85,108 @@ class behat_groups extends behat_base { $this->find_button(get_string('backtogroups', 'group'))->click(); } + /** + * A comma-separated list of groups expected on the page, with a list of groupings each of them should be found in. + * + * @Given /^the groups overview should include groups "(?P(?:[^"]|\\")*)" in groupings "(?P(?:[^"]|\\")*)"$/ + * @param string $grouplist + * @param string $groupinglist + * @return Then[] + */ + public function the_groups_overview_should_include_groups($grouplist, $groupinglist) { + if (!$grouplist) { + return array(); + } + + $steps = array(); + $groups = array_map('trim', explode(',', $grouplist)); + $groupings = array_map('trim', explode(',', $groupinglist)); + if (count($groups) != count($groupings)) { + throw new Exception('Groups and groupings lists must contain the same number of items'); + } + + $groupingname = reset($groupings); + foreach ($groups as $groupname) { + // Find the table after the H3 containing the grouping name, then look for the group name in the first column. + $xpath = "//h3[normalize-space(.) = '{$groupingname}']/following-sibling::table//tr//". + "td[contains(concat(' ', normalize-space(@class), ' '), ' c0 ')][normalize-space(.) = '{$groupname}' ]"; + + $steps[] = new Then('"'.$xpath.'" "xpath_element" should exist'); + + $groupingname = next($groupings); + } + + return $steps; + } + + /** + * A comma-separated list of the names of groups not expected on the page + * + * @Given /^the groups overview should not include groups "(?P(?:[^"]|\\")*)"$/ + * @param string $grouplist + * @return Then[] + */ + public function the_groups_overview_should_not_include_groups($grouplist) { + if (!$grouplist) { + return array(); + } + + $steps = array(); + $groups = array_map('trim', explode(',', $grouplist)); + foreach ($groups as $groupname) { + $steps[] = new Then('"'.$groupname.'" "table_row" should not exist'); + } + + return $steps; + } + + /** + * A comma-separated list of the group members expected on the page, with a list of groups each of them should be found in. + * + * @Given /^the groups overview should include members "(?P(?:[^"]|\\")*)" in groups "(?P(?:[^"]|\\")*)"$/ + * @param string $memberlist + * @param string $grouplist + * @return Then[] + */ + public function the_groups_overview_should_include_members($memberlist, $grouplist) { + if (!$memberlist) { + return array(); + } + + $steps = array(); + $members = array_map('trim', explode(',', $memberlist)); + $groups = array_map('trim', explode(',', $grouplist)); + if (count($members) != count($groups)) { + throw new Exception('Group members and groups lists must contain the same number of items'); + } + + $groupname = reset($groups); + foreach ($members as $membername) { + $steps[] = new Then('"'.$membername.'" "text" should exist in the "'.$groupname.'" "table_row"'); + $groupname = next($groups); + } + + return $steps; + } + + /** + * A comma-separated list of the names of group members not expected on the page + * + * @Given /^the groups overview should not include members "(?P(?:[^"]|\\")*)"$/ + * @param string $memberlist + * @return Then[] + */ + public function the_groups_overview_should_not_include_members($memberlist) { + if (!$memberlist) { + return array(); + } + + $steps = array(); + $members = array_map('trim', explode(',', $memberlist)); + foreach ($members as $membername) { + $steps[] = new Then('I should not see "'.$membername.'"'); + } + + return $steps; + } } diff --git a/group/tests/behat/overview.feature b/group/tests/behat/overview.feature new file mode 100644 index 00000000000..dd82092f216 --- /dev/null +++ b/group/tests/behat/overview.feature @@ -0,0 +1,80 @@ +@core @core_group +Feature: Group overview + In order to view an overview of the groups + As a teacher + I need to visit the group overview page + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student0 | Student | 0 | student0@example.com | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + | student3 | Student | 3 | student3@example.com | + | student4 | Student | 4 | student4@example.com | + | student5 | Student | 5 | student5@example.com | + | student6 | Student | 6 | student6@example.com | + | student7 | Student | 7 | student7@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student0 | C1 | student | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | student4 | C1 | student | + | student5 | C1 | student | + | student6 | C1 | student | + | student7 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | + | Group 1 | C1 | G1 | + | Group 2 | C1 | G2 | + | Group 3 | C1 | G3 | + | Group 4 | C1 | G4 | + And the following "group members" exist: + | user | group | + | student0 | G1 | + | student1 | G1 | + | student2 | G2 | + | student3 | G3 | + | student4 | G3 | + | student5 | G4 | + And the following "groupings" exist: + | name | course | idnumber | + | Grouping 1 | C1 | GG1 | + | Grouping 2 | C1 | GG2 | + And the following "grouping groups" exist: + | grouping | group | + | GG1 | G1 | + | GG1 | G2 | + | GG2 | G2 | + | GG2 | G3 | + + Scenario Outline: Filter the overview in various different ways + Given I log in as "teacher1" + And I follow "Course 1" + And I navigate to "Groups" node in "Course administration > Users" + And I follow "Overview" + + When I select "" from the "Grouping" singleselect + And I select "" from the "group" singleselect + + Then the groups overview should include groups "" in groupings "" + And the groups overview should not include groups "" + And the groups overview should include members "" in groups "" + And the groups overview should not include members "" + + Examples: + | grouping | group | expectedgroups | expectedgroupings | notexpectedgroups | expectedmembers | expectedmembergroups | notexpectedmembers | + | All | All | Group 1, Group 2, Group 2, Group 3, Group 4, No group | Grouping 1, Grouping 1, Grouping 2, Grouping 2, [Not in a grouping], [Not in a group] | | Student 0, Student 1, Student 2, Student 3, Student 4, Student 5, Student 6, Student 7 | Group 1, Group 1, Group 2, Group 3, Group 3, Group 4, No group, No group | | + | Grouping 1 | All | Group 1, Group 2 | Grouping 1, Grouping 1 | Group 3, No group | Student 0, Student 1, Student 2 | Group 1, Group 1, Group 2 | Student 3, Student 4, Student 5, Student 6, Student 7 | + | Grouping 2 | All | Group 2, Group 3 | Grouping 2, Grouping 2 | Group 1, No group | Student 2, Student 3, Student 4 | Group 2, Group 3, Group 3 | Student 0, Student 1, Student 5, Student 6, Student 7 | + | No grouping | All | Group 4, No group | [Not in a grouping], [Not in a group] | Group 1, Group 2, Group 3 | Student 5, Student 6, Student 7 | Group 4, No group, No group | Student 0, Student 1, Student 2, Student 3, Student 4 | + | All | Group 1 | Group 1 | Grouping 1 | Group 2, Group 3, Group 4, No group | Student 0, Student 1 | Group 1, Group 1 | Student 2, Student 3, Student 4, Student 5, Student 6, Student 7 | + | All | Group 2 | Group 2, Group 2 | Grouping 1, Grouping 2 | Group 1, Group 3, Group 4, No group | Student 2 | Group 2 | Student 0, Student 1, Student 3, Student 4, Student 5, Student 6, Student 7 | + | All | No group | No group | [Not in a group] | Group 1, Group 2, Group 3, Group 4 | Student 6, Student 7 | No group, No group | Student 0, Student 1, Student 2, Student 3, Student 4, Student 5 | diff --git a/lang/en/group.php b/lang/en/group.php index 5536a7a9bee..17007edf5dd 100644 --- a/lang/en/group.php +++ b/lang/en/group.php @@ -149,12 +149,15 @@ $string['newpicture'] = 'New picture'; $string['newpicture_help'] = 'Select an image in JPG or PNG format. The image will be cropped to a square and resized to 100x100 pixels.'; $string['noallocation'] = 'No allocation'; $string['nogrouping'] = 'No grouping'; +$string['nogroup'] = 'No group'; +$string['nogrouping'] = 'No grouping'; $string['nogroups'] = 'There are no groups set up in this course yet'; $string['nogroupsassigned'] = 'No groups assigned'; $string['nopermissionforcreation'] = 'Can\'t create group "{$a}" as you don\'t have the required permissions'; $string['nosmallgroups'] = 'Prevent last small group'; $string['notingroup'] = 'Ignore users in groups'; $string['notingrouping'] = '[Not in a grouping]'; +$string['notingrouplist'] = '[Not in a group]'; $string['nousersinrole'] = 'There are no suitable users in the selected role'; $string['number'] = 'Group/member count'; $string['numgroups'] = 'Number of groups';