diff --git a/classes/external.php b/classes/external.php index 4396877..0395ec8 100755 --- a/classes/external.php +++ b/classes/external.php @@ -291,9 +291,23 @@ public static function submit_choicegroup_response($choicegroupid, $data) { } if (!choicegroup_get_user_answer($choicegroup, $USER) || $choicegroup->allowupdate) { + // Comparable to view.php. + if ($choicegroup->restrictbygrouping) { + $usergroupassigments = choicegroup_get_all_relevant_group_assignments($choicegroup->id); + $groupgrouping = choicegroup_structure_groupings_to_group($choicegroup->option); + } if ($choicegroup->multipleenrollmentspossible) { foreach($choicegroup->option as $optionid => $text) { if (in_array($optionid, $responses)) { + if ($choicegroup->restrictbygrouping && !(empty($usergroupassigments))) { + $choicerecord = $DB->get_record('choicegroup_options', array('id' => $optionid), + '*', MUST_EXIST); + $groupavailable = choicegroup_check_group_available($choicerecord->groupid, + $usergroupassigments, $groupgrouping); + if (!$groupavailable) { + throw new moodle_exception('restrictgroupingsconflict', 'webservice'); + } + } choicegroup_user_submit_response($optionid, $choicegroup, $USER->id, $course, $cm); } else { // Remove group selection if selected. @@ -315,6 +329,15 @@ public static function submit_choicegroup_response($choicegroupid, $data) { } else { // !multipleenrollmentspossible if (count($responses) == 1) { $responses = reset($responses); + if ($choicegroup->restrictbygrouping && !(empty($usergroupassigments))) { + $choicerecord = $DB->get_record('choicegroup_options', array('id' => $responses->id), + '*', MUST_EXIST); + $groupavailable = choicegroup_check_group_available($choicerecord->groupid, + $usergroupassigments, $groupgrouping); + if (!$groupavailable) { + throw new moodle_exception('restrictgroupingsconflict', 'webservice'); + } + } choicegroup_user_submit_response($responses, $choicegroup, $USER->id, $course, $cm); } } diff --git a/db/install.xml b/db/install.xml index d023fa2..7a257e8 100644 --- a/db/install.xml +++ b/db/install.xml @@ -18,8 +18,10 @@ - - + + + + @@ -46,5 +48,17 @@ + + + + + + + + + + + +
diff --git a/db/upgrade.php b/db/upgrade.php index af0afde..62dfb35 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -48,7 +48,7 @@ function xmldb_choicegroup_upgrade($oldversion) { // Adding fields to table choicegroup $newField = $table->add_field('multipleenrollmentspossible', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0'); - $dbman->add_field($table, $newField); + $dbman->add_field($table, $newField); upgrade_mod_savepoint(true, 2013070900, 'choicegroup'); @@ -96,6 +96,39 @@ function xmldb_choicegroup_upgrade($oldversion) { // Group choice savepoint reached. upgrade_mod_savepoint(true, 2021080500, 'choicegroup'); } + if ($oldversion < 2023011600) { + + // Define field restrictbygrouping to be added to choicegroup. + $table = new xmldb_table('choicegroup'); + $field = new xmldb_field('restrictbygrouping', XMLDB_TYPE_INTEGER, '2', null, null, null, '0', 'onlyactive'); + + // Conditionally launch add field restrictbygrouping. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Define field restrictchoicesbehaviour to be added to choicegroup. + $field = new xmldb_field('restrictchoicesbehaviour', XMLDB_TYPE_INTEGER, '2', null, null, null, '0', 'restrictbygrouping'); + + // Conditionally launch add field restrictchoicesbehaviour. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Choicegroup savepoint reached. + + // Create Table for groupings. + // Define field id to be added to choicegroup_groupings. + $table = new xmldb_table('choicegroup_groupings'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null); + $table->add_field('choicegroupid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'id'); + $table->add_field('groupingid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'choicegroupid'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + // Conditionally create the table. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + upgrade_mod_savepoint(true, 2023011600, 'choicegroup'); + } return true; } diff --git a/lang/en/choicegroup.php b/lang/en/choicegroup.php index e07219d..f4e10fc 100644 --- a/lang/en/choicegroup.php +++ b/lang/en/choicegroup.php @@ -172,3 +172,23 @@ $string['maxenrollments'] = 'Max. enrollments'; $string['maxenrollments_help'] = 'This option allows to limit the number of group enrollments for a participant. Use default value **0** if there is no limit.'; +$string['groupingrestrict'] = 'Restrict choices based on grouping assignments'; +$string['selectedgroupings'] = 'Restrict by grouping?'; +$string['selectedgroupings_help'] = 'If enabled, participants can only choose groups which do not belong to a grouping they are already assigned to (manually or by a group choice activity).'; +$string['assignedtogrouping'] = '(You are already assigned to a group which contradicts this choice)'; +$string['submitfailedgroupingrestriction'] = 'You are already assigned to a group which contradicts this choice.'; +$string['limitationbehaviour'] = 'Limitation behaviour'; +$string['restrictchoicesbehaviour'] = 'Choose limitation behaviour?'; +$string['restrictchoicesbehaviour_help'] = '"Hide group from group list" - will hide all groups which can not be selected due to previous assignments
+ "Show group as dimmed" - will show the choice but choice can not be selected
+ "Show group with limitation notice" - inform user about conflicting groups '; +$string['hidegroup'] = 'Hide group from group list'; +$string['dimmgroup'] = 'Show group as dimmed'; +$string['informlimit'] = 'Show group with limitation notice'; +$string['informlimitgroup'] = 'Show group with information on conflicting groups'; +$string['reasongrouplimitationplural'] = '(You can not select this group as you are already assigned to the groups: {$a} )'; +$string['reasongrouplimitationsingular'] = '(You can not select this group as you are already assigned to the group: {$a} )'; +$string['reasongrouplimitationempty'] = '(You can not select this group, however no conflicting group can be found - this might be an error in the programm code)'; +$string['hidechoicetostudents'] = '(If a student had the same group assigment, this choice would not be shown)'; +$string['conflictinggroupassignment'] = 'Your Group assigment are conflicting. Therefore you can not participate in this groupchoice activity.'; + diff --git a/lib.php b/lib.php index 4bbf52f..1316ea0 100644 --- a/lib.php +++ b/lib.php @@ -51,6 +51,10 @@ define('CHOICEGROUP_SORTGROUPS_CREATEDATE', '1'); define('CHOICEGROUP_SORTGROUPS_NAME', '2'); +define('CHOICEGROUP_RESTRICTGROUPINGS_NOTAVAILABLE', '0'); +define('CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE', '1'); +define('CHOICEGROUP_RESTRICTGROUPINGS_CONFLICT', '2'); + // Ugly hack to make 3.11 and 4.0 work seamlessly. if (!defined('FEATURE_MOD_PURPOSE')) { define('FEATURE_MOD_PURPOSE', 'mod_purpose'); @@ -197,6 +201,11 @@ function choicegroup_add_instance($choicegroup) { } //insert answers + if (isset($choicegroup->restrictbygrouping) && $choicegroup->restrictbygrouping) { + $choicegroup->restrictbygrouping = $choicegroup->restrictbygrouping; + } else { + $choicegroup->restrictbygrouping = false; + } $choicegroup->id = $DB->insert_record("choicegroup", $choicegroup); // deserialize the selected groups @@ -216,7 +225,18 @@ function choicegroup_add_instance($choicegroup) { } $option->timemodified = time(); $DB->insert_record("choicegroup_options", $option); - } + } + } + if (property_exists($choicegroup, 'restrictbygrouping') && $choicegroup->restrictbygrouping) { + if (!empty($choicegroup->selectedgroupings)) { + $DB->delete_records("choicegroup_groupings", array('choicegroupid' => $choicegroup->id)); + foreach ($choicegroup->selectedgroupings as $selectedgrouping) { + $groupchoiceassignment = new stdClass(); + $groupchoiceassignment->choicegroupid = $choicegroup->id; + $groupchoiceassignment->groupingid = $selectedgrouping; + $DB->insert_record("choicegroup_groupings", $groupchoiceassignment); + } + } } if (class_exists('\core_completion\api')) { @@ -288,20 +308,34 @@ function choicegroup_update_instance($choicegroup) { continue 2; // continue the big loop } } - $DB->insert_record("choicegroup_options", $option); + + $DB->insert_record("choicegroup_options", $option); } - } // remove all remaining pre-existing groups which did not appear in the form (and are thus assumed to have been deleted) foreach ($preExistingGroups as $preExistingGroup) { $DB->delete_records("choicegroup_options", array("id"=>$preExistingGroup->id)); } - + if (property_exists($choicegroup, 'restrictbygrouping') && $choicegroup->restrictbygrouping) { + if (!empty($choicegroup->selectedgroupings)) { + $DB->delete_records("choicegroup_groupings", array('choicegroupid' => $choicegroup->id)); + foreach ($choicegroup->selectedgroupings as $selectedgrouping) { + $groupchoiceassignment = new stdClass(); + $groupchoiceassignment->choicegroupid = $choicegroup->id; + $groupchoiceassignment->groupingid = $selectedgrouping; + $DB->insert_record("choicegroup_groupings", $groupchoiceassignment); + } + } + } if (class_exists('\core_completion\api')) { $completiontimeexpected = !empty($choicegroup->completionexpected) ? $choicegroup->completionexpected : null; \core_completion\api::update_completion_date_event($choicegroup->coursemodule, 'choicegroup', $choicegroup->id, $completiontimeexpected); } - + if (isset($choicegroup->restrictbygrouping) && $choicegroup->restrictbygrouping) { + $choicegroup->restrictbygrouping = $choicegroup->restrictbygrouping; + } else { + $choicegroup->restrictbygrouping = false; + } return $DB->update_record('choicegroup', $choicegroup); @@ -316,7 +350,6 @@ function choicegroup_update_instance($choicegroup) { * @return array */ function choicegroup_prepare_options($choicegroup, $user, $coursemodule, $allresponses) { - $cdisplay = array('options'=>array()); $cdisplay['limitanswers'] = true; @@ -326,6 +359,11 @@ function choicegroup_prepare_options($choicegroup, $user, $coursemodule, $allres if (!isset($choicegroup->option)) { $choicegroup->option = []; } + if (isset($choicegroup->restrictbygrouping) && $choicegroup->restrictbygrouping) { + $usergroupassigments = choicegroup_get_all_relevant_group_assignments($choicegroup->id); + $groupgrouping = choicegroup_structure_groupings_to_group($choicegroup->option); + } + foreach ($choicegroup->option as $optionid => $text) { if (isset($text)) { //make sure there are no dud entries in the db with blank text values. $option = new stdClass; @@ -347,6 +385,19 @@ function choicegroup_prepare_options($choicegroup, $user, $coursemodule, $allres } } } + // If we have grouping restrictions AND assigments to relevant groupings are not empty... + // - we check if group is available. + if ($choicegroup->restrictbygrouping) { + $option->groupavailable = choicegroup_check_group_available($option->groupid, $usergroupassigments, + $groupgrouping); + // If the grouping is not clickable AND we show additional information. + if (!($option->groupavailable) && $choicegroup->restrictchoicesbehaviour == 3) { + $option->conflictinggroups = choicegroup_get_conflictinggroups($option->groupid, $groupgrouping, + $choicegroup->id); + } + } else { + $option->groupavailable = CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE; + } if ( $choicegroup->limitanswers && ($option->countanswers >= $option->maxanswers) && empty($option->attributes->checked)) { $option->attributes->disabled = true; } @@ -363,6 +414,172 @@ function choicegroup_prepare_options($choicegroup, $user, $coursemodule, $allres return $cdisplay; } +/** + * Collects all group memberships which are relevant for the choicegroup activity. + * As those query get very fast hard to understand. + * If a user is member in a group which belongs to a grouping which is a restriction in the choice, the user is listed. + * Gets all groups from the selected groupings where the current user is a member. + * @param int $choicegroupid + * @return array + * @throws dml_exception + */ +function choicegroup_get_all_relevant_group_assignments($choicegroupid) { + global $DB, $USER; + $assignments = $DB->get_records_sql(' + SELECT gm.id as id, cg.choicegroupid as choicegroupid, cg.groupingid as groupingid, gg.groupid as groupid, + gm.userid as userid + FROM {choicegroup_groupings} as cg +LEFT JOIN {groupings_groups} as gg on cg.groupingid = gg.groupingid +LEFT JOIN {groups_members} as gm on gg.groupid = gm.groupid +WHERE cg.choicegroupid=:choicegroupid AND gm.userid=:userid;', + array('choicegroupid' => $choicegroupid, 'userid' => $USER->id)); + return $assignments; +} + + +/** + * In case the user should be informed about additional groups additional information is catched. + * Improvement: For sure there are more efficient DB calls. + * Current approach -> + * 1) Get all relevant groupings which is A) currentgroup is assigned to it && B) grouping is restrictioncriteria of choicegroup + * 2) For relevantgroupings Check for all groups if user is member - if it is not the current group it is conflicting. + * + * @param int $currentgroupid current choice group id + * @param array $groupgroupings only assigments from groups in the current choicegroup + * @param int $choicegroupid id of current mod_choicegroup + * @return array of conflicting group names + */ +function choicegroup_get_conflictinggroups($currentgroupid, $groupgroupings, $choicegroupid) { + global $DB, $USER; + // Current group does not belong to a relevant grouping. + if (!isset($groupgroupings[$currentgroupid])) { + return array(); + } + // Get all groupings of group. + $groupings = $groupgroupings[$currentgroupid]; + $relevantgroupings = array(); + // Get the groupings which are mentioned in the restriction. + $restrictcombinations = $DB->get_records('choicegroup_groupings', array('choicegroupid' => $choicegroupid)); + foreach ($restrictcombinations as $restrictcombination) { + // If the grouping is a restriction and the current group belongs to that restriction. + if (in_array($restrictcombination->groupingid, $groupings)) { + array_push($relevantgroupings, $restrictcombination->groupingid); + } + } + if (empty($relevantgroupings)) { + return array(); + } + // Final Groups. + $conflictinggroups = array(); + + foreach ($relevantgroupings as $relevantgrouping) { + $allgroups = $DB->get_records('groupings_groups', array('groupingid' => $relevantgrouping)); + foreach ($allgroups as $group) { + if (groups_is_member($group->groupid, $USER->id) && $group->groupid !== $currentgroupid) { + // Most likely not called very often. + array_push($conflictinggroups, groups_get_group_name($group->groupid)); + } + } + } + return $conflictinggroups; +} + + +/** + * Structure Groups to Grouping to make checks easier. + * E.g. Group A1 with id A1, Group B with id B1 + * Grouping A id A, Grouping B id B, Grouping 1 id 1 + * would result in: + * [ + * [A1] => [A,1] + * [B1] => [B,1] + * ] + * @param array $options get for each group a grouping + * @return array of groupid->array([groupingids]) + * @throws coding_exception + * @throws dml_exception + */ +function choicegroup_structure_groupings_to_group($options) { + global $DB; + $relevantgroups = array(); + // Get all groupids of choices. + foreach ($options as $groupid) { + if (isset($groupid)) { + array_push($relevantgroups, $groupid); + } + } + + list($sqlgroup, $pgroup) = $DB->get_in_or_equal($relevantgroups); + $groupgroupingassigments = $DB->get_records_select('groupings_groups', 'groupid ' . $sqlgroup, + $pgroup, '', 'id, groupingid, groupid'); + $structuredgroups = array(); + // Make an index in the array for each group and add groupings. + foreach ($groupgroupingassigments as $groupgroupingassignment) { + if (!isset($structuredgroups[$groupgroupingassignment->groupid])) { + $structuredgroups[$groupgroupingassignment->groupid] = array(); + } + array_push($structuredgroups[$groupgroupingassignment->groupid], $groupgroupingassignment->groupingid); + } + return $structuredgroups; +} +/** + * Check if a user is assigned to a group belonging to the same grouping as the option and if we have a conflict. + * Example : Gropu A1, A2 and B1, B2 A1 and B1 are in the same grouping + * Choicegroup is restricted by grouping 1 and has Group B1 B2 + * If a student 1 is assigned to A1: + * Group B1 returns 1 as the group is not available + * Group B2 return 0 as the group is available to choose + * If student 2 is assigned to A1 and B1: + * The function returns 2 for group B1 as we have a real conflict - more assigment than allowed in one grouping. + * + * @param int $optiongroupid id of group + * @param array $assignments groups a user is assigned to + * @param array $structuredgroups array groupid => array([groupingsids]) + * @return int 1 -> No Problem 0 -> group not available 2 -> CONFLICT + * @throws dml_exception + */ +function choicegroup_check_group_available($optiongroupid, $assignments, $structuredgroups) { + $owngroup = false; + $uniquegroups = []; + foreach ($assignments as $assigment) { + // Checks: 1) Do we have groupings to the current group + // 2) Gehört die Zuweisung zu einer Gruppierung die für die option relevant ist? + // 3) ist die zurzeitige + if (array_key_exists($optiongroupid, $structuredgroups) && + in_array($assigment->groupingid, $structuredgroups[$optiongroupid]) && $assigment->groupid !== $optiongroupid) { + $uniquegroups[$assigment->groupid] = $assigment->groupid; + } + if ($assigment->groupid == $optiongroupid) { + $owngroup = true; + $uniquegroups[$assigment->groupid] = $assigment->groupid; + } + } + + $counter = count($uniquegroups); + + if (!($owngroup)) { + if ($counter == 0) { + return CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE; + } + if ($counter == 1) { + return CHOICEGROUP_RESTRICTGROUPINGS_NOTAVAILABLE; + } else if ($counter > 1) { + return CHOICEGROUP_RESTRICTGROUPINGS_CONFLICT; + } + } else { + if ($counter == 0) { + return CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE; + } + if ($counter == 1) { + return CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE; + } + if ($counter > 1) { + return CHOICEGROUP_RESTRICTGROUPINGS_CONFLICT; + } + } + + return CHOICEGROUP_RESTRICTGROUPINGS_AVAILABLE; +} /** * @throws \moodle_exception */ @@ -755,6 +972,9 @@ function choicegroup_delete_instance($id) { if (! $DB->delete_records("choicegroup_options", array("choicegroupid"=>"$choicegroup->id"))) { $result = false; } + if (! $DB->delete_records("choicegroup_groupings", array("choicegroupid" => "$choicegroup->id"))) { + $result = false; + } if (! $DB->delete_records("choicegroup", array("id"=>"$choicegroup->id"))) { $result = false; diff --git a/mod_form.php b/mod_form.php index 811295f..5f809c5 100644 --- a/mod_form.php +++ b/mod_form.php @@ -254,6 +254,38 @@ function definition() $mform->addElement('date_time_selector', 'timeclose', get_string("choicegroupclose", "choicegroup")); $mform->disabledIf('timeclose', 'timerestrict'); + // ------------------------- + // Enable restrictions by grouping over multiple mod_choicegroup instances. + // ------------------------- + + $mform->addElement('header', 'restrictbygroupinghdr', get_string('groupingrestrict', 'choicegroup')); + $mform->addElement('checkbox', 'restrictbygrouping', get_string('groupingrestrict', 'choicegroup')); + + $arrayofgroupings = array(); + foreach ($db_groupings as $dbgrouping) { + $arrayofgroupings[$dbgrouping->id] = $dbgrouping->name; + } + $groupingdefault = array(); + if (isset($this->current->id) && $this->current->id != '') { + $selectedgropings = $DB->get_records('choicegroup_groupings', array('choicegroupid' => $this->current->id)); + foreach ($selectedgropings as $selectedgrouping) { + array_push($groupingdefault, $selectedgrouping->groupingid); + } + } + $select = $mform->addElement('select', 'selectedgroupings', get_string('grouping', 'group'), + $arrayofgroupings); + $mform->setDefault('selectedgroupings', $groupingdefault); + $mform->disabledIf('selectedgroupings', 'restrictbygrouping'); + $mform->addHelpButton('selectedgroupings', 'selectedgroupings', 'mod_choicegroup'); + $select->setMultiple(true); + + $behaviouroptions = array(get_string('hidegroup', 'mod_choicegroup'), + get_string('dimmgroup', 'mod_choicegroup'), + get_string('informlimit', 'mod_choicegroup'), + get_string('informlimitgroup', 'mod_choicegroup')); + $mform->addElement('select', 'restrictchoicesbehaviour', get_string('limitationbehaviour', 'mod_choicegroup'), $behaviouroptions); + $mform->disabledIf('restrictchoicesbehaviour', 'restrictbygrouping'); + $mform->addHelpButton('restrictchoicesbehaviour', 'restrictchoicesbehaviour', 'mod_choicegroup'); //------------------------------------------------------------------------------- $this->standard_coursemodule_elements(); //------------------------------------------------------------------------------- diff --git a/renderer.php b/renderer.php index 02c6ee1..6d283bb 100644 --- a/renderer.php +++ b/renderer.php @@ -46,8 +46,8 @@ class mod_choicegroup_renderer extends plugin_renderer_base { * * @return string */ - public function display_options($options, $coursemoduleid, $vertical = true, $publish = false, $limitanswers = false, $showresults = false, $current = false, $choicegroupopen = false, $disabled = false, $multipleenrollmentspossible = false, $onlyactive = false) { - global $DB, $PAGE, $choicegroup_groups; + public function display_options($options, $coursemoduleid, $vertical = true, $publish = false, $limitanswers = false, $showresults = false, $current = false, $choicegroupopen = false, $disabled = false, $multipleenrollmentspossible = false, $onlyactive = false, $restrictchoicesbehaviour = 0) { + global $DB, $PAGE, $OUTPUT, $choicegroup_groups; $target = new moodle_url('/mod/choicegroup/view.php'); $attributes = array('method'=>'POST', 'action'=>$target, 'class'=> 'tableform'); @@ -85,6 +85,13 @@ public function display_options($options, $coursemoduleid, $vertical = true, $pu $answer_to_groupid_mappings = ''; } $initiallyHideSubmitButton = false; + // If we have a conflict do not show the form. + foreach ($options['options'] as $option) { + if (isset($option->groupavailable) && $option->groupavailable == 2) { + return html_writer::div($OUTPUT->notification(get_string('conflictinggroupassignment', 'choicegroup'), + 'notifyproblem', false), 'choicegrouprestrictionerror'); + } + } foreach ($options['options'] as $option) { $group = (isset($choicegroup_groups[$option->groupid])) ? ($choicegroup_groups[$option->groupid]) : (false); if (!$group) { @@ -99,6 +106,13 @@ public function display_options($options, $coursemoduleid, $vertical = true, $pu $html .= html_writer::tag('tr', $cell); break; } + $context = \context_course::instance($group->courseid); + + // Shows users with readcapability also "hidden" choices. + if (isset($option->groupavailable) && $option->groupavailable == 0 && $restrictchoicesbehaviour == 0 && + !(has_capability('mod/choicegroup:readresponses', $context))) { + continue; + } $html .= html_writer::start_tag('tr', array('class'=>'option')); $html .= html_writer::start_tag('td', array('class'=>'center')); @@ -115,7 +129,6 @@ public function display_options($options, $coursemoduleid, $vertical = true, $pu } } - $context = \context_course::instance($group->courseid); $labeltext = html_writer::tag('label', format_string($group->name), array('for' => 'choiceid_' . $option->attributes->value)); $group_members = get_enrolled_users($context, '', $group->id, 'u.*', 'u.lastname, u.firstname', 0, 0, $onlyactive); $group_members_names = array(); @@ -127,6 +140,33 @@ public function display_options($options, $coursemoduleid, $vertical = true, $pu $option->attributes->disabled=true; $availableoption--; } + // In case the group is not available due to grouping restrictions, + // we need to change the depiction of the choice. + if (isset($option->groupavailable) && $option->groupavailable == 0) { + // Show general info. + if ($restrictchoicesbehaviour == 2) { + $labeltext .= '
' . html_writer::tag('em', get_string('assignedtogrouping', 'choicegroup')); + } else if ($restrictchoicesbehaviour == 3) { + // Show which groups conflict. + if (count($option->conflictinggroups) > 1) { + $labeltext .= '
' . html_writer::tag('em', get_string('reasongrouplimitationplural', + 'choicegroup', implode(', ', $option->conflictinggroups))); + } else if (count($option->conflictinggroups) == 1){ + $labeltext .= '
' . html_writer::tag('em', get_string('reasongrouplimitationsingular', + 'choicegroup', implode('', $option->conflictinggroups))); + } else { + // Should not happen no conflicting group found. + $labeltext .= '
' . html_writer::tag('em', get_string('reasongrouplimitationempty', + 'choicegroup', implode('', $option->conflictinggroups))); + } + } else if (has_capability('mod/choicegroup:readresponses', $context) && $restrictchoicesbehaviour == 0) { + // In case we have a "managing" person, he/she still gets to see the choices... + // but with additional information. + $labeltext .= '
' . html_writer::tag('em', get_string('hidechoicetostudents', 'choicegroup')); + } + $option->attributes->disabled = true; + $availableoption--; + } $labeltext .= html_writer::tag('div', format_text(file_rewrite_pluginfile_urls($group->description, 'pluginfile.php', $context->id, diff --git a/tests/behat/choicegroup_groupingsrestriction.feature b/tests/behat/choicegroup_groupingsrestriction.feature new file mode 100644 index 0000000..656b3f7 --- /dev/null +++ b/tests/behat/choicegroup_groupingsrestriction.feature @@ -0,0 +1,159 @@ +@mod @mod_choicegroup @mod_choicegroup_groupingrestriction +Feature: In case we have a grouping restriction conflicting choices are not shown in the choicegroup activity. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Vinnie | Student1 | student1@example.com | + | student2 | Ann | Student2 | student2@example.com | + | teacher1 | Darrell | Teacher1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + | teacher1 | C1 | editingteacher | + And the following "groups" exist: + | name | course | idnumber | + | Group A1 | C1 | GGA1 | + | Group A2 | C1 | GGA2 | + | Group B1 | C1 | GGB1 | + | Group B2 | C1 | GGB2 | + And the following "groupings" exist: + | name | course | idnumber | + | Grouping A | C1 | GA | + | Grouping B | C1 | GB | + | Grouping 1 | C1 | G1 | + | Grouping 2 | C1 | G2 | + And the following "grouping groups" exist: + | grouping | group | + | GA | GGA1 | + | GA | GGA2 | + | GB | GGB1 | + | GB | GGB2 | + | G1 | GGA1 | + | G1 | GGB1 | + | G2 | GGB2 | + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + # Create a new Group Choice with Group A1 available. + And I press "Add an activity or resource" + And I click on "Add a new Group choice" "link" in the "Add an activity or resource" "dialogue" + And I set the following fields to these values: + | Group choice name | Choose your group | + And I set the field "availablegroups" to "Grouping A" + And I set the field "availablegroups" to "Group A1" + And I press "Add Group" + And I press "Save and return to course" + And I am on "Course 1" course homepage with editing mode on + And I press "Add an activity or resource" + # Create a new Group Choice with Group B1,2 available and restricted. + And I click on "Add a new Group choice" "link" in the "Add an activity or resource" "dialogue" + And I click on "collapseElement-4" "link" + And I set the following fields to these values: + | Group choice name | Choose second group | + | restrictbygrouping| 1 | + And I set the field "availablegroups" to "Grouping B" + And I press "Add Grouping" + And I set the field "id_selectedgroupings" to "Grouping 1" + And I set the field "id_restrictchoicesbehaviour" to "Hide group from group list" + And I press "Save and return to course" + And I log out + + @javascript + Scenario: Basic Test - I can still select a choice (normally use the choicegroup activity) + without the groupingrestriction feature. + Given I log in as "student1" + And I am on "Course 1" course homepage + And I am on the "Choose your group" "choicegroup activity" page logged in as student1 + And I should see "Group A1" in the ".choicegroups" "css_element" + And I click on "//table[@class='choicegroups']/tbody/tr[2]/td[1]/input" "xpath_element" + And I click on "Save my choice" "button" + And I should see "Your selection: Group A1" in the "#yourselection" "css_element" + + @javascript + Scenario: Basic Test - I can still select a choice (normally use the choicegroup activity) without the groupingrestriction feature. + Given I log in as "student1" + And I am on "Course 1" course homepage + And I am on the "Choose your group" "choicegroup activity" page logged in as student1 + And I should see "Group A1" in the ".choicegroups" "css_element" + And I click on "//table[@class='choicegroups']/tbody/tr[2]/td[1]/input" "xpath_element" + And I click on "Save my choice" "button" + And I am on "Course 1" course homepage + And I am on the "Choose second group" "choicegroup activity" page logged in as student1 + Then I should not see "Group B1" in the ".choicegroups" "css_element" + And I should see "Group B2" in the ".choicegroups" "css_element" + + @javascript + Scenario Outline: With two choicegroup activities (one restricted to Groping1 and with hide group limitationbehaviour) + student1 assigned to A1 can not see B1, student2 who is assigned to GGB1 can see B1 and A1 (as not restircted) + Given the following "group members" exist: + | user | group | + | | | + # As a student assigned to A1 I do see A1, B2 and NOT B1. As a student assigned to B1 I can see all since + # 1. mod_choicegroup is not restricted! + And I log in as "" + And I am on "Course 1" course homepage + And I am on the "Choose your group" "choicegroup activity" page logged in as + And I should "Group A1" in the ".choicegroups" "css_element" + And I am on the "Choose second group" "choicegroup activity" page logged in as + And I should "Group B1" in the ".choicegroups" "css_element" + And I should see "Group B2" in the ".choicegroups" "css_element" + + Examples: + | user | groupassign | B1visible | A1visible | + | student1 | GGA1 | not see | see | + | student2 | GGB1 | see | see | + + @javascript + Scenario Outline: Depending on the visibility of the activity different layouts are shown + Given the following "group members" exist: + | user | group | + | student1 | GGA1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I open "Choose second group" actions menu + And I click on "Edit settings" "link" in the "Choose second group" activity + And I click on "collapseElement-4" "link" + And I set the field "id_restrictchoicesbehaviour" to "" + And I press "Save and return to course" + And I log out + And I log in as "student1" + And I am on "Course 1" course homepage + And I am on the "Choose your group" "choicegroup activity" page logged in as student1 + And I should see "Group A1" in the ".choicegroups" "css_element" + And I am on "Course 1" course homepage + And I am on the "Choose second group" "choicegroup activity" page logged in as student1 + And I should see "Group B1" in the ".choicegroups" "css_element" + And I should see "Group B2" in the ".choicegroups" "css_element" + And I should "(You are already assigned to a group which contradicts this choice)" in the ".choicegroups" "css_element" + + Examples: + | limitationbehaviour | seenotice | + | Show group as dimmed | not see | + | Show group with limitation notice | see | + + @javascript + Scenario: With a choicegroup activities restricted to a grouping the teacher sees all choices. + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + # As a teacher without group assignment I see all choices. + And I am on the "Choose your group" "choicegroup activity" page logged in as teacher1 + Then I should see "Group A1" in the ".choicegroups" "css_element" + And I am on the "Choose second group" "choicegroup activity" page logged in as teacher1 + And I should see "Group B1" in the ".choicegroups" "css_element" + And I should see "Group B2" in the ".choicegroups" "css_element" + + @javascript + Scenario: Corner Case illegal assignment + Given the following "group members" exist: + | user | group | + | student1 | GGA1 | + | student1 | GGB1 | + And I log in as "student1" + And I am on the "Choose your group" "choicegroup activity" page logged in as student1 + Then I should see "Group A1" in the ".choicegroups" "css_element" + And I am on the "Choose second group" "choicegroup activity" page logged in as student1 + And I should see "Your Group assigment are conflicting. Therefore you can not participate in this groupchoice activity." in the ".choicegrouprestrictionerror" "css_element" diff --git a/version.php b/version.php index 9dd7e4d..5fdd70d 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023010300; +$plugin->version = 2023011600.03; $plugin->requires = 2020061500; // Moodle 3.9 $plugin->maturity = MATURITY_STABLE; $plugin->release = '1.39 for Moodle 3.9-4.1 (Build: 2023010300)'; diff --git a/view.php b/view.php index 30a269c..a4ff437 100644 --- a/view.php +++ b/view.php @@ -99,7 +99,11 @@ /// Submit any new data if there is any if (data_submitted() && is_enrolled($context, null, 'mod/choicegroup:choose') && confirm_sesskey()) { - + // Get data once. + if ($choicegroup->restrictbygrouping) { + $usergroupassigments = choicegroup_get_all_relevant_group_assignments($choicegroup->id); + $groupgrouping = choicegroup_structure_groupings_to_group($choicegroup->option); + } if ($choicegroup->multipleenrollmentspossible == 1) { $number_of_groups = optional_param('number_of_groups', '', PARAM_INT); $enrollmentscount = 0; @@ -120,6 +124,15 @@ for ($i = 0; $i < $number_of_groups; $i++) { $answer_value = optional_param('answer_' . $i, '', PARAM_INT); if ($answer_value != '') { + // Check finally if the group is still available. + if ($choicegroup->restrictbygrouping && !(empty($usergroupassigments))) { + $choicerecord = $DB->get_record('choicegroup_options', array('id' => $answer_value), '*', MUST_EXIST); + $groupavailable = choicegroup_check_group_available($choicerecord->groupid, $usergroupassigments, $groupgrouping); + if (!$groupavailable) { + redirect(new moodle_url('/mod/choicegroup/view.php', + array('id' => $cm->id, 'notify' => 'submitfailedgroupingrestriction', 'sesskey' => sesskey()))); + } + } choicegroup_user_submit_response($answer_value, $choicegroup, $USER->id, $course, $cm); } else { $answer_value_group_id = optional_param('answer_'.$i.'_groupid', '', PARAM_INT); @@ -152,6 +165,15 @@ redirect(new moodle_url('/mod/choicegroup/view.php', array('id' => $cm->id, 'notify' => 'mustchooseone', 'sesskey' => sesskey()))); } else { + // Check finally if the group is still available. + if ($choicegroup->restrictbygrouping && !(empty($usergroupassigments))) { + $choicerecord = $DB->get_record('choicegroup_options', array('id' => $answer), '*', MUST_EXIST); + $groupavailable = choicegroup_check_group_available($choicerecord->groupid, $usergroupassigments, $groupgrouping); + if (!$groupavailable) { + redirect(new moodle_url('/mod/choicegroup/view.php', + array('id' => $cm->id, 'notify' => 'submitfailedgroupingrestriction', 'sesskey' => sesskey()))); + } + } choicegroup_user_submit_response($answer, $choicegroup, $USER->id, $course, $cm); redirect(new moodle_url('/mod/choicegroup/view.php', array('id' => $cm->id, 'notify' => 'choicegroupsaved', 'sesskey' => sesskey()))); @@ -180,6 +202,8 @@ echo $OUTPUT->notification(get_string('mustchooseone', 'choicegroup'), 'notifyproblem'); } else if ($notify === 'mustchoosemax') { echo $OUTPUT->notification(get_string('mustchoosemax', 'choicegroup', $choicegroup->maxenrollments), 'notifyproblem'); + } else if ($notify === 'submitfailedgroupingrestriction') { + echo $OUTPUT->notification(get_string('submitfailedgroupingrestriction', 'choicegroup'), 'notifyproblem'); } } @@ -256,10 +280,10 @@ if ( (!$current or $choicegroup->allowupdate) and $choicegroupopen and is_enrolled($context, null, 'mod/choicegroup:choose')) { // They haven't made their choicegroup yet or updates allowed and choicegroup is open - echo $renderer->display_options($options, $cm->id, $choicegroup->display, $choicegroup->publish, $choicegroup->limitanswers, $choicegroup->showresults, $current, $choicegroupopen, false, $choicegroup->multipleenrollmentspossible, $choicegroup->onlyactive); + echo $renderer->display_options($options, $cm->id, $choicegroup->display, $choicegroup->publish, $choicegroup->limitanswers, $choicegroup->showresults, $current, $choicegroupopen, false, $choicegroup->multipleenrollmentspossible, $choicegroup->onlyactive, $choicegroup->restrictchoicesbehaviour); } else { // form can not be updated - echo $renderer->display_options($options, $cm->id, $choicegroup->display, $choicegroup->publish, $choicegroup->limitanswers, $choicegroup->showresults, $current, $choicegroupopen, true, $choicegroup->multipleenrollmentspossible, $choicegroup->onlyactive); + echo $renderer->display_options($options, $cm->id, $choicegroup->display, $choicegroup->publish, $choicegroup->limitanswers, $choicegroup->showresults, $current, $choicegroupopen, true, $choicegroup->multipleenrollmentspossible, $choicegroup->onlyactive, $choicegroup->restrictchoicesbehaviour); } $choicegroupformshown = true;