moodle/group/lib.php

1327 lines
47 KiB
PHP
Raw Normal View History

<?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) {
2008-06-01 13:09:04 +00:00
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);
}
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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 specified
if (!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 found
throw new coding_exception('Invalid call to groups_add_member(). A component must be specified if an itemid is given');
}
2009-02-17 16:44:48 +00:00
$DB->insert_record('groups_members', $member);
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// Update group info, and group object.
2008-06-01 13:09:04 +00:00
$DB->set_field('groups', 'timemodified', $member->timeadded, array('id'=>$groupid));
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
$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);
}
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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 entry
if (!($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 it
if (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 removal
return 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) {
2008-06-01 13:09:04 +00:00
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;
}
2009-02-17 16:44:48 +00:00
$DB->delete_records('groups_members', array('groupid'=>$groupid, 'userid'=>$userid));
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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);
}
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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);
2008-06-01 13:09:04 +00:00
$data->timecreated = time();
$data->timemodified = $data->timecreated;
2008-06-01 13:09:04 +00:00
$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');
}
}
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.
2022-09-06 09:14:24 +01:00
$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);
2023-08-09 11:02:49 +10:00
$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));
2009-02-17 16:44:48 +00:00
if ($editform) {
groups_update_group_icon($group, $data, $editform);
}
// Invalidate the grouping cache for the course
cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($course->id));
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.
2022-09-06 09:14:24 +01:00
// 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);
}
}
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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();
2009-02-17 16:44:48 +00:00
// 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) {
2008-06-01 13:09:04 +00:00
global $DB;
2008-06-01 13:09:04 +00:00
$data->timecreated = time();
$data->timemodified = $data->timecreated;
2008-06-01 13:09:04 +00:00
$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);
2009-02-17 16:44:48 +00:00
$data->id = $id;
2023-08-09 11:02:49 +10:00
$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 course
cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($data->courseid));
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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');
}
}
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.
2022-09-06 09:14:24 +01:00
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);
}
2009-02-17 16:44:48 +00:00
$DB->update_record('groups', $data);
2023-08-09 11:02:49 +10:00
$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));
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.
2022-09-06 09:14:24 +01:00
// Rebuild the coursehiddengroups cache for the course.
\core_group\visibility::update_hiddengroups_cache($data->courseid);
$group = $DB->get_record('groups', array('id'=>$data->id));
2009-02-17 16:44:48 +00:00
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);
}
}
}
}
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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();
2009-02-17 16:44:48 +00:00
// 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);
2009-02-17 16:44:48 +00:00
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) {
2008-06-01 13:09:04 +00:00
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);
}
2009-02-17 16:44:48 +00:00
$DB->update_record('groupings', $data);
2023-08-09 11:02:49 +10:00
$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));
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// Trigger group event.
$params = array(
'context' => context_course::instance($data->courseid),
'objectid' => $data->id
);
$event = \core\event\grouping_updated::create($params);
$event->trigger();
2009-02-17 16:44:48 +00:00
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
2008-06-01 13:09:04 +00:00
$DB->delete_records('event', array('groupid'=>$groupid));
//first delete usage in groupings_groups
2008-06-01 13:09:04 +00:00
$DB->delete_records('groupings_groups', array('groupid'=>$groupid));
//delete members
2008-06-01 13:09:04 +00:00
$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
2009-02-17 16:44:48 +00:00
$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 course
cache_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');
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.
2022-09-06 09:14:24 +01:00
// Rebuild the coursehiddengroups cache for the course.
\core_group\visibility::update_hiddengroups_cache($group->courseid);
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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);
2009-02-17 16:44:48 +00:00
return true;
}
/**
* Delete grouping
*
* @param int $groupingorid
* @return bool success
*/
function groups_delete_grouping($groupingorid) {
2008-06-01 13:09:04 +00:00
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
2008-06-01 13:09:04 +00:00
$DB->delete_records('groupings_groups', array('groupingid'=>$groupingid));
// remove the default groupingid from course
2008-06-01 13:09:04 +00:00
$DB->set_field('course', 'defaultgroupingid', 0, array('defaultgroupingid'=>$groupingid));
// remove the groupingid from all course modules
2008-06-01 13:09:04 +00:00
$DB->set_field('course_modules', 'groupingid', 0, array('groupingid'=>$groupingid));
//group itself last
2009-02-17 16:44:48 +00:00
$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 course
cache_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');
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// Trigger group event.
$params = array(
'context' => $context,
'objectid' => $groupingid
);
$event = \core\event\grouping_deleted::create($params);
$event->add_record_snapshot('groupings', $grouping);
$event->trigger();
2009-02-17 16:44:48 +00:00
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} gm
INNER JOIN {groups} g
ON gm.groupid = g.id
WHERE 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;
2008-06-01 13:09:04 +00:00
$groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?";
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
$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 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');
// no need to show any feedback here - we delete usually first groupings and then groups
return 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;
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
$groups = $DB->get_recordset('groups', array('courseid' => $courseid));
foreach ($groups as $group) {
groups_delete_group($group);
}
$groups->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');
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.
2022-09-06 09:14:24 +01:00
// 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;
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
$groupings = $DB->get_recordset_select('groupings', 'courseid = ?', array($courseid));
foreach ($groupings as $grouping) {
groups_delete_grouping($grouping);
}
$groupings->close();
MDL-39959: Replace Legacy events - Groups This combines the following changes: * Event for group member added * Event for group member removed * Event for group created * Event for grouping created * Event for group updated * Event for grouping updated * Event for group deleted * Event for grouping deleted * Adding tests for deleting functions * Bulk remove of members uses low-level API The reason for this is that a bulk event has no value from a logging perspective as it is not granular. So now, the API is a bit slower, but the information the events contain makes sense, beside this is not (and should not be) used very often. The reason why the events_trigger_legacy() is kept is because we cannot create a new event for this, as we don't encourage developers to created bulk events, for the reasons mentioned above. I removed the call that gets the user record from the function groups_remove_member() as it was not required and only appeared to check if the user existed. It appears to be safe not to do this check as nothing would fail down the line. * Bulk unassign of groupings uses low-level API As the previous commit, we keep the legacy event for now as it would be wrong to create a new event to replace it. Also, the code has been changed to call the low-level API to unassign groups from groupins, even though at the moment there are no events for that function. * Bulk deletion of groups uses low-level API Again, we keep the legacy event because replacing it would force us to create a new event that does not make sense. See MDL-41312. * Bulk deleting of groupings uses low-level API * Asserting legacy event name in unit tests * Minor SQL query and code improvements
2013-08-19 16:17:49 +08:00
// 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
2010-05-21 19:25:13 +00:00
* @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 = []) {
2009-12-16 22:14:17 +00:00
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 userid
FROM {groups_members}
WHERE groupid IN (SELECT id
FROM {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 userid
FROM {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.idnumber
2008-06-01 13:09:04 +00:00
FROM {user} u
JOIN ($esql) e ON e.id = u.id
$sourcejoin
$where
2008-06-01 13:09:04 +00:00
ORDER BY $orderby";
2008-06-01 13:09:04 +00:00
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 {
2009-12-16 22:14:17 +00:00
$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.
2009-02-17 16:44:48 +00:00
* @return bool true or exception
*/
function groups_assign_grouping($groupingid, $groupid, $timeadded = null, $invalidatecache = true) {
2008-06-01 13:09:04 +00:00
global $DB;
if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
return true;
}
$assign = new stdClass();
$assign->groupingid = $groupingid;
2008-06-01 13:09:04 +00:00
$assign->groupid = $groupid;
if ($timeadded != null) {
$assign->timeadded = (integer)$timeadded;
} else {
$assign->timeadded = time();
}
2009-02-17 16:44:48 +00:00
$DB->insert_record('groupings_groups', $assign);
$courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
if ($invalidatecache) {
// 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');
}
// 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();
2009-02-17 16:44:48 +00:00
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) {
2008-06-01 13:09:04 +00:00
global $DB;
2009-02-17 16:44:48 +00:00
$DB->delete_records('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid));
2008-06-01 13:09:04 +00:00
$courseid = $DB->get_field('groupings', 'courseid', array('id' => $groupingid));
if ($invalidatecache) {
// 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');
}
// 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();
2009-02-17 16:44:48 +00:00
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);
2008-06-01 13:09:04 +00:00
// 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, $fields
2008-06-01 13:09:04 +00:00
FROM {groups_members} gm
JOIN {user} u ON u.id = gm.userid
LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid $relatedctxsql)
LEFT JOIN {role} r ON r.id = ra.roleid
$joins
WHERE gm.groupid=:mgroupid
".$extrawheretest."
2008-06-01 13:09:04 +00:00
ORDER BY r.sortorder, $sort";
$whereorsortparams = array_merge($whereorsortparams, $relatedctxparams, array('mgroupid' => $groupid));
$rs = $DB->get_recordset_sql($sql, $whereorsortparams);
2008-06-01 13:09:04 +00:00
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
*/
2008-06-01 13:09:04 +00:00
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);
2008-06-01 13:09:04 +00:00
// Array of all involved roles
2008-06-01 13:09:04 +00:00
$roles = array();
// Array of all retrieved users
2008-06-01 13:09:04 +00:00
$users = array();
// Fill arrays
2008-06-01 13:09:04 +00:00
foreach ($rs as $rec) {
// Create information about user if this is a new one
2008-06-01 13:09:04 +00:00
if (!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);
2008-06-01 13:09:04 +00:00
$userdata->id = $rec->userid;
// Make an array to hold the list of roles for this user
2008-06-01 13:09:04 +00:00
$userdata->roles = array();
$users[$rec->userid] = $userdata;
}
// If user has a role...
2008-06-01 13:09:04 +00:00
if (!is_null($rec->roleid)) {
// Create information about role if this is a new one
if (!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;
2008-06-01 13:09:04 +00:00
$roledata->users = array();
$roles[$roledata->id] = $roledata;
}
// Record that user has role
$users[$rec->userid]->roles[$rec->roleid] = $roles[$rec->roleid];
}
}
2008-06-01 13:09:04 +00:00
$rs->close();
// Return false if there weren't any users
if (count($users) == 0) {
return false;
}
// Add pseudo-role for multiple roles
$roledata = new stdClass();
2008-06-01 13:09:04 +00:00
$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 role
2008-06-01 13:09:04 +00:00
foreach ($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) {
2008-06-01 13:09:04 +00:00
$roleid = '*';
} else {
$userrole = reset($visibleuserroles);
$roleid = $userrole->id;
}
2008-06-01 13:09:04 +00:00
$roles[$roleid]->users[$userid] = $userdata;
}
// Delete roles not used
2008-06-01 13:09:04 +00:00
foreach ($roles as $key=>$roledata) {
if (count($roledata->users)===0) {
unset($roles[$key]);
}
}
// Return list of roles containing their users
return $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 groupname
FROM {groups_members} gm
JOIN {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 groupname
FROM {user_enrolments} ue
JOIN {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);
}
}
2023-08-09 11:02:49 +10:00
/**
* 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;
}