MDL-78449 choice: Support groups in get_choice_results WS

This commit is contained in:
Dani Palou
2025-03-05 09:34:16 +01:00
parent 5670447ece
commit 3df726539b
5 changed files with 229 additions and 11 deletions
@@ -0,0 +1,10 @@
issueNumber: MDL-78449
notes:
mod_choice:
- message: >
The WebService `mod_choice_get_choice_results` has a new parameter
`groupid` that allows specifying the group to get the results for. The
default behaviour hasn't changed: if a choice has groups and the
parameter isn't specified the WebService will return the results for the
active group.
type: changed
@@ -0,0 +1,9 @@
issueNumber: MDL-78449
notes:
mod_choice:
- message: >
The function `choice_get_response_data` has a new parameter that allows
specifying the group to get the results for. The default behaviour
hasn't changed: if a choice has groups and the parameter isn't used, the
function will return the results for the active group.
type: changed
+29 -6
View File
@@ -54,20 +54,32 @@ class mod_choice_external extends external_api {
* @since Moodle 3.0
*/
public static function get_choice_results_parameters() {
return new external_function_parameters (array('choiceid' => new external_value(PARAM_INT, 'choice instance id')));
return new external_function_parameters ([
'choiceid' => new external_value(PARAM_INT, 'choice instance id'),
'groupid' => new external_value(
PARAM_INT,
'Group ID. 0 for all participants, empty for active group.',
VALUE_DEFAULT,
null,
),
]);
}
/**
* Returns user's results for a specific choice
* and a list of those users that did not answered yet.
*
* @param int $choiceid the choice instance id
* @param int $groupid The group to use, 0 for all participants, null to calculate active group.
* @return array of responses details
* @since Moodle 3.0
*/
public static function get_choice_results($choiceid) {
global $USER, $PAGE;
public static function get_choice_results($choiceid, ?int $groupid = null) {
global $PAGE;
$params = self::validate_parameters(self::get_choice_results_parameters(), array('choiceid' => $choiceid));
$params = self::validate_parameters(self::get_choice_results_parameters(), [
'choiceid' => $choiceid,
'groupid' => $groupid,
]);
if (!$choice = choice_get_choice($params['choiceid'])) {
throw new moodle_exception("invalidcoursemodule", "error");
@@ -77,10 +89,21 @@ class mod_choice_external extends external_api {
$context = context_module::instance($cm->id);
self::validate_context($context);
$groupmode = groups_get_activity_groupmode($cm);
if ($groupmode = groups_get_activity_groupmode($cm)) {
if (is_null($groupid)) {
$groupid = groups_get_activity_group($cm);
}
if (!groups_group_visible($groupid, $course, $cm)) {
throw new moodle_exception('notingroup');
}
} else {
$groupid = 0;
}
// Check if we have to include responses from inactive users.
$onlyactive = $choice->includeinactive ? false : true;
$users = choice_get_response_data($choice, $cm, $groupmode, $onlyactive);
$users = choice_get_response_data($choice, $cm, $groupmode, $onlyactive, $groupid);
// Show those who haven't answered the question.
if (!empty($choice->showunanswered)) {
$choice->option[0] = get_string('notanswered', 'choice');
+10 -5
View File
@@ -782,6 +782,8 @@ function choice_reset_userdata($data) {
}
/**
* Get response data for a choice and group.
*
* @global object
* @global object
* @global object
@@ -790,18 +792,21 @@ function choice_reset_userdata($data) {
* @param object $cm
* @param int $groupmode
* @param bool $onlyactive Whether to get response data for active users only.
* @param int $groupid Group id, null for current group if choice has groups.
* @return array
*/
function choice_get_response_data($choice, $cm, $groupmode, $onlyactive) {
function choice_get_response_data($choice, $cm, $groupmode, $onlyactive, ?int $groupid = null) {
global $CFG, $USER, $DB;
$context = context_module::instance($cm->id);
/// Get the current group
if ($groupmode > 0) {
$currentgroup = groups_get_activity_group($cm);
if (is_null($groupid)) {
// Get the current group.
$groupid = groups_get_activity_group($cm);
}
} else {
$currentgroup = 0;
$groupid = 0;
}
/// Initialise the returned array, which is a matrix: $allresponses[responseid][userid] = responseobject
@@ -812,7 +817,7 @@ function choice_get_response_data($choice, $cm, $groupmode, $onlyactive) {
// TODO Does not support custom user profile fields (MDL-70456).
$userfieldsapi = \core_user\fields::for_identity($context, false)->with_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$allresponses[0] = get_enrolled_users($context, 'mod/choice:choose', $currentgroup,
$allresponses[0] = get_enrolled_users($context, 'mod/choice:choose', $groupid,
$userfields, null, 0, 0, $onlyactive);
/// Get all the recorded responses for this choice
+171
View File
@@ -34,6 +34,7 @@ require_once($CFG->dirroot . '/mod/choice/lib.php');
* @category external
* @copyright 2015 Costantino Cito <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mod_choice_external
*/
final class externallib_test extends externallib_advanced_testcase {
@@ -144,6 +145,176 @@ final class externallib_test extends externallib_advanced_testcase {
$this->assertEquals(1, $resultsarr[$myanswer]['numberofuser']);
}
/**
* Test get_choice_results using groups.
*/
public function test_get_choice_results_with_groups(): void {
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
// Create 3 choices: one with separate groups, one with visible groups and one with no groups.
$commonparams = [
'course' => $course->id,
'option' => ['fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'],
'name' => 'Separate groups choice',
'showresults' => CHOICE_SHOWRESULTS_ALWAYS,
'publish' => 1,
];
$nogroupschoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => NOGROUPS]));
$separatechoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => SEPARATEGROUPS]));
$visiblechoice = $choicegenerator->create_instance(array_merge($commonparams, ['groupmode' => VISIBLEGROUPS]));
$nogroupsoptions = array_keys(choice_get_choice($nogroupschoice->id)->option);
$separateoptions = array_keys(choice_get_choice($separatechoice->id)->option);
$visibleoptions = array_keys(choice_get_choice($visiblechoice->id)->option);
$nogroupscm = get_coursemodule_from_id('choice', $nogroupschoice->cmid);
$separatecm = get_coursemodule_from_id('choice', $separatechoice->cmid);
$visiblecm = get_coursemodule_from_id('choice', $visiblechoice->cmid);
// Enrol 3 students in the course. One student will have no group, the others will belong one to each group.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$studentnogroup = $this->getDataGenerator()->create_user();
self::getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
self::getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
self::getDataGenerator()->enrol_user($studentnogroup->id, $course->id, 'student');
groups_add_member($group1, $student1);
groups_add_member($group2, $student2);
// Add answers for both users in all choices.
$this->setUser($student1);
choice_user_submit_response($nogroupsoptions[0], $nogroupschoice, $student1->id, $course, $nogroupscm);
choice_user_submit_response($separateoptions[0], $separatechoice, $student1->id, $course, $separatecm);
choice_user_submit_response($visibleoptions[0], $visiblechoice, $student1->id, $course, $visiblecm);
$this->setUser($student2);
choice_user_submit_response($nogroupsoptions[1], $nogroupschoice, $student2->id, $course, $nogroupscm);
choice_user_submit_response($separateoptions[1], $separatechoice, $student2->id, $course, $separatecm);
choice_user_submit_response($visibleoptions[1], $visiblechoice, $student2->id, $course, $visiblecm);
// No groups: check that the groupid parameter is ignored.
$results = mod_choice_external::get_choice_results($nogroupschoice->id, $group1->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$nogroupsoptions[0]][0]['userid'], $student1->id);
$this->assertEquals($resultsarr[$nogroupsoptions[1]][0]['userid'], $student2->id);
$results = mod_choice_external::get_choice_results($nogroupschoice->id, $group2->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$nogroupsoptions[0]][0]['userid'], $student1->id);
$this->assertEquals($resultsarr[$nogroupsoptions[1]][0]['userid'], $student2->id);
// Separate groups: check that students can only see results of the group they belong.
$this->setUser($student1);
$results = mod_choice_external::get_choice_results($separatechoice->id, $group1->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$separateoptions[0]][0]['userid'], $student1->id);
$this->assertCount(0, $resultsarr[$separateoptions[1]]); // The answer for the group 2 is not returned.
try {
mod_choice_external::get_choice_results($separatechoice->id, $group2->id);
$this->fail('Exception expected due to not visible group.');
} catch (\moodle_exception $e) {
$this->assertEquals('notingroup', $e->errorcode);
}
try {
mod_choice_external::get_choice_results($separatechoice->id, 0); // All participants also throws error.
$this->fail('Exception expected due to not visible group.');
} catch (\moodle_exception $e) {
$this->assertEquals('notingroup', $e->errorcode);
}
$this->setUser($student2);
$results = mod_choice_external::get_choice_results($separatechoice->id, $group2->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$separateoptions[1]][0]['userid'], $student2->id);
$this->assertCount(0, $resultsarr[$separateoptions[0]]); // The answer for the group 1 is not returned.
try {
mod_choice_external::get_choice_results($separatechoice->id, $group1->id);
$this->fail('Exception expected due to not visible group.');
} catch (\moodle_exception $e) {
$this->assertEquals('notingroup', $e->errorcode);
}
// Visible groups: check that students can see results of all groups, including 'All participants'.
$this->setUser($student1);
$results = mod_choice_external::get_choice_results($visiblechoice->id, $group1->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
$this->assertCount(0, $resultsarr[$visibleoptions[1]]); // The answer for the other group is not returned.
$results = mod_choice_external::get_choice_results($visiblechoice->id, $group2->id);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);
$this->assertCount(0, $resultsarr[$visibleoptions[0]]); // The answer for the other group is not returned.
$results = mod_choice_external::get_choice_results($visiblechoice->id, 0);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
$this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);
// User with no groups can view results for the visible groups choice, but not for separate groups.
$this->setUser($studentnogroup);
$results = mod_choice_external::get_choice_results($visiblechoice->id, 0);
$results = external_api::clean_returnvalue(mod_choice_external::get_choice_results_returns(), $results);
$resultsarr = [];
foreach ($results['options'] as $option) {
$resultsarr[$option['id']] = $option['userresponses'];
}
$this->assertEquals($resultsarr[$visibleoptions[0]][0]['userid'], $student1->id);
$this->assertEquals($resultsarr[$visibleoptions[1]][0]['userid'], $student2->id);
try {
mod_choice_external::get_choice_results($separatechoice->id, 0);
$this->fail('Exception expected due to not visible group.');
} catch (\moodle_exception $e) {
$this->assertEquals('notingroup', $e->errorcode);
}
}
/**
* Test get_choice_options
*/