AutorÃa | Ultima modificación | Ver Log |
<?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/>./*** Extra library for groups and groupings.** @copyright 2006 The Open University, J.White AT open.ac.uk, Petr Skoda (skodak)* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @package core_group*//** INTERNAL FUNCTIONS - to be used by moodle core only* require_once $CFG->dirroot.'/group/lib.php' must be used*//*** Adds a specified user to a group** @param mixed $grouporid The group id or group object* @param mixed $userorid The user id or user object* @param string $component Optional component name e.g. 'enrol_imsenterprise'* @param int $itemid Optional itemid associated with component* @return bool True if user added successfully or the user is already a* member of the group, false otherwise.*/function groups_add_member($grouporid, $userorid, $component=null, $itemid=0) {global $DB;if (is_object($userorid)) {$userid = $userorid->id;$user = $userorid;if (!isset($user->deleted)) {$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);}} else {$userid = $userorid;$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);}if ($user->deleted) {return false;}if (is_object($grouporid)) {$groupid = $grouporid->id;$group = $grouporid;} else {$groupid = $grouporid;$group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);}// Check if the user a participant of the group course.$context = context_course::instance($group->courseid);if (!is_enrolled($context, $userid)) {return false;}if (groups_is_member($groupid, $userid)) {return true;}$member = new stdClass();$member->groupid = $groupid;$member->userid = $userid;$member->timeadded = time();$member->component = '';$member->itemid = 0;// Check the component exists if specifiedif (!empty($component)) {$dir = core_component::get_component_directory($component);if ($dir && is_dir($dir)) {// Component exists and can be used$member->component = $component;$member->itemid = $itemid;} else {throw new coding_exception('Invalid call to groups_add_member(). An invalid component was specified');}}if ($itemid !== 0 && empty($member->component)) {// An itemid can only be specified if a valid component was foundthrow new coding_exception('Invalid call to groups_add_member(). A component must be specified if an itemid is given');}$DB->insert_record('groups_members', $member);// Update group info, and group object.$DB->set_field('groups', 'timemodified', $member->timeadded, array('id'=>$groupid));$group->timemodified = $member->timeadded;// Invalidate the group and grouping cache for users.cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));// Group conversation messaging.if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {\core_message\api::add_members_to_conversation([$userid], $conversation->id);}// Trigger group event.$params = array('context' => $context,'objectid' => $groupid,'relateduserid' => $userid,'other' => array('component' => $member->component,'itemid' => $member->itemid));$event = \core\event\group_member_added::create($params);$event->add_record_snapshot('groups', $group);$event->trigger();// Dispatch the hook for a user added to the group.$hook = new \core_group\hook\after_group_membership_added(groupinstance: $group,userids: [$userid],);\core\di::get(\core\hook\manager::class)->dispatch($hook);return true;}/*** Checks whether the current user is permitted (using the normal UI) to* remove a specific group member, assuming that they have access to remove* group members in general.** For automatically-created group member entries, this checks with the* relevant plugin to see whether it is permitted. The default, if the plugin* doesn't provide a function, is true.** For other entries (and any which have already been deleted/don't exist) it* just returns true.** @param mixed $grouporid The group id or group object* @param mixed $userorid The user id or user object* @return bool True if permitted, false otherwise*/function groups_remove_member_allowed($grouporid, $userorid) {global $DB;if (is_object($userorid)) {$userid = $userorid->id;} else {$userid = $userorid;}if (is_object($grouporid)) {$groupid = $grouporid->id;} else {$groupid = $grouporid;}// Get entryif (!($entry = $DB->get_record('groups_members',array('groupid' => $groupid, 'userid' => $userid), '*', IGNORE_MISSING))) {// If the entry does not exist, they are allowed to remove it (this// is consistent with groups_remove_member below).return true;}// If the entry does not have a component value, they can remove itif (empty($entry->component)) {return true;}// It has a component value, so we need to call a plugin function (if it// exists); the default is to allow removalreturn component_callback($entry->component, 'allow_group_member_remove',array($entry->itemid, $entry->groupid, $entry->userid), true);}/*** Deletes the link between the specified user and group.** @param mixed $grouporid The group id or group object* @param mixed $userorid The user id or user object* @return bool True if deletion was successful, false otherwise*/function groups_remove_member($grouporid, $userorid) {global $DB;if (is_object($userorid)) {$userid = $userorid->id;} else {$userid = $userorid;}if (is_object($grouporid)) {$groupid = $grouporid->id;$group = $grouporid;} else {$groupid = $grouporid;$group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);}if (!groups_is_member($groupid, $userid)) {return true;}$DB->delete_records('groups_members', array('groupid'=>$groupid, 'userid'=>$userid));// Update group info.$time = time();$DB->set_field('groups', 'timemodified', $time, array('id' => $groupid));$group->timemodified = $time;// Invalidate the group and grouping cache for users.cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));// Group conversation messaging.$context = context_course::instance($group->courseid);if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {\core_message\api::remove_members_from_conversation([$userid], $conversation->id);}// Trigger group event.$params = array('context' => context_course::instance($group->courseid),'objectid' => $groupid,'relateduserid' => $userid);$event = \core\event\group_member_removed::create($params);$event->add_record_snapshot('groups', $group);$event->trigger();// Dispatch the hook for a user removed from the group.$hook = new \core_group\hook\after_group_membership_removed(groupinstance: $group,userids: [$userid],);\core\di::get(\core\hook\manager::class)->dispatch($hook);return true;}/*** Add a new group** @param stdClass $data group properties* @param stdClass $editform* @param array $editoroptions* @return int id of group or throws an exception on error* @throws moodle_exception*/function groups_create_group($data, $editform = false, $editoroptions = false) {global $CFG, $DB, $USER;//check that courseid exists$course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);$context = context_course::instance($course->id);$data->timecreated = time();$data->timemodified = $data->timecreated;$data->name = trim($data->name);if (isset($data->idnumber)) {$data->idnumber = trim($data->idnumber);if (groups_get_group_by_idnumber($course->id, $data->idnumber)) {throw new moodle_exception('idnumbertaken');}}$data->visibility ??= GROUPS_VISIBILITY_ALL;if (!in_array($data->visibility, [GROUPS_VISIBILITY_ALL, GROUPS_VISIBILITY_MEMBERS])) {$data->participation = false;$data->enablemessaging = false;}if ($editform and $editoroptions) {$data->description = $data->description_editor['text'];$data->descriptionformat = $data->description_editor['format'];}$data->id = $DB->insert_record('groups', $data);$handler = \core_group\customfield\group_handler::create();$handler->instance_form_save($data, true);if ($editform and $editoroptions) {// Update description from editor with fixed files$data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);$upd = new stdClass();$upd->id = $data->id;$upd->description = $data->description;$upd->descriptionformat = $data->descriptionformat;$DB->update_record('groups', $upd);}$group = $DB->get_record('groups', array('id'=>$data->id));if ($editform) {groups_update_group_icon($group, $data, $editform);}// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($course->id));// Rebuild the coursehiddengroups cache for the course.\core_group\visibility::update_hiddengroups_cache($course->id);// Group conversation messaging.if (\core_message\api::can_create_group_conversation($USER->id, $context)) {if (!empty($data->enablemessaging)) {\core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,[],$group->name,\core_message\api::MESSAGE_CONVERSATION_ENABLED,'core_group','groups',$group->id,$context->id);}}// Trigger group event.$params = array('context' => $context,'objectid' => $group->id);$event = \core\event\group_created::create($params);$event->add_record_snapshot('groups', $group);$event->trigger();// Dispatch the hook for post group creation actions.$hook = new \core_group\hook\after_group_created(groupinstance: $group,);\core\di::get(\core\hook\manager::class)->dispatch($hook);return $group->id;}/*** Add a new grouping** @param stdClass $data grouping properties* @param array $editoroptions* @return int id of grouping or throws an exception on error* @throws moodle_exception*/function groups_create_grouping($data, $editoroptions=null) {global $DB;$data->timecreated = time();$data->timemodified = $data->timecreated;$data->name = trim($data->name);if (isset($data->idnumber)) {$data->idnumber = trim($data->idnumber);if (groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) {throw new moodle_exception('idnumbertaken');}}if ($editoroptions !== null) {$data->description = $data->description_editor['text'];$data->descriptionformat = $data->description_editor['format'];}$id = $DB->insert_record('groupings', $data);$data->id = $id;$handler = \core_group\customfield\grouping_handler::create();$handler->instance_form_save($data, true);if ($editoroptions !== null) {$description = new stdClass;$description->id = $data->id;$description->description_editor = $data->description_editor;$description = file_postupdate_standard_editor($description, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $description->id);$DB->update_record('groupings', $description);}// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));// Trigger group event.$params = array('context' => context_course::instance($data->courseid),'objectid' => $id);$event = \core\event\grouping_created::create($params);$event->trigger();return $id;}/*** Update the group icon from form data** @param stdClass $group group information* @param stdClass $data* @param stdClass $editform*/function groups_update_group_icon($group, $data, $editform) {global $CFG, $DB;require_once("$CFG->libdir/gdlib.php");$fs = get_file_storage();$context = context_course::instance($group->courseid, MUST_EXIST);$newpicture = $group->picture;if (!empty($data->deletepicture)) {$fs->delete_area_files($context->id, 'group', 'icon', $group->id);$newpicture = 0;} else if ($iconfile = $editform->save_temp_file('imagefile')) {if ($rev = process_new_icon($context, 'group', 'icon', $group->id, $iconfile)) {$newpicture = $rev;} else {$fs->delete_area_files($context->id, 'group', 'icon', $group->id);$newpicture = 0;}@unlink($iconfile);}if ($newpicture != $group->picture) {$DB->set_field('groups', 'picture', $newpicture, array('id' => $group->id));$group->picture = $newpicture;// Invalidate the group data as we've updated the group record.cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));}}/*** Update group** @param stdClass $data group properties (with magic quotes)* @param stdClass $editform* @param array $editoroptions* @return bool true or exception*/function groups_update_group($data, $editform = false, $editoroptions = false) {global $CFG, $DB, $USER;$context = context_course::instance($data->courseid);$data->timemodified = time();if (isset($data->name)) {$data->name = trim($data->name);}if (isset($data->idnumber)) {$data->idnumber = trim($data->idnumber);if (($existing = groups_get_group_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {throw new moodle_exception('idnumbertaken');}}if (isset($data->visibility) && !in_array($data->visibility, [GROUPS_VISIBILITY_ALL, GROUPS_VISIBILITY_MEMBERS])) {$data->participation = false;$data->enablemessaging = false;}if ($editform and $editoroptions) {$data = file_postupdate_standard_editor($data, 'description', $editoroptions, $context, 'group', 'description', $data->id);}$DB->update_record('groups', $data);$handler = \core_group\customfield\group_handler::create();$handler->instance_form_save($data);// Invalidate the group data.cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));// Rebuild the coursehiddengroups cache for the course.\core_group\visibility::update_hiddengroups_cache($data->courseid);$group = $DB->get_record('groups', array('id'=>$data->id));if ($editform) {groups_update_group_icon($group, $data, $editform);}// Group conversation messaging.if (\core_message\api::can_create_group_conversation($USER->id, $context)) {if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $group->id, $context->id)) {if ($data->enablemessaging && $data->enablemessaging != $conversation->enabled) {\core_message\api::enable_conversation($conversation->id);}if (!$data->enablemessaging && $data->enablemessaging != $conversation->enabled) {\core_message\api::disable_conversation($conversation->id);}\core_message\api::update_conversation_name($conversation->id, $group->name);} else {if (!empty($data->enablemessaging)) {$conversation = \core_message\api::create_conversation(\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,[],$group->name,\core_message\api::MESSAGE_CONVERSATION_ENABLED,'core_group','groups',$group->id,$context->id);// Add members to conversation if they exists in the group.if ($groupmemberroles = groups_get_members_by_role($group->id, $group->courseid, 'u.id')) {$users = [];foreach ($groupmemberroles as $roleid => $roledata) {foreach ($roledata->users as $member) {$users[] = $member->id;}}\core_message\api::add_members_to_conversation($users, $conversation->id);}}}}// Trigger group event.$params = array('context' => $context,'objectid' => $group->id);$event = \core\event\group_updated::create($params);$event->add_record_snapshot('groups', $group);$event->trigger();// Dispatch the hook for post group update actions.$hook = new \core_group\hook\after_group_updated(groupinstance: $group,);\core\di::get(\core\hook\manager::class)->dispatch($hook);return true;}/*** Update grouping** @param stdClass $data grouping properties (with magic quotes)* @param array $editoroptions* @return bool true or exception*/function groups_update_grouping($data, $editoroptions=null) {global $DB;$data->timemodified = time();if (isset($data->name)) {$data->name = trim($data->name);}if (isset($data->idnumber)) {$data->idnumber = trim($data->idnumber);if (($existing = groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {throw new moodle_exception('idnumbertaken');}}if ($editoroptions !== null) {$data = file_postupdate_standard_editor($data, 'description', $editoroptions, $editoroptions['context'], 'grouping', 'description', $data->id);}$DB->update_record('groupings', $data);$handler = \core_group\customfield\grouping_handler::create();$handler->instance_form_save($data);// Invalidate the group data.cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));// Trigger group event.$params = array('context' => context_course::instance($data->courseid),'objectid' => $data->id);$event = \core\event\grouping_updated::create($params);$event->trigger();return true;}/*** Delete a group best effort, first removing members and links with courses and groupings.* Removes group avatar too.** @param mixed $grouporid The id of group to delete or full group object* @return bool True if deletion was successful, false otherwise*/function groups_delete_group($grouporid) {global $CFG, $DB;require_once("$CFG->libdir/gdlib.php");if (is_object($grouporid)) {$groupid = $grouporid->id;$group = $grouporid;} else {$groupid = $grouporid;if (!$group = $DB->get_record('groups', array('id'=>$groupid))) {//silently ignore attempts to delete missing already deleted groups ;-)return true;}}$context = context_course::instance($group->courseid);// delete group calendar events$DB->delete_records('event', array('groupid'=>$groupid));//first delete usage in groupings_groups$DB->delete_records('groupings_groups', array('groupid'=>$groupid));//delete members$DB->delete_records('groups_members', array('groupid'=>$groupid));// Delete any members in a conversation related to this group.if ($conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $groupid, $context->id)) {\core_message\api::delete_all_conversation_data($conversation->id);}//group itself last$DB->delete_records('groups', array('id'=>$groupid));// Delete all files associated with this group$context = context_course::instance($group->courseid);$fs = get_file_storage();$fs->delete_area_files($context->id, 'group', 'description', $groupid);$fs->delete_area_files($context->id, 'group', 'icon', $groupid);// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($group->courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');// Rebuild the coursehiddengroups cache for the course.\core_group\visibility::update_hiddengroups_cache($group->courseid);// Trigger group event.$params = array('context' => $context,'objectid' => $groupid);$event = \core\event\group_deleted::create($params);$event->add_record_snapshot('groups', $group);$event->trigger();// Dispatch the hook for post group delete actions.$hook = new \core_group\hook\after_group_deleted(groupinstance: $group,);\core\di::get(\core\hook\manager::class)->dispatch($hook);return true;}/*** Delete grouping** @param int $groupingorid* @return bool success*/function groups_delete_grouping($groupingorid) {global $DB;if (is_object($groupingorid)) {$groupingid = $groupingorid->id;$grouping = $groupingorid;} else {$groupingid = $groupingorid;if (!$grouping = $DB->get_record('groupings', array('id'=>$groupingorid))) {//silently ignore attempts to delete missing already deleted groupings ;-)return true;}}//first delete usage in groupings_groups$DB->delete_records('groupings_groups', array('groupingid'=>$groupingid));// remove the default groupingid from course$DB->set_field('course', 'defaultgroupingid', 0, array('defaultgroupingid'=>$groupingid));// remove the groupingid from all course modules$DB->set_field('course_modules', 'groupingid', 0, array('groupingid'=>$groupingid));//group itself last$DB->delete_records('groupings', array('id'=>$groupingid));$context = context_course::instance($grouping->courseid);$fs = get_file_storage();$files = $fs->get_area_files($context->id, 'grouping', 'description', $groupingid);foreach ($files as $file) {$file->delete();}// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($grouping->courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');// Trigger group event.$params = array('context' => $context,'objectid' => $groupingid);$event = \core\event\grouping_deleted::create($params);$event->add_record_snapshot('groupings', $grouping);$event->trigger();return true;}/*** Remove all users (or one user) from all groups in course** @param int $courseid* @param int $userid 0 means all users* @param bool $unused - formerly $showfeedback, is no longer used.* @return bool success*/function groups_delete_group_members($courseid, $userid=0, $unused=false) {global $DB, $OUTPUT;// Get the users in the course which are in a group.$sql = "SELECT gm.id as gmid, gm.userid, g.*FROM {groups_members} gmINNER JOIN {groups} gON gm.groupid = g.idWHERE g.courseid = :courseid";$params = array();$params['courseid'] = $courseid;// Check if we want to delete a specific user.if ($userid) {$sql .= " AND gm.userid = :userid";$params['userid'] = $userid;}$rs = $DB->get_recordset_sql($sql, $params);foreach ($rs as $usergroup) {groups_remove_member($usergroup, $usergroup->userid);}$rs->close();return true;}/*** Remove all groups from all groupings in course** @param int $courseid* @param bool $showfeedback* @return bool success*/function groups_delete_groupings_groups($courseid, $showfeedback=false) {global $DB, $OUTPUT;$groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";$results = $DB->get_recordset_select('groupings_groups', "groupid IN ($groupssql)",array($courseid), '', 'groupid, groupingid');foreach ($results as $result) {groups_unassign_grouping($result->groupingid, $result->groupid, false);}$results->close();// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');// no need to show any feedback here - we delete usually first groupings and then groupsreturn true;}/*** Delete all groups from course** @param int $courseid* @param bool $showfeedback* @return bool success*/function groups_delete_groups($courseid, $showfeedback=false) {global $CFG, $DB, $OUTPUT;$groups = $DB->get_recordset('groups', array('courseid' => $courseid));foreach ($groups as $group) {groups_delete_group($group);}$groups->close();// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');// Rebuild the coursehiddengroups cache for the course.\core_group\visibility::update_hiddengroups_cache($courseid);if ($showfeedback) {echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groups', 'group'), 'notifysuccess');}return true;}/*** Delete all groupings from course** @param int $courseid* @param bool $showfeedback* @return bool success*/function groups_delete_groupings($courseid, $showfeedback=false) {global $DB, $OUTPUT;$groupings = $DB->get_recordset_select('groupings', 'courseid = ?', array($courseid));foreach ($groupings as $grouping) {groups_delete_grouping($grouping);}$groupings->close();// Invalidate the grouping cache for the course.cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');if ($showfeedback) {echo $OUTPUT->notification(get_string('deleted').' - '.get_string('groupings', 'group'), 'notifysuccess');}return true;}/* =================================== *//* various functions used by groups UI *//* =================================== *//*** Obtains a list of the possible roles that group members might come from,* on a course. Generally this includes only profile roles.** @param context $context Context of course* @return Array of role ID integers, or false if error/none.*/function groups_get_possible_roles($context) {$roles = get_profile_roles($context);return array_keys($roles);}/*** Gets potential group members for grouping** @param int $courseid The id of the course* @param int $roleid The role to select users from* @param mixed $source restrict to cohort, grouping or group id* @param string $orderby The column to sort users by* @param int $notingroup restrict to users not in existing groups* @param bool $onlyactiveenrolments restrict to users who have an active enrolment in the course* @param array $extrafields Extra user fields to return* @return array An array of the users*/function groups_get_potential_members($courseid, $roleid = null, $source = null,$orderby = 'lastname ASC, firstname ASC',$notingroup = null, $onlyactiveenrolments = false, $extrafields = []) {global $DB;$context = context_course::instance($courseid);list($esql, $params) = get_enrolled_sql($context, '', 0, $onlyactiveenrolments);$notingroupsql = "";if ($notingroup) {// We want to eliminate users that are already associated with a course group.$notingroupsql = "u.id NOT IN (SELECT useridFROM {groups_members}WHERE groupid IN (SELECT idFROM {groups}WHERE courseid = :courseid))";$params['courseid'] = $courseid;}if ($roleid) {// We are looking for all users with this role assigned in this context or higher.list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),SQL_PARAMS_NAMED,'relatedctx');$params = array_merge($params, $relatedctxparams, array('roleid' => $roleid));$where = "WHERE u.id IN (SELECT useridFROM {role_assignments}WHERE roleid = :roleid AND contextid $relatedctxsql)";$where .= $notingroup ? "AND $notingroupsql" : "";} else if ($notingroup) {$where = "WHERE $notingroupsql";} else {$where = "";}$sourcejoin = "";if (is_int($source)) {$sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";$params['cohortid'] = $source;} else {// Auto-create groups from an existing cohort membership.if (isset($source['cohortid'])) {$sourcejoin .= "JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid) ";$params['cohortid'] = $source['cohortid'];}// Auto-create groups from an existing group membership.if (isset($source['groupid'])) {$sourcejoin .= "JOIN {groups_members} gp ON (gp.userid = u.id AND gp.groupid = :groupid) ";$params['groupid'] = $source['groupid'];}// Auto-create groups from an existing grouping membership.if (isset($source['groupingid'])) {$sourcejoin .= "JOIN {groupings_groups} gg ON gg.groupingid = :groupingid ";$sourcejoin .= "JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = gg.groupid) ";$params['groupingid'] = $source['groupingid'];}}$userfieldsapi = \core_user\fields::for_userpic()->including(...$extrafields);$allusernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;$sql = "SELECT DISTINCT u.id, u.username, $allusernamefields, u.idnumberFROM {user} uJOIN ($esql) e ON e.id = u.id$sourcejoin$whereORDER BY $orderby";return $DB->get_records_sql($sql, $params);}/*** Parse a group name for characters to replace** @param string $format The format a group name will follow* @param int $groupnumber The number of the group to be used in the parsed format string* @return string the parsed format string*/function groups_parse_name($format, $groupnumber) {if (strstr($format, '@') !== false) { // Convert $groupnumber to a character series$letter = 'A';for($i=0; $i<$groupnumber; $i++) {$letter++;}$str = str_replace('@', $letter, $format);} else {$str = str_replace('#', $groupnumber+1, $format);}return($str);}/*** Assigns group into grouping** @param int groupingid* @param int groupid* @param int $timeadded The time the group was added to the grouping.* @param bool $invalidatecache If set to true the course group cache and the user group cache will be invalidated as well.* @return bool true or exception*/function groups_assign_grouping($groupingid, $groupid, $timeadded = null, $invalidatecache = true) {global $DB;if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {return true;}$assign = new stdClass();$assign->groupingid = $groupingid;$assign->groupid = $groupid;if ($timeadded != null) {$assign->timeadded = (integer)$timeadded;} else {$assign->timeadded = time();}$DB->insert_record('groupings_groups', $assign);$courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));if ($invalidatecache) {// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');}// Trigger event.$params = array('context' => context_course::instance($courseid),'objectid' => $groupingid,'other' => array('groupid' => $groupid));$event = \core\event\grouping_group_assigned::create($params);$event->trigger();return true;}/*** Unassigns group from grouping** @param int groupingid* @param int groupid* @param bool $invalidatecache If set to true the course group cache and the user group cache will be invalidated as well.* @return bool success*/function groups_unassign_grouping($groupingid, $groupid, $invalidatecache = true) {global $DB;$DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));$courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));if ($invalidatecache) {// Invalidate the grouping cache for the coursecache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));// Purge the group and grouping cache for users.cache_helper::purge_by_definition('core', 'user_group_groupings');}// Trigger event.$params = array('context' => context_course::instance($courseid),'objectid' => $groupingid,'other' => array('groupid' => $groupid));$event = \core\event\grouping_group_unassigned::create($params);$event->trigger();return true;}/*** Lists users in a group based on their role on the course.* Returns false if there's an error or there are no users in the group.* Otherwise returns an array of role ID => role data, where role data includes:* (role) $id, $shortname, $name* $users: array of objects for each user which include the specified fields* Users who do not have a role are stored in the returned array with key '-'* and pseudo-role details (including a name, 'No role'). Users with multiple* roles, same deal with key '*' and name 'Multiple roles'. You can find out* which roles each has by looking in the $roles array of the user object.** @param int $groupid* @param int $courseid Course ID (should match the group's course)* @param string $fields List of fields from user table (prefixed with u) and joined tables, default 'u.*'* @param string|null $sort SQL ORDER BY clause, default (when null passed) is what comes from users_order_by_sql.* @param string $extrawheretest extra SQL conditions ANDed with the existing where clause.* @param array $whereorsortparams any parameters required by $extrawheretest or $joins (named parameters).* @param string $joins any joins required to get the specified fields.* @return array Complex array as described above*/function groups_get_members_by_role(int $groupid, int $courseid, string $fields = 'u.*',?string $sort = null, string $extrawheretest = '', array $whereorsortparams = [], string $joins = '') {global $DB;// Retrieve information about all users and their roles on the course or// parent ('related') contexts$context = context_course::instance($courseid);// We are looking for all users with this role assigned in this context or higher.list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');if ($extrawheretest) {$extrawheretest = ' AND ' . $extrawheretest;}if (is_null($sort)) {list($sort, $sortparams) = users_order_by_sql('u');$whereorsortparams = array_merge($whereorsortparams, $sortparams);}$sql = "SELECT r.id AS roleid, u.id AS userid, $fieldsFROM {groups_members} gmJOIN {user} u ON u.id = gm.useridLEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid $relatedctxsql)LEFT JOIN {role} r ON r.id = ra.roleid$joinsWHERE gm.groupid=:mgroupid".$extrawheretest."ORDER BY r.sortorder, $sort";$whereorsortparams = array_merge($whereorsortparams, $relatedctxparams, array('mgroupid' => $groupid));$rs = $DB->get_recordset_sql($sql, $whereorsortparams);return groups_calculate_role_people($rs, $context);}/*** Internal function used by groups_get_members_by_role to handle the* results of a database query that includes a list of users and possible* roles on a course.** @param moodle_recordset $rs The record set (may be false)* @param int $context ID of course context* @return array As described in groups_get_members_by_role*/function groups_calculate_role_people($rs, $context) {global $CFG, $DB;if (!$rs) {return array();}$allroles = role_fix_names(get_all_roles($context), $context);$visibleroles = get_viewable_roles($context);// Array of all involved roles$roles = array();// Array of all retrieved users$users = array();// Fill arraysforeach ($rs as $rec) {// Create information about user if this is a new oneif (!array_key_exists($rec->userid, $users)) {// User data includes all the optional fields, but not any of the// stuff we added to get the role details$userdata = clone($rec);unset($userdata->roleid);unset($userdata->roleshortname);unset($userdata->rolename);unset($userdata->userid);$userdata->id = $rec->userid;// Make an array to hold the list of roles for this user$userdata->roles = array();$users[$rec->userid] = $userdata;}// If user has a role...if (!is_null($rec->roleid)) {// Create information about role if this is a new oneif (!array_key_exists($rec->roleid, $roles)) {$role = $allroles[$rec->roleid];$roledata = new stdClass();$roledata->id = $role->id;$roledata->shortname = $role->shortname;$roledata->name = $role->localname;$roledata->users = array();$roles[$roledata->id] = $roledata;}// Record that user has role$users[$rec->userid]->roles[$rec->roleid] = $roles[$rec->roleid];}}$rs->close();// Return false if there weren't any usersif (count($users) == 0) {return false;}// Add pseudo-role for multiple roles$roledata = new stdClass();$roledata->name = get_string('multipleroles','role');$roledata->users = array();$roles['*'] = $roledata;$roledata = new stdClass();$roledata->name = get_string('noroles','role');$roledata->users = array();$roles[0] = $roledata;// Now we rearrange the data to store users by roleforeach ($users as $userid=>$userdata) {$visibleuserroles = array_intersect_key($userdata->roles, $visibleroles);$rolecount = count($visibleuserroles);if ($rolecount == 0) {// does not have any roles$roleid = 0;} else if($rolecount > 1) {$roleid = '*';} else {$userrole = reset($visibleuserroles);$roleid = $userrole->id;}$roles[$roleid]->users[$userid] = $userdata;}// Delete roles not usedforeach ($roles as $key=>$roledata) {if (count($roledata->users)===0) {unset($roles[$key]);}}// Return list of roles containing their usersreturn $roles;}/*** Synchronises enrolments with the group membership** Designed for enrolment methods provide automatic synchronisation between enrolled users* and group membership, such as enrol_cohort and enrol_meta .** @param string $enrolname name of enrolment method without prefix* @param int $courseid course id where sync needs to be performed (0 for all courses)* @param string $gidfield name of the field in 'enrol' table that stores group id* @return array Returns the list of removed and added users. Each record contains fields:* userid, enrolid, courseid, groupid, groupname*/function groups_sync_with_enrolment($enrolname, $courseid = 0, $gidfield = 'customint2') {global $DB;$onecourse = $courseid ? "AND e.courseid = :courseid" : "";$params = array('enrolname' => $enrolname,'component' => 'enrol_'.$enrolname,'courseid' => $courseid);$affectedusers = array('removed' => array(),'added' => array());// Remove invalid.$sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupnameFROM {groups_members} gmJOIN {groups} g ON (g.id = gm.groupid)JOIN {enrol} e ON (e.enrol = :enrolname AND e.courseid = g.courseid $onecourse)JOIN {user_enrolments} ue ON (ue.userid = gm.userid AND ue.enrolid = e.id)WHERE gm.component=:component AND gm.itemid = e.id AND g.id <> e.{$gidfield}";$rs = $DB->get_recordset_sql($sql, $params);foreach ($rs as $gm) {groups_remove_member($gm->groupid, $gm->userid);$affectedusers['removed'][] = $gm;}$rs->close();// Add missing.$sql = "SELECT ue.userid, ue.enrolid, e.courseid, g.id AS groupid, g.name AS groupnameFROM {user_enrolments} ueJOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrolname $onecourse)JOIN {groups} g ON (g.courseid = e.courseid AND g.id = e.{$gidfield})JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0)LEFT JOIN {groups_members} gm ON (gm.groupid = g.id AND gm.userid = ue.userid)WHERE gm.id IS NULL";$rs = $DB->get_recordset_sql($sql, $params);foreach ($rs as $ue) {groups_add_member($ue->groupid, $ue->userid, 'enrol_'.$enrolname, $ue->enrolid);$affectedusers['added'][] = $ue;}$rs->close();return $affectedusers;}/*** Callback for inplace editable API.** @param string $itemtype - Only user_groups is supported.* @param string $itemid - Userid and groupid separated by a :* @param string $newvalue - json encoded list of groupids.* @return \core\output\inplace_editable*/function core_group_inplace_editable($itemtype, $itemid, $newvalue) {if ($itemtype === 'user_groups') {return \core_group\output\user_groups_editable::update($itemid, $newvalue);}}/*** Updates group messaging to enable/disable in bulk.** @param array $groupids array of group id numbers.* @param bool $enabled if true, enables messaging else disables messaging*/function set_groups_messaging(array $groupids, bool $enabled): void {foreach ($groupids as $groupid) {$data = groups_get_group($groupid, '*', MUST_EXIST);$data->enablemessaging = $enabled;groups_update_group($data);}}/*** Returns custom fields data for provided groups.** @param array $groupids a list of group IDs to provide data for.* @return \core_customfield\data_controller[]*/function get_group_custom_fields_data(array $groupids): array {$result = [];if (!empty($groupids)) {$handler = \core_group\customfield\group_handler::create();$customfieldsdata = $handler->get_instances_data($groupids, true);foreach ($customfieldsdata as $groupid => $fieldcontrollers) {foreach ($fieldcontrollers as $fieldcontroller) {$result[$groupid][] = ['type' => $fieldcontroller->get_field()->get('type'),'value' => $fieldcontroller->export_value(),'valueraw' => $fieldcontroller->get_value(),'name' => $fieldcontroller->get_field()->get('name'),'shortname' => $fieldcontroller->get_field()->get('shortname'),];}}}return $result;}/*** Returns custom fields data for provided groupings.** @param array $groupingids a list of group IDs to provide data for.* @return \core_customfield\data_controller[]*/function get_grouping_custom_fields_data(array $groupingids): array {$result = [];if (!empty($groupingids)) {$handler = \core_group\customfield\grouping_handler::create();$customfieldsdata = $handler->get_instances_data($groupingids, true);foreach ($customfieldsdata as $groupingid => $fieldcontrollers) {foreach ($fieldcontrollers as $fieldcontroller) {$result[$groupingid][] = ['type' => $fieldcontroller->get_field()->get('type'),'value' => $fieldcontroller->export_value(),'valueraw' => $fieldcontroller->get_value(),'name' => $fieldcontroller->get_field()->get('name'),'shortname' => $fieldcontroller->get_field()->get('shortname'),];}}}return $result;}