From 7c0b2fbfb07c8ed80ecff6f410f9104ae1434f57 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Mon, 7 Apr 2025 20:34:53 -0500 Subject: [PATCH 1/2] User: Enable roles combination - refs #5648 --- public/main/admin/user_add.php | 45 ++++++-- public/main/admin/user_edit.php | 76 +++++++----- public/main/admin/user_list.php | 197 ++++++++++++++++---------------- public/main/inc/lib/api.lib.php | 189 ++++++++++++++++-------------- 4 files changed, 292 insertions(+), 215 deletions(-) diff --git a/public/main/admin/user_add.php b/public/main/admin/user_add.php index 8b59de1e47b..3b509da8f38 100644 --- a/public/main/admin/user_add.php +++ b/public/main/admin/user_add.php @@ -244,18 +244,22 @@ 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, ] ); //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 +358,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 +401,24 @@ function updateStatus(){ $template = isset($user['email_template_option']) ? $user['email_template_option'] : []; + $roles = $user['roles'] ?? []; + $priorityRoles = [ + 'ROLE_SESSION_MANAGER' => SESSIONADMIN, + 'ROLE_HR' => DRH, + 'ROLE_TEACHER' => COURSEMANAGER, + 'ROLE_STUDENT_BOSS' => STUDENT_BOSS, + 'ROLE_INVITEE' => INVITEE, + 'ROLE_STUDENT' => STUDENT, + ]; + + $status = STUDENT; + foreach ($priorityRoles as $role => $roleStatus) { + if (in_array($role, $roles, true)) { + $status = $roleStatus; + break; + } + } + $user_id = UserManager::create_user( $firstname, $lastname, @@ -456,6 +477,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 f7fdb18c651..4301c39330f 100644 --- a/public/main/admin/user_edit.php +++ b/public/main/admin/user_edit.php @@ -250,26 +250,21 @@ 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, ] ); -$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()) { @@ -403,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; @@ -417,8 +415,8 @@ function confirmation(name) { } $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'] ?? []; + if (in_array('ROLE_HR', $roles, true) && $is_user_subscribed_in_course) { $error_drh = true; } else { $picture_element = $form->getElement('picture'); @@ -441,12 +439,14 @@ function confirmation(name) { $lastname = $user['lastname']; $firstname = $user['firstname']; $password = $user['password']; - $auth_source = $user['auth_source'] ?? $userInfo['auth_source']; + $auth_source = $user['auth_source'] ?? $userInfo['auth_source'] ?? []; + if (!is_array($auth_source)) { + $auth_source = [$auth_source]; + } $official_code = $user['official_code']; $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()) { @@ -463,17 +463,34 @@ 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'] ?? []; + $priorityRoles = [ + 'ROLE_SESSION_MANAGER' => SESSIONADMIN, + 'ROLE_HR' => DRH, + 'ROLE_TEACHER' => COURSEMANAGER, + 'ROLE_STUDENT_BOSS' => STUDENT_BOSS, + 'ROLE_INVITEE' => INVITEE, + 'ROLE_STUDENT' => STUDENT, + ]; + + $status = STUDENT; + foreach ($priorityRoles as $role => $roleStatus) { + if (in_array($role, $roles, true)) { + $status = $roleStatus; + break; + } + } + + //If the user is set to admin the status will be overwrite by COURSEMANAGER = 1 + if (1 == $platform_admin) { + $status = COURSEMANAGER; + } + UserManager::update_user( $user_id, $firstname, @@ -499,7 +516,7 @@ function confirmation(name) { $template ); - $studentBossListSent = isset($user['student_boss']) ? $user['student_boss'] : []; + $studentBossListSent = $user['student_boss'] ?? []; UserManager::subscribeUserToBossList( $user_id, $studentBossListSent, @@ -516,6 +533,13 @@ 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); diff --git a/public/main/admin/user_list.php b/public/main/admin/user_list.php index 635d0eb4b96..1ff58d364f8 100644 --- a/public/main/admin/user_list.php +++ b/public/main/admin/user_list.php @@ -3,6 +3,7 @@ /* For licensing terms, see /license.txt */ use Chamilo\CoreBundle\Entity\User; +use Chamilo\CoreBundle\Framework\Container; use ChamiloSession as Session; use Chamilo\CoreBundle\Component\Utils\StateIcon; @@ -214,7 +215,7 @@ 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', @@ -230,65 +231,64 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): } } - 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)) { + 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)) { $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'])) { + foreach ((array) $keywordListValues['keyword_roles'] as $sub) { + $roles = array_merge($roles, (array) $sub); + } } - if ('%' === $keywordListValues['keyword_status']) { - $keywordListValues['keyword_status'] = ''; + if (in_array('ROLE_PLATFORM_ADMIN', $roles, true)) { + $query_admin_table = " , $admin_table a "; + $keyword_admin = ' AND a.user_id = u.id '; } - $keyword_extra_value = ''; - $sql .= " $query_admin_table - WHERE ( 1 = 1 "; + $sql .= " $query_admin_table 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']."%")."'"; - } - - if (!empty($keywordListValues['keyword_status'])) { - $sql .= "AND u.status = '".Database::escape_string($keywordListValues['keyword_status'])."'"; + $sql .= " AND u.email LIKE '".Database::escape_string("%".$keywordListValues['keyword_email']."%")."'"; } if (!empty($keywordListValues['keyword_officialcode'])) { - $sql .= " AND u.official_code LIKE '".Database::escape_string("%".$keywordListValues['keyword_officialcode']."%")."' "; + $sql .= " AND u.official_code LIKE '".Database::escape_string("%".$keywordListValues['keyword_officialcode']."%")."'"; } - $sql .= " $keyword_admin $keyword_extra_value "; + if (!empty($roles)) { + $roleConditions = []; + foreach ($roles as $role) { + $escaped = Database::escape_string($role); + $roleConditions[] = "u.roles LIKE '%\"$escaped\"%'"; + } + $sql .= ' AND (' . implode(' OR ', $roleConditions) . ')'; + } if (isset($keywordListValues['keyword_active']) && !isset($keywordListValues['keyword_inactive']) @@ -299,27 +299,23 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): ) { $sql .= ' AND u.active = 0'; } - $sql .= ' ) '; + + $sql .= " $keyword_admin )"; } 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; - + // Extra fields (sin cambios) $variables = Session::read('variables_to_show', []); $extraFields = api_get_setting('profile.user_search_on_extra_fields', true); @@ -338,11 +334,9 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): $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; @@ -358,7 +352,7 @@ function prepare_user_sql_query(bool $getCount, bool $showDeletedUsers = false): if (empty($value)) { 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,14 +367,10 @@ 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)."')"; } } @@ -419,9 +409,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 +445,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 +555,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 +576,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 +607,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', @@ -807,7 +799,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 +816,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 +830,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,13 +910,25 @@ 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(); + $repo = Container::getUserRepository(); + $user = $repo->find($userId); + if (!$user) { + return ''; + } - return $name[$status]; + $roles = array_filter( + $user->getRoles(), + fn ($role) => $role !== 'ROLE_USER' + ); + + return implode('
', array_map('htmlentities', $roles)); } if (isset($_GET['keyword']) || isset($_GET['keyword_firstname'])) { @@ -1183,7 +1189,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 +1239,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('Course sessionsAdmin'); -$status_options[PLATFORM_ADMIN] = get_lang('Administrator'); -$status_options[STUDENT_BOSS] = get_lang('RoleStudentBoss'); - $form->addSelect( - 'keyword_status', - get_lang('Profile'), - $status_options + 'keyword_roles', + get_lang('Roles'), + array_combine(api_get_roles(), api_get_roles()), + ['multiple' => true, 'size' => 6] ); $active_group = []; @@ -1291,7 +1292,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 +1301,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 3d935b3f693..c8bda15b2f1 100644 --- a/public/main/inc/lib/api.lib.php +++ b/public/main/inc/lib/api.lib.php @@ -3026,21 +3026,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(); } @@ -3049,9 +3040,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(); } @@ -3310,110 +3299,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. + * Determines whether the current user is allowed to edit the current context. * - * @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 + * 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. * - * @author Roan Embrechts - * @author Patrick Cool - * @author Julio Montoya - * - * @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'; } /** @@ -6348,15 +6375,11 @@ function api_set_default_visibility( } } -function api_get_roles() +function api_get_roles(): array { - $hierarchy = Container::$container->getParameter('security.role_hierarchy.roles'); - $roles = []; - array_walk_recursive($hierarchy, function ($role) use (&$roles) { - $roles[$role] = $role; - }); + $roles = Container::$container->get(\Chamilo\CoreBundle\ServiceHelper\PermissionServiceHelper::class)->getUserRoles(); - return $roles; + return array_combine($roles, $roles); } function api_get_user_roles(): array From 20b053ab8c626cc742129e13899d7417853f6583 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Tue, 22 Apr 2025 15:16:35 +0200 Subject: [PATCH 2/2] Internal: Move call to api_get_session_visibility() to increase performance - refs #5648 --- public/main/inc/lib/api.lib.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/main/inc/lib/api.lib.php b/public/main/inc/lib/api.lib.php index c8bda15b2f1..7bfaf127ae0 100644 --- a/public/main/inc/lib/api.lib.php +++ b/public/main/inc/lib/api.lib.php @@ -3320,7 +3320,6 @@ function api_is_allowed_to_edit( ): bool { $allowSessionAdminEdit = 'true' === api_get_setting('session.session_admins_edit_courses_content'); $sessionId = api_get_session_id(); - $sessionVisibility = api_get_session_visibility($sessionId); $studentView = api_is_student_view_active(); $isAllowed = false; @@ -3352,6 +3351,8 @@ function api_is_allowed_to_edit( $isCourseAdmin = api_is_course_tutor(); } + $sessionVisibility = api_get_session_visibility($sessionId); + if (!$isCourseAdmin && $coach) { if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) { $isCoach = false;