moodle/group/externallib.php
Mark Johnson 958da5b67e MDL-68093 groups: Add visibility and participation settings
These new settings are designed to enchance user privacy surrounding
groups. They allow groups to be configured so that users outside the
group cannot see the group, so that users in the group cannot see each
other, or so that users cannot see the group at all, even if they are in
it. This avoids issues where a group may be assigned based on sensitive
personal information (such as a person requiring special arrangements
due to a disability).

By default, groups are visible to all and available for participation in
activities, which maintains the current behaviour.

For performance, a new cache has been added to track the number of
groups on a course that are not visible to non-members. This allows us
to revert to the existing behaviour if the new features are not being
used at all on a course, and only apply the new visibility conditions if
they are.

Users who have the moodle/course:viewhiddengroups capability should be
concious of exposing hidden groups when showing their screen to other
users. The "Switch role to..." feature can be used to show a course page
on screen without exposing private availability conditions, for example.

The changes cover several specific areas:
* grouplib functions, which most code should use to get lists of groups
  and members (this includes the participants page).
* Activities supporting group overrides will not allow overrides for
  groups that are hidden from all users.
* Activities supporting separate/visible groups modes will only allow
  groups with the new "participation" flag enabled to be selected.
* Group messaging will be disabled for groups where members cannot see
  each other, or cannot see the group at all.
2023-03-14 08:52:25 +00:00

1646 lines
63 KiB
PHP

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use core_external\external_api;
use core_external\external_format_value;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util;
use core_group\visibility;
/**
* Group external functions
*
* @package core_group
* @category external
* @copyright 2011 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
class core_group_external extends external_api {
/**
* Validate visibility.
*
* @param int $visibility Visibility string, must one of the visibility class constants.
* @throws invalid_parameter_exception if visibility is not an allowed value.
*/
protected static function validate_visibility(int $visibility): void {
$allowed = [
GROUPS_VISIBILITY_ALL,
GROUPS_VISIBILITY_MEMBERS,
GROUPS_VISIBILITY_OWN,
GROUPS_VISIBILITY_NONE,
];
if (!array_key_exists($visibility, $allowed)) {
throw new invalid_parameter_exception('Invalid group visibility provided. Must be one of '
. join(',', $allowed));
}
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function create_groups_parameters() {
return new external_function_parameters(
array(
'groups' => new external_multiple_structure(
new external_single_structure(
array(
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase', VALUE_OPTIONAL),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
'visibility' => new external_value(PARAM_INT,
'group visibility mode. 0 = Visible to all. 1 = Visible to members. '
. '2 = See own membership. 3 = Membership is hidden. default: 0',
VALUE_DEFAULT, 0),
'participation' => new external_value(PARAM_BOOL,
'activity participation enabled? Only for "all" and "members" visibility. Default true.',
VALUE_DEFAULT, true),
)
), 'List of group object. A group has a courseid, a name, a description and an enrolment key.'
)
)
);
}
/**
* Create groups
*
* @param array $groups array of group description arrays (with keys groupname and courseid)
* @return array of newly created groups
* @since Moodle 2.2
*/
public static function create_groups($groups) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::create_groups_parameters(), array('groups'=>$groups));
$transaction = $DB->start_delegated_transaction();
$groups = array();
foreach ($params['groups'] as $group) {
$group = (object)$group;
if (trim($group->name) == '') {
throw new invalid_parameter_exception('Invalid group name');
}
if ($DB->get_record('groups', array('courseid'=>$group->courseid, 'name'=>$group->name))) {
throw new invalid_parameter_exception('Group with the same name already exists in the course');
}
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
// Validate format.
$group->descriptionformat = util::validate_format($group->descriptionformat);
// Validate visibility.
self::validate_visibility($group->visibility);
// finally create the group
$group->id = groups_create_group($group, false);
if (!isset($group->enrolmentkey)) {
$group->enrolmentkey = '';
}
if (!isset($group->idnumber)) {
$group->idnumber = '';
}
$groups[] = (array)$group;
}
$transaction->allow_commit();
return $groups;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.2
*/
public static function create_groups_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
'idnumber' => new external_value(PARAM_RAW, 'id number'),
'visibility' => new external_value(PARAM_INT,
'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
. '3 = Membership is hidden.'),
'participation' => new external_value(PARAM_BOOL, 'participation mode'),
)
), 'List of group object. A group has an id, a courseid, a name, a description and an enrolment key.'
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function get_groups_parameters() {
return new external_function_parameters(
array(
'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')
,'List of group id. A group id is an integer.'),
)
);
}
/**
* Get groups definition specified by ids
*
* @param array $groupids arrays of group ids
* @return array of group objects (id, courseid, name, enrolmentkey)
* @since Moodle 2.2
*/
public static function get_groups($groupids) {
$params = self::validate_parameters(self::get_groups_parameters(), array('groupids'=>$groupids));
$groups = array();
foreach ($params['groupids'] as $groupid) {
// validate params
$group = groups_get_group($groupid, 'id, courseid, name, idnumber, description, descriptionformat, enrolmentkey, '
. 'visibility, participation', MUST_EXIST);
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
list($group->description, $group->descriptionformat) =
\core_external\util::format_text($group->description, $group->descriptionformat,
$context, 'group', 'description', $group->id);
$groups[] = (array)$group;
}
return $groups;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.2
*/
public static function get_groups_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
'idnumber' => new external_value(PARAM_RAW, 'id number'),
'visibility' => new external_value(PARAM_INT,
'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
. '3 = Membership is hidden.'),
'participation' => new external_value(PARAM_BOOL, 'participation mode'),
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function get_course_groups_parameters() {
return new external_function_parameters(
array(
'courseid' => new external_value(PARAM_INT, 'id of course'),
)
);
}
/**
* Get all groups in the specified course
*
* @param int $courseid id of course
* @return array of group objects (id, courseid, name, enrolmentkey)
* @since Moodle 2.2
*/
public static function get_course_groups($courseid) {
$params = self::validate_parameters(self::get_course_groups_parameters(), array('courseid'=>$courseid));
// now security checks
$context = context_course::instance($params['courseid'], IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $params['courseid'];
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
$gs = groups_get_all_groups($params['courseid'], 0, 0,
'g.id, g.courseid, g.name, g.idnumber, g.description, g.descriptionformat, g.enrolmentkey, '
. 'g.visibility, g.participation');
$groups = array();
foreach ($gs as $group) {
list($group->description, $group->descriptionformat) =
\core_external\util::format_text($group->description, $group->descriptionformat,
$context, 'group', 'description', $group->id);
$groups[] = (array)$group;
}
return $groups;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.2
*/
public static function get_course_groups_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
'idnumber' => new external_value(PARAM_RAW, 'id number'),
'visibility' => new external_value(PARAM_INT,
'group visibility mode. 0 = Visible to all. 1 = Visible to members. 2 = See own membership. '
. '3 = Membership is hidden.'),
'participation' => new external_value(PARAM_BOOL, 'participation mode'),
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function delete_groups_parameters() {
return new external_function_parameters(
array(
'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')),
)
);
}
/**
* Delete groups
*
* @param array $groupids array of group ids
* @since Moodle 2.2
*/
public static function delete_groups($groupids) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::delete_groups_parameters(), array('groupids'=>$groupids));
$transaction = $DB->start_delegated_transaction();
foreach ($params['groupids'] as $groupid) {
// validate params
$groupid = validate_param($groupid, PARAM_INT);
if (!$group = groups_get_group($groupid, '*', IGNORE_MISSING)) {
// silently ignore attempts to delete nonexisting groups
continue;
}
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
groups_delete_group($group);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.2
*/
public static function delete_groups_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function get_group_members_parameters() {
return new external_function_parameters(
array(
'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')),
)
);
}
/**
* Return all members for a group
*
* @param array $groupids array of group ids
* @return array with group id keys containing arrays of user ids
* @since Moodle 2.2
*/
public static function get_group_members($groupids) {
$members = array();
$params = self::validate_parameters(self::get_group_members_parameters(), array('groupids'=>$groupids));
foreach ($params['groupids'] as $groupid) {
// validate params
$group = groups_get_group($groupid, 'id, courseid, name, enrolmentkey', MUST_EXIST);
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
$groupmembers = groups_get_members($group->id, 'u.id', 'lastname ASC, firstname ASC');
$members[] = array('groupid'=>$groupid, 'userids'=>array_keys($groupmembers));
}
return $members;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.2
*/
public static function get_group_members_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'groupid' => new external_value(PARAM_INT, 'group record id'),
'userids' => new external_multiple_structure(new external_value(PARAM_INT, 'user id')),
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function add_group_members_parameters() {
return new external_function_parameters(
array(
'members'=> new external_multiple_structure(
new external_single_structure(
array(
'groupid' => new external_value(PARAM_INT, 'group record id'),
'userid' => new external_value(PARAM_INT, 'user id'),
)
)
)
)
);
}
/**
* Add group members
*
* @param array $members of arrays with keys userid, groupid
* @since Moodle 2.2
*/
public static function add_group_members($members) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::add_group_members_parameters(), array('members'=>$members));
$transaction = $DB->start_delegated_transaction();
foreach ($params['members'] as $member) {
// validate params
$groupid = $member['groupid'];
$userid = $member['userid'];
$group = groups_get_group($groupid, '*', MUST_EXIST);
$user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
// now make sure user is enrolled in course - this is mandatory requirement,
// unfortunately this is slow
if (!is_enrolled($context, $userid)) {
throw new invalid_parameter_exception('Only enrolled users may be members of groups');
}
groups_add_member($group, $user);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.2
*/
public static function add_group_members_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function delete_group_members_parameters() {
return new external_function_parameters(
array(
'members'=> new external_multiple_structure(
new external_single_structure(
array(
'groupid' => new external_value(PARAM_INT, 'group record id'),
'userid' => new external_value(PARAM_INT, 'user id'),
)
)
)
)
);
}
/**
* Delete group members
*
* @param array $members of arrays with keys userid, groupid
* @since Moodle 2.2
*/
public static function delete_group_members($members) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::delete_group_members_parameters(), array('members'=>$members));
$transaction = $DB->start_delegated_transaction();
foreach ($params['members'] as $member) {
// validate params
$groupid = $member['groupid'];
$userid = $member['userid'];
$group = groups_get_group($groupid, '*', MUST_EXIST);
$user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
// now security checks
$context = context_course::instance($group->courseid, IGNORE_MISSING);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
if (!groups_remove_member_allowed($group, $user)) {
$fullname = fullname($user, has_capability('moodle/site:viewfullnames', $context));
throw new moodle_exception('errorremovenotpermitted', 'group', '', $fullname);
}
groups_remove_member($group, $user);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.2
*/
public static function delete_group_members_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function create_groupings_parameters() {
return new external_function_parameters(
array(
'groupings' => new external_multiple_structure(
new external_single_structure(
array(
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'grouping description text'),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL)
)
), 'List of grouping object. A grouping has a courseid, a name and a description.'
)
)
);
}
/**
* Create groupings
*
* @param array $groupings array of grouping description arrays (with keys groupname and courseid)
* @return array of newly created groupings
* @since Moodle 2.3
*/
public static function create_groupings($groupings) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::create_groupings_parameters(), array('groupings'=>$groupings));
$transaction = $DB->start_delegated_transaction();
$groupings = array();
foreach ($params['groupings'] as $grouping) {
$grouping = (object)$grouping;
if (trim($grouping->name) == '') {
throw new invalid_parameter_exception('Invalid grouping name');
}
if ($DB->count_records('groupings', array('courseid'=>$grouping->courseid, 'name'=>$grouping->name))) {
throw new invalid_parameter_exception('Grouping with the same name already exists in the course');
}
// Now security checks .
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $grouping->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
$grouping->descriptionformat = util::validate_format($grouping->descriptionformat);
// Finally create the grouping.
$grouping->id = groups_create_grouping($grouping);
$groupings[] = (array)$grouping;
}
$transaction->allow_commit();
return $groupings;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.3
*/
public static function create_groupings_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'grouping record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'grouping description text'),
'descriptionformat' => new external_format_value('description'),
'idnumber' => new external_value(PARAM_RAW, 'id number')
)
), 'List of grouping object. A grouping has an id, a courseid, a name and a description.'
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function update_groupings_parameters() {
return new external_function_parameters(
array(
'groupings' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'id of grouping'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'grouping description text'),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL)
)
), 'List of grouping object. A grouping has a courseid, a name and a description.'
)
)
);
}
/**
* Update groupings
*
* @param array $groupings array of grouping description arrays (with keys groupname and courseid)
* @return array of newly updated groupings
* @since Moodle 2.3
*/
public static function update_groupings($groupings) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::update_groupings_parameters(), array('groupings'=>$groupings));
$transaction = $DB->start_delegated_transaction();
foreach ($params['groupings'] as $grouping) {
$grouping = (object)$grouping;
if (trim($grouping->name) == '') {
throw new invalid_parameter_exception('Invalid grouping name');
}
if (! $currentgrouping = $DB->get_record('groupings', array('id'=>$grouping->id))) {
throw new invalid_parameter_exception("Grouping $grouping->id does not exist in the course");
}
// Check if the new modified grouping name already exists in the course.
if ($grouping->name != $currentgrouping->name and
$DB->count_records('groupings', array('courseid'=>$currentgrouping->courseid, 'name'=>$grouping->name))) {
throw new invalid_parameter_exception('A different grouping with the same name already exists in the course');
}
$grouping->courseid = $currentgrouping->courseid;
// Now security checks.
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $grouping->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
// We must force allways FORMAT_HTML.
$grouping->descriptionformat = util::validate_format($grouping->descriptionformat);
// Finally update the grouping.
groups_update_grouping($grouping);
}
$transaction->allow_commit();
return null;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.3
*/
public static function update_groupings_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function get_groupings_parameters() {
return new external_function_parameters(
array(
'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')
, 'List of grouping id. A grouping id is an integer.'),
'returngroups' => new external_value(PARAM_BOOL, 'return associated groups', VALUE_DEFAULT, 0)
)
);
}
/**
* Get groupings definition specified by ids
*
* @param array $groupingids arrays of grouping ids
* @param boolean $returngroups return the associated groups if true. The default is false.
* @return array of grouping objects (id, courseid, name)
* @since Moodle 2.3
*/
public static function get_groupings($groupingids, $returngroups = false) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
require_once("$CFG->libdir/filelib.php");
$params = self::validate_parameters(self::get_groupings_parameters(),
array('groupingids' => $groupingids,
'returngroups' => $returngroups));
$groupings = array();
foreach ($params['groupingids'] as $groupingid) {
// Validate params.
$grouping = groups_get_grouping($groupingid, '*', MUST_EXIST);
// Now security checks.
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $grouping->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
list($grouping->description, $grouping->descriptionformat) =
\core_external\util::format_text($grouping->description, $grouping->descriptionformat,
$context, 'grouping', 'description', $grouping->id);
$groupingarray = (array)$grouping;
if ($params['returngroups']) {
$grouprecords = $DB->get_records_sql("SELECT * FROM {groups} g INNER JOIN {groupings_groups} gg ".
"ON g.id = gg.groupid WHERE gg.groupingid = ? ".
"ORDER BY groupid", array($groupingid));
if ($grouprecords) {
$groups = array();
foreach ($grouprecords as $grouprecord) {
list($grouprecord->description, $grouprecord->descriptionformat) =
\core_external\util::format_text($grouprecord->description, $grouprecord->descriptionformat,
$context, 'group', 'description', $grouprecord->groupid);
$groups[] = array('id' => $grouprecord->groupid,
'name' => $grouprecord->name,
'idnumber' => $grouprecord->idnumber,
'description' => $grouprecord->description,
'descriptionformat' => $grouprecord->descriptionformat,
'enrolmentkey' => $grouprecord->enrolmentkey,
'courseid' => $grouprecord->courseid
);
}
$groupingarray['groups'] = $groups;
}
}
$groupings[] = $groupingarray;
}
return $groupings;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.3
*/
public static function get_groupings_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'grouping record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'grouping description text'),
'descriptionformat' => new external_format_value('description'),
'idnumber' => new external_value(PARAM_RAW, 'id number'),
'groups' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
'idnumber' => new external_value(PARAM_RAW, 'id number')
)
),
'optional groups', VALUE_OPTIONAL)
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function get_course_groupings_parameters() {
return new external_function_parameters(
array(
'courseid' => new external_value(PARAM_INT, 'id of course'),
)
);
}
/**
* Get all groupings in the specified course
*
* @param int $courseid id of course
* @return array of grouping objects (id, courseid, name, enrolmentkey)
* @since Moodle 2.3
*/
public static function get_course_groupings($courseid) {
global $CFG;
require_once("$CFG->dirroot/group/lib.php");
require_once("$CFG->libdir/filelib.php");
$params = self::validate_parameters(self::get_course_groupings_parameters(), array('courseid'=>$courseid));
// Now security checks.
$context = context_course::instance($params['courseid']);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $params['courseid'];
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
$gs = groups_get_all_groupings($params['courseid']);
$groupings = array();
foreach ($gs as $grouping) {
list($grouping->description, $grouping->descriptionformat) =
\core_external\util::format_text($grouping->description, $grouping->descriptionformat,
$context, 'grouping', 'description', $grouping->id);
$groupings[] = (array)$grouping;
}
return $groupings;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.3
*/
public static function get_course_groupings_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'grouping record id'),
'courseid' => new external_value(PARAM_INT, 'id of course'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'grouping description text'),
'descriptionformat' => new external_format_value('description'),
'idnumber' => new external_value(PARAM_RAW, 'id number')
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function delete_groupings_parameters() {
return new external_function_parameters(
array(
'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')),
)
);
}
/**
* Delete groupings
*
* @param array $groupingids array of grouping ids
* @return void
* @since Moodle 2.3
*/
public static function delete_groupings($groupingids) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::delete_groupings_parameters(), array('groupingids'=>$groupingids));
$transaction = $DB->start_delegated_transaction();
foreach ($params['groupingids'] as $groupingid) {
if (!$grouping = groups_get_grouping($groupingid)) {
// Silently ignore attempts to delete nonexisting groupings.
continue;
}
// Now security checks.
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $grouping->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
groups_delete_grouping($grouping);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.3
*/
public static function delete_groupings_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function assign_grouping_parameters() {
return new external_function_parameters(
array(
'assignments'=> new external_multiple_structure(
new external_single_structure(
array(
'groupingid' => new external_value(PARAM_INT, 'grouping record id'),
'groupid' => new external_value(PARAM_INT, 'group record id'),
)
)
)
)
);
}
/**
* Assign a group to a grouping
*
* @param array $assignments of arrays with keys groupid, groupingid
* @return void
* @since Moodle 2.3
*/
public static function assign_grouping($assignments) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::assign_grouping_parameters(), array('assignments'=>$assignments));
$transaction = $DB->start_delegated_transaction();
foreach ($params['assignments'] as $assignment) {
// Validate params.
$groupingid = $assignment['groupingid'];
$groupid = $assignment['groupid'];
$grouping = groups_get_grouping($groupingid, 'id, courseid', MUST_EXIST);
$group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
// Continue silently if the group is yet assigned to the grouping.
continue;
}
// Now security checks.
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
groups_assign_grouping($groupingid, $groupid);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.3
*/
public static function assign_grouping_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.3
*/
public static function unassign_grouping_parameters() {
return new external_function_parameters(
array(
'unassignments'=> new external_multiple_structure(
new external_single_structure(
array(
'groupingid' => new external_value(PARAM_INT, 'grouping record id'),
'groupid' => new external_value(PARAM_INT, 'group record id'),
)
)
)
)
);
}
/**
* Unassign a group from a grouping
*
* @param array $unassignments of arrays with keys groupid, groupingid
* @return void
* @since Moodle 2.3
*/
public static function unassign_grouping($unassignments) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::unassign_grouping_parameters(), array('unassignments'=>$unassignments));
$transaction = $DB->start_delegated_transaction();
foreach ($params['unassignments'] as $unassignment) {
// Validate params.
$groupingid = $unassignment['groupingid'];
$groupid = $unassignment['groupid'];
$grouping = groups_get_grouping($groupingid, 'id, courseid', MUST_EXIST);
$group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
if (!$DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
// Continue silently if the group is not assigned to the grouping.
continue;
}
// Now security checks.
$context = context_course::instance($grouping->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
groups_unassign_grouping($groupingid, $groupid);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.3
*/
public static function unassign_grouping_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.9
*/
public static function get_course_user_groups_parameters() {
return new external_function_parameters(
array(
'courseid' => new external_value(PARAM_INT,
'Id of course (empty or 0 for all the courses where the user is enrolled).', VALUE_DEFAULT, 0),
'userid' => new external_value(PARAM_INT, 'Id of user (empty or 0 for current user).', VALUE_DEFAULT, 0),
'groupingid' => new external_value(PARAM_INT, 'returns only groups in the specified grouping', VALUE_DEFAULT, 0)
)
);
}
/**
* Get all groups in the specified course for the specified user.
*
* @throws moodle_exception
* @param int $courseid id of course.
* @param int $userid id of user.
* @param int $groupingid optional returns only groups in the specified grouping.
* @return array of group objects (id, name, description, format) and possible warnings.
* @since Moodle 2.9
*/
public static function get_course_user_groups($courseid = 0, $userid = 0, $groupingid = 0) {
global $USER;
// Warnings array, it can be empty at the end but is mandatory.
$warnings = array();
$params = array(
'courseid' => $courseid,
'userid' => $userid,
'groupingid' => $groupingid
);
$params = self::validate_parameters(self::get_course_user_groups_parameters(), $params);
$courseid = $params['courseid'];
$userid = $params['userid'];
$groupingid = $params['groupingid'];
// Validate user.
if (empty($userid)) {
$userid = $USER->id;
} else {
$user = core_user::get_user($userid, '*', MUST_EXIST);
core_user::require_active_user($user);
}
// Get courses.
if (empty($courseid)) {
$courses = enrol_get_users_courses($userid, true);
$checkenrolments = false; // No need to check enrolments here since they are my courses.
} else {
$courses = array($courseid => get_course($courseid));
$checkenrolments = true;
}
// Security checks.
list($courses, $warnings) = util::validate_courses(array_keys($courses), $courses, true);
$usergroups = array();
foreach ($courses as $course) {
// Check if we have permissions for retrieve the information.
if ($userid != $USER->id && !has_capability('moodle/course:managegroups', $course->context)) {
$warnings[] = array(
'item' => 'course',
'itemid' => $course->id,
'warningcode' => 'cannotmanagegroups',
'message' => "User $USER->id cannot manage groups in course $course->id",
);
continue;
}
// Check if the user being check is enrolled in the given course.
if ($checkenrolments && !is_enrolled($course->context, $userid)) {
// We return a warning because the function does not fail for not enrolled users.
$warnings[] = array(
'item' => 'course',
'itemid' => $course->id,
'warningcode' => 'notenrolled',
'message' => "User $userid is not enrolled in course $course->id",
);
}
$groups = groups_get_all_groups($course->id, $userid, $groupingid,
'g.id, g.name, g.description, g.descriptionformat, g.idnumber');
foreach ($groups as $group) {
list($group->description, $group->descriptionformat) =
\core_external\util::format_text($group->description, $group->descriptionformat,
$course->context, 'group', 'description', $group->id);
$group->courseid = $course->id;
$usergroups[] = $group;
}
}
$results = array(
'groups' => $usergroups,
'warnings' => $warnings
);
return $results;
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description A single structure containing groups and possible warnings.
* @since Moodle 2.9
*/
public static function get_course_user_groups_returns() {
return new external_single_structure(
array(
'groups' => new external_multiple_structure(self::group_description()),
'warnings' => new external_warnings(),
)
);
}
/**
* Create group return value description.
*
* @return external_single_structure The group description
*/
public static function group_description() {
return new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'group record id'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text'),
'descriptionformat' => new external_format_value('description'),
'idnumber' => new external_value(PARAM_RAW, 'id number'),
'courseid' => new external_value(PARAM_INT, 'course id', VALUE_OPTIONAL),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_activity_allowed_groups_parameters() {
return new external_function_parameters(
array(
'cmid' => new external_value(PARAM_INT, 'course module id'),
'userid' => new external_value(PARAM_INT, 'id of user, empty for current user', VALUE_DEFAULT, 0)
)
);
}
/**
* Gets a list of groups that the user is allowed to access within the specified activity.
*
* @throws moodle_exception
* @param int $cmid course module id
* @param int $userid id of user.
* @return array of group objects (id, name, description, format) and possible warnings.
* @since Moodle 3.0
*/
public static function get_activity_allowed_groups($cmid, $userid = 0) {
global $USER;
// Warnings array, it can be empty at the end but is mandatory.
$warnings = array();
$params = array(
'cmid' => $cmid,
'userid' => $userid
);
$params = self::validate_parameters(self::get_activity_allowed_groups_parameters(), $params);
$cmid = $params['cmid'];
$userid = $params['userid'];
$cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
// Security checks.
$context = context_module::instance($cm->id);
$coursecontext = context_course::instance($cm->course);
self::validate_context($context);
if (empty($userid)) {
$userid = $USER->id;
}
$user = core_user::get_user($userid, '*', MUST_EXIST);
core_user::require_active_user($user);
// Check if we have permissions for retrieve the information.
if ($user->id != $USER->id) {
if (!has_capability('moodle/course:managegroups', $context)) {
throw new moodle_exception('accessdenied', 'admin');
}
// Validate if the user is enrolled in the course.
$course = get_course($cm->course);
if (!can_access_course($course, $user, '', true)) {
// We return a warning because the function does not fail for not enrolled users.
$warning = array();
$warning['item'] = 'course';
$warning['itemid'] = $cm->course;
$warning['warningcode'] = '1';
$warning['message'] = "User $user->id cannot access course $cm->course";
$warnings[] = $warning;
}
}
$usergroups = array();
if (empty($warnings)) {
$groups = groups_get_activity_allowed_groups($cm, $user->id);
foreach ($groups as $group) {
list($group->description, $group->descriptionformat) =
\core_external\util::format_text($group->description, $group->descriptionformat,
$coursecontext, 'group', 'description', $group->id);
$group->courseid = $cm->course;
$usergroups[] = $group;
}
}
$results = array(
'groups' => $usergroups,
'canaccessallgroups' => has_capability('moodle/site:accessallgroups', $context, $user),
'warnings' => $warnings
);
return $results;
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description A single structure containing groups and possible warnings.
* @since Moodle 3.0
*/
public static function get_activity_allowed_groups_returns() {
return new external_single_structure(
array(
'groups' => new external_multiple_structure(self::group_description()),
'canaccessallgroups' => new external_value(PARAM_BOOL,
'Whether the user will be able to access all the activity groups.', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_activity_groupmode_parameters() {
return new external_function_parameters(
array(
'cmid' => new external_value(PARAM_INT, 'course module id')
)
);
}
/**
* Returns effective groupmode used in a given activity.
*
* @throws moodle_exception
* @param int $cmid course module id.
* @return array containing the group mode and possible warnings.
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function get_activity_groupmode($cmid) {
global $USER;
// Warnings array, it can be empty at the end but is mandatory.
$warnings = array();
$params = array(
'cmid' => $cmid
);
$params = self::validate_parameters(self::get_activity_groupmode_parameters(), $params);
$cmid = $params['cmid'];
$cm = get_coursemodule_from_id(null, $cmid, 0, false, MUST_EXIST);
// Security checks.
$context = context_module::instance($cm->id);
self::validate_context($context);
$groupmode = groups_get_activity_groupmode($cm);
$results = array(
'groupmode' => $groupmode,
'warnings' => $warnings
);
return $results;
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function get_activity_groupmode_returns() {
return new external_single_structure(
array(
'groupmode' => new external_value(PARAM_INT, 'group mode:
0 for no groups, 1 for separate groups, 2 for visible groups'),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.6
*/
public static function update_groups_parameters() {
return new external_function_parameters(
array(
'groups' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'ID of the group'),
'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
'description' => new external_value(PARAM_RAW, 'group description text', VALUE_OPTIONAL),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase', VALUE_OPTIONAL),
'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
'visibility' => new external_value(PARAM_TEXT,
'group visibility mode. 0 = Visible to all. 1 = Visible to members. '
. '2 = See own membership. 3 = Membership is hidden.', VALUE_OPTIONAL),
'participation' => new external_value(PARAM_BOOL,
'activity participation enabled? Only for "all" and "members" visibility', VALUE_OPTIONAL),
)
), 'List of group objects. A group is found by the id, then all other details provided will be updated.'
)
)
);
}
/**
* Update groups
*
* @param array $groups
* @return null
* @since Moodle 3.6
*/
public static function update_groups($groups) {
global $CFG, $DB;
require_once("$CFG->dirroot/group/lib.php");
$params = self::validate_parameters(self::update_groups_parameters(), array('groups' => $groups));
$transaction = $DB->start_delegated_transaction();
foreach ($params['groups'] as $group) {
$group = (object) $group;
if (trim($group->name) == '') {
throw new invalid_parameter_exception('Invalid group name');
}
if (!$currentgroup = $DB->get_record('groups', array('id' => $group->id))) {
throw new invalid_parameter_exception("Group $group->id does not exist");
}
// Check if the modified group name already exists in the course.
if ($group->name != $currentgroup->name and
$DB->get_record('groups', array('courseid' => $currentgroup->courseid, 'name' => $group->name))) {
throw new invalid_parameter_exception('A different group with the same name already exists in the course');
}
if (isset($group->visibility) || isset($group->participation)) {
$hasmembers = $DB->record_exists('groups_members', ['groupid' => $group->id]);
if (isset($group->visibility)) {
// Validate visibility.
self::validate_visibility($group->visibility);
if ($hasmembers && $group->visibility != $currentgroup->visibility) {
throw new invalid_parameter_exception(
'The visibility of this group cannot be changed as it currently has members.');
}
} else {
$group->visibility = $currentgroup->visibility;
}
if (isset($group->participation) && $hasmembers && $group->participation != $currentgroup->participation) {
throw new invalid_parameter_exception(
'The participation mode of this group cannot be changed as it currently has members.');
}
}
$group->courseid = $currentgroup->courseid;
// Now security checks.
$context = context_course::instance($group->courseid);
try {
self::validate_context($context);
} catch (Exception $e) {
$exceptionparam = new stdClass();
$exceptionparam->message = $e->getMessage();
$exceptionparam->courseid = $group->courseid;
throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
}
require_capability('moodle/course:managegroups', $context);
if (!empty($group->description)) {
$group->descriptionformat = util::validate_format($group->descriptionformat);
}
groups_update_group($group);
}
$transaction->allow_commit();
return null;
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 3.6
*/
public static function update_groups_returns() {
return null;
}
}