diff --git a/public/main/admin/user_add.php b/public/main/admin/user_add.php index 00814e80f4c..6c14b1d81fb 100644 --- a/public/main/admin/user_add.php +++ b/public/main/admin/user_add.php @@ -244,18 +244,23 @@ function updateStatus(){ $status[STUDENT_BOSS] = get_lang('Student\'s superior'); $status[INVITEE] = get_lang('Invitee'); -$form->addSelect( - 'status', - get_lang('Profile'), - $status, +$form->addElement( + 'select', + 'roles', + get_lang('Roles'), + api_get_roles(), [ - 'id' => 'status_select', - 'onchange' => 'javascript: updateStatus();', + 'multiple' => 'multiple', + 'size' => 8, ] ); +$form->addRule('roles', get_lang('Required field'), 'required'); //drh list (display only if student) -$display = (isset($_POST['status']) && STUDENT == $_POST['status']) || !isset($_POST['status']) ? 'block' : 'none'; +$display = 'none'; +if (isset($_POST['roles']) && is_array($_POST['roles'])) { + $display = in_array('ROLE_TEACHER', $_POST['roles']) || in_array('ROLE_SESSION_MANAGER', $_POST['roles']) ? 'block' : 'none'; +} if (api_is_platform_admin()) { // Platform admin @@ -354,7 +359,6 @@ function updateStatus(){ $email = $user['email']; $phone = $user['phone']; $username = 'true' !== api_get_setting('login_is_email') ? $user['username'] : ''; - $status = (int) $user['status']; $language = $user['locale']; $picture = $_FILES['picture']; $platform_admin = 0; @@ -398,6 +402,12 @@ function updateStatus(){ $template = isset($user['email_template_option']) ? $user['email_template_option'] : []; + $roles = $user['roles'] ?? []; + $status = api_status_from_roles($roles); + if ((int) ($user['admin']['platform_admin'] ?? 0) === 1) { + $status = COURSEMANAGER; + } + $user_id = UserManager::create_user( $firstname, $lastname, @@ -456,6 +466,14 @@ function updateStatus(){ ); } + $repo = Container::getUserRepository(); + $userEntity = $repo->find($user_id); + + if ($userEntity) { + $userEntity->setRoles($roles); + $repo->updateUser($userEntity); + } + $extraFieldValues = new ExtraFieldValue('user'); $user['item_id'] = $user_id; $extraFieldValues->saveFieldValues($user); diff --git a/public/main/admin/user_edit.php b/public/main/admin/user_edit.php index 9ac69e7602f..07ec6639bec 100644 --- a/public/main/admin/user_edit.php +++ b/public/main/admin/user_edit.php @@ -250,26 +250,22 @@ function confirmation(name) { $form->addGroup($group, 'password', null, null, false); $form->addPasswordRule('password', 'password'); -// Status -$status = []; -$status[COURSEMANAGER] = get_lang('Trainer'); -$status[STUDENT] = get_lang('Learner'); -$status[DRH] = get_lang('Human Resources Manager'); -$status[SESSIONADMIN] = get_lang('Sessions administrator'); -$status[STUDENT_BOSS] = get_lang('Student\'s superior'); -$status[INVITEE] = get_lang('Invitee'); - -$form->addSelect( - 'status', - get_lang('Profile'), - $status, +$form->addElement( + 'select', + 'roles', + get_lang('Roles'), + api_get_roles(), [ - 'id' => 'status_select', - 'onchange' => 'javascript: display_drh_list();', + 'multiple' => 'multiple', + 'size' => 8, ] ); +$form->addRule('roles', get_lang('Required field'), 'required'); -$display = isset($user_data['status']) && (STUDENT == $user_data['status'] || (isset($_POST['status']) && STUDENT == $_POST['status'])) ? 'block' : 'none'; +$display = 'none'; +if (isset($_POST['roles']) && is_array($_POST['roles'])) { + $display = in_array('ROLE_TEACHER', $_POST['roles']) || in_array('ROLE_SESSION_MANAGER', $_POST['roles']) ? 'block' : 'none'; +} // Platform admin if (api_is_platform_admin()) { @@ -402,6 +398,9 @@ function confirmation(name) { $user_data['expiration_date'] = api_get_local_time($expiration_date); } } +$availableRoles = array_keys(api_get_roles()); +$userRoles = array_intersect($userObj->getRoles(), $availableRoles); +$user_data['roles'] = $userRoles; $form->setDefaults($user_data); $error_drh = false; @@ -415,9 +414,9 @@ function confirmation(name) { exit(); } - $is_user_subscribed_in_course = CourseManager::is_user_subscribed_in_course($user['user_id']); - - if (DRH == $user['status'] && $is_user_subscribed_in_course) { + $roles = $user['roles'] ?? []; + $newStatus = api_status_from_roles($roles); + if ($newStatus === DRH && CourseManager::is_user_subscribed_in_course((int) $user_id)) { $error_drh = true; } else { $picture_element = $form->getElement('picture'); @@ -445,7 +444,6 @@ function confirmation(name) { $email = $user['email']; $phone = $user['phone']; $username = $user['username'] ?? $userInfo['username']; - $status = (int) $user['status']; $platform_admin = 0; // Only platform admin can change user status to admin. if (api_is_platform_admin()) { @@ -462,23 +460,21 @@ function confirmation(name) { } $active = isset($user['active']) ? (int) $user['active'] : USER_SOFT_DELETED; - //If the user is set to admin the status will be overwrite by COURSEMANAGER = 1 - if (1 == $platform_admin) { - $status = COURSEMANAGER; - } - if ('true' === api_get_setting('login_is_email')) { $username = $email; } $template = $user['email_template_option'] ?? []; + if ((int) ($user['platform_admin'] ?? 0) === 1) { + $newStatus = COURSEMANAGER; + } $incompatible = false; $conflicts = []; - $oldStatus = $userObj->getStatus(); - $newStatus = (int) $user['status']; + $oldStatus = (int) $userObj->getStatus(); + if ($oldStatus !== $newStatus) { - $isNowStudent = $newStatus === STUDENT; + $isNowStudent = ($newStatus === STUDENT); if ($isNowStudent) { $courseTeacherCount = $userObj->getCourses()->count(); $coachSessions = $userObj->getSessionsAsGeneralCoach(); @@ -525,7 +521,7 @@ function confirmation(name) { $password, $auth_source, $email, - $status, + $newStatus, $official_code, $phone, $picture_uri, @@ -542,7 +538,7 @@ function confirmation(name) { $template ); - $studentBossListSent = isset($user['student_boss']) ? $user['student_boss'] : []; + $studentBossListSent = $user['student_boss'] ?? []; UserManager::subscribeUserToBossList( $user_id, $studentBossListSent, @@ -559,13 +555,20 @@ function confirmation(name) { } } + $repo = Container::getUserRepository(); + $userEntity = $repo->find($user_id); + if ($userEntity) { + $userEntity->setRoles($roles); + $repo->updateUser($userEntity); + } + $extraFieldValue = new ExtraFieldValue('user'); $extraFieldValue->saveFieldValues($user); $userInfo = api_get_user_info($user_id); $message = get_lang('User updated').': '.Display::url( - $userInfo['complete_name_with_username'], - api_get_path(WEB_CODE_PATH).'admin/user_edit.php?user_id='.$user_id - ); + $userInfo['complete_name_with_username'], + api_get_path(WEB_CODE_PATH).'admin/user_edit.php?user_id='.$user_id + ); Session::erase('system_timezone'); diff --git a/public/main/admin/user_list.php b/public/main/admin/user_list.php index 0a30ba833df..52f452400af 100644 --- a/public/main/admin/user_list.php +++ b/public/main/admin/user_list.php @@ -4,6 +4,7 @@ use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Enums\StateIcon; +use Chamilo\CoreBundle\Framework\Container; use ChamiloSession as Session; /** @@ -165,7 +166,7 @@ function trimVariables() function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): string { $sql = ''; - $user_table = Database::get_main_table(TABLE_MAIN_USER); + $user_table = Database::get_main_table(TABLE_MAIN_USER); $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN); $isMultipleUrl = (api_is_platform_admin() || api_is_session_admin()) && api_get_multiple_access_url(); @@ -174,7 +175,9 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): if ($getCount) { $sql .= "SELECT COUNT(u.id) AS total_number_of_items FROM $user_table u"; } else { - $sql .= 'SELECT u.id AS col0, u.official_code AS col2, '; + $sql .= 'SELECT + u.id AS col0, + u.official_code AS col2, '; if (api_is_western_name_order()) { $sql .= 'u.firstname AS col3, u.lastname AS col4, '; @@ -183,26 +186,24 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): } $sql .= " u.username AS col5, - u.email AS col6, - u.status AS col7, - u.active AS col8, - u.created_at AS col9, - u.last_login as col10, - u.id AS col11, - u.expiration_date AS exp, - u.password - FROM $user_table u"; + u.email AS col6, + u.status AS col7, + u.active AS col8, + u.created_at AS col9, + u.last_login AS col10, + u.id AS col11, + u.expiration_date AS exp, + u.password + FROM $user_table u"; } - // adding the filter to see the user's only of the current access_url if ($isMultipleUrl) { $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER); $sql .= " INNER JOIN $access_url_rel_user_table url_rel_user - ON (u.id=url_rel_user.user_id)"; + ON (u.id = url_rel_user.user_id)"; } $classId = isset($_REQUEST['class_id']) && !empty($_REQUEST['class_id']) ? (int) $_REQUEST['class_id'] : 0; - if ($classId) { $userGroupTable = Database::get_main_table(TABLE_USERGROUP_REL_USER); $sql .= " INNER JOIN $userGroupTable ug ON (ug.user_id = u.id)"; @@ -214,112 +215,145 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): 'keyword_username', 'keyword_email', 'keyword_officialcode', - 'keyword_status', + 'keyword_roles', 'keyword_active', 'keyword_inactive', 'check_easy_passwords', ]; - $keywordListValues = []; $atLeastOne = false; foreach ($keywordList as $keyword) { $keywordListValues[$keyword] = null; - if (isset($_GET[$keyword]) && !empty($_GET[$keyword])) { + if (isset($_GET[$keyword]) && $_GET[$keyword] !== '') { $keywordListValues[$keyword] = Security::remove_XSS($_GET[$keyword]); $atLeastOne = true; } } - - if (false == $atLeastOne) { + if (!$atLeastOne) { $keywordListValues = []; } - if (isset($_GET['keyword']) && !empty($_GET['keyword'])) { - $keywordFiltered = Database::escape_string("%".$_GET['keyword']."%"); - $sql .= " WHERE ( - u.firstname LIKE '$keywordFiltered' OR - u.lastname LIKE '$keywordFiltered' OR - concat(u.firstname, ' ', u.lastname) LIKE '$keywordFiltered' OR - concat(u.lastname,' ',u.firstname) LIKE '$keywordFiltered' OR - u.username LIKE '$keywordFiltered' OR - u.official_code LIKE '$keywordFiltered' OR - u.email LIKE '$keywordFiltered' - ) - "; - } elseif (isset($keywordListValues) && !empty($keywordListValues)) { - $query_admin_table = ''; - $keyword_admin = ''; - - if (isset($keywordListValues['keyword_status']) && - PLATFORM_ADMIN == $keywordListValues['keyword_status'] - ) { - $query_admin_table = " , $admin_table a "; - $keyword_admin = ' AND a.user_id = u.id '; - $keywordListValues['keyword_status'] = ''; - } + $roles = []; + if (!empty($keywordListValues['keyword_roles'])) { + $raw = $keywordListValues['keyword_roles']; + $roles = is_array($raw) ? $raw : array_filter(array_map('trim', explode(',', (string) $raw))); + $available = array_keys(api_get_roles()); + if (empty($available)) { $available = array_values(api_get_roles()); } + $roles = array_values(array_unique(array_intersect($roles, $available))); + } - if ('%' === $keywordListValues['keyword_status']) { - $keywordListValues['keyword_status'] = ''; + $roleToStatus = [ + 'ROLE_TEACHER' => COURSEMANAGER, + 'TEACHER' => COURSEMANAGER, + 'ROLE_STUDENT' => STUDENT, + 'STUDENT' => STUDENT, + 'ROLE_HR' => DRH, + 'HR' => DRH, + 'ROLE_SESSION_MANAGER' => SESSIONADMIN, + 'SESSION_MANAGER' => SESSIONADMIN, + 'ROLE_STUDENT_BOSS' => STUDENT_BOSS, + 'STUDENT_BOSS' => STUDENT_BOSS, + 'ROLE_INVITEE' => INVITEE, + 'INVITEE' => INVITEE, + ]; + $mappedStatuses = []; + foreach ($roles as $r) { + if (isset($roleToStatus[$r])) { + $mappedStatuses[] = (int) $roleToStatus[$r]; } + } + $mappedStatuses = array_values(array_unique($mappedStatuses)); + + $adminVariants = ['ROLE_PLATFORM_ADMIN','PLATFORM_ADMIN','ROLE_SUPER_ADMIN','SUPER_ADMIN','ROLE_GLOBAL_ADMIN','GLOBAL_ADMIN','ROLE_ADMIN','ADMIN']; + $needsAdminLeftJoin = (bool) array_intersect($roles, $adminVariants); + if ($needsAdminLeftJoin) { + $sql .= " LEFT JOIN $admin_table a ON (a.user_id = u.id) "; + } - $keyword_extra_value = ''; - $sql .= " $query_admin_table - WHERE ( 1 = 1 "; + if (isset($_GET['keyword']) && $_GET['keyword'] !== '') { + $keywordFiltered = Database::escape_string("%".$_GET['keyword']."%"); + $sql .= " WHERE ( + u.firstname LIKE '$keywordFiltered' OR + u.lastname LIKE '$keywordFiltered' OR + concat(u.firstname, ' ', u.lastname) LIKE '$keywordFiltered' OR + concat(u.lastname, ' ', u.firstname) LIKE '$keywordFiltered' OR + u.username LIKE '$keywordFiltered' OR + u.official_code LIKE '$keywordFiltered' OR + u.email LIKE '$keywordFiltered' + )"; + } elseif (!empty($keywordListValues)) { + $sql .= " WHERE (1 = 1 "; if (!empty($keywordListValues['keyword_firstname'])) { - $sql .= "AND u.firstname LIKE '".Database::escape_string("%".$keywordListValues['keyword_firstname']."%")."'"; + $sql .= " AND u.firstname LIKE '".Database::escape_string("%".$keywordListValues['keyword_firstname']."%")."'"; } - // This block is never executed because $keyword_extra_data never exists if (!empty($keywordListValues['keyword_lastname'])) { - $sql .= "AND u.lastname LIKE '".Database::escape_string("%".$keywordListValues['keyword_lastname']."%")."'"; + $sql .= " AND u.lastname LIKE '".Database::escape_string("%".$keywordListValues['keyword_lastname']."%")."'"; } if (!empty($keywordListValues['keyword_username'])) { - $sql .= "AND u.username LIKE '".Database::escape_string("%".$keywordListValues['keyword_username']."%")."'"; + $sql .= " AND u.username LIKE '".Database::escape_string("%".$keywordListValues['keyword_username']."%")."'"; } if (!empty($keywordListValues['keyword_email'])) { - $sql .= "AND u.email LIKE '".Database::escape_string("%".$keywordListValues['keyword_email']."%")."'"; + $sql .= " AND u.email LIKE '".Database::escape_string("%".$keywordListValues['keyword_email']."%")."'"; } - - if (!empty($keywordListValues['keyword_status'])) { - $sql .= "AND u.status = '".Database::escape_string($keywordListValues['keyword_status'])."'"; + if (!empty($keywordListValues['keyword_officialcode'])) { + $sql .= " AND u.official_code LIKE '".Database::escape_string("%".$keywordListValues['keyword_officialcode']."%")."'"; } - if (!empty($keywordListValues['keyword_officialcode'])) { - $sql .= " AND u.official_code LIKE '".Database::escape_string("%".$keywordListValues['keyword_officialcode']."%")."' "; + if (!empty($roles)) { + $roleConds = []; + + foreach ($roles as $role) { + $u = strtoupper($role); + $variants = [$u]; + if (strpos($u, 'ROLE_') === 0) { + $variants[] = substr($u, 5); + } else { + $variants[] = 'ROLE_'.$u; + } + $variants = array_values(array_unique($variants)); + + $likes = []; + foreach ($variants as $v) { + $esc = Database::escape_string($v); + $likes[] = "u.roles LIKE '%\"$esc\"%'"; + } + $roleConds[] = '('.implode(' OR ', $likes).')'; + } + + if (!empty($mappedStatuses)) { + $roleConds[] = 'u.status IN ('.implode(',', array_map('intval', $mappedStatuses)).')'; + } + + if ($needsAdminLeftJoin) { + $roleConds[] = 'a.user_id IS NOT NULL'; + } + + $sql .= ' AND ('.implode(' OR ', $roleConds).')'; } - $sql .= " $keyword_admin $keyword_extra_value "; - if (isset($keywordListValues['keyword_active']) && - !isset($keywordListValues['keyword_inactive']) - ) { + if (isset($keywordListValues['keyword_active']) && !isset($keywordListValues['keyword_inactive'])) { $sql .= ' AND u.active = 1'; - } elseif (isset($keywordListValues['keyword_inactive']) && - !isset($keywordListValues['keyword_active']) - ) { + } elseif (isset($keywordListValues['keyword_inactive']) && !isset($keywordListValues['keyword_active'])) { $sql .= ' AND u.active = 0'; } - $sql .= ' ) '; + + $sql .= ' )'; } if ($classId) { $sql .= " AND ug.usergroup_id = $classId"; } - $preventSessionAdminsToManageAllUsers = api_get_setting('prevent_session_admins_to_manage_all_users'); - - $extraConditions = ''; - if (api_is_session_admin() && 'true' === $preventSessionAdminsToManageAllUsers) { - $extraConditions .= ' AND u.creator_id = '.api_get_user_id(); + if (api_is_session_admin() && api_get_setting('prevent_session_admins_to_manage_all_users') === 'true') { + $sql .= ' AND u.creator_id = '.api_get_user_id(); } - // adding the filter to see the user's only of the current access_url if ($isMultipleUrl) { - $extraConditions .= ' AND url_rel_user.access_url_id = '.$urlId; + $sql .= ' AND url_rel_user.access_url_id = '.$urlId; } - $sql .= $extraConditions; - $variables = Session::read('variables_to_show', []); $extraFields = api_get_setting('profile.user_search_on_extra_fields', true); @@ -332,33 +366,32 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): } $variables = array_merge($extraFieldList, $variables); } + if (!empty($variables)) { $extraField = new ExtraField('user'); $extraFieldResult = []; $extraFieldHasData = []; + foreach ($variables as $variable) { if (isset($_GET['extra_'.$variable])) { - if (is_array($_GET['extra_'.$variable])) { - $values = $_GET['extra_'.$variable]; - } else { - $values = [$_GET['extra_'.$variable]]; - } + $values = is_array($_GET['extra_'.$variable]) + ? $_GET['extra_'.$variable] + : [$_GET['extra_'.$variable]]; if (empty($values)) { continue; } $info = $extraField->get_handler_field_info_by_field_variable($variable); - if (empty($info)) { continue; } foreach ($values as $value) { - if (empty($value)) { + if ($value === '' || $value === null) { continue; } - if (ExtraField::FIELD_TYPE_TAG == $info['value_type']) { + if (ExtraField::FIELD_TYPE_TAG === $info['value_type']) { $result = $extraField->getAllUserPerTag($info['id'], $value); $result = empty($result) ? [] : array_column($result, 'user_id'); } else { @@ -373,22 +406,18 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): } } - $condition = ' AND '; - // If simple search then use "OR" - if (isset($_GET['keyword']) && !empty($_GET['keyword'])) { - $condition = ' OR '; - } - + $condition = isset($_GET['keyword']) ? ' OR ' : ' AND '; if (!empty($extraFieldHasData) && !empty($extraFieldResult)) { - $sql .= " $condition (u.id IN ('".implode("','", $extraFieldResult)."') $extraConditions ) "; + $sql .= " $condition u.id IN ('".implode("','", $extraFieldResult)."')"; } } if ($showDeletedUsers) { - $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active = '.USER_SOFT_DELETED : ' AND u.active = '.USER_SOFT_DELETED; + $sql .= (!str_contains($sql, 'WHERE') ? ' WHERE ' : ' AND ').'u.active = '.USER_SOFT_DELETED; } else { - $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED; + $sql .= (!str_contains($sql, 'WHERE') ? ' WHERE ' : ' AND ').'u.active <> '.USER_SOFT_DELETED; } + $sql .= ' AND u.status <> '.User::ROLE_FALLBACK; return $sql; @@ -419,9 +448,6 @@ function get_user_data(int $from, int $number_of_items, int $column, string $dir if (!in_array($direction, ['ASC', 'DESC'])) { $direction = 'ASC'; } - $column = (int) $column; - $from = (int) $from; - $number_of_items = (int) $number_of_items; $sql .= " ORDER BY col$column $direction "; $sql .= " LIMIT $from, $number_of_items"; @@ -458,7 +484,7 @@ function get_user_data(int $from, int $number_of_items, int $column, string $dir $user[3], $user[4], // username $user[5], // email - $user[6], + $user[0], $user[7], // active api_get_local_time($user[8]), api_get_local_time($user[9], null, null, true), @@ -568,15 +594,15 @@ function modify_filter($user_id, $url_params, $row): string { $_admins_list = Session::read('admin_list', []); $is_admin = in_array($user_id, $_admins_list); - $statusname = api_get_status_langvars(); + + $repo = Container::getUserRepository(); + $userEntity = $repo->find($user_id); + $userRoles = $userEntity ? $userEntity->getRoles() : []; + $currentUserId = api_get_user_id(); - $user_is_anonymous = false; + $user_is_anonymous = in_array('ROLE_ANONYMOUS', $userRoles, true); $current_user_status_label = $row['7']; - - if ($current_user_status_label == $statusname[ANONYMOUS]) { - $user_is_anonymous = true; - } $result = ''; if (api_is_platform_admin()) { @@ -589,16 +615,21 @@ function modify_filter($user_id, $url_params, $row): string } } - // Only allow platform admins to login_as, or session admins only for students (not teachers nor other admins) - $loginAsStatusForSessionAdmins = [$statusname[STUDENT]]; + $loginAsRolesForSessionAdmins = ['ROLE_STUDENT']; - // Except when session.allow_session_admin_login_as_teacher is enabled, then can login_as teachers also if ('true' === api_get_setting('session.allow_session_admin_login_as_teacher')) { - $loginAsStatusForSessionAdmins[] = $statusname[COURSEMANAGER]; + $loginAsRolesForSessionAdmins[] = 'ROLE_TEACHER'; } - $sessionAdminCanLoginAs = api_is_session_admin() && - in_array($current_user_status_label, $loginAsStatusForSessionAdmins); + $sessionAdminCanLoginAs = false; + if (api_is_session_admin()) { + foreach ($loginAsRolesForSessionAdmins as $role) { + if (in_array($role, $userRoles, true)) { + $sessionAdminCanLoginAs = true; + break; + } + } + } if (api_is_platform_admin() || $sessionAdminCanLoginAs) { if (!$user_is_anonymous) { @@ -615,7 +646,7 @@ function modify_filter($user_id, $url_params, $row): string $result .= Display::getMdiIcon('account-key', 'ch-tool-icon-disabled', null, 22, get_lang('Login as')); } - if ($current_user_status_label != $statusname[STUDENT]) { + if (!in_array('ROLE_STUDENT', $userRoles, true)) { $result .= Display::getMdiIcon( 'chart-box', 'ch-tool-icon-disabled', @@ -650,12 +681,12 @@ function modify_filter($user_id, $url_params, $row): string ''; } else { $result .= Display::getMdiIcon( - 'pencil', - 'ch-tool-icon-disabled', - null, - 22, - get_lang('Edit') - ).''; + 'pencil', + 'ch-tool-icon-disabled', + null, + 22, + get_lang('Edit') + ).''; } } @@ -807,7 +838,12 @@ function modify_filter($user_id, $url_params, $row): string // actions for assigning sessions, courses or users if (!api_is_session_admin()) { - if ($current_user_status_label == $statusname[SESSIONADMIN]) { + $isSessionManager = in_array('ROLE_SESSION_MANAGER', $userRoles, true); + $isHR = in_array('ROLE_HR', $userRoles, true); + $isStudentBoss = in_array('ROLE_STUDENT_BOSS', $userRoles, true); + $isAdmin = UserManager::is_admin($user_id); + + if ($isSessionManager) { $result .= Display::url( Display::getMdiIcon( 'google-classroom', @@ -819,10 +855,7 @@ function modify_filter($user_id, $url_params, $row): string "dashboard_add_sessions_to_user.php?user={$user_id}" ); } else { - if ($current_user_status_label == $statusname[DRH] || - UserManager::is_admin($user_id) || - $current_user_status_label == $statusname[STUDENT_BOSS] - ) { + if ($isHR || $isAdmin || $isStudentBoss) { $result .= Display::url( Display::getMdiIcon( 'account-child', @@ -836,7 +869,7 @@ function modify_filter($user_id, $url_params, $row): string ); } - if ($current_user_status_label == $statusname[DRH] || UserManager::is_admin($user_id)) { + if ($isHR || $isAdmin) { $result .= Display::url( Display::getMdiIcon( 'book-open-page-variant', @@ -916,15 +949,40 @@ function active_filter(int $active, string $params, array $row): string } /** - * Instead of displaying the integer of the status, we give a translation for the status. + * Returns a list of user roles excluding the default ROLE_USER. + * + * @param int $userId The user ID. + * @return string HTML string with roles separated by
. */ -function status_filter(int $status): string +function roles_filter($userId): string { - $name = api_get_status_langvars(); + static $map = null; + if ($map === null) { + $map = api_get_roles(); + } + + $repo = Container::getUserRepository(); + $user = $repo->find($userId); + if (!$user) { + return ''; + } + + $codes = array_filter($user->getRoles(), static function ($code) { + $u = strtoupper($code); + return $u !== 'ROLE_USER' && $u !== 'USER' && $u !== 'ROLE_ANONYMOUS' && $u !== 'ANONYMOUS'; + }); - return $name[$status]; + $labels = array_map(static function ($code) use ($map) { + $u = strtoupper($code); + $bare = preg_replace('/^ROLE_/', '', $u); + $label = $map[$u] ?? $map[$bare] ?? ucwords(strtolower(str_replace('_', ' ', $bare))); + return htmlentities($label); + }, $codes); + + return implode('
', $labels); } + if (isset($_GET['keyword']) || isset($_GET['keyword_firstname'])) { $interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('Administration')]; $interbreadcrumb[] = ['url' => 'user_list.php', 'name' => get_lang('User list')]; @@ -1169,7 +1227,7 @@ class="btn btn--plain advanced_options" onclick="display_advanced_search_form(); $actionsRight = ''; if (api_is_platform_admin() && !$showDeletedUsers) { $actionsLeft .= ''. - Display::getMdiIcon('account-plus', 'ch-tool-icon-gradient', null, 32, get_lang('Add a user')).''; + Display::getMdiIcon('account-plus', 'ch-tool-icon-gradient', null, 32, get_lang('Add a user')).''; } $actionsRight .= $form->returnForm(); @@ -1183,7 +1241,10 @@ class="btn btn--plain advanced_options" onclick="display_advanced_search_form(); $parameters['keyword_username'] = Security::remove_XSS($_GET['keyword_username']); $parameters['keyword_email'] = Security::remove_XSS($_GET['keyword_email']); $parameters['keyword_officialcode'] = Security::remove_XSS($_GET['keyword_officialcode']); - $parameters['keyword_status'] = Security::remove_XSS($_GET['keyword_status']); + if (isset($_GET['keyword_roles'])) { + $keywordRoles = is_array($_GET['keyword_roles']) ? $_GET['keyword_roles'] : [$_GET['keyword_roles']]; + $parameters['keyword_roles'] = implode(',', array_map('Security::remove_XSS', $keywordRoles)); + } if (isset($_GET['keyword_active'])) { $parameters['keyword_active'] = Security::remove_XSS($_GET['keyword_active']); } @@ -1230,19 +1291,11 @@ class="btn btn--plain advanced_options" onclick="display_advanced_search_form(); ['url' => api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?a=get_class_by_keyword'] ); -$status_options = []; -$status_options['%'] = get_lang('All'); -$status_options[STUDENT] = get_lang('Learner'); -$status_options[COURSEMANAGER] = get_lang('Trainer'); -$status_options[DRH] = get_lang('Human Resources Manager'); -$status_options[SESSIONADMIN] = get_lang('Sessions administrator'); -$status_options[PLATFORM_ADMIN] = get_lang('Administrator'); -$status_options[STUDENT_BOSS] = get_lang("Student's superior"); - $form->addSelect( - 'keyword_status', - get_lang('Profile'), - $status_options + 'keyword_roles', + get_lang('Roles'), + api_get_roles(), + ['multiple' => true, 'size' => 6] ); $active_group = []; @@ -1291,7 +1344,7 @@ function($from, $number_of_items, $column, $direction) use ($showDeletedUsers) { } $table->set_header(5, get_lang('Username')); $table->set_header(6, get_lang('E-mail')); -$table->set_header(7, get_lang('Profile')); +$table->set_header(7, get_lang('Roles')); $table->set_header(8, get_lang('active'), true, 'width="15px"'); $table->set_header(9, get_lang('Registration date'), true, 'width="90px"'); $table->set_header(10, get_lang('Latest login'), true, 'width="90px"'); @@ -1300,7 +1353,7 @@ function($from, $number_of_items, $column, $direction) use ($showDeletedUsers) { $table->set_column_filter(3, 'user_filter'); $table->set_column_filter(4, 'user_filter'); $table->set_column_filter(6, 'email_filter'); -$table->set_column_filter(7, 'status_filter'); +$table->set_column_filter(7, 'roles_filter'); $table->set_column_filter(8, 'active_filter'); $actionsList = []; if ($showDeletedUsers) { diff --git a/public/main/inc/lib/api.lib.php b/public/main/inc/lib/api.lib.php index 3845ef11d9f..38c0495e42c 100644 --- a/public/main/inc/lib/api.lib.php +++ b/public/main/inc/lib/api.lib.php @@ -3030,21 +3030,12 @@ function api_is_course_session_coach($user_id, $courseId, $session_id) /** * Checks whether the current user is a course or session coach. - * - * @param int $session_id - * @param int $courseId - * @param bool Check whether we are in student view and, if we are, return false - * @param int $userId - * - * @return bool True if current user is a course or session coach */ -function api_is_coach($session_id = 0, $courseId = null, $check_student_view = true, $userId = 0) +function api_is_coach(int $session_id = 0, ?int $courseId = null, bool $check_student_view = true, int $userId = 0): bool { - $userId = empty($userId) ? api_get_user_id() : (int) $userId; + $userId = empty($userId) ? api_get_user_id() : $userId; - if (!empty($session_id)) { - $session_id = (int) $session_id; - } else { + if (empty($session_id)) { $session_id = api_get_session_id(); } @@ -3053,9 +3044,7 @@ function api_is_coach($session_id = 0, $courseId = null, $check_student_view = t return false; } - if (!empty($courseId)) { - $courseId = (int) $courseId; - } else { + if (empty($courseId)) { $courseId = api_get_course_int_id(); } @@ -3314,110 +3303,148 @@ function api_display_tool_view_option() } /** - * Function that removes the need to directly use is_courseAdmin global in - * tool scripts. It returns true or false depending on the user's rights in - * this particular course. - * Optionally checking for tutor and coach roles here allows us to use the - * student_view feature altogether with these roles as well. - * - * @param bool Whether to check if the user has the tutor role - * @param bool Whether to check if the user has the coach role - * @param bool Whether to check if the user has the session coach role - * @param bool check the student view or not + * Determines whether the current user is allowed to edit the current context. * - * @author Roan Embrechts - * @author Patrick Cool - * @author Julio Montoya + * This includes checks for platform admin, course admin, tutor, coach, + * session coach, and optionally verifies if the user is in student view mode. + * If not in a course context, it falls back to a role-based permission system. * - * @version 1.1, February 2004 + * @param bool $tutor Allow if the user is a tutor. + * @param bool $coach Allow if the user is a coach and setting allows it. + * @param bool $session_coach Allow if the user is a session coach. + * @param bool $check_student_view Check if student view mode is active. * - * @return bool true: the user has the rights to edit, false: he does not + * @return bool True if the user is allowed to edit, false otherwise. */ function api_is_allowed_to_edit( - $tutor = false, - $coach = false, - $session_coach = false, - $check_student_view = true -) { + bool $tutor = false, + bool $coach = false, + bool $session_coach = false, + bool $check_student_view = true +): bool { $allowSessionAdminEdit = 'true' === api_get_setting('session.session_admins_edit_courses_content'); - // Admins can edit anything. + $sessionId = api_get_session_id(); + $sessionVisibility = api_get_session_visibility($sessionId); + $studentView = api_is_student_view_active(); + $isAllowed = false; + + // If platform admin, allow unless student view is active if (api_is_platform_admin($allowSessionAdminEdit)) { - //The student preview was on - if ($check_student_view && api_is_student_view_active()) { - return false; + if ($check_student_view && $studentView) { + $isAllowed = false; + } else { + return true; } - - return true; } - $sessionId = api_get_session_id(); - + // Respect session course read-only mode from extra field if ($sessionId && 'true' === api_get_setting('session.session_courses_read_only_mode')) { $efv = new ExtraFieldValue('course'); - $lockExrafieldField = $efv->get_values_by_handler_and_field_variable( + $lock = $efv->get_values_by_handler_and_field_variable( api_get_course_int_id(), 'session_courses_read_only_mode' ); - - if (!empty($lockExrafieldField['value'])) { + if (!empty($lock['value'])) { return false; } } - $is_allowed_coach_to_edit = api_is_coach(null, null, $check_student_view); - $session_visibility = api_get_session_visibility($sessionId); - $is_courseAdmin = api_is_course_admin(); + $isCourseAdmin = api_is_course_admin(); + $isCoach = api_is_coach(0, null, $check_student_view); - if (!$is_courseAdmin && $tutor) { - // If we also want to check if the user is a tutor... - $is_courseAdmin = $is_courseAdmin || api_is_course_tutor(); + if (!$isCourseAdmin && $tutor) { + $isCourseAdmin = api_is_course_tutor(); } - if (!$is_courseAdmin && $coach) { - // If we also want to check if the user is a coach...'; - // Check if session visibility is read only for coaches. - if (SESSION_VISIBLE_READ_ONLY == $session_visibility) { - $is_allowed_coach_to_edit = false; + if (!$isCourseAdmin && $coach) { + if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) { + $isCoach = false; } - if ('true' === api_get_setting('allow_coach_to_edit_course_session')) { - // Check if coach is allowed to edit a course. - $is_courseAdmin = $is_courseAdmin || $is_allowed_coach_to_edit; + $isCourseAdmin = $isCoach; } } - if (!$is_courseAdmin && $session_coach) { - $is_courseAdmin = $is_courseAdmin || $is_allowed_coach_to_edit; + if (!$isCourseAdmin && $session_coach) { + $isCourseAdmin = $isCoach; } - // Check if the student_view is enabled, and if so, if it is activated. + // Handle student view mode if ('true' === api_get_setting('student_view_enabled')) { - $studentView = api_is_student_view_active(); if (!empty($sessionId)) { - // Check if session visibility is read only for coaches. - if (SESSION_VISIBLE_READ_ONLY == $session_visibility) { - $is_allowed_coach_to_edit = false; + if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) { + $isCoach = false; } - - $is_allowed = false; if ('true' === api_get_setting('allow_coach_to_edit_course_session')) { - // Check if coach is allowed to edit a course. - $is_allowed = $is_allowed_coach_to_edit; + $isAllowed = $isCoach; } + if ($check_student_view) { - $is_allowed = $is_allowed && false === $studentView; + $isAllowed = $isAllowed && !$studentView; } } else { - $is_allowed = $is_courseAdmin; + $isAllowed = $isCourseAdmin; if ($check_student_view) { - $is_allowed = $is_courseAdmin && false === $studentView; + $isAllowed = $isCourseAdmin && !$studentView; } } - return $is_allowed; + if ($isAllowed) { + return true; + } } else { - return $is_courseAdmin; + if ($isCourseAdmin) { + return true; + } + } + + // Final fallback: permission-based system (only if nothing before returned true) + $courseId = api_get_course_id(); + $inCourse = !empty($courseId) && $courseId != -1; + + if (!$inCourse) { + $userRoles = api_get_user_roles(); + $feature = api_detect_feature_context(); + $permission = $feature.':edit'; + + return api_get_permission($permission, $userRoles); } + + return $isAllowed; +} + +/** + * Returns the current main feature (module) based on the current script path. + * Used to determine permissions for non-course tools. + */ +function api_detect_feature_context(): string +{ + $script = $_SERVER['SCRIPT_NAME'] ?? ''; + $script = basename($script); + + $map = [ + 'user_list.php' => 'user', + 'user_add.php' => 'user', + 'user_edit.php' => 'user', + 'session_list.php' => 'session', + 'session_add.php' => 'session', + 'session_edit.php' => 'session', + 'skill_list.php' => 'skill', + 'skill_edit.php' => 'skill', + 'badge_list.php' => 'badge', + 'settings.php' => 'settings', + 'course_list.php' => 'course', + ]; + + if (isset($map[$script])) { + return $map[$script]; + } + + if (preg_match('#/main/([a-z_]+)/#i', $_SERVER['SCRIPT_NAME'], $matches)) { + return $matches[1]; + } + + return 'unknown'; } /** @@ -6354,15 +6381,120 @@ function api_set_default_visibility( } } -function api_get_roles() +/** + * Returns available role codes => translated labels. + * Uses DI PermissionHelper and caches results. + */ +function api_get_roles(): array +{ + static $cache = null; + if ($cache !== null) { + return $cache; + } + + $codes = Container::$container + ->get(\Chamilo\CoreBundle\Helpers\PermissionHelper::class) + ->getUserRoles(); // list of role codes from DB + + // Built-in labels fallbacks. DB codes are used as keys. + $labels = [ + 'ROLE_STUDENT' => get_lang('Learner'), + 'STUDENT' => get_lang('Learner'), + 'ROLE_TEACHER' => get_lang('Teacher'), + 'TEACHER' => get_lang('Teacher'), + 'ROLE_HR' => get_lang('Human Resources Manager'), + 'HR' => get_lang('Human Resources Manager'), + 'ROLE_SESSION_MANAGER' => get_lang('Session administrator'), + 'SESSION_MANAGER' => get_lang('Session administrator'), + 'ROLE_STUDENT_BOSS' => get_lang('Superior (n+1)'), + 'STUDENT_BOSS' => get_lang('Superior (n+1)'), + 'ROLE_INVITEE' => get_lang('Invitee'), + 'INVITEE' => get_lang('Invitee'), + 'ROLE_QUESTION_MANAGER' => get_lang('Question manager'), + 'QUESTION_MANAGER' => get_lang('Question manager'), + 'ROLE_ADMIN' => get_lang('Admin'), + 'ADMIN' => get_lang('Admin'), + 'ROLE_PLATFORM_ADMIN' => get_lang('Administrator'), + 'PLATFORM_ADMIN' => get_lang('Administrator'), + 'ROLE_SUPER_ADMIN' => get_lang('Super admin'), + 'SUPER_ADMIN' => get_lang('Super admin'), + 'ROLE_GLOBAL_ADMIN' => get_lang('Global admin'), + 'GLOBAL_ADMIN' => get_lang('Global admin'), + 'ROLE_ANONYMOUS' => 'Anonymous', + 'ANONYMOUS' => 'Anonymous', + 'ROLE_USER' => 'User', + 'USER' => 'User', + ]; + + $out = []; + foreach ((array) $codes as $code) { + $canon = strtoupper(trim((string) $code)); + $label = + ($labels[$canon] ?? null) ?? + ($labels['ROLE_'.$canon] ?? null) ?? + ucwords(strtolower(str_replace('_', ' ', preg_replace('/^ROLE_/', '', $canon)))); + $out[$code] = $label; + } + + ksort($out, SORT_STRING); + return $cache = $out; +} + +/** + * Normalizes a role code to canonical "ROLE_*" uppercase form. + */ +function api_normalize_role_code(string $code): string { - $hierarchy = Container::$container->getParameter('security.role_hierarchy.roles'); - $roles = []; - array_walk_recursive($hierarchy, function ($role) use (&$roles) { - $roles[$role] = $role; - }); + $code = strtoupper(trim($code)); + return str_starts_with($code, 'ROLE_') ? $code : 'ROLE_'.$code; +} - return $roles; +/** + * Priority when deriving legacy status from roles (first match wins). + */ +function api_roles_priority(): array +{ + return [ + 'ROLE_SESSION_MANAGER', + 'ROLE_HR', + 'ROLE_TEACHER', + 'ROLE_STUDENT_BOSS', + 'ROLE_INVITEE', + 'ROLE_STUDENT', + ]; +} + +/** + * Canonical role -> legacy status map. + */ +function api_role_status_map(): array +{ + return [ + 'ROLE_SESSION_MANAGER' => SESSIONADMIN, + 'ROLE_HR' => DRH, + 'ROLE_TEACHER' => COURSEMANAGER, + 'ROLE_STUDENT_BOSS' => STUDENT_BOSS, + 'ROLE_INVITEE' => INVITEE, + 'ROLE_STUDENT' => STUDENT, + ]; +} + +/** + * Returns legacy status from a set of roles using priority. + * Defaults to STUDENT if none matched. + */ +function api_status_from_roles(array $roles): int +{ + $norm = array_map('api_normalize_role_code', $roles); + $priority = api_roles_priority(); + $map = api_role_status_map(); + + foreach ($priority as $p) { + if (in_array($p, $norm, true)) { + return $map[$p]; + } + } + return STUDENT; } function api_get_user_roles(): array