From 3bbeba3821ca81c38501a88c9b160033fcb834ce Mon Sep 17 00:00:00 2001 From: Esteban Ristich Date: Tue, 11 Mar 2025 16:49:55 +0100 Subject: [PATCH 1/4] Group: Allow to create linked group to usergroup --- public/main/group/group.php | 39 +++- public/main/group/group_category.php | 58 ++++- public/main/group/group_creation.php | 106 ++++++--- public/main/group/group_space.php | 8 + public/main/group/member_settings.php | 26 ++- public/main/group/settings.php | 22 +- public/main/inc/lib/groupmanager.lib.php | 212 +++++++++++++++++- src/CoreBundle/Framework/Container.php | 6 + .../Entity/CGroupRelUsergroup.php | 8 +- .../CGroupRelUsergroupRepository.php | 15 ++ 10 files changed, 438 insertions(+), 62 deletions(-) create mode 100644 src/CourseBundle/Repository/CGroupRelUsergroupRepository.php diff --git a/public/main/group/group.php b/public/main/group/group.php index ef16868feaf..ff69d31b966 100644 --- a/public/main/group/group.php +++ b/public/main/group/group.php @@ -234,12 +234,26 @@ /* List all categories */ if ('true' === api_get_setting('allow_group_categories')) { - $defaultCategory = [ - 'iid' => null, - 'description' => '', - 'title' => get_lang('Default groups'), - ]; - $categories = array_merge([$defaultCategory], $categories); + if (empty($categories)) { + $defaultCategoryId = GroupManager::create_category( + get_lang('Default groups'), + '', + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0 + ); + $defaultCategory = GroupManager::get_category($defaultCategoryId); + $categories = [$defaultCategory]; + } $course = api_get_course_entity(); foreach ($categories as $index => $category) { $categoryId = $category['iid']; @@ -264,13 +278,20 @@ // Edit $actions .= ''. - Display::getMdiIcon('edit', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit this category')).''; + Display::getMdiIcon('pencil', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Edit this category')).''; + + // Add group + $actions .= ' '. + Display::getMdiIcon(ActionIcon::SUBSCRIBE_GROUP_USERS_TO_RESOURCE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Create new group(s)')).''; // Delete $actions .= Display::url( - Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')), + Display::getMdiIcon(ActionIcon::DELETE, count($categories) == 1 ? 'ch-tool-icon-disabled' : 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')), 'group.php?'.api_get_cidreq().'&action=delete_category&category_id='.$categoryId, - [ + count($categories) == 1 ? [ + 'onclick' => 'javascript:alert('."'".addslashes(api_htmlentities(get_lang('You cannot delete the last category'), ENT_QUOTES))."'".'); return false;', + ] : [ 'onclick' => 'javascript:if(!confirm('."'".addslashes(api_htmlentities(get_lang('Please confirm your choice'), ENT_QUOTES))."'".')) return false;', ] ); diff --git a/public/main/group/group_category.php b/public/main/group/group_category.php index 9fe24a037b2..119cc273ee5 100644 --- a/public/main/group/group_category.php +++ b/public/main/group/group_category.php @@ -76,6 +76,8 @@ function check_groups_per_user($value) 'forum_state' => GroupManager::TOOL_PRIVATE, 'max_student' => 0, 'document_access' => 0, + 'allow_change_group_name' => GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, + 'only_me' => GroupManager::GROUP_VISIBILITY_DEFAULT, ]; } @@ -103,7 +105,11 @@ function check_groups_per_user($value) } else { // Create a new category $action = 'add_category'; - $form = new FormValidator('group_category'); + $form = new FormValidator( + 'group_category', + 'post', + api_get_self().'?'.api_get_cidreq() + ); } $form->addElement('header', $nameTools); @@ -160,6 +166,42 @@ function check_groups_per_user($value) $form->addRule('max_member_group', get_lang('Please enter a valid number for the maximum number of members.'), 'callback', 'check_max_number_of_members'); $form->addElement('html', ''); +// group naming settings +$form->addElement('html', '
'); + +$group = [ + $form->createElement('radio', 'allow_change_group_name', null, 'Seul les enseignants peuvent renommer les groupes', GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME), + $form->createElement('radio', 'allow_change_group_name', null, 'Les membres peuvent renommer les groupes', GroupManager::GROUP_STUDENT_CAN_RENAME), + $form->createElement('radio', 'allow_change_group_name', null, 'Le groupe sera nommé avec les noms prénoms de ses membres', GroupManager::GROUP_AUTO_RENAME_WITH_SELF_MEMBERS), +]; +$form->addGroup( + $group, + '', + Display::getMdiIcon('rename', 'ch-tool-icon', null, ICON_SIZE_SMALL, 'Renommer').' Renommer', + null, + false +); + +$form->addElement('html', '
'); + +// visibility settings +$form->addElement('html', '
'); + +$group = [ + $form->createElement('radio', 'only_me', null, 'Par défaut', GroupManager::GROUP_VISIBILITY_DEFAULT), + $form->createElement('radio', 'only_me', null, 'Afficher uniquement les groupes dont l\'étudiant est membre', GroupManager::GROUP_ONLY_FOR_THOSE_JOINED), + $form->createElement('radio', 'only_me', null, 'Afficher, sans leur nom ni la liste des membres, uniquement les groupes dont l\'étudiant est membre', GroupManager::GROUP_ONLY_FOR_THOSE_JOINED_WITHOUT_MEMBERS_INFOS), +]; +$form->addGroup( + $group, + '', + Display::getMdiIcon('eye', 'ch-tool-icon', null, ICON_SIZE_SMALL, 'Visibilité').' Visibilité', + null, + false +); + +$form->addElement('html', '
'); + $form->addElement('html', '
'); // Self registration $group = [ @@ -403,7 +445,9 @@ function check_groups_per_user($value) $self_unreg_allowed, $max_member, $values['groups_per_user'], - $values['document_access'] ?? 0 + $values['document_access'] ?? 0, + $values['allow_change_group_name'], + $values['only_me'] ); Display::addFlash(Display::return_message(get_lang('Group settings have been modified'))); header('Location: '.$currentUrl.'&category='.$values['id']); @@ -423,7 +467,9 @@ function check_groups_per_user($value) $self_unreg_allowed, $max_member, $values['groups_per_user'], - $values['document_access'] ?? 0 + $values['document_access'] ?? 0, + $values['allow_change_group_name'], + $values['only_me'] ); Display::addFlash(Display::return_message(get_lang('Category created'))); header('Location: '.$currentUrl); @@ -436,7 +482,7 @@ function check_groups_per_user($value) // Else display the form Display::display_header($nameTools, 'Group'); -$actions = ''. +$actions = ''. Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Back to Groups list')).''; echo Display::toolbarAction('toolbar', [$actions]); @@ -448,6 +494,10 @@ function check_groups_per_user($value) $defaults['max_member_no_limit'] = 1; $defaults['max_member'] = $defaults['max_student']; } +if (api_get_setting('allow_group_categories')) { + $defaults['id'] = $_GET['id']; +} + $form->setDefaults($defaults); $form->display(); diff --git a/public/main/group/group_creation.php b/public/main/group/group_creation.php index 4f57327fa74..bf71569aac5 100644 --- a/public/main/group/group_creation.php +++ b/public/main/group/group_creation.php @@ -77,7 +77,18 @@ break; case 'create_class_groups': - GroupManager::create_class_groups($_POST['group_category']); + $classIds = []; + foreach (array_keys($_POST) as $key) { + if (strpos($key, 'checkbox_class_id_') !== false) { + $classId = str_replace('checkbox_class_id_', '', $key); + $classIds[] = (int)$classId; + } + } + if (isset($_POST['is_consistent_link'])) { + GroupManager::create_usergroup_consistent_groups($_POST['group_category'], $classIds); + } else { + GroupManager::create_class_groups($_POST['group_category'], $classIds); + } Display::addFlash(Display::return_message(get_lang('group(s) has (have) been added'))); header('Location: '.$currentUrl); exit; @@ -181,13 +192,17 @@ function copy_value(key) { $group_el = []; $group_el[] = $form->createElement('static', null, null, ' '); if ($allowGroupCategories) { - $group_el[] = $form->createElement( - 'checkbox', - 'same_category', - null, - get_lang('same for all'), - ['onclick' => "javascript: switch_state('category');"] - ); + if (!isset($_GET['category_id'])) { + $group_el[] = $form->createElement( + 'checkbox', + 'same_category', + null, + get_lang('same for all'), + ['onclick' => "javascript: switch_state('category');"] + ); + } else { + $group_el[] = $form->createElement('static', null, null, ' '); + } } $group_el[] = $form->createElement( 'checkbox', @@ -203,13 +218,29 @@ function copy_value(key) { $group_el = []; $group_el[] = $form->createElement('text', 'group_'.$group_number.'_name'); if ($allowGroupCategories) { - $group_el[] = $form->createElement( - 'select', - 'group_'.$group_number.'_category', - null, - $categories, - ['id' => 'category_'.$group_number] - ); + if(isset($_GET['category_id'])) { + $group_el[] = $form->createElement( + 'select', + 'group_' . $group_number . '_category', + null, + $categories, + [ + 'id' => 'category_' . $group_number, + 'disabled' => 'true', + 'style' => 'background-color: #f0f0f0;' + ] + ); + $group_el[] = $form->createElement('hidden', 'group_'.$group_number.'_category', $_GET['category_id']); + $defaults['group_'.$group_number.'_category'] = $_GET['category_id']; + } else { + $group_el[] = $form->createElement( + 'select', + 'group_' . $group_number . '_category', + null, + $categories, + ['id' => 'category_' . $group_number] + ); + } } else { $group_el[] = $form->createElement('hidden', 'group_'.$group_number.'_category', 0); @@ -247,7 +278,7 @@ function copy_value(key) { /* * Show form to generate new groups */ - $create_groups_form = new FormValidator('create_groups', 'post', api_get_self().'?'.api_get_cidreq()); + $create_groups_form = new FormValidator('create_groups', 'post', api_get_self().'?'.api_get_cidreq().(isset($_GET['category_id']) ? '&category_id='.$_GET['category_id'] : '')); $create_groups_form->addElement('header', $nameTools); $create_groups_form->addText('number_of_groups', get_lang('Number of groups to create'), null, ['value' => '1']); $create_groups_form->addButton('submit', get_lang('Proceed to create group(s)'), 'plus', 'primary'); @@ -306,16 +337,7 @@ function copy_value(key) { $obj = new UserGroupModel(); $classes = $obj->getUserGroupInCourse($options); if (count($classes) > 0) { - $description = '

'.get_lang('Using this option, you can create groups based on the classes subscribed to your course.').'

'; - $description .= ''; + $description = '

'.get_lang('Using this option, you can create groups based on the classes subscribed to your course.').'


'; $classForm = new FormValidator( 'create_class_groups_form', @@ -325,14 +347,42 @@ function copy_value(key) { $classForm->addHeader(get_lang('Groups from classes')); $classForm->addHtml($description); + + $classGroup = []; + + foreach ($classes as $index => $class) { + $number_of_users = count($obj->get_users_by_usergroup($class['id'])); + // $classForm->addCheckBox('checkbox_class_id_'.$class['id'], $class['title'] . ' ('.$number_of_users.' '.get_lang('Users').')'); + $classGroup[] = $classForm->createElement('checkbox', 'checkbox_class_id_'.$class['id'], null, $class['title'] . ' ('.$number_of_users.' '.get_lang('Users').')'); + } + + $classForm->addGroup( + $classGroup, + '', + null, + null, + false + ); + $classForm->addElement('hidden', 'action'); if ($allowGroupCategories) { - $classForm->addSelect('group_category', null, $categories); + if (isset($_GET['category_id'])) { + $classForm->addElement('hidden', 'group_category', $_GET['category_id']); + } else { + $classForm->addSelect('group_category', null, $categories); + } } else { $classForm->addElement('hidden', 'group_category'); } + + $classForm->addHtml('

'); + + $classForm->addCheckBox('is_consistent_link', null, 'Lier les classes aux groupes crées ?', + ['title' => 'Si elle est désactivé les classes lié à ce groupe sont simplement une copie des utilisateurs de la classe séléctionnée. A contrario si elle est activé, la classe séléctionnée est lié au groupe et les modifications de la classe sont répercuté sur le groupe (ce mode empêche la modification du groupe)'] + ); + $classForm->addHtml('

Si elle est désactivé les classes lié à ce groupe sont simplement une copie des utilisateurs de la classe séléctionnée. A contrario si elle est activé, la classe séléctionnée est lié au groupe et les modifications de la classe sont répercuté sur le groupe (ce mode empêche la modification du groupe)

'); + $classForm->addButtonSave(get_lang('Validate')); - $defaults['group_category'] = GroupManager::DEFAULT_GROUP_CATEGORY; $defaults['action'] = 'create_class_groups'; $classForm->setDefaults($defaults); $classForm->display(); diff --git a/public/main/group/group_space.php b/public/main/group/group_space.php index fbd56238169..acf9eae4371 100644 --- a/public/main/group/group_space.php +++ b/public/main/group/group_space.php @@ -141,6 +141,14 @@ class="btn btn--plain" href="'.api_get_self().'?selfUnReg=1" } } + // add edit tool + if (true) { + $actions_array[] = [ + 'url' => 'settings.php?'.api_get_cidreq(true, false).'&gid='.$group_id, + 'content' => Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Edit')), + ]; + } + if (GroupManager::TOOL_NOT_AVAILABLE != $groupEntity->getDocState()) { $params = [ 'toolName' => 'document', diff --git a/public/main/group/member_settings.php b/public/main/group/member_settings.php index c916ef8ef18..c35135535a0 100644 --- a/public/main/group/member_settings.php +++ b/public/main/group/member_settings.php @@ -3,6 +3,9 @@ /* For licensing terms, see /license.txt */ require_once __DIR__.'/../inc/global.inc.php'; + +use Chamilo\CoreBundle\Framework\Container; + $this_section = SECTION_COURSES; $current_course_tool = TOOL_GROUP; @@ -14,7 +17,7 @@ $groupEntity = api_get_group_entity($group_id); $nameTools = get_lang('Edit this group'); -$interbreadcrumb[] = ['url' => 'group.php', 'name' => get_lang('Groups')]; +$interbreadcrumb[] = ['url' => 'group.php?'.api_get_cidreq(), 'name' => get_lang('Groups')]; $interbreadcrumb[] = ['url' => 'group_space.php?'.api_get_cidreq(), 'name' => $groupEntity->getTitle()]; $is_group_member = GroupManager::isTutorOfGroup(api_get_user_id(), $groupEntity); @@ -227,6 +230,25 @@ function check_group_members($value) $form->setDefaults($defaults); echo GroupManager::getSettingBar('member'); -$form->display(); + +// check if group has a CGroupRelUsergroup +$courseInfo = api_get_course_info_by_id(api_get_course_int_id()); + +if (GroupManager::is_group_linked_to_usergroup($groupEntity)) { + + echo '
Ce groupe est lié à la classe ' .$courseInfo['title'].'. La liste de ses membres dépend des membres de la classe et ne peut-être modifiée. + Allez dans les paramètres du groupe pour rompre ce lien si vous souhaitez ajouter ou ôter des membres de la classe. +
'; + + echo '

Membres du groupe

'; + $memberInfos = GroupManager::get_subscribed_users($groupEntity); + echo '
    '; + foreach ($memberInfos as $memberInfo) { + echo '
  • '.$memberInfo['firstname'].' '.$memberInfo['lastname'].' ('.$memberInfo['username'].')'.'
  • '; + } + echo '
'; +} else { + $form->display(); +} Display::display_footer(); diff --git a/public/main/group/settings.php b/public/main/group/settings.php index 8ca77e2660f..5a05289d399 100644 --- a/public/main/group/settings.php +++ b/public/main/group/settings.php @@ -25,6 +25,13 @@ $groupRepo = Container::getGroupRepository(); /** @var CGroup $groupEntity */ $groupEntity = $groupRepo->find($group_id); +$linkedCategory = GroupManager::get_category_from_group($group_id); + +if (isset($_GET['remove_consistent_link'])) { + GroupManager::remove_group_consistent_link($groupEntity); + Display::addFlash(Display::return_message(get_lang('Group is no longer linked to the course'), 'normal')); + header('Location: group.php?'.api_get_cidreq(true, false)); +} if (null === $groupEntity) { api_not_allowed(true); @@ -35,9 +42,7 @@ $interbreadcrumb[] = ['url' => 'group_space.php?'.api_get_cidreq(), 'name' => $groupEntity->getTitle()]; $groupMember = GroupManager::isTutorOfGroup(api_get_user_id(), $groupEntity); -if (!$groupMember && !api_is_allowed_to_edit(false, true)) { - api_not_allowed(true); -} +$courseInfo = api_get_course_info_by_id(api_get_course_int_id()); // Build form $form = new FormValidator('group_edit', 'post', api_get_self().'?'.api_get_cidreq()); @@ -55,6 +60,17 @@ // Group name $form->addElement('text', 'name', get_lang('Group name')); +if (!$groupMember && !api_is_allowed_to_edit(false, true)) { + api_not_allowed(true); +} + +// Message for group rel usergroup +if (GroupManager::is_group_linked_to_usergroup($groupEntity)) { + $form->addHtml('
Ce groupe est lié à la '.$courseInfo['title'].'. Les ajouts ou suppression + de membres à la classe seront reportés sur le groupe. Vous ne pouvez pas ajouter ou ôter des membres à un groupe lié + à une classe. Vous pouvez cependant rompre ce lien en cliquant sur le bouton ci-dessous
Rompre le lien du groupe avec la classe
'); +} + if ('true' === api_get_setting('allow_group_categories')) { $groupCategories = GroupManager::get_categories(); $categoryList = []; diff --git a/public/main/inc/lib/groupmanager.lib.php b/public/main/inc/lib/groupmanager.lib.php index dc3709cd11f..38ead77446c 100644 --- a/public/main/inc/lib/groupmanager.lib.php +++ b/public/main/inc/lib/groupmanager.lib.php @@ -11,6 +11,9 @@ use Doctrine\Common\Collections\Criteria; use Chamilo\CoreBundle\Component\Utils\ActionIcon; use Chamilo\CoreBundle\Component\Utils\ToolIcon; +use Chamilo\CourseBundle\Entity\CGroupRelUsergroup; +use Chamilo\CoreBundle\Entity\Usergroup; +use Chamilo\CoreBundle\Entity\Session; /** * This library contains some functions for group-management. @@ -65,6 +68,21 @@ class GroupManager public const DOCUMENT_MODE_READ_ONLY = 1; public const DOCUMENT_MODE_COLLABORATION = 2; + /** + * Constant for the group category's naming rules + */ + public const GROUP_ONLY_TEACHER_CAN_RENAME = 0; + public const GROUP_STUDENT_CAN_RENAME = 1; + public const GROUP_AUTO_RENAME_WITH_SELF_MEMBERS = 2; + + /** + * Constant for the group category's visibility rules + */ + public const GROUP_VISIBILITY_DEFAULT = 0; + public const GROUP_ONLY_FOR_THOSE_JOINED = 1; + public const GROUP_ONLY_FOR_THOSE_JOINED_WITHOUT_MEMBERS_INFOS = 2; + + public function __construct() { } @@ -390,7 +408,7 @@ public static function create_subgroups($group_id, $number_of_groups) * * @return array */ - public static function create_class_groups($categoryId) + public static function create_class_groups($categoryId, $classIds = []) { $options['where'] = [' usergroup.course_id = ? ' => api_get_course_int_id()]; $obj = new UserGroupModel(); @@ -398,23 +416,175 @@ public static function create_class_groups($categoryId) $group_ids = []; foreach ($classes as $class) { - $userList = $obj->get_users_by_usergroup($class['id']); + if (in_array((int)$class['id'], $classIds)) { + $userList = $obj->get_users_by_usergroup($class['id']); + $groupId = self::create_group( + $class['title'], + $categoryId, + 0, + null + ); + + if ($groupId) { + self::subscribeUsers($userList, api_get_group_entity($groupId)); + $group_ids[] = $groupId; + } + } + } + + return $group_ids; + } + + /** + * Create a group for every class subscribed to the current course + * + * @author Esteban Ristich + * + * @param int $category_id The category in which the groups should be created + * @param $category_id + * + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public static function create_usergroup_consistent_groups($category_id = 0, $classIds = []) + { + // check if group is not duplicated in same category + /** @var \Chamilo\CourseBundle\Repository\CGroupRelUsergroupRepository $relRepository */ + $relRepository = Database::getManager()->getRepository(CGroupRelUsergroup::class); + $filteredClassIds = $classIds; + + foreach ($classIds as $k => $classId) { + // get count of duclicated groups in same category + $qb = $relRepository->createQueryBuilder('gru'); + $qb = $qb->select('count(gru.id)'); + $qb = $qb->innerJoin('gru.group', 'g', \Doctrine\ORM\Query\Expr\Join::WITH, 'g.category = :categoryId'); + $qb->setParameter('categoryId', $category_id); + $qb->where($qb->expr()->eq('gru.usergroup', ':usergroupId')); + $qb->setParameter('usergroupId', $classId); + // $rawSQL = $qb->getQuery()->getSql(); + $groupNb = $qb->getQuery()->getSingleScalarResult(); + $isDuplicate = $groupNb !== 0; + if ($isDuplicate) { + unset($filteredClassIds[$k]); + } + } + + // check if a category has been done, create one otherwise + $categorieGroupe = self::get_category($category_id); + if (count($categorieGroupe) == 0) { + $category_id = self::create_category( + get_lang('DefaultGroupCategory'), + '', + 1, + 1, + 1, + 1, + 1, + 1 + ); + } + + if ($category_id == 0) { + return []; + } + + // BEGIN TEST + $courseId = api_get_course_int_id(); + $sessionId = api_get_session_id(); + $group_ids = []; + $session = Database::getManager()->getRepository(Session::class)->find($sessionId); + $course = Database::getManager()->getRepository(Course::class)->find($courseId); + + foreach ($filteredClassIds as $classId) { + $usergroup = Database::getManager()->getRepository(Usergroup::class)->find($classId); + $groupId = self::create_group( - $class['name'], - $categoryId, + $usergroup->getTitle(), // name of class (usergroup) + $category_id, // category_id 0, null ); + $group = Database::getManager()->getRepository(CGroup::class)->find($groupId); + + // fill the group from usergroup + $obj = new UserGroupModel(); + $userList = $obj->get_users_by_usergroup($classId); if ($groupId) { self::subscribeUsers($userList, api_get_group_entity($groupId)); - $group_ids[] = $groupId; } - } + $groupRelUsergroup = (new CGroupRelUsergroup()) + ->setGroup($group) + ->setUsergroup($usergroup) + ->setSession($session) + ->setCourse($course) + ->setReadyAutogroup(0) + ; + + $em = Database::getManager(); + $em->persist($groupRelUsergroup); + $em->flush(); + + $group_ids[] = $groupRelUsergroup->getId(); + } return $group_ids; } + /** + * Get the usergroup linked to the group if it exists. + * + * @author Esteban Ristich + * + * @param CGroup $groupId + * @return Usergroup|null + */ + public static function get_usergroup_link(CGroup $groupId): ?Usergroup + { + $em = Database::getManager(); + $repo = $em->getRepository(CGroupRelUsergroup::class); + $criteria = [ + 'group' => $groupId, + ]; + + $rel = $repo->findOneBy($criteria); + if ($rel instanceof CGroupRelUsergroup) { + return $rel->getUsergroup(); + } else { + return null; + } + } + + public static function is_group_linked_to_usergroup(CGroup $group): bool + { + $em = Database::getManager(); + $repo = $em->getRepository(CGroupRelUsergroup::class); + $criteria = [ + 'group' => $group, + ]; + + $rel = $repo->findOneBy($criteria); + return $rel instanceof CGroupRelUsergroup; + } + + public static function remove_group_consistent_link(CGroup $group): bool + { + $em = Database::getManager(); + $repo = Database::getManager()->getRepository(CGroupRelUsergroup::class); + $criteria = [ + 'group' => $group, + ]; + + $rel = $repo->findOneBy($criteria); + if ($rel instanceof CGroupRelUsergroup) { + $em->remove($rel); + $em->flush(); + return true; + } else { + return false; + } + } + /** * Deletes groups and their data. * @@ -901,7 +1071,9 @@ public static function create_category( $selfUnRegistrationAllowed = 0, $maxStudent = 8, $groupsPerUser = 0, - $documentAccess = 0 + $documentAccess = 0, + $namingRule = GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, + $visibilityRule = GroupManager::GROUP_VISIBILITY_DEFAULT ) { if (empty($title)) { return false; @@ -928,6 +1100,8 @@ public static function create_category( ->setGroupsPerUser($groupsPerUser) ->setParent($course) ->addCourseLink($course, $session) + ->setAllowChangeGroupName($namingRule) + ->setOnlyMe($visibilityRule) ; $repo = Container::getGroupCategoryRepository(); @@ -970,7 +1144,9 @@ public static function update_category( $selfUnRegistrationAllowed, $maximum_number_of_students, $groups_per_user, - $documentAccess + $documentAccess, + $namingRule = GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, + $visibilityRule = GroupManager::GROUP_VISIBILITY_DEFAULT ) { $table = Database::get_course_table(TABLE_GROUP_CATEGORY); $id = (int) $id; @@ -995,6 +1171,8 @@ public static function update_category( groups_per_user = '".Database::escape_string($groups_per_user)."', self_reg_allowed = '".Database::escape_string($selfRegistrationAllowed)."', self_unreg_allowed = '".Database::escape_string($selfUnRegistrationAllowed)."', + allow_change_group_name = '".Database::escape_string($namingRule)."', + only_me = '".Database::escape_string($visibilityRule)."', $documentCondition max_student = ".intval($maximum_number_of_students)." WHERE iid = $id"; @@ -2236,6 +2414,12 @@ class = "btn btn--plain" } } + // linked class + if (api_get_setting('allow_group_categories') === 'true') { + $usergroup = GroupManager::get_usergroup_link($group); + isset($usergroup) ? $row[] = $usergroup->getTitle() : $row[] = '-'; + } + // @todo fix group session access. $groupSessionId = null; @@ -2317,6 +2501,9 @@ class = "btn btn--plain" $table->set_header($column++, get_lang('Groups')); $table->set_header($column++, get_lang('Group tutor')); $table->set_header($column++, get_lang('Registered'), false); + if (api_get_setting('allow_group_categories') === 'true') { + $table->set_header($column++, get_lang('Classe liée'), false); + } if (!api_is_allowed_to_edit(false, true)) { // If self-registration allowed @@ -2385,7 +2572,9 @@ public static function importCategoriesAndGroupsFromArray($groupData, $deleteNot $data['self_unreg_allowed'], $data['max_student'], $data['groups_per_user'], - $data['document_access'] + $data['document_access'], + $data['allow_change_group_name'], + $data['only_me'] ); $data['category_id'] = $categoryId; $result['updated']['category'][] = $data; @@ -2404,7 +2593,10 @@ public static function importCategoriesAndGroupsFromArray($groupData, $deleteNot $data['self_reg_allowed'], $data['self_unreg_allowed'], $data['max_student'], - $data['groups_per_user'] + $data['groups_per_user'], + $data['document_access'] ?? 0, + $data['allow_change_group_name'], + $data['only_me'] ); if ($categoryId) { diff --git a/src/CoreBundle/Framework/Container.php b/src/CoreBundle/Framework/Container.php index a15e42fe2e0..f6bddbc82b4 100644 --- a/src/CoreBundle/Framework/Container.php +++ b/src/CoreBundle/Framework/Container.php @@ -59,6 +59,7 @@ use Chamilo\CourseBundle\Repository\CForumThreadRepository; use Chamilo\CourseBundle\Repository\CGlossaryRepository; use Chamilo\CourseBundle\Repository\CGroupCategoryRepository; +use Chamilo\CourseBundle\Repository\CGroupRelUsergroupRepository; use Chamilo\CourseBundle\Repository\CGroupRepository; use Chamilo\CourseBundle\Repository\CLinkCategoryRepository; use Chamilo\CourseBundle\Repository\CLinkRepository; @@ -656,4 +657,9 @@ public static function getThemeHelper(): ThemeHelper { return self::$container->get(ThemeHelper::class); } + + public static function getGroupRelUsergroupRepository(): CGroupRelUsergroupRepository + { + return self::$container->get(CGroupRelUsergroupRepository::class); + } } diff --git a/src/CourseBundle/Entity/CGroupRelUsergroup.php b/src/CourseBundle/Entity/CGroupRelUsergroup.php index 01505164551..45e98c6af2a 100644 --- a/src/CourseBundle/Entity/CGroupRelUsergroup.php +++ b/src/CourseBundle/Entity/CGroupRelUsergroup.php @@ -10,12 +10,13 @@ use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\Usergroup; use Doctrine\ORM\Mapping as ORM; +use Chamilo\CourseBundle\Repository\CGroupRelUsergroupRepository; /** * CGroupRelUsergroup. */ #[ORM\Table(name: 'c_group_rel_usergroup')] -#[ORM\Entity] +#[ORM\Entity(repositoryClass: CGroupRelUsergroupRepository::class)] class CGroupRelUsergroup { #[ORM\Id] @@ -55,7 +56,6 @@ public function getGroup(): CGroup public function setGroup(CGroup $group): self { $this->group = $group; - return $this; } @@ -67,7 +67,6 @@ public function getUsergroup(): Usergroup public function setUsergroup(Usergroup $usergroup): self { $this->usergroup = $usergroup; - return $this; } @@ -79,7 +78,6 @@ public function getSession(): ?Session public function setSession(?Session $session): self { $this->session = $session; - return $this; } @@ -91,7 +89,6 @@ public function getCourse(): ?Course public function setCourse(?Course $course): self { $this->course = $course; - return $this; } @@ -103,7 +100,6 @@ public function isReadyAutogroup(): bool public function setReadyAutogroup(bool $readyAutogroup): self { $this->readyAutogroup = $readyAutogroup; - return $this; } } diff --git a/src/CourseBundle/Repository/CGroupRelUsergroupRepository.php b/src/CourseBundle/Repository/CGroupRelUsergroupRepository.php new file mode 100644 index 00000000000..fb6aec2b0a3 --- /dev/null +++ b/src/CourseBundle/Repository/CGroupRelUsergroupRepository.php @@ -0,0 +1,15 @@ + Date: Tue, 29 Apr 2025 11:29:55 +0200 Subject: [PATCH 2/4] Language: Add new terms for groups --- public/main/group/group_creation.php | 6 +++--- public/main/group/settings.php | 6 +++--- translations/messages.en.po | 15 +++++++++++++++ translations/messages.fr.po | 15 +++++++++++++++ translations/messages.pot | 15 +++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/public/main/group/group_creation.php b/public/main/group/group_creation.php index bf71569aac5..8b67539b0d6 100644 --- a/public/main/group/group_creation.php +++ b/public/main/group/group_creation.php @@ -377,10 +377,10 @@ function copy_value(key) { $classForm->addHtml('

'); - $classForm->addCheckBox('is_consistent_link', null, 'Lier les classes aux groupes crées ?', - ['title' => 'Si elle est désactivé les classes lié à ce groupe sont simplement une copie des utilisateurs de la classe séléctionnée. A contrario si elle est activé, la classe séléctionnée est lié au groupe et les modifications de la classe sont répercuté sur le groupe (ce mode empêche la modification du groupe)'] + $classForm->addCheckBox('is_consistent_link', null, get_lang('Link classes to created groups ?'), + ['title' => get_lang('Info message about deactivation of consistent link')] ); - $classForm->addHtml('

Si elle est désactivé les classes lié à ce groupe sont simplement une copie des utilisateurs de la classe séléctionnée. A contrario si elle est activé, la classe séléctionnée est lié au groupe et les modifications de la classe sont répercuté sur le groupe (ce mode empêche la modification du groupe)

'); + $classForm->addHtml('

'.get_lang('Info message about deactivation of consistent link').'

'); $classForm->addButtonSave(get_lang('Validate')); $defaults['action'] = 'create_class_groups'; diff --git a/public/main/group/settings.php b/public/main/group/settings.php index 5a05289d399..78264db0929 100644 --- a/public/main/group/settings.php +++ b/public/main/group/settings.php @@ -66,9 +66,9 @@ // Message for group rel usergroup if (GroupManager::is_group_linked_to_usergroup($groupEntity)) { - $form->addHtml('
Ce groupe est lié à la '.$courseInfo['title'].'. Les ajouts ou suppression - de membres à la classe seront reportés sur le groupe. Vous ne pouvez pas ajouter ou ôter des membres à un groupe lié - à une classe. Vous pouvez cependant rompre ce lien en cliquant sur le bouton ci-dessous
Rompre le lien du groupe avec la classe
'); + $form->addHtml('
'.get_lang('Warning message to warn that the user cannot modify members of linked group').'
+ '.get_lang('Remove the group link with the class'). + '
'); } if ('true' === api_get_setting('allow_group_categories')) { diff --git a/translations/messages.en.po b/translations/messages.en.po index f9d047b6ff2..416ae4cdbc3 100644 --- a/translations/messages.en.po +++ b/translations/messages.en.po @@ -27826,3 +27826,18 @@ msgstr "Autolaunch settings" msgid "Auto-launch" msgstr "Auto-launch" + +msgid "Link classes to created groups ?" +msgstr "Link classes to created groups ?" + +msgid "Info message about deactivation of consistent link" +msgstr "If desactivated, the classes linked to this group are simply a copy of the users of the selected class. But, if it is enabled, the selected class is linked to the group, and modifications to the class are reflected in the group (this mode prevents modification of the group)." + +msgid "Warning message to warn that the user cannot modify members of linked group" +msgstr "This group is linked to a class. Any additions or deletions of members to the class will be push the group. You cannot add or remove members to a group linked to a class. However you can break this link by clicking on the button below" + +msgid "Remove the group link with the class" +msgstr "Remove the group link with the class" + +msgid "Group is no longer linked to the course" +msgstr "Group is no longer linked to the course" diff --git a/translations/messages.fr.po b/translations/messages.fr.po index 1e7914592b8..dd6bc8eb1d1 100644 --- a/translations/messages.fr.po +++ b/translations/messages.fr.po @@ -29606,3 +29606,18 @@ msgstr "Paramètres d'auto démarrage" msgid "Auto-launch" msgstr "Auto-démarrage" + +msgid "Link classes to created groups ?" +msgstr "Lier les classes aux groupes crées ?" + +msgid "Info message about deactivation of consistent link" +msgstr "Si elle est désactivé les classes lié à ce groupe sont simplement une copie des utilisateurs de la classe séléctionnée. A contrario si elle est activé, la classe séléctionnée est lié au groupe et les modifications de la classe sont répercuté sur le groupe (ce mode empêche la modification du groupe)" + +msgid "Warning message to warn that the user cannot modify members of linked group" +msgstr "Ce groupe est lié à la une classe. Les ajouts ou suppression de membres à la classe seront reportés sur le groupe. Vous ne pouvez pas ajouter ou ôter des membres à un groupe lié une classe. Vous pouvez cependant rompre ce lien en cliquant sur le bouton ci-dessous" + +msgid "Remove the group link with the class" +msgstr "Rompre le lien du groupe avec la classe" + +msgid "Group is no longer linked to the course" +msgstr "Le groupe n'esp plus lié au cours" diff --git a/translations/messages.pot b/translations/messages.pot index 11efd85d20d..a2334b29e6f 100644 --- a/translations/messages.pot +++ b/translations/messages.pot @@ -27812,3 +27812,18 @@ msgstr "" msgid "Auto-launch" msgstr "" + +msgid "Link classes to created groups ?" +msgstr "" + +msgid "Info message about deactivation of consistent link" +msgstr "" + +msgid "Warning message to warn that the user cannot modify members of linked group" +msgstr "" + +msgid "Remove the group link with the class" +msgstr "" + +msgid "Group is no longer linked to the course" +msgstr "" From d5735895644dd86ba0346e670523b307186903a0 Mon Sep 17 00:00:00 2001 From: Esteban Ristich Date: Tue, 29 Apr 2025 11:37:13 +0200 Subject: [PATCH 3/4] Minor: add phpdoc block and type hinting --- public/main/inc/lib/groupmanager.lib.php | 38 +++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/public/main/inc/lib/groupmanager.lib.php b/public/main/inc/lib/groupmanager.lib.php index 38ead77446c..3d841e73e56 100644 --- a/public/main/inc/lib/groupmanager.lib.php +++ b/public/main/inc/lib/groupmanager.lib.php @@ -404,11 +404,14 @@ public static function create_subgroups($group_id, $number_of_groups) /** * Create a group for every class subscribed to the current course. * + * @author Esteban Ristich + * * @param int $categoryId The category in which the groups should be created + * @param array $classIds The ids of the classes to be created * * @return array */ - public static function create_class_groups($categoryId, $classIds = []) + public static function create_class_groups(int $categoryId, array $classIds = []): array { $options['where'] = [' usergroup.course_id = ? ' => api_get_course_int_id()]; $obj = new UserGroupModel(); @@ -438,15 +441,17 @@ public static function create_class_groups($categoryId, $classIds = []) /** * Create a group for every class subscribed to the current course * + * @todo: move part of doctrine query in repository for future + * * @author Esteban Ristich * * @param int $category_id The category in which the groups should be created - * @param $category_id + * @param array $classIds The ids of the classes to be created * * @return array * @throws \Doctrine\DBAL\DBALException */ - public static function create_usergroup_consistent_groups($category_id = 0, $classIds = []) + public static function create_usergroup_consistent_groups(int $category_id = 0, array $classIds = []): array { // check if group is not duplicated in same category /** @var \Chamilo\CourseBundle\Repository\CGroupRelUsergroupRepository $relRepository */ @@ -532,19 +537,20 @@ public static function create_usergroup_consistent_groups($category_id = 0, $cla } /** - * Get the usergroup linked to the group if it exists. + * Get the usergroup (class) linked to the group if it exists. * * @author Esteban Ristich * - * @param CGroup $groupId + * @param CGroup $group + * * @return Usergroup|null */ - public static function get_usergroup_link(CGroup $groupId): ?Usergroup + public static function get_usergroup_link(CGroup $group): ?Usergroup { $em = Database::getManager(); $repo = $em->getRepository(CGroupRelUsergroup::class); $criteria = [ - 'group' => $groupId, + 'group' => $group, ]; $rel = $repo->findOneBy($criteria); @@ -555,6 +561,15 @@ public static function get_usergroup_link(CGroup $groupId): ?Usergroup } } + /** + * Check if a group has a consistent link to an usergroup. + * + * @author Esteban Ristich + * + * @param CGroup $group + * + * @return bool + */ public static function is_group_linked_to_usergroup(CGroup $group): bool { $em = Database::getManager(); @@ -567,6 +582,15 @@ public static function is_group_linked_to_usergroup(CGroup $group): bool return $rel instanceof CGroupRelUsergroup; } + /** + * Remove the consistent link between usergroup and group. + * + * @author Esteban Ristich + * + * @param CGroup $group + * + * @return bool + */ public static function remove_group_consistent_link(CGroup $group): bool { $em = Database::getManager(); From 27fa710b313090238d04cea220dd3a916afaad32 Mon Sep 17 00:00:00 2001 From: Esteban Ristich Date: Thu, 15 May 2025 09:24:21 +0200 Subject: [PATCH 4/4] Group: revert new group category options for future PR --- public/main/group/group_category.php | 50 +++--------------------- public/main/inc/lib/groupmanager.lib.php | 36 ++--------------- 2 files changed, 9 insertions(+), 77 deletions(-) diff --git a/public/main/group/group_category.php b/public/main/group/group_category.php index 119cc273ee5..0fb889c2eb3 100644 --- a/public/main/group/group_category.php +++ b/public/main/group/group_category.php @@ -75,9 +75,7 @@ function check_groups_per_user($value) 'announcements_state' => GroupManager::TOOL_PRIVATE, 'forum_state' => GroupManager::TOOL_PRIVATE, 'max_student' => 0, - 'document_access' => 0, - 'allow_change_group_name' => GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, - 'only_me' => GroupManager::GROUP_VISIBILITY_DEFAULT, + 'document_access' => 0 ]; } @@ -166,42 +164,6 @@ function check_groups_per_user($value) $form->addRule('max_member_group', get_lang('Please enter a valid number for the maximum number of members.'), 'callback', 'check_max_number_of_members'); $form->addElement('html', '
'); -// group naming settings -$form->addElement('html', '
'); - -$group = [ - $form->createElement('radio', 'allow_change_group_name', null, 'Seul les enseignants peuvent renommer les groupes', GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME), - $form->createElement('radio', 'allow_change_group_name', null, 'Les membres peuvent renommer les groupes', GroupManager::GROUP_STUDENT_CAN_RENAME), - $form->createElement('radio', 'allow_change_group_name', null, 'Le groupe sera nommé avec les noms prénoms de ses membres', GroupManager::GROUP_AUTO_RENAME_WITH_SELF_MEMBERS), -]; -$form->addGroup( - $group, - '', - Display::getMdiIcon('rename', 'ch-tool-icon', null, ICON_SIZE_SMALL, 'Renommer').' Renommer', - null, - false -); - -$form->addElement('html', '
'); - -// visibility settings -$form->addElement('html', '
'); - -$group = [ - $form->createElement('radio', 'only_me', null, 'Par défaut', GroupManager::GROUP_VISIBILITY_DEFAULT), - $form->createElement('radio', 'only_me', null, 'Afficher uniquement les groupes dont l\'étudiant est membre', GroupManager::GROUP_ONLY_FOR_THOSE_JOINED), - $form->createElement('radio', 'only_me', null, 'Afficher, sans leur nom ni la liste des membres, uniquement les groupes dont l\'étudiant est membre', GroupManager::GROUP_ONLY_FOR_THOSE_JOINED_WITHOUT_MEMBERS_INFOS), -]; -$form->addGroup( - $group, - '', - Display::getMdiIcon('eye', 'ch-tool-icon', null, ICON_SIZE_SMALL, 'Visibilité').' Visibilité', - null, - false -); - -$form->addElement('html', '
'); - $form->addElement('html', '
'); // Self registration $group = [ @@ -445,9 +407,7 @@ function check_groups_per_user($value) $self_unreg_allowed, $max_member, $values['groups_per_user'], - $values['document_access'] ?? 0, - $values['allow_change_group_name'], - $values['only_me'] + $values['document_access'] ?? 0 ); Display::addFlash(Display::return_message(get_lang('Group settings have been modified'))); header('Location: '.$currentUrl.'&category='.$values['id']); @@ -467,9 +427,7 @@ function check_groups_per_user($value) $self_unreg_allowed, $max_member, $values['groups_per_user'], - $values['document_access'] ?? 0, - $values['allow_change_group_name'], - $values['only_me'] + $values['document_access'] ?? 0 ); Display::addFlash(Display::return_message(get_lang('Category created'))); header('Location: '.$currentUrl); @@ -494,9 +452,11 @@ function check_groups_per_user($value) $defaults['max_member_no_limit'] = 1; $defaults['max_member'] = $defaults['max_student']; } +/* if (api_get_setting('allow_group_categories')) { $defaults['id'] = $_GET['id']; } +*/ $form->setDefaults($defaults); $form->display(); diff --git a/public/main/inc/lib/groupmanager.lib.php b/public/main/inc/lib/groupmanager.lib.php index 3d841e73e56..fcda5f56fd4 100644 --- a/public/main/inc/lib/groupmanager.lib.php +++ b/public/main/inc/lib/groupmanager.lib.php @@ -68,21 +68,6 @@ class GroupManager public const DOCUMENT_MODE_READ_ONLY = 1; public const DOCUMENT_MODE_COLLABORATION = 2; - /** - * Constant for the group category's naming rules - */ - public const GROUP_ONLY_TEACHER_CAN_RENAME = 0; - public const GROUP_STUDENT_CAN_RENAME = 1; - public const GROUP_AUTO_RENAME_WITH_SELF_MEMBERS = 2; - - /** - * Constant for the group category's visibility rules - */ - public const GROUP_VISIBILITY_DEFAULT = 0; - public const GROUP_ONLY_FOR_THOSE_JOINED = 1; - public const GROUP_ONLY_FOR_THOSE_JOINED_WITHOUT_MEMBERS_INFOS = 2; - - public function __construct() { } @@ -1095,9 +1080,7 @@ public static function create_category( $selfUnRegistrationAllowed = 0, $maxStudent = 8, $groupsPerUser = 0, - $documentAccess = 0, - $namingRule = GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, - $visibilityRule = GroupManager::GROUP_VISIBILITY_DEFAULT + $documentAccess = 0 ) { if (empty($title)) { return false; @@ -1124,8 +1107,6 @@ public static function create_category( ->setGroupsPerUser($groupsPerUser) ->setParent($course) ->addCourseLink($course, $session) - ->setAllowChangeGroupName($namingRule) - ->setOnlyMe($visibilityRule) ; $repo = Container::getGroupCategoryRepository(); @@ -1168,9 +1149,7 @@ public static function update_category( $selfUnRegistrationAllowed, $maximum_number_of_students, $groups_per_user, - $documentAccess, - $namingRule = GroupManager::GROUP_ONLY_TEACHER_CAN_RENAME, - $visibilityRule = GroupManager::GROUP_VISIBILITY_DEFAULT + $documentAccess ) { $table = Database::get_course_table(TABLE_GROUP_CATEGORY); $id = (int) $id; @@ -1195,8 +1174,6 @@ public static function update_category( groups_per_user = '".Database::escape_string($groups_per_user)."', self_reg_allowed = '".Database::escape_string($selfRegistrationAllowed)."', self_unreg_allowed = '".Database::escape_string($selfUnRegistrationAllowed)."', - allow_change_group_name = '".Database::escape_string($namingRule)."', - only_me = '".Database::escape_string($visibilityRule)."', $documentCondition max_student = ".intval($maximum_number_of_students)." WHERE iid = $id"; @@ -2596,9 +2573,7 @@ public static function importCategoriesAndGroupsFromArray($groupData, $deleteNot $data['self_unreg_allowed'], $data['max_student'], $data['groups_per_user'], - $data['document_access'], - $data['allow_change_group_name'], - $data['only_me'] + $data['document_access'] ); $data['category_id'] = $categoryId; $result['updated']['category'][] = $data; @@ -2617,10 +2592,7 @@ public static function importCategoriesAndGroupsFromArray($groupData, $deleteNot $data['self_reg_allowed'], $data['self_unreg_allowed'], $data['max_student'], - $data['groups_per_user'], - $data['document_access'] ?? 0, - $data['allow_change_group_name'], - $data['only_me'] + $data['groups_per_user'] ); if ($categoryId) {