From 6f554d1380ca874e404e4573c029643b12b8ea25 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 23 Apr 2024 12:27:51 -0500 Subject: [PATCH 001/413] Agenda: Fix param passed to GroupManager::get_users when executing the cron for reminders - refs BT#20991 --- public/main/cron/agenda_reminders.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/public/main/cron/agenda_reminders.php b/public/main/cron/agenda_reminders.php index d5478b7eaf3..090252750ed 100644 --- a/public/main/cron/agenda_reminders.php +++ b/public/main/cron/agenda_reminders.php @@ -23,6 +23,7 @@ $em = Database::getManager(); $remindersRepo = $em->getRepository(AgendaReminder::class); +/** @var array $reminders */ $reminders = $remindersRepo->findBy(['sent' => false]); $senderId = (int) api_get_setting('agenda.agenda_reminders_sender_id'); @@ -159,7 +160,14 @@ if ($resourceLink->getUser()) { $userIdList[] = $resourceLink->getUser()->getId(); } elseif ($resourceLink->getGroup()) { - $groupUsers = GroupManager::get_users($resourceLink->getGroup()->getId(), false, null, null, false, $event->getSessionId()); + $groupUsers = GroupManager::get_users( + $resourceLink->getGroup()->getIid(), + false, + null, + null, + false, + $resourceLink->getCourse()?->getId() + ); foreach ($groupUsers as $groupUserId) { $groupUserIdList[] = $groupUserId; } From c7814ee5234e9b5f74833e7c98e28202bedd6003 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 23 Apr 2024 13:20:56 -0500 Subject: [PATCH 002/413] Minor: Remove undefined store variable --- assets/vue/components/social/UserProfileCard.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/vue/components/social/UserProfileCard.vue b/assets/vue/components/social/UserProfileCard.vue index d14132eadb6..8ec53e4fc4b 100644 --- a/assets/vue/components/social/UserProfileCard.vue +++ b/assets/vue/components/social/UserProfileCard.vue @@ -119,7 +119,6 @@ import { useSecurityStore } from "../../store/securityStore" import BaseUserAvatar from "../basecomponents/BaseUserAvatar.vue" const { t } = useI18n() -const store = useStore() const { isAdmin } = useSecurityStore() const user = inject("social-user") const isCurrentUser = inject("is-current-user") From 86646ab1bf1c39d8fbf18a196f39df83cd7c91f7 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Tue, 23 Apr 2024 14:17:48 -0500 Subject: [PATCH 003/413] Internal: Fix forum notification issue for subscribed users in sessions - refs BT#21550 --- public/main/forum/forumfunction.inc.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/public/main/forum/forumfunction.inc.php b/public/main/forum/forumfunction.inc.php index 53ae3789c8c..9e4ad10c691 100644 --- a/public/main/forum/forumfunction.inc.php +++ b/public/main/forum/forumfunction.inc.php @@ -4306,21 +4306,12 @@ function get_notifications(string $content, int $id): array $field = 'forum_id'; } - $sessionId = api_get_session_id(); - $conditionSession = ""; - if (!empty($sessionId)) { - $users = SessionManager::getUsersByCourseSession($sessionId, api_get_course_info()); - if (!empty($users)) { - $conditionSession = " AND user.id IN(".implode(',', $users).")"; - } - } - $sql = "SELECT user.id as user_id, user.firstname, user.lastname, user.email, user.id user FROM $table_users user, $table_notification notification WHERE notification.c_id = $course_id AND user.active = 1 AND user.id = notification.user_id AND - notification.$field = $id $conditionSession"; + notification.$field = $id "; $result = Database::query($sql); $return = []; @@ -4369,6 +4360,11 @@ function send_notifications(CForum $forum, CForumThread $thread, $post_id = 0) // Merging the two $users_to_be_notified = array_merge($users_to_be_notified_by_forum, $users_to_be_notified_by_thread); + $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications'); + if (1 === $subscribe) { + $users_to_be_notified = CourseManager::get_user_list_from_course_code(api_get_course_id(), api_get_session_id()); + } + if (is_array($users_to_be_notified)) { foreach ($users_to_be_notified as $value) { $userInfo = api_get_user_info($value['user_id']); From ebcd3708506b6ca6cae1897533fd02a4a6cd9556 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 23 Apr 2024 16:05:28 -0500 Subject: [PATCH 004/413] Minor: CI: Fix psalm MissingTemplateParam --- .../Security/Authorization/Voter/AnonymousVoter.php | 3 +++ .../Security/Authorization/Voter/CCalendarEventVoter.php | 3 +++ src/CoreBundle/Security/Authorization/Voter/CourseVoter.php | 3 +++ src/CoreBundle/Security/Authorization/Voter/GroupVoter.php | 3 +++ .../Security/Authorization/Voter/MessageTagVoter.php | 3 +++ src/CoreBundle/Security/Authorization/Voter/MessageVoter.php | 3 +++ .../Security/Authorization/Voter/ResourceNodeVoter.php | 3 +++ src/CoreBundle/Security/Authorization/Voter/ResourceVoter.php | 4 ++++ src/CoreBundle/Security/Authorization/Voter/SessionVoter.php | 1 + .../Security/Authorization/Voter/SocialPostVoter.php | 3 +++ .../Security/Authorization/Voter/TrackEExerciseVoter.php | 3 +++ .../Security/Authorization/Voter/UserRelUserVoter.php | 3 +++ src/CoreBundle/Security/Authorization/Voter/UserVoter.php | 3 +++ .../Security/Authorization/Voter/UsergroupVoter.php | 3 +++ src/CoreBundle/State/CCalendarEventStateProcessor.php | 3 --- src/CoreBundle/State/GroupMembersStateProvider.php | 2 +- src/CoreBundle/State/MessageByGroupStateProvider.php | 2 +- src/CoreBundle/State/UserRelUserStateProcessor.php | 3 --- src/CoreBundle/State/UsergroupStateProvider.php | 2 +- 19 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/CoreBundle/Security/Authorization/Voter/AnonymousVoter.php b/src/CoreBundle/Security/Authorization/Voter/AnonymousVoter.php index 1f1bf2ccb76..4b5e265f86c 100644 --- a/src/CoreBundle/Security/Authorization/Voter/AnonymousVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/AnonymousVoter.php @@ -10,6 +10,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +/** + * @extends Voter<'ROLE_ANONYMOUS', User> + */ class AnonymousVoter extends Voter { protected function supports(string $attribute, $subject): bool diff --git a/src/CoreBundle/Security/Authorization/Voter/CCalendarEventVoter.php b/src/CoreBundle/Security/Authorization/Voter/CCalendarEventVoter.php index 2368c9ace38..c3d229b5163 100644 --- a/src/CoreBundle/Security/Authorization/Voter/CCalendarEventVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/CCalendarEventVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', CCalendarEvent> + */ class CCalendarEventVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/CourseVoter.php b/src/CoreBundle/Security/Authorization/Voter/CourseVoter.php index 708c215f41c..2e1a2f5d782 100644 --- a/src/CoreBundle/Security/Authorization/Voter/CourseVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/CourseVoter.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'VIEW'|'EDIT'|'DELETE', Course> + */ class CourseVoter extends Voter { public const VIEW = 'VIEW'; diff --git a/src/CoreBundle/Security/Authorization/Voter/GroupVoter.php b/src/CoreBundle/Security/Authorization/Voter/GroupVoter.php index 7be17fc044b..2df552e8a23 100644 --- a/src/CoreBundle/Security/Authorization/Voter/GroupVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/GroupVoter.php @@ -19,6 +19,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'VIEW'|'EDIT'|'DELETE', CGroup> + */ class GroupVoter extends Voter { public const VIEW = 'VIEW'; diff --git a/src/CoreBundle/Security/Authorization/Voter/MessageTagVoter.php b/src/CoreBundle/Security/Authorization/Voter/MessageTagVoter.php index 431a4f16153..f247156e6d3 100644 --- a/src/CoreBundle/Security/Authorization/Voter/MessageTagVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/MessageTagVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', MessageTag> + */ class MessageTagVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/MessageVoter.php b/src/CoreBundle/Security/Authorization/Voter/MessageVoter.php index fa5dbbe65d0..cc69f1e70a9 100644 --- a/src/CoreBundle/Security/Authorization/Voter/MessageVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/MessageVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', Message> + */ class MessageVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php b/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php index b8ec225c226..a20e15d649a 100644 --- a/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php @@ -23,6 +23,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE'|'EXPORT', ResourceNode> + */ class ResourceNodeVoter extends Voter { public const VIEW = 'VIEW'; diff --git a/src/CoreBundle/Security/Authorization/Voter/ResourceVoter.php b/src/CoreBundle/Security/Authorization/Voter/ResourceVoter.php index df2ae626ba8..e9fad08dd5c 100644 --- a/src/CoreBundle/Security/Authorization/Voter/ResourceVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/ResourceVoter.php @@ -8,11 +8,15 @@ use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Entity\Course; +use Chamilo\CourseBundle\Component\CourseCopy\Resources\Resource; use Chamilo\CourseBundle\Entity\CGroup; use Symfony\Component\Security\Acl\Permission\MaskBuilder; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE'|'EXPORT', Resource> + */ class ResourceVoter extends Voter { public const VIEW = 'VIEW'; diff --git a/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php b/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php index 7620bbffa08..c50f8510625 100644 --- a/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php @@ -19,6 +19,7 @@ /** * @todo remove legacy code. + * @extends Voter<'VIEW'|'EDIT'|'DELETE', Session> */ class SessionVoter extends Voter { diff --git a/src/CoreBundle/Security/Authorization/Voter/SocialPostVoter.php b/src/CoreBundle/Security/Authorization/Voter/SocialPostVoter.php index 6bcd1d9cce5..ae7b538a0f7 100644 --- a/src/CoreBundle/Security/Authorization/Voter/SocialPostVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/SocialPostVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', SocialPost> + */ class SocialPostVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/TrackEExerciseVoter.php b/src/CoreBundle/Security/Authorization/Voter/TrackEExerciseVoter.php index 5bcc4b675d0..3278f80741c 100644 --- a/src/CoreBundle/Security/Authorization/Voter/TrackEExerciseVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/TrackEExerciseVoter.php @@ -11,6 +11,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'VIEW', TrackEExercise> + */ class TrackEExerciseVoter extends Voter { public const VIEW = 'VIEW'; diff --git a/src/CoreBundle/Security/Authorization/Voter/UserRelUserVoter.php b/src/CoreBundle/Security/Authorization/Voter/UserRelUserVoter.php index 35ac8cd92c4..8da5be6cc5a 100644 --- a/src/CoreBundle/Security/Authorization/Voter/UserRelUserVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/UserRelUserVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', UserRelUser> + */ class UserRelUserVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/UserVoter.php b/src/CoreBundle/Security/Authorization/Voter/UserVoter.php index d089c8d93e7..792602e9e44 100644 --- a/src/CoreBundle/Security/Authorization/Voter/UserVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/UserVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', UserVoter> + */ class UserVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/Security/Authorization/Voter/UsergroupVoter.php b/src/CoreBundle/Security/Authorization/Voter/UsergroupVoter.php index 2bd40d4a8ad..0bb2ba01492 100644 --- a/src/CoreBundle/Security/Authorization/Voter/UsergroupVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/UsergroupVoter.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @extends Voter<'CREATE'|'VIEW'|'EDIT'|'DELETE', Usergroup> + */ class UsergroupVoter extends Voter { public const CREATE = 'CREATE'; diff --git a/src/CoreBundle/State/CCalendarEventStateProcessor.php b/src/CoreBundle/State/CCalendarEventStateProcessor.php index 1e387306e14..1a9408c04c4 100644 --- a/src/CoreBundle/State/CCalendarEventStateProcessor.php +++ b/src/CoreBundle/State/CCalendarEventStateProcessor.php @@ -15,9 +15,6 @@ use Exception; use Symfony\Bundle\SecurityBundle\Security; -/** - * @implements ProcessorInterface - */ final class CCalendarEventStateProcessor implements ProcessorInterface { public function __construct( diff --git a/src/CoreBundle/State/GroupMembersStateProvider.php b/src/CoreBundle/State/GroupMembersStateProvider.php index 9e97ef40fdb..ab67dd109ba 100644 --- a/src/CoreBundle/State/GroupMembersStateProvider.php +++ b/src/CoreBundle/State/GroupMembersStateProvider.php @@ -10,7 +10,7 @@ use Doctrine\ORM\EntityManagerInterface; /** - * @template-implements ProviderInterface> + * @template-implements ProviderInterface */ final class GroupMembersStateProvider implements ProviderInterface { diff --git a/src/CoreBundle/State/MessageByGroupStateProvider.php b/src/CoreBundle/State/MessageByGroupStateProvider.php index 55927c8ad90..433d80bd1db 100644 --- a/src/CoreBundle/State/MessageByGroupStateProvider.php +++ b/src/CoreBundle/State/MessageByGroupStateProvider.php @@ -10,7 +10,7 @@ use Chamilo\CoreBundle\Repository\MessageRepository; /** - * @template-implements ProviderInterface> + * @template-implements ProviderInterface */ final class MessageByGroupStateProvider implements ProviderInterface { diff --git a/src/CoreBundle/State/UserRelUserStateProcessor.php b/src/CoreBundle/State/UserRelUserStateProcessor.php index 6446556a327..0c5bd6c65ff 100644 --- a/src/CoreBundle/State/UserRelUserStateProcessor.php +++ b/src/CoreBundle/State/UserRelUserStateProcessor.php @@ -13,9 +13,6 @@ use Chamilo\CoreBundle\Entity\UserRelUser; use Doctrine\ORM\EntityManagerInterface; -/** - * @template-implements - */ final class UserRelUserStateProcessor implements ProcessorInterface { public function __construct( diff --git a/src/CoreBundle/State/UsergroupStateProvider.php b/src/CoreBundle/State/UsergroupStateProvider.php index 4f0ed9641e3..5fd7fb32ebc 100644 --- a/src/CoreBundle/State/UsergroupStateProvider.php +++ b/src/CoreBundle/State/UsergroupStateProvider.php @@ -16,7 +16,7 @@ use Symfony\Bundle\SecurityBundle\Security; /** - * @template-implements ProviderInterface> + * @template-implements ProviderInterface */ final class UsergroupStateProvider implements ProviderInterface { From 560cea8cc1af87f51617403f7bc501f96435a7e5 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Tue, 23 Apr 2024 18:41:16 -0500 Subject: [PATCH 005/413] Internal: Fix 'Undefined array key path' error on gradebook import - refs BT#21541 --- public/main/inc/lib/document.lib.php | 99 +++++++------------------ public/main/inc/lib/extra_field.lib.php | 2 +- 2 files changed, 29 insertions(+), 72 deletions(-) diff --git a/public/main/inc/lib/document.lib.php b/public/main/inc/lib/document.lib.php index 97a4e4c52d8..68163cd708e 100644 --- a/public/main/inc/lib/document.lib.php +++ b/public/main/inc/lib/document.lib.php @@ -869,97 +869,54 @@ public static function get_document_id($courseInfo, $path, $sessionId = 0) * @return array document content */ public static function get_document_data_by_id( - $id, - $course_code, - $load_parents = false, - $session_id = null, - $ignoreDeleted = false - ) { + int $id, + string $course_code, + bool $load_parents = false, + int $session_id = null, + bool $ignoreDeleted = false + ): bool|array { $course_info = api_get_course_info($course_code); - $course_id = $course_info['real_id']; - if (empty($course_info)) { return false; } + $course_id = $course_info['real_id']; $session_id = empty($session_id) ? api_get_session_id() : (int) $session_id; $groupId = api_get_group_id(); $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT); $id = (int) $id; - $sessionCondition = api_get_session_condition($session_id, true, true); - - $sql = "SELECT * FROM $TABLE_DOCUMENT - WHERE iid = $id"; - - if ($ignoreDeleted) { - $sql .= " AND path NOT LIKE '%_DELETED_%' "; - } + $sql = "SELECT * FROM $TABLE_DOCUMENT WHERE iid = $id"; $result = Database::query($sql); - $courseParam = '&cid='.$course_id.'&id='.$id.'&sid='.$session_id.'&gid='.$groupId; if ($result && 1 == Database::num_rows($result)) { $row = Database::fetch_assoc($result); - //@todo need to clarify the name of the URLs not nice right now - $url_path = urlencode($row['path']); - $path = str_replace('%2F', '/', $url_path); - $pathinfo = pathinfo($row['path']); - - $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.$courseParam; - $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id.$courseParam; - //$row['absolute_path'] = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document'.$row['path']; - $row['absolute_path_from_document'] = '/document'.$row['path']; - //$row['absolute_parent_path'] = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document'.$pathinfo['dirname'].'/'; - //$row['direct_url'] = $www.$path; - $row['basename'] = basename($row['path']); - - if ('.' == dirname($row['path'])) { - $row['parent_id'] = '0'; - } else { - $row['parent_id'] = self::get_document_id($course_info, dirname($row['path']), $session_id); - if (empty($row['parent_id'])) { - // Try one more with session id = 0 - $row['parent_id'] = self::get_document_id($course_info, dirname($row['path']), 0); - } - } - $parents = []; + // Adjust paths for URLs based on new system + $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id; + $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id; + // Consider storing a relative path or identifier in the database to construct paths + $row['absolute_path_from_document'] = api_get_path(SYS_COURSE_PATH) . $course_info['directory'] . '/document/' . $id; + $row['basename'] = $id; // You may store and use titles or another unique identifier + + // Handling the parent ID should be adjusted if the path isn't available + $row['parent_id'] = $row['parent_resource_node_id'] ?? '0'; // Adjust according to your schema - //Use to generate parents (needed for the breadcrumb) - //@todo sorry but this for is here because there's not a parent_id in the document table so we parsed the path!! + $parents = []; if ($load_parents) { - $dir_array = explode('/', $row['path']); - $dir_array = array_filter($dir_array); - $array_len = count($dir_array) + 1; - $real_dir = ''; - - for ($i = 1; $i < $array_len; $i++) { - $real_dir .= '/'.(isset($dir_array[$i]) ? $dir_array[$i] : ''); - $parent_id = self::get_document_id($course_info, $real_dir); - if (0 != $session_id && empty($parent_id)) { - $parent_id = self::get_document_id($course_info, $real_dir, 0); - } - if (!empty($parent_id)) { - $sub_document_data = self::get_document_data_by_id( - $parent_id, - $course_code, - false, - $session_id - ); - if (0 != $session_id and !$sub_document_data) { - $sub_document_data = self::get_document_data_by_id( - $parent_id, - $course_code, - false, - 0 - ); - } - //@todo add visibility here - $parents[] = $sub_document_data; + // Modify this logic to work with parent IDs stored directly in the database + $current_id = $row['parent_id']; + while ($current_id != '0') { + $parent_data = self::get_document_data_by_id($current_id, $course_code, false, $session_id); + if ($parent_data) { + $parents[] = $parent_data; + $current_id = $parent_data['parent_id'] ?? '0'; + } else { + break; } } } - $row['parents'] = $parents; + $row['parents'] = array_reverse($parents); return $row; } diff --git a/public/main/inc/lib/extra_field.lib.php b/public/main/inc/lib/extra_field.lib.php index 5ae6bd3e37e..7df50a117bf 100644 --- a/public/main/inc/lib/extra_field.lib.php +++ b/public/main/inc/lib/extra_field.lib.php @@ -3176,7 +3176,7 @@ public function getDataAndFormattedValues($itemId, $filter = false, $onlyShow = } break; case self::FIELD_TYPE_SELECT_MULTIPLE: - $displayedValue = $valueData['value']; + $displayedValue = $valueData['value'] ?? $valueData['field_value']; break; default: $displayedValue = $valueData['field_value']; From b2729ae2e73ab6e172bffdf8bf07f4ac3a48db07 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 23 Apr 2024 19:01:20 -0500 Subject: [PATCH 006/413] Minor: Fix return types - refs BT#21561 --- public/main/gradebook/lib/be/abstractlink.class.php | 7 +------ public/main/gradebook/lib/be/category.class.php | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/public/main/gradebook/lib/be/abstractlink.class.php b/public/main/gradebook/lib/be/abstractlink.class.php index b471a8a18c2..8477935cce0 100644 --- a/public/main/gradebook/lib/be/abstractlink.class.php +++ b/public/main/gradebook/lib/be/abstractlink.class.php @@ -754,12 +754,7 @@ public static function getGradebookLinksFromItem( return []; } - /** - * @param Doctrine\DBAL\Driver\Statement|null $result - * - * @return array - */ - private static function create_objects_from_sql_result($result) + private static function create_objects_from_sql_result(\Doctrine\DBAL\Result $result): array { $links = []; $allow = ('true' === api_get_setting('gradebook.allow_gradebook_stats')); diff --git a/public/main/gradebook/lib/be/category.class.php b/public/main/gradebook/lib/be/category.class.php index a4a568f001c..4646029b288 100644 --- a/public/main/gradebook/lib/be/category.class.php +++ b/public/main/gradebook/lib/be/category.class.php @@ -420,7 +420,7 @@ public static function loadSessionCategories( * @param ?bool $order_by Whether to show all "session" * categories (true) or hide them (false) in case there is no session id * - * @return array + * @return array * @throws \Doctrine\DBAL\Exception * @throws Exception */ From 723991695ed608c6845f289a651358e7f8dfce65 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Tue, 23 Apr 2024 19:04:23 -0500 Subject: [PATCH 007/413] Gradebook: Fix c_id when loading links - refs BT#21561 --- public/main/gradebook/lib/be/abstractlink.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/main/gradebook/lib/be/abstractlink.class.php b/public/main/gradebook/lib/be/abstractlink.class.php index 8477935cce0..73a497b4751 100644 --- a/public/main/gradebook/lib/be/abstractlink.class.php +++ b/public/main/gradebook/lib/be/abstractlink.class.php @@ -769,7 +769,7 @@ private static function create_objects_from_sql_result(\Doctrine\DBAL\Result $re $link->set_type($data['type']); $link->set_ref_id($data['ref_id']); $link->set_user_id($data['user_id']); - $link->setCourseId(api_get_course_int_id()); + $link->setCourseId($data['c_id']); $link->set_category_id($data['category_id']); $link->set_date($data['created_at']); $link->set_weight($data['weight']); From 5b9c36206ad323243fafaf8cf5b0a27a438b80b2 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Tue, 23 Apr 2024 20:58:45 -0500 Subject: [PATCH 008/413] Internal: Fix datepicker localization on registration page - refs BT#21532 --- public/main/inc/lib/formvalidator/Element/DatePicker.php | 2 +- public/main/inc/lib/formvalidator/Element/DateTimePicker.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/main/inc/lib/formvalidator/Element/DatePicker.php b/public/main/inc/lib/formvalidator/Element/DatePicker.php index d121cb951af..28d19c7aebb 100644 --- a/public/main/inc/lib/formvalidator/Element/DatePicker.php +++ b/public/main/inc/lib/formvalidator/Element/DatePicker.php @@ -137,7 +137,7 @@ private function getLocaleCode(): string { $locale = api_get_language_isocode(); $userInfo = api_get_user_info(); - if (is_array($userInfo) && !empty($userInfo['language'])) { + if (is_array($userInfo) && !empty($userInfo['language']) && ANONYMOUS != $userInfo['status']) { $locale = $userInfo['language']; } diff --git a/public/main/inc/lib/formvalidator/Element/DateTimePicker.php b/public/main/inc/lib/formvalidator/Element/DateTimePicker.php index e12d10680ce..066742bf042 100644 --- a/public/main/inc/lib/formvalidator/Element/DateTimePicker.php +++ b/public/main/inc/lib/formvalidator/Element/DateTimePicker.php @@ -119,7 +119,7 @@ private function getLocaleCode(): string { $locale = api_get_language_isocode(); $userInfo = api_get_user_info(); - if (is_array($userInfo) && !empty($userInfo['language'])) { + if (is_array($userInfo) && !empty($userInfo['language']) && ANONYMOUS != $userInfo['status']) { $locale = $userInfo['language']; } From ccfa6cf45b99707c81b596031fcabb916df64cb6 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Tue, 23 Apr 2024 23:52:48 -0500 Subject: [PATCH 009/413] Internal: Fix HTML escaping, overlapping ui elements, and untranslated labels and options --- assets/css/app.scss | 4 ++- .../ccalendarevent/CCalendarEventList.vue | 2 +- public/main/my_space/myStudents.php | 2 +- .../Settings/AbstractSettingsSchema.php | 30 +++++++++++++------ .../Settings/CourseSettingsSchema.php | 14 ++++----- .../Settings/DisplaySettingsSchema.php | 8 ++--- .../Settings/DocumentSettingsSchema.php | 10 +++---- .../Settings/ExerciseSettingsSchema.php | 8 ++--- .../Settings/GlossarySettingsSchema.php | 2 +- .../Settings/PlatformSettingsSchema.php | 8 ++--- .../Settings/RegistrationSettingsSchema.php | 8 ++--- .../Settings/SearchSettingsSchema.php | 4 +-- .../Settings/SurveySettingsSchema.php | 4 +-- 13 files changed, 59 insertions(+), 45 deletions(-) diff --git a/assets/css/app.scss b/assets/css/app.scss index 3bc7254fb5b..c5d99b6eaae 100644 --- a/assets/css/app.scss +++ b/assets/css/app.scss @@ -606,7 +606,9 @@ form .field { } .freeze + label, - .advmultiselect + label { + .advmultiselect + label, + textarea + label.settings-label + { font-size: 13px; @apply absolute top-0 left-0 text-support-3 text-caption px-1 bg-white text-primary; } diff --git a/assets/vue/views/ccalendarevent/CCalendarEventList.vue b/assets/vue/views/ccalendarevent/CCalendarEventList.vue index 1afed113d89..442c9104b58 100644 --- a/assets/vue/views/ccalendarevent/CCalendarEventList.vue +++ b/assets/vue/views/ccalendarevent/CCalendarEventList.vue @@ -79,7 +79,7 @@ v-if="allowToEdit && showEditButton" :label="t('Edit')" type="secondary" - icon="delete" + icon="edit" @click="dialog = true" /> diff --git a/public/main/my_space/myStudents.php b/public/main/my_space/myStudents.php index 71a645370da..1532d24c1a7 100644 --- a/public/main/my_space/myStudents.php +++ b/public/main/my_space/myStudents.php @@ -2182,7 +2182,7 @@ 'javascript: void(0);', [ 'onClick' => "$('#compose_message').show();", - 'class' => 'btn btn--plain', + 'class' => 'btn btn--plain mb-6', ] ); diff --git a/src/CoreBundle/Settings/AbstractSettingsSchema.php b/src/CoreBundle/Settings/AbstractSettingsSchema.php index e4a3f82828c..d512ff62445 100644 --- a/src/CoreBundle/Settings/AbstractSettingsSchema.php +++ b/src/CoreBundle/Settings/AbstractSettingsSchema.php @@ -9,6 +9,7 @@ use Doctrine\ORM\EntityRepository; use Sylius\Bundle\SettingsBundle\Schema\AbstractSettingsBuilder; use Sylius\Bundle\SettingsBundle\Schema\SchemaInterface; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -62,26 +63,37 @@ protected function updateFormFieldsFromSettingsInfo(FormBuilderInterface $builde { $settingsInfo = $this->getSettingsInfoFromDatabase(); foreach ($builder->all() as $fieldName => $field) { + $options = $field->getOptions(); + $labelAttributes = $options['label_attr'] ?? []; + $labelAttributes['class'] = (isset($labelAttributes['class']) ? $labelAttributes['class'] . ' ' : '') . 'settings-label'; + $options['label_attr'] = $labelAttributes; + if (isset($settingsInfo[$fieldName])) { $fieldConfig = $settingsInfo[$fieldName]; - $options = $field->getOptions(); $labelFromDb = $this->translator->trans($fieldConfig['label']); $helpFromDb = $this->translator->trans($fieldConfig['help']); $existingHelp = $options['help'] ?? ''; - if (!empty($existingHelp)) { - $combinedHelp = $helpFromDb.'
'.$existingHelp; - } else { - $combinedHelp = $helpFromDb; - } + $combinedHelp = !empty($existingHelp) ? $helpFromDb . '
' . $existingHelp : $helpFromDb; $options['label'] = $labelFromDb; $options['help'] = $combinedHelp; - - $builder->remove($fieldName); - $builder->add($fieldName, \get_class($field->getType()->getInnerType()), $options); + $options['label_html'] = true; + $options['help_html'] = true; + + if ($field->getType()->getInnerType() instanceof ChoiceType && isset($options['choices'])) { + $translatedChoices = []; + foreach ($options['choices'] as $key => $value) { + $readableKey = ucfirst(strtolower(str_replace('_', ' ', $key))); + $translatedChoices[$this->translator->trans($readableKey)] = $value; + } + $options['choices'] = $translatedChoices; + } } + + $builder->remove($fieldName); + $builder->add($fieldName, \get_class($field->getType()->getInnerType()), $options); } } } diff --git a/src/CoreBundle/Settings/CourseSettingsSchema.php b/src/CoreBundle/Settings/CourseSettingsSchema.php index cd35fd87a90..e0f476bc0f1 100644 --- a/src/CoreBundle/Settings/CourseSettingsSchema.php +++ b/src/CoreBundle/Settings/CourseSettingsSchema.php @@ -170,9 +170,9 @@ public function buildForm(FormBuilderInterface $builder): void [ 'choices' => [ 'No' => 'false', - 'IconsOnly' => 'icons', - 'TextOnly' => 'text', - 'IconsText' => 'iconstext', + 'Icons only' => 'icons', + 'Text only' => 'text', + 'Icons text' => 'iconstext', ], ] ) @@ -182,10 +182,10 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'CourseHomepage' => 'course_home', - 'CourseCode' => 'course_code', - 'CourseTitle' => 'course_title', - 'SessionNameAndCourseTitle' => 'session_name_and_course_title', + 'Course homepage' => 'course_home', + 'Course code' => 'course_code', + 'Course title' => 'course_title', + 'Session name and course title' => 'session_name_and_course_title', ], ] ) diff --git a/src/CoreBundle/Settings/DisplaySettingsSchema.php b/src/CoreBundle/Settings/DisplaySettingsSchema.php index d5435d9e632..1dc67ef5c9b 100644 --- a/src/CoreBundle/Settings/DisplaySettingsSchema.php +++ b/src/CoreBundle/Settings/DisplaySettingsSchema.php @@ -97,10 +97,10 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'DoNotShow' => 'do_not_show', - 'ShowToAdminsOnly' => 'show_to_admin', - 'ShowToAdminsAndTeachers' => 'show_to_admin_and_teachers', - 'ShowToAllUsers' => 'show_to_all', + 'Do not show' => 'do_not_show', + 'Show to admins only' => 'show_to_admin', + 'Show to admins and teachers' => 'show_to_admin_and_teachers', + 'Show to all users' => 'show_to_all', ], ] ) diff --git a/src/CoreBundle/Settings/DocumentSettingsSchema.php b/src/CoreBundle/Settings/DocumentSettingsSchema.php index 646f7d7e651..4ff01148820 100644 --- a/src/CoreBundle/Settings/DocumentSettingsSchema.php +++ b/src/CoreBundle/Settings/DocumentSettingsSchema.php @@ -91,8 +91,8 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'Blacklist' => 'blacklist', - 'Whitelist' => 'whitelist', + 'Black list' => 'blacklist', + 'White list' => 'whitelist', ], ] ) @@ -107,9 +107,9 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'ShowGlossaryInDocumentsIsNone' => 'none', - 'ShowGlossaryInDocumentsIsManual' => 'ismanual', - 'ShowGlossaryInDocumentsIsAutomatic' => 'isautomatic', + 'Show glossary in documents is none' => 'none', + 'Show glossary in documents is manual' => 'ismanual', + 'Show glossary in documents is automatic' => 'isautomatic', ], ] ) diff --git a/src/CoreBundle/Settings/ExerciseSettingsSchema.php b/src/CoreBundle/Settings/ExerciseSettingsSchema.php index f25bb2a24c2..33d2bea02fa 100644 --- a/src/CoreBundle/Settings/ExerciseSettingsSchema.php +++ b/src/CoreBundle/Settings/ExerciseSettingsSchema.php @@ -139,10 +139,10 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'none' => '0', - 'SCORE_AVERAGE' => '1', - 'SCORE_PERCENT' => '2', - 'SCORE_DIV_PERCENT' => '3', + 'None' => '0', + 'Score average' => '1', + 'Score percent' => '2', + 'Score div percent' => '3', ], ], ) diff --git a/src/CoreBundle/Settings/GlossarySettingsSchema.php b/src/CoreBundle/Settings/GlossarySettingsSchema.php index f3a88fed9db..190f6faf38f 100644 --- a/src/CoreBundle/Settings/GlossarySettingsSchema.php +++ b/src/CoreBundle/Settings/GlossarySettingsSchema.php @@ -42,7 +42,7 @@ public function buildForm(FormBuilderInterface $builder): void 'None' => 'none', 'Exercise' => 'exercise', 'LearningPath' => 'lp', - 'ExerciseAndLearningPath' => 'exercise_and_lp', + 'Exercise and LearningPath' => 'exercise_and_lp', ], ] ) diff --git a/src/CoreBundle/Settings/PlatformSettingsSchema.php b/src/CoreBundle/Settings/PlatformSettingsSchema.php index b1d26d8c024..c15d51ff8c2 100644 --- a/src/CoreBundle/Settings/PlatformSettingsSchema.php +++ b/src/CoreBundle/Settings/PlatformSettingsSchema.php @@ -144,10 +144,10 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'CatalogueHide' => '-1', - 'CatalogueShowOnlyCourses' => '0', - 'CatalogueShowOnlySessions' => '1', - 'CatalogueShowCoursesAndSessions' => '2', + 'Catalogue hide' => '-1', + 'Catalogue show only courses' => '0', + 'Catalogue show only sessions' => '1', + 'Catalogue show courses and sessions' => '2', ], ] ) diff --git a/src/CoreBundle/Settings/RegistrationSettingsSchema.php b/src/CoreBundle/Settings/RegistrationSettingsSchema.php index 4d09f14f491..0ea9ed0e729 100644 --- a/src/CoreBundle/Settings/RegistrationSettingsSchema.php +++ b/src/CoreBundle/Settings/RegistrationSettingsSchema.php @@ -96,7 +96,7 @@ public function buildForm(FormBuilderInterface $builder): void 'choices' => [ 'Yes' => 'true', 'No' => 'false', - 'MailConfirmation' => 'confirmation', + 'Mail confirmation' => 'confirmation', 'Approval' => 'approval', ], ] @@ -108,9 +108,9 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'CampusHomepage' => 'index.php', - 'MyCourses' => 'user_portal.php', - 'CourseCatalog' => 'main/auth/courses.php', + 'Campus homepage' => 'index.php', + 'My courses' => 'user_portal.php', + 'Course catalog' => 'main/auth/courses.php', ], ] ) diff --git a/src/CoreBundle/Settings/SearchSettingsSchema.php b/src/CoreBundle/Settings/SearchSettingsSchema.php index e73a8f229fc..caa8a519d3e 100644 --- a/src/CoreBundle/Settings/SearchSettingsSchema.php +++ b/src/CoreBundle/Settings/SearchSettingsSchema.php @@ -38,8 +38,8 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'SearchShowUnlinkedResults' => 'true', - 'SearchHideUnlinkedResults' => 'false', + 'Search show unlinked results' => 'true', + 'Search hide unlinked results' => 'false', ], ] ) diff --git a/src/CoreBundle/Settings/SurveySettingsSchema.php b/src/CoreBundle/Settings/SurveySettingsSchema.php index ba977d60535..5106b326557 100644 --- a/src/CoreBundle/Settings/SurveySettingsSchema.php +++ b/src/CoreBundle/Settings/SurveySettingsSchema.php @@ -48,8 +48,8 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'CourseCoachEmailSender' => 'coach', - 'NoReplyEmailSender' => 'noreply', + 'Course coach email sender' => 'coach', + 'No reply email sender' => 'noreply', ], ] ) From 459b9923318f0b14dc0c19adbe80da99d3746b6e Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 24 Apr 2024 13:39:48 +0200 Subject: [PATCH 010/413] Language: Update language terms --- translations/messages.fr.po | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/translations/messages.fr.po b/translations/messages.fr.po index 4054373a748..ddc10394fa2 100644 --- a/translations/messages.fr.po +++ b/translations/messages.fr.po @@ -26044,6 +26044,7 @@ msgid "Manage the skills of your users, through courses and badges" msgstr "" "Gérez les compétences de vos utilisateurs au travers de cours et de badges" +#, fuzzy msgid "Disable editor announcements" msgstr "Désactiver les annonces de l'éditeur" @@ -29109,9 +29110,3 @@ msgstr "" msgid "Enable extra test answers recording" msgstr "Activer l'enregistrement étendu des réponses du test" - -msgid "Contact Us" -msgstr "Nous contacter" - -msgid "Send" -msgstr "Envoyer" From aa09ff38e1f339af0931c09f261c198b34b274d7 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 24 Apr 2024 13:41:52 +0200 Subject: [PATCH 011/413] Language: Update language terms --- translations/messages.fr.po | 84 ++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/translations/messages.fr.po b/translations/messages.fr.po index ddc10394fa2..2b4672c3144 100644 --- a/translations/messages.fr.po +++ b/translations/messages.fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: chamilo\n" -"PO-Revision-Date: 2024-04-22 22:44+0000\n" +"PO-Revision-Date: 2024-04-24 09:40+0000\n" "Last-Translator: Yannick Warnier \n" "Language-Team: French \n" @@ -26044,7 +26044,6 @@ msgid "Manage the skills of your users, through courses and badges" msgstr "" "Gérez les compétences de vos utilisateurs au travers de cours et de badges" -#, fuzzy msgid "Disable editor announcements" msgstr "Désactiver les annonces de l'éditeur" @@ -29110,3 +29109,84 @@ msgstr "" msgid "Enable extra test answers recording" msgstr "Activer l'enregistrement étendu des réponses du test" + +msgid "Contact Us" +msgstr "Nous contacter" + +msgid "Send" +msgstr "Envoyer" + +msgid "Sender profile picture" +msgstr "Photo de profil de l'expéditeur" + +msgid "This is one simple measure to increase the security of your portal by asking users to immediately change their password, so the one that was transfered by e-mail is no longer valid and they then will use one that they came up with and that they are the only person to know." +msgstr "" +"Il s'agit d'une mesure simple pour renforcer la sécurité de votre portail en " +"demandant aux utilisateurs de changer immédiatement leur mot de passe, afin " +"que celui qui a été transmis par e-mail ne soit plus valide et qu'ils " +"utilisent ensuite un mot de passe qu'ils ont créé et qu'ils sont les seuls à " +"connaître." + +msgid "Enables a mechanism of gradebook dependencies that lets people know which other items they need to go through first in order to complete the gradebook." +msgstr "" +"Active un mécanisme de dépendances du cahier de notes qui informe les " +"utilisateurs des éléments qu'ils doivent achever avant de compléter le " +"cahier de notes actuel." + +msgid "Generate a block inside the side menu where a few badges can be shown as pending approval. Requires gradebooks to be listed here, by (numerical) ID." +msgstr "" +"Générer un bloc dans le menu latéral, où quelques badges en attente " +"d'approbation peuvent être affichés. Nécessite que les cahiers de notes " +"soient répertoriés ici, par ID (numérique)." + +msgid "Skills are normally attributed for completing a whole gradebook. By enabling this option, you allow skills to be attached to sub-sections of gradebooks." +msgstr "" +"Les compétences sont normalement attribuées pour la complétion d'un cahier " +"de notes entier. En activant cette option, vous permettez l'association de " +"compétences à des sous-sections de cahiers de notes." + +msgid "Define the default sub-elements of the 'Courses' entry to display if user is not registered to any course nor session." +msgstr "" +"Définissez les sous-éléments par défaut de l'entrée 'Cours' à afficher si " +"l'utilisateur n'est inscrit à aucun cours ni session." + +msgid "Update SCO status autonomously" +msgstr "Mettre à jour le statut SCO de manière autonome" + +msgid "When using Chamilo for a specific purpose (like one massive online exam), you might want to reduce distraction even more by removing the side menu." +msgstr "" +"Lorsque vous utilisez Chamilo pour un objectif spécifique (comme un examen " +"en ligne massif), vous pourriez vouloir réduire encore plus les distractions " +"en supprimant le menu latéral." + +msgid "On the learning paths lists, display a visual element to show that other learning paths are currently blocked by some prerequisites rule." +msgstr "" +"Sur les listes de parcours d'apprentissage, affichez un élément visuel pour " +"indiquer que d'autres parcours d'apprentissage sont actuellement bloqués par " +"une règle de prérequis." + +msgid "Enable the possibility to export any of your learning paths in a Chamilo course backup format." +msgstr "" +"Activez la possibilité d'exporter n'importe quel parcours d'apprentissage au " +"format de sauvegarde de cours Chamilo." + +msgid "Carbon copy receiver profile picture" +msgstr "Photo de profil du destinataire en copie carbone" + +msgid "Enable courses managed in more than one language. This option adds a language selector within the course page to let users switch easily, and adds a 'multiple_language' extra field to courses which allows for remote management procedures." +msgstr "" +"Activez les cours gérés en plusieurs langues. Cette option ajoute un " +"sélecteur de langue à la page du cours pour permettre aux utilisateurs d'en " +"changer facilement, et ajoute un champ de profil 'multiple_language' aux " +"cours, ce qui permet des procédures de gestion plus complexes via des " +"services web ou des plugins." + +msgid "When importing users (batch processes), automatically generate a random string for username. Otherwise, the username will be generated on the basis of the firstname and lastname, or the prefix of the e-mail." +msgstr "" +"Lors de l'importation des utilisateurs (processus par lots), générer " +"automatiquement une chaîne aléatoire pour le nom d'utilisateur. Sinon, le " +"nom d'utilisateur sera généré sur la base du prénom et du nom, ou du préfixe " +"de l'e-mail." + +msgid "Receiver profile picture" +msgstr "Photo de profil du destinataire" From 68824eb32665b138a471bd1ca9e0f4e4f7d4240b Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 24 Apr 2024 14:14:05 +0200 Subject: [PATCH 012/413] Internal: Fix variable name typo in commit ccfa6cf45b --- src/CoreBundle/Settings/GlossarySettingsSchema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CoreBundle/Settings/GlossarySettingsSchema.php b/src/CoreBundle/Settings/GlossarySettingsSchema.php index 190f6faf38f..5ac2b755331 100644 --- a/src/CoreBundle/Settings/GlossarySettingsSchema.php +++ b/src/CoreBundle/Settings/GlossarySettingsSchema.php @@ -41,8 +41,8 @@ public function buildForm(FormBuilderInterface $builder): void 'choices' => [ 'None' => 'none', 'Exercise' => 'exercise', - 'LearningPath' => 'lp', - 'Exercise and LearningPath' => 'exercise_and_lp', + 'Learning path' => 'lp', + 'Exercise and learning path' => 'exercise_and_lp', ], ] ) From 0ea02de13630256c907121ccdae5543934ea81d7 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 24 Apr 2024 14:47:03 +0200 Subject: [PATCH 013/413] Internal: Improve variable names from commit ccfa6cf45b --- src/CoreBundle/Settings/PlatformSettingsSchema.php | 12 ++++++------ .../Settings/RegistrationSettingsSchema.php | 2 +- src/CoreBundle/Settings/SearchSettingsSchema.php | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/CoreBundle/Settings/PlatformSettingsSchema.php b/src/CoreBundle/Settings/PlatformSettingsSchema.php index c15d51ff8c2..a8cedbb798e 100644 --- a/src/CoreBundle/Settings/PlatformSettingsSchema.php +++ b/src/CoreBundle/Settings/PlatformSettingsSchema.php @@ -144,10 +144,10 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'Catalogue hide' => '-1', - 'Catalogue show only courses' => '0', - 'Catalogue show only sessions' => '1', - 'Catalogue show courses and sessions' => '2', + 'Hide catalogue' => '-1', + 'Show only courses' => '0', + 'Show only sessions' => '1', + 'Show courses and sessions' => '2', ], ] ) @@ -278,8 +278,8 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'My Courses' => 'my_courses', - 'My Sessions' => 'my_sessions', + 'My courses' => 'my_courses', + 'My sessions' => 'my_sessions', ], ] ) diff --git a/src/CoreBundle/Settings/RegistrationSettingsSchema.php b/src/CoreBundle/Settings/RegistrationSettingsSchema.php index 0ea9ed0e729..13ca0bc56ad 100644 --- a/src/CoreBundle/Settings/RegistrationSettingsSchema.php +++ b/src/CoreBundle/Settings/RegistrationSettingsSchema.php @@ -110,7 +110,7 @@ public function buildForm(FormBuilderInterface $builder): void 'choices' => [ 'Campus homepage' => 'index.php', 'My courses' => 'user_portal.php', - 'Course catalog' => 'main/auth/courses.php', + 'Course catalogue' => 'main/auth/courses.php', ], ] ) diff --git a/src/CoreBundle/Settings/SearchSettingsSchema.php b/src/CoreBundle/Settings/SearchSettingsSchema.php index caa8a519d3e..4b9064d57b2 100644 --- a/src/CoreBundle/Settings/SearchSettingsSchema.php +++ b/src/CoreBundle/Settings/SearchSettingsSchema.php @@ -38,8 +38,8 @@ public function buildForm(FormBuilderInterface $builder): void ChoiceType::class, [ 'choices' => [ - 'Search show unlinked results' => 'true', - 'Search hide unlinked results' => 'false', + 'Search shows unlinked results' => 'true', + 'Search hides unlinked results' => 'false', ], ] ) From a49a20d9e5a5b93d0c1e79b49d32f616d9431fb6 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 24 Apr 2024 14:50:11 +0200 Subject: [PATCH 014/413] Language: Add new language terms for settings options --- translations/messages.en.po | 102 ++++++++++++++++++++++++++++++++++++ translations/messages.pot | 102 ++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/translations/messages.en.po b/translations/messages.en.po index 5d8187b9b6c..fd393275d32 100644 --- a/translations/messages.en.po +++ b/translations/messages.en.po @@ -27381,3 +27381,105 @@ msgstr "Receiver profile picture" msgid "Carbon copy receiver profile picture" msgstr "Carbon copy receiver profile picture" + +msgid "Ppt_to_lp" +msgstr "Document converter" + +msgid "Left" +msgstr "Left" + +msgid "Right" +msgstr "Right" + +msgid "Month" +msgstr "Month" + +msgid "Week" +msgstr "Week" + +msgid "Icons text" +msgstr "Icons text" + +msgid "Session name and course title" +msgstr "Session name and course title" + +msgid "Public" +msgstr "Public" + +msgid "Private" +msgstr "Private" + +msgid "No link" +msgstr "No link" + +msgid "Session link" +msgstr "Session link" + +msgid "World" +msgstr "World" + +msgid "Show to all users" +msgstr "Show to all users" + +msgid "Black list" +msgstr "Black list" + +msgid "White list" +msgstr "White list" + +msgid "Show glossary in documents is none" +msgstr "Show glossary in documents is none" + +msgid "Show glossary in documents is manual" +msgstr "Show glossary in documents is manual" + +msgid "Show glossary in documents is automatic" +msgstr "Show glossary in documents is automatic" + +msgid "None" +msgstr "None" + +msgid "Score average" +msgstr "Score average" + +msgid "Score percent" +msgstr "Score percent" + +msgid "Score div percent" +msgstr "Score div percent" + +msgid "Exercise and learning path" +msgstr "Exercise and learning path" + +msgid "Hide catalogue" +msgstr "Hide catalogue" + +msgid "Show only courses" +msgstr "Show only courses" + +msgid "Show only sessions" +msgstr "Show only sessions" + +msgid "Show courses and sessions" +msgstr "Show courses and sessions" + +msgid "Mail confirmation" +msgstr "Mail confirmation" + +msgid "Campus homepage" +msgstr "Campus homepage" + +msgid "Course catalogue" +msgstr "Course catalogue" + +msgid "Search shows unlinked results" +msgstr "Search shows unlinked results" + +msgid "Search hides unlinked results" +msgstr "Search hides unlinked results" + +msgid "Course coach email sender" +msgstr "Course coach email sender" + +msgid "No reply email sender" +msgstr "No reply email sender" diff --git a/translations/messages.pot b/translations/messages.pot index bddf15a7cb0..75191c09ed2 100644 --- a/translations/messages.pot +++ b/translations/messages.pot @@ -27374,3 +27374,105 @@ msgstr "" msgid "Carbon copy receiver profile picture" msgstr "" + +msgid "Ppt_to_lp" +msgstr "" + +msgid "Left" +msgstr "" + +msgid "Right" +msgstr "" + +msgid "Month" +msgstr "" + +msgid "Week" +msgstr "" + +msgid "Icons text" +msgstr "" + +msgid "Session name and course title" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Private" +msgstr "" + +msgid "No link" +msgstr "" + +msgid "Session link" +msgstr "" + +msgid "World" +msgstr "" + +msgid "Show to all users" +msgstr "" + +msgid "Black list" +msgstr "" + +msgid "White list" +msgstr "" + +msgid "Show glossary in documents is none" +msgstr "" + +msgid "Show glossary in documents is manual" +msgstr "" + +msgid "Show glossary in documents is automatic" +msgstr "" + +msgid "None" +msgstr "" + +msgid "Score average" +msgstr "" + +msgid "Score percent" +msgstr "" + +msgid "Score div percent" +msgstr "" + +msgid "Exercise and learning path" +msgstr "" + +msgid "Hide catalogue" +msgstr "" + +msgid "Show only courses" +msgstr "" + +msgid "Show only sessions" +msgstr "" + +msgid "Show courses and sessions" +msgstr "" + +msgid "Mail confirmation" +msgstr "" + +msgid "Campus homepage" +msgstr "" + +msgid "Course catalogue" +msgstr "" + +msgid "Search shows unlinked results" +msgstr "" + +msgid "Search hides unlinked results" +msgstr "" + +msgid "Course coach email sender" +msgstr "" + +msgid "No reply email sender" +msgstr "" From 28170ab893bc20188972374542b192e1a77da9ab Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Wed, 24 Apr 2024 15:06:31 +0200 Subject: [PATCH 015/413] Language: Legal: fix translation string to use the one that exist with all the translations --- public/main/auth/inscription.php | 2 +- public/main/social/personal_data.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/main/auth/inscription.php b/public/main/auth/inscription.php index 808028c34ca..d305317060a 100644 --- a/public/main/auth/inscription.php +++ b/public/main/auth/inscription.php @@ -1100,7 +1100,7 @@ $currentUserInfo['complete_name'] ); $contentEmail = sprintf( - get_lang('User %s signed the agreement.TheY'), + get_lang('User %s signed the agreement the %s.'), $currentUserInfo['complete_name'], api_get_local_time($time) ); diff --git a/public/main/social/personal_data.php b/public/main/social/personal_data.php index c0ca9b9e151..16f2643cc26 100644 --- a/public/main/social/personal_data.php +++ b/public/main/social/personal_data.php @@ -88,7 +88,7 @@ $currentUserInfo['complete_name'] ); $contentEmail = sprintf( - get_lang('User %s signed the agreement.TheDateY'), + get_lang('User %s signed the agreement the %s.'), $currentUserInfo['complete_name'], api_get_local_time() ); From d5c8fd4d38713f09320846ca1395e86898b75d1a Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 10:45:00 -0500 Subject: [PATCH 016/413] Internal: Refactoring event listeners - refs BT#21561 --- config/packages/security.yaml | 4 +- public/main/admin/course_edit.php | 2 +- .../Extension/CDocumentExtension.php | 2 +- src/CoreBundle/Entity/Session.php | 4 +- .../EventListener/AssetListener.php | 13 +----- ...{CourseListener.php => CidReqListener.php} | 22 +--------- .../EventListener/CourseAccessListener.php | 28 ++++--------- .../EventListener/ExceptionListener.php | 14 +------ .../EventListener/HTTPExceptionListener.php | 14 +------ .../EventListener/LegacyListener.php | 27 ++---------- .../EventListener/LoginSuccessHandler.php | 42 ++++--------------- .../EventListener/LogoutListener.php | 13 +----- .../EventListener/OnlineListener.php | 2 +- .../EventListener/SessionAccessListener.php | 26 +++--------- .../EventListener/SettingListener.php | 14 +------ src/CoreBundle/EventListener/TwigListener.php | 34 ++++----------- src/CoreBundle/Filter/SidFilter.php | 2 +- src/CoreBundle/Resources/config/listeners.yml | 28 +++---------- src/CoreBundle/Resources/config/services.yml | 2 +- .../Authorization/Voter/SessionVoter.php | 2 +- src/CoreBundle/ServiceHelper/CidReqHelper.php | 4 +- .../Controller/CourseControllerInterface.php | 2 +- 22 files changed, 63 insertions(+), 238 deletions(-) rename src/CoreBundle/EventListener/{CourseListener.php => CidReqListener.php} (94%) diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 0ab7d0b1baf..fece3179401 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -51,9 +51,9 @@ security: ROLE_STUDENT_BOSS: [ROLE_STUDENT] ROLE_INVITEE: [ROLE_STUDENT] - ROLE_CURRENT_COURSE_STUDENT: [ROLE_CURRENT_COURSE_STUDENT] # Set in the CourseListener + ROLE_CURRENT_COURSE_STUDENT: [ROLE_CURRENT_COURSE_STUDENT] # Set in the CidReqListener ROLE_CURRENT_COURSE_TEACHER: [ROLE_CURRENT_COURSE_TEACHER, ROLE_CURRENT_COURSE_STUDENT] # Set in the course listener - ROLE_CURRENT_COURSE_GROUP_STUDENT: [ROLE_CURRENT_COURSE_GROUP_STUDENT] # Set in the CourseListener + ROLE_CURRENT_COURSE_GROUP_STUDENT: [ROLE_CURRENT_COURSE_GROUP_STUDENT] # Set in the CidReqListener ROLE_CURRENT_COURSE_GROUP_TEACHER: [ROLE_CURRENT_COURSE_GROUP_TEACHER, ROLE_CURRENT_COURSE_GROUP_STUDENT] ROLE_CURRENT_COURSE_SESSION_STUDENT: [ROLE_CURRENT_COURSE_SESSION_STUDENT] ROLE_CURRENT_COURSE_SESSION_TEACHER: [ROLE_CURRENT_COURSE_SESSION_STUDENT, ROLE_CURRENT_COURSE_SESSION_TEACHER] diff --git a/public/main/admin/course_edit.php b/public/main/admin/course_edit.php index 85f0517f33a..bb020af0e47 100644 --- a/public/main/admin/course_edit.php +++ b/public/main/admin/course_edit.php @@ -296,7 +296,7 @@ $course = $form->getSubmitValues(); $visibility = $course['visibility']; - // @todo should be check in the CourseListener + // @todo should be check in the CidReqListener /*global $_configuration; if (isset($_configuration[$urlId]) && diff --git a/src/CoreBundle/DataProvider/Extension/CDocumentExtension.php b/src/CoreBundle/DataProvider/Extension/CDocumentExtension.php index 6b83a08adc1..b6c2856ac55 100644 --- a/src/CoreBundle/DataProvider/Extension/CDocumentExtension.php +++ b/src/CoreBundle/DataProvider/Extension/CDocumentExtension.php @@ -60,7 +60,7 @@ private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): vo $request = $this->requestStack->getCurrentRequest(); // Listing documents must contain the resource node parent (resourceNode.parent) and the course (cid) - // At least the cid so the CourseListener can be called. + // At least the cid so the CidReqListener can be called. $resourceParentId = $request->query->get('resourceNode_parent'); $courseId = $request->query->getInt('cid'); diff --git a/src/CoreBundle/Entity/Session.php b/src/CoreBundle/Entity/Session.php index c75d74c64ab..da716fa993d 100644 --- a/src/CoreBundle/Entity/Session.php +++ b/src/CoreBundle/Entity/Session.php @@ -910,7 +910,7 @@ public function addUserInCourse(int $status, User $user, Course $course): Sessio } /** - * currentCourse is set in CourseListener. + * currentCourse is set in CidReqListener. */ public function getCurrentCourse(): ?Course { @@ -918,7 +918,7 @@ public function getCurrentCourse(): ?Course } /** - * currentCourse is set in CourseListener. + * currentCourse is set in CidReqListener. */ public function setCurrentCourse(Course $course): self { diff --git a/src/CoreBundle/EventListener/AssetListener.php b/src/CoreBundle/EventListener/AssetListener.php index 6ca5200b13c..9f4c5471f36 100644 --- a/src/CoreBundle/EventListener/AssetListener.php +++ b/src/CoreBundle/EventListener/AssetListener.php @@ -8,10 +8,9 @@ use Chamilo\CoreBundle\Entity\Asset; use Chamilo\CoreBundle\Repository\AssetRepository; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Vich\UploaderBundle\Event\Event; -class AssetListener implements EventSubscriberInterface +class AssetListener { protected AssetRepository $assetRepository; @@ -20,7 +19,7 @@ public function __construct(AssetRepository $assetRepository) $this->assetRepository = $assetRepository; } - public function onVichUploaderPostRemove(Event $event): void + public function __invoke(Event $event): void { /** @var Asset $asset */ $asset = $event->getObject(); @@ -36,12 +35,4 @@ public function onVichUploaderPostRemove(Event $event): void }*/ } } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return ['vich_uploader.post_remove' => 'onVichUploaderPostRemove']; - } } diff --git a/src/CoreBundle/EventListener/CourseListener.php b/src/CoreBundle/EventListener/CidReqListener.php similarity index 94% rename from src/CoreBundle/EventListener/CourseListener.php rename to src/CoreBundle/EventListener/CidReqListener.php index 73a9c735b87..39dfa781dd0 100644 --- a/src/CoreBundle/EventListener/CourseListener.php +++ b/src/CoreBundle/EventListener/CidReqListener.php @@ -18,13 +18,10 @@ use Chamilo\CourseBundle\Entity\CGroup; use ChamiloSession; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -33,17 +30,16 @@ use Twig\Environment; /** - * Class CourseListener. * Sets the course and session objects in the controller that implements the CourseControllerInterface. */ -class CourseListener implements EventSubscriberInterface +class CidReqListener { public function __construct( private readonly Environment $twig, private readonly AuthorizationCheckerInterface $authorizationChecker, private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager, - private TokenStorageInterface $tokenStorage, + private readonly TokenStorageInterface $tokenStorage, ) {} /** @@ -197,8 +193,6 @@ public function onKernelRequest(RequestEvent $event): void } } - public function onKernelResponse(ResponseEvent $event): void {} - /** * Once the onKernelRequest was fired, we check if the course/session object were set and we inject them in the controller. */ @@ -314,16 +308,4 @@ private function generateCourseUrl(?Course $course, int $sessionId, int $groupId return ''; } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => ['onKernelRequest', 6], - KernelEvents::RESPONSE => 'onKernelResponse', - KernelEvents::CONTROLLER => 'onKernelController', - ]; - } } diff --git a/src/CoreBundle/EventListener/CourseAccessListener.php b/src/CoreBundle/EventListener/CourseAccessListener.php index 67943e30a2b..6091639e209 100644 --- a/src/CoreBundle/EventListener/CourseAccessListener.php +++ b/src/CoreBundle/EventListener/CourseAccessListener.php @@ -9,31 +9,25 @@ use Chamilo\CoreBundle\Entity\TrackECourseAccess; use Chamilo\CourseBundle\Event\CourseAccess; use Doctrine\ORM\EntityManager; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; /** * In and outs of a course - * This listeners is always called when user enters the course home. + * This listener is always called when user enters the course home. */ -class CourseAccessListener implements EventSubscriberInterface +class CourseAccessListener { - protected EntityManager $em; - protected ?Request $request = null; - public function __construct(EntityManager $em) - { - $this->em = $em; - } - - public function setRequest(RequestStack $requestStack): void - { + public function __construct( + private readonly EntityManager $em, + RequestStack $requestStack + ) { $this->request = $requestStack->getCurrentRequest(); } - public function onCourseAccessEvent(CourseAccess $event): void + public function __invoke(CourseAccess $event): void { // CourseAccess $user = $event->getUser(); @@ -51,12 +45,4 @@ public function onCourseAccessEvent(CourseAccess $event): void $this->em->persist($access); $this->em->flush(); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return ['chamilo_course.course.access' => 'onCourseAccessEvent']; - } } diff --git a/src/CoreBundle/EventListener/ExceptionListener.php b/src/CoreBundle/EventListener/ExceptionListener.php index 165e5594ea4..9de73311dca 100644 --- a/src/CoreBundle/EventListener/ExceptionListener.php +++ b/src/CoreBundle/EventListener/ExceptionListener.php @@ -7,17 +7,15 @@ namespace Chamilo\CoreBundle\EventListener; use Chamilo\CoreBundle\Exception\NotAllowedException; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Twig\Environment; -class ExceptionListener implements EventSubscriberInterface +class ExceptionListener { protected Environment $twig; protected TokenStorageInterface $tokenStorage; @@ -30,7 +28,7 @@ public function __construct(Environment $twig, TokenStorageInterface $tokenStora $this->router = $router; } - public function onKernelException(ExceptionEvent $event): void + public function __invoke(ExceptionEvent $event): void { // You get the exception object from the received event $exception = $event->getThrowable(); @@ -80,12 +78,4 @@ public function onKernelException(ExceptionEvent $event): void // sends the modified response object to the event $event->setResponse($response); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [KernelEvents::EXCEPTION => 'onKernelException']; - } } diff --git a/src/CoreBundle/EventListener/HTTPExceptionListener.php b/src/CoreBundle/EventListener/HTTPExceptionListener.php index 41f68530503..8a028555a9a 100644 --- a/src/CoreBundle/EventListener/HTTPExceptionListener.php +++ b/src/CoreBundle/EventListener/HTTPExceptionListener.php @@ -6,15 +6,13 @@ namespace Chamilo\CoreBundle\EventListener; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpException; -use Symfony\Component\HttpKernel\KernelEvents; -final class HTTPExceptionListener implements EventSubscriberInterface +final class HTTPExceptionListener { - public function onKernelException(ExceptionEvent $event): void + public function __invoke(ExceptionEvent $event): void { $exception = $event->getThrowable(); if (!($exception instanceof HttpException) @@ -30,12 +28,4 @@ public function onKernelException(ExceptionEvent $event): void $event->setResponse($response); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [KernelEvents::EXCEPTION => 'onKernelException']; - } } diff --git a/src/CoreBundle/EventListener/LegacyListener.php b/src/CoreBundle/EventListener/LegacyListener.php index ccffe2d67f7..01d699cc6bc 100644 --- a/src/CoreBundle/EventListener/LegacyListener.php +++ b/src/CoreBundle/EventListener/LegacyListener.php @@ -11,14 +11,9 @@ use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository; use Chamilo\CoreBundle\Settings\SettingsManager; use Exception; -use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\Event\ResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -29,7 +24,7 @@ * Works as old global.inc.php * Setting old php requirements so pages inside main/* could work correctly. */ -class LegacyListener implements EventSubscriberInterface +class LegacyListener { public function __construct( private readonly Environment $twig, @@ -41,7 +36,7 @@ public function __construct( private readonly ContainerInterface $container, ) {} - public function onKernelRequest(RequestEvent $event): void + public function __invoke(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -142,7 +137,7 @@ public function onKernelRequest(RequestEvent $event): void $twig->addGlobal('header_extra_content', $extraHeader); // We set cid_reset = true if we enter inside a main/admin url - // CourseListener check this variable and deletes the course session + // CidReqListener check this variable and deletes the course session if (str_contains((string) $request->get('name'), 'admin/')) { $session->set('cid_reset', true); } else { @@ -161,20 +156,4 @@ public function onKernelRequest(RequestEvent $event): void $session->set('access_url_id', $urlId); } - - public function onKernelResponse(ResponseEvent $event): void {} - - public function onKernelController(ControllerEvent $event): void {} - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - KernelEvents::REQUEST => 'onKernelRequest', - KernelEvents::RESPONSE => 'onKernelResponse', - KernelEvents::CONTROLLER => 'onKernelController', - ]; - } } diff --git a/src/CoreBundle/EventListener/LoginSuccessHandler.php b/src/CoreBundle/EventListener/LoginSuccessHandler.php index ec228f0c5ed..05f68bfb6ec 100644 --- a/src/CoreBundle/EventListener/LoginSuccessHandler.php +++ b/src/CoreBundle/EventListener/LoginSuccessHandler.php @@ -17,7 +17,6 @@ use Chamilo\CoreBundle\Settings\SettingsManager; use DateTime; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -25,32 +24,17 @@ use UserManager; // class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface -class LoginSuccessHandler implements EventSubscriberInterface +class LoginSuccessHandler { - protected UrlGeneratorInterface $router; - protected AuthorizationCheckerInterface $checker; - protected SettingsManager $settingsManager; - protected EntityManagerInterface $entityManager; - private LoginAttemptLogger $loginAttemptLogger; - public function __construct( - UrlGeneratorInterface $urlGenerator, - AuthorizationCheckerInterface $checker, - SettingsManager $settingsManager, - EntityManagerInterface $entityManager, - LoginAttemptLogger $loginAttemptLogger - ) { - $this->router = $urlGenerator; - $this->checker = $checker; - $this->settingsManager = $settingsManager; - $this->entityManager = $entityManager; - $this->loginAttemptLogger = $loginAttemptLogger; - } - - /** - * @return null|RedirectResponse - */ - public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) + private readonly UrlGeneratorInterface $router, + private readonly AuthorizationCheckerInterface $checker, + private readonly SettingsManager $settingsManager, + private readonly EntityManagerInterface $entityManager, + private readonly LoginAttemptLogger $loginAttemptLogger + ) {} + + public function __invoke(InteractiveLoginEvent $event): ?RedirectResponse { $request = $event->getRequest(); $session = $request->getSession(); @@ -179,12 +163,4 @@ public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) return $response; } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return ['security.interactive_login' => 'onSecurityInteractiveLogin']; - } } diff --git a/src/CoreBundle/EventListener/LogoutListener.php b/src/CoreBundle/EventListener/LogoutListener.php index 5c26c6b2a29..29487d2e902 100644 --- a/src/CoreBundle/EventListener/LogoutListener.php +++ b/src/CoreBundle/EventListener/LogoutListener.php @@ -13,14 +13,13 @@ use DateTimeZone; use Doctrine\ORM\EntityManagerInterface; use Exception; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Http\Event\LogoutEvent; -class LogoutListener implements EventSubscriberInterface +class LogoutListener { protected UrlGeneratorInterface $router; protected AuthorizationCheckerInterface $checker; @@ -42,7 +41,7 @@ public function __construct( /** * @throws Exception */ - public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $event): ?RedirectResponse + public function __invoke(LogoutEvent $event): ?RedirectResponse { $request = $event->getRequest(); @@ -78,12 +77,4 @@ public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $even return new RedirectResponse($login); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [LogoutEvent::class => ['onSymfonyComponentSecurityHttpEventLogoutEvent', 20]]; - } } diff --git a/src/CoreBundle/EventListener/OnlineListener.php b/src/CoreBundle/EventListener/OnlineListener.php index 8cb630c65d6..5c40ba70329 100644 --- a/src/CoreBundle/EventListener/OnlineListener.php +++ b/src/CoreBundle/EventListener/OnlineListener.php @@ -30,7 +30,7 @@ public function __construct(TokenStorageInterface $context, EntityManagerInterfa /** * Update the user "lastActivity" on each request. */ - public function onCoreController(ControllerEvent $event): void + public function __invoke(ControllerEvent $event): void { /* Here we are checking that the current request is a "MASTER_REQUEST", and ignore any subrequest in the process (for example when doing a diff --git a/src/CoreBundle/EventListener/SessionAccessListener.php b/src/CoreBundle/EventListener/SessionAccessListener.php index bdaee83b9db..f2f34b0fac8 100644 --- a/src/CoreBundle/EventListener/SessionAccessListener.php +++ b/src/CoreBundle/EventListener/SessionAccessListener.php @@ -9,27 +9,21 @@ use Chamilo\CoreBundle\Entity\TrackECourseAccess; use Chamilo\CourseBundle\Event\SessionAccess; use Doctrine\ORM\EntityManager; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -class SessionAccessListener implements EventSubscriberInterface +class SessionAccessListener { - protected EntityManager $em; - protected ?Request $request = null; - public function __construct(EntityManager $em) - { - $this->em = $em; - } - - public function setRequest(RequestStack $requestStack): void - { + public function __construct( + private readonly EntityManager $em, + RequestStack $requestStack + ) { $this->request = $requestStack->getCurrentRequest(); } - public function onSessionAccessEvent(SessionAccess $event): void + public function __invoke(SessionAccess $event): void { $user = $event->getUser(); $course = $event->getCourse(); @@ -47,12 +41,4 @@ public function onSessionAccessEvent(SessionAccess $event): void $this->em->persist($access); $this->em->flush(); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return ['chamilo_course.course.session' => 'onSessionAccessEvent']; - } } diff --git a/src/CoreBundle/EventListener/SettingListener.php b/src/CoreBundle/EventListener/SettingListener.php index 62e2c1569ca..97db0feed28 100644 --- a/src/CoreBundle/EventListener/SettingListener.php +++ b/src/CoreBundle/EventListener/SettingListener.php @@ -7,14 +7,12 @@ namespace Chamilo\CoreBundle\EventListener; use Sylius\Bundle\SettingsBundle\Event\SettingsEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; -class SettingListener implements EventSubscriberInterface +class SettingListener { public function __construct() {} - public function onSettingPreSave(SettingsEvent $event): void + public function __invoke(SettingsEvent $event): void { /*$urlId = $this->container->get('request')->getSession()->get('access_url_id'); $url = $this->container->get('doctrine')->getRepository('ChamiloCoreBundle:AccessUrl')->find($urlId); @@ -25,12 +23,4 @@ public function onSettingPreSave(SettingsEvent $event): void // $settings->setAccessUrl($url); // $event->setArgument('url', $url); } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return ['sylius.settings.pre_save' => 'onSettingPreSave']; - } } diff --git a/src/CoreBundle/EventListener/TwigListener.php b/src/CoreBundle/EventListener/TwigListener.php index 52c47b6a26a..55ff5cf8c69 100644 --- a/src/CoreBundle/EventListener/TwigListener.php +++ b/src/CoreBundle/EventListener/TwigListener.php @@ -8,8 +8,6 @@ use Chamilo\CoreBundle\Repository\ColorThemeRepository; use Chamilo\CoreBundle\Repository\LanguageRepository; -use Chamilo\CoreBundle\Settings\SettingsManager; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -20,31 +18,18 @@ /** * Twig-related event listener. For filters, look into ChamiloExtension.php. */ -class TwigListener implements EventSubscriberInterface +class TwigListener { - private SerializerInterface $serializer; - private Environment $twig; - private TokenStorageInterface $tokenStorage; - private SettingsManager $settingsManager; - private LanguageRepository $languageRepository; - public function __construct( - Environment $twig, - SerializerInterface $serializer, - TokenStorageInterface $tokenStorage, - SettingsManager $settingsManager, - LanguageRepository $languageRepository, + private readonly Environment $twig, + private readonly SerializerInterface $serializer, + private readonly TokenStorageInterface $tokenStorage, + private readonly LanguageRepository $languageRepository, private readonly ColorThemeRepository $colorThemeRepository, private readonly RouterInterface $router, - ) { - $this->twig = $twig; - $this->tokenStorage = $tokenStorage; - $this->serializer = $serializer; - $this->settingsManager = $settingsManager; - $this->languageRepository = $languageRepository; - } + ) {} - public function onControllerEvent(ControllerEvent $event): void + public function __invoke(ControllerEvent $event): void { $request = $event->getRequest(); $token = $this->tokenStorage->getToken(); @@ -87,9 +72,4 @@ private function loadColorTheme(): void $this->twig->addGlobal('color_theme_link', $link); } - - public static function getSubscribedEvents(): array - { - return [ControllerEvent::class => 'onControllerEvent']; - } } diff --git a/src/CoreBundle/Filter/SidFilter.php b/src/CoreBundle/Filter/SidFilter.php index 49c3742a630..ab16f5b9503 100644 --- a/src/CoreBundle/Filter/SidFilter.php +++ b/src/CoreBundle/Filter/SidFilter.php @@ -73,7 +73,7 @@ protected function filterProperty( true ); - // Session was set with a kernel request from CoreBundle\EventListener\CourseListener class + // Session was set with a kernel request from CoreBundle\EventListener\CidReqListener class $session = $this->getSession(); if (null === $session) { diff --git a/src/CoreBundle/Resources/config/listeners.yml b/src/CoreBundle/Resources/config/listeners.yml index d7098668598..c3b234ae970 100644 --- a/src/CoreBundle/Resources/config/listeners.yml +++ b/src/CoreBundle/Resources/config/listeners.yml @@ -7,43 +7,32 @@ services: # Event listeners Chamilo\CoreBundle\EventListener\AssetListener: - arguments: - - '@Chamilo\CoreBundle\Repository\AssetRepository' tags: - {name: kernel.event_listener, event: vich_uploader.post_remove} - Chamilo\CoreBundle\EventListener\CourseListener: + Chamilo\CoreBundle\EventListener\CidReqListener: tags: - {name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 6} - - {name: kernel.event_listener, event: kernel.response, method: onKernelResponse} - {name: kernel.event_listener, event: kernel.controller, method: onKernelController} # Sets the user access in a course listener Chamilo\CoreBundle\EventListener\CourseAccessListener: arguments: - '@doctrine.orm.entity_manager' - calls: - - [setRequest, ['@request_stack']] tags: - - {name: kernel.event_listener, event: chamilo_course.course.access, method: onCourseAccessEvent} + - {name: kernel.event_listener, event: chamilo_course.course.access} # Sets the user access in a course session listener Chamilo\CoreBundle\EventListener\SessionAccessListener: - arguments: - - '@doctrine.orm.entity_manager' - calls: - - [setRequest, ['@request_stack']] tags: - - {name: kernel.event_listener, event: chamilo_course.course.session, method: onSessionAccessEvent} + - {name: kernel.event_listener, event: chamilo_course.course.session} # Setting user Chamilo\CoreBundle\EventListener\LegacyListener: bind: $container: '@service_container' tags: - - {name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 7} - - {name: kernel.event_listener, event: kernel.response, method: onKernelResponse} - - {name: kernel.event_listener, event: kernel.controller, method: onKernelController} + - {name: kernel.event_listener, event: kernel.request, priority: 7} # User locale listener # Chamilo\CoreBundle\EventListener\UserLocaleListener: @@ -52,9 +41,8 @@ services: # Settings listener Chamilo\CoreBundle\EventListener\SettingListener: - arguments: ['@service_container'] tags: - - {name: kernel.event_listener, event: sylius.settings.pre_save, method: onSettingPreSave} + - {name: kernel.event_listener, event: sylius.settings.pre_save} Chamilo\CoreBundle\EventListener\TwigListener: tags: @@ -62,16 +50,13 @@ services: # Auth listeners Chamilo\CoreBundle\EventListener\LoginSuccessHandler: - arguments: ['@router', '@security.authorization_checker', '@Chamilo\CoreBundle\Settings\SettingsManager', '@doctrine.orm.entity_manager', '@Chamilo\CoreBundle\ServiceHelper\LoginAttemptLogger'] tags: - - {name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin} + - {name: kernel.event_listener, event: security.interactive_login} Chamilo\CoreBundle\EventListener\LogoutListener: - arguments: ['@router', '@security.authorization_checker', '@security.token_storage', '@doctrine.orm.entity_manager'] tags: - name: kernel.event_listener event: Symfony\Component\Security\Http\Event\LogoutEvent - dispatcher: security.event_dispatcher.main priority: 20 Chamilo\CoreBundle\EventListener\HTTPExceptionListener: @@ -79,7 +64,6 @@ services: - {name: kernel.event_listener, event: kernel.exception} Chamilo\CoreBundle\EventListener\ExceptionListener: - arguments: ['@twig'] tags: - {name: kernel.event_listener, event: kernel.exception} diff --git a/src/CoreBundle/Resources/config/services.yml b/src/CoreBundle/Resources/config/services.yml index d2927d732dd..b35bcc83bde 100644 --- a/src/CoreBundle/Resources/config/services.yml +++ b/src/CoreBundle/Resources/config/services.yml @@ -65,7 +65,7 @@ services: # class: Chamilo\CoreBundle\EventListener\OnlineListener # arguments: [@security.context, @doctrine.orm.entity_manager ] # tags: -# - {name: kernel.event_listener, event: kernel.controller, method: onCoreController} +# - {name: kernel.event_listener, event: kernel.controller} Chamilo\CoreBundle\Repository\AssetRepository: ~ diff --git a/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php b/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php index c50f8510625..50c124b957d 100644 --- a/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/SessionVoter.php @@ -64,7 +64,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ } // Checks if the current course was set up - // $session->getCurrentCourse() is set in the class CourseListener. + // $session->getCurrentCourse() is set in the class CidReqListener. /** @var Session $session */ $session = $subject; $currentCourse = $session->getCurrentCourse(); diff --git a/src/CoreBundle/ServiceHelper/CidReqHelper.php b/src/CoreBundle/ServiceHelper/CidReqHelper.php index 713fcb461ba..b76177af8f0 100644 --- a/src/CoreBundle/ServiceHelper/CidReqHelper.php +++ b/src/CoreBundle/ServiceHelper/CidReqHelper.php @@ -8,13 +8,13 @@ use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\Session; -use Chamilo\CoreBundle\EventListener\CourseListener; +use Chamilo\CoreBundle\EventListener\CidReqListener; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** - * @see CourseListener::onKernelRequest() + * @see CidReqListener::onKernelRequest() */ class CidReqHelper { diff --git a/src/CourseBundle/Controller/CourseControllerInterface.php b/src/CourseBundle/Controller/CourseControllerInterface.php index 18843fc4bf3..758bd11504d 100644 --- a/src/CourseBundle/Controller/CourseControllerInterface.php +++ b/src/CourseBundle/Controller/CourseControllerInterface.php @@ -12,7 +12,7 @@ /** * CourseControllerInterface. * This interface provides getters and setters to a controller. - * These functions are loaded when the CourseListener.php fires when a c_id/cidReq/ or courses/XXX/ parameter and + * These functions are loaded when the CidReqListener.php fires when a c_id/cidReq/ or courses/XXX/ parameter and * the controller implements this interface. See the ResourceController class as an example. * is loaded in the URL. */ From d210f0088ed421515c0754939a2a44d9ca76e229 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 11:44:15 -0500 Subject: [PATCH 017/413] Internal: Remove _real_cid and _course stored in legacy ChamiloSession - refs BT#21561 --- src/CoreBundle/EventListener/CidReqListener.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CoreBundle/EventListener/CidReqListener.php b/src/CoreBundle/EventListener/CidReqListener.php index 39dfa781dd0..04f4ebe9006 100644 --- a/src/CoreBundle/EventListener/CidReqListener.php +++ b/src/CoreBundle/EventListener/CidReqListener.php @@ -276,6 +276,8 @@ public function cleanSessionHandler(Request $request): void $sessionHandler->remove('session'); $sessionHandler->remove('course_url_params'); $sessionHandler->remove('origin'); + ChamiloSession::erase('_real_cid'); + ChamiloSession::erase('_course'); // Remove user temp roles $token = $this->tokenStorage->getToken(); From 3f3b7f21ef506dbf651a6d9f02118835988b4806 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 12:53:17 -0500 Subject: [PATCH 018/413] Internal: Fix empty intro content in session-specific course editing - refs BT#21569 --- .../Controller/CourseController.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/CoreBundle/Controller/CourseController.php b/src/CoreBundle/Controller/CourseController.php index a83cc7ecb43..d8d8cc2e3f2 100644 --- a/src/CoreBundle/Controller/CourseController.php +++ b/src/CoreBundle/Controller/CourseController.php @@ -579,9 +579,12 @@ public function getToolIntro(Request $request, Course $course, EntityManagerInte /** @var CToolIntro $ctoolintro */ $ctoolintro = $ctoolintroRepo->findOneBy(['courseTool' => $ctool]); if ($ctoolintro) { + $introText = $ctoolintro->getIntroText(); + $cleanedHtml = $this->processHtmlContent($introText); + $responseData = [ 'iid' => $ctoolintro->getIid(), - 'introText' => $ctoolintro->getIntroText(), + 'introText' => $cleanedHtml, 'createInSession' => $createInSession, 'cToolId' => $ctool->getIid(), ]; @@ -972,4 +975,29 @@ private function isUserEnrolledInAnySession(User $user, EntityManagerInterface $ return $enrollmentCount > 0; } + + private function processHtmlContent($htmlText): array|string|null + { + $doc = new \DOMDocument(); + libxml_use_internal_errors(true); + $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + libxml_clear_errors(); + + // Extract everything inside the element, if present, or fallback to the document itself + $htmlContent = ''; + $html = $doc->getElementsByTagName('html')->item(0); + if ($html) { + foreach ($html->childNodes as $child) { + $htmlContent .= $doc->saveHTML($child); + } + } else { + $htmlContent = $doc->saveHTML($doc->documentElement); + } + + // Remove , , and tags manually + $htmlContent = preg_replace('/<\/?(html|head|body)[^>]*>/i', '', $htmlContent); + + return $htmlContent; + } + } From a040aedd7f18e6e099d51699d683214187e7f819 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 13:06:07 -0500 Subject: [PATCH 019/413] Don't destructure the securityStore in UserProfileCard component --- assets/vue/components/social/UserProfileCard.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/vue/components/social/UserProfileCard.vue b/assets/vue/components/social/UserProfileCard.vue index 8ec53e4fc4b..6b90232c48b 100644 --- a/assets/vue/components/social/UserProfileCard.vue +++ b/assets/vue/components/social/UserProfileCard.vue @@ -97,7 +97,7 @@ isCurrentUser.value || isAdmin.value) +const showFullProfile = computed(() => isCurrentUser.value || securityStore.isAdmin) const languageInfo = ref(null) const vCardUserLink = ref("") const visibility = ref({}) From a69a6f0c45cf5e70aafe32d6a249f92582342130 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 13:22:57 -0500 Subject: [PATCH 020/413] Simplify functions to do requests in social posts --- .../vue/components/social/SocialWallPost.vue | 24 +++++++++---------- .../components/social/SocialWallPostList.vue | 14 ++++------- .../vue/components/social/UserProfileCard.vue | 4 ++-- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/assets/vue/components/social/SocialWallPost.vue b/assets/vue/components/social/SocialWallPost.vue index 15bc79ac1a7..f10cfb11c4f 100644 --- a/assets/vue/components/social/SocialWallPost.vue +++ b/assets/vue/components/social/SocialWallPost.vue @@ -121,9 +121,9 @@ const currentUser = inject("social-user") const isCurrentUser = inject("is-current-user") const isOwner = computed(() => currentUser["@id"] === props.post.sender["@id"]) -onMounted(async () => { +onMounted(() => { loadComments() - await loadAttachments() + loadAttachments() }) const computedAttachments = computed(() => { return attachments.value @@ -140,16 +140,16 @@ async function loadAttachments() { } } -function loadComments() { - axios - .get(ENTRYPOINT + "social_posts", { - params: { - parent: props.post["@id"], - "order[sendDate]": "desc", - itemsPerPage: 3, - }, - }) - .then((response) => comments.push(...response.data["hydra:member"])) +async function loadComments() { + const { data } = await axios.get(ENTRYPOINT + "social_posts", { + params: { + parent: props.post["@id"], + "order[sendDate]": "desc", + itemsPerPage: 3, + }, + }) + + comments.push(...data["hydra:member"]) } function onCommentDeleted(event) { diff --git a/assets/vue/components/social/SocialWallPostList.vue b/assets/vue/components/social/SocialWallPostList.vue index 947016d4acd..b288b9f2dfc 100644 --- a/assets/vue/components/social/SocialWallPostList.vue +++ b/assets/vue/components/social/SocialWallPostList.vue @@ -44,7 +44,7 @@ defineExpose({ refreshPosts, }) -function listPosts() { +async function listPosts() { postList.splice(0, postList.length) if (!user.value["@id"]) { return @@ -61,14 +61,10 @@ function listPosts() { params.type = SOCIAL_TYPE_PROMOTED_MESSAGE } - axios - .get(ENTRYPOINT + "social_posts", { params }) - .then((response) => { - postList.push(...response.data["hydra:member"]) - }) - .finally(() => { - isLoading.value = false - }) + const { data } = await axios.get(ENTRYPOINT + "social_posts", { params }) + postList.push(...data["hydra:member"]) + + isLoading.value = false } function onPostDeleted(event) { diff --git a/assets/vue/components/social/UserProfileCard.vue b/assets/vue/components/social/UserProfileCard.vue index 6b90232c48b..970101ae38c 100644 --- a/assets/vue/components/social/UserProfileCard.vue +++ b/assets/vue/components/social/UserProfileCard.vue @@ -142,8 +142,8 @@ const editProfile = () => { async function fetchUserProfile(userId) { try { - const response = await axios.get(`/social-network/user-profile/${userId}`) - const data = response.data + const { data } = await axios.get(`/social-network/user-profile/${userId}`) + languageInfo.value = data.language vCardUserLink.value = data.vCardUserLink visibility.value = data.visibility From d776bc8d1034c61950a95bd7d14ce97906948fb9 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 13:49:25 -0500 Subject: [PATCH 021/413] Social: Use socialService.js instead of socialpost.js --- .../vue/components/social/SocialWallPostForm.vue | 9 +++++---- assets/vue/services/socialService.js | 16 ++++++++++++++++ assets/vue/services/socialpost.js | 14 ++------------ 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/assets/vue/components/social/SocialWallPostForm.vue b/assets/vue/components/social/SocialWallPostForm.vue index 7e369b2bf3e..a973048cc09 100644 --- a/assets/vue/components/social/SocialWallPostForm.vue +++ b/assets/vue/components/social/SocialWallPostForm.vue @@ -59,7 +59,7 @@ import BaseCheckbox from "../basecomponents/BaseCheckbox.vue" import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue" import { useRoute } from "vue-router" import { useSecurityStore } from "../../store/securityStore" -import socialPostService from "../../services/socialpost" +import socialService from "../../services/socialService" import { useNotification } from "../../composables/notification" const emit = defineEmits(["post-created"]) @@ -138,13 +138,13 @@ async function sendPost() { } try { - const response = await socialPostService.create({ + const post = await socialService.createPost({ content: postState.content, type: postState.isPromoted ? SOCIAL_TYPE_PROMOTED_MESSAGE : SOCIAL_TYPE_WALL_POST, sender: securityStore.user["@id"], userReceiver: securityStore.user["@id"] === user.value["@id"] ? null : user.value["@id"], }) - const post = await response.json() + if (selectedFile.value) { const formData = new FormData() let idUrl = post["@id"] @@ -152,7 +152,8 @@ async function sendPost() { let socialPostId = parts[parts.length - 1] formData.append("file", selectedFile.value) formData.append("messageId", socialPostId) - await socialPostService.addPostAttachment(formData) + + await socialService.addAttachment(formData) } postState.content = "" diff --git a/assets/vue/services/socialService.js b/assets/vue/services/socialService.js index 2cd14193507..c7b6ce4da87 100644 --- a/assets/vue/services/socialService.js +++ b/assets/vue/services/socialService.js @@ -3,6 +3,10 @@ import baseService from "./baseService" const API_URL = "/social-network" +async function createPost(params) { + return baseService.post("/api/social_posts", params) +} + /** * @param {string} postIri * @returns {Promise} @@ -19,6 +23,14 @@ async function sendPostDislike(postIri) { return baseService.post(`${postIri}/dislike`) } +/** + * @param {FormData} formData + * @returns {Promise} + */ +async function addAttachment(formData) { + return await baseService.postForm("/api/social_post_attachments", formData) +} + export default { async fetchPersonalData(userId) { try { @@ -168,9 +180,13 @@ export default { } }, + createPost, + sendPostLike, sendPostDislike, + addAttachment, + delete: baseService.delete, } diff --git a/assets/vue/services/socialpost.js b/assets/vue/services/socialpost.js index 5553b8704d0..7a48f82b4bb 100644 --- a/assets/vue/services/socialpost.js +++ b/assets/vue/services/socialpost.js @@ -1,13 +1,3 @@ -import makeService from './api'; -import axios from "axios" +import makeService from "./api" -export default makeService('social_posts', { - async addPostAttachment(formData) { - const endpoint = "/api/social_post_attachments" - return await axios.post(endpoint, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }) - } -}); +export default makeService("social_posts") From 6d82a9e7df907ce58addf5100bde00566a193c43 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 14:44:52 -0500 Subject: [PATCH 022/413] Internal: Disable full-page mode in BaseTinyEditor for intro forms - refs BT#21569 --- assets/vue/components/ctoolintro/Form.vue | 1 + .../Controller/CourseController.php | 30 +------------------ 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/assets/vue/components/ctoolintro/Form.vue b/assets/vue/components/ctoolintro/Form.vue index 9337c7423ca..ed43da97ee6 100644 --- a/assets/vue/components/ctoolintro/Form.vue +++ b/assets/vue/components/ctoolintro/Form.vue @@ -3,6 +3,7 @@ diff --git a/src/CoreBundle/Controller/CourseController.php b/src/CoreBundle/Controller/CourseController.php index d8d8cc2e3f2..a83cc7ecb43 100644 --- a/src/CoreBundle/Controller/CourseController.php +++ b/src/CoreBundle/Controller/CourseController.php @@ -579,12 +579,9 @@ public function getToolIntro(Request $request, Course $course, EntityManagerInte /** @var CToolIntro $ctoolintro */ $ctoolintro = $ctoolintroRepo->findOneBy(['courseTool' => $ctool]); if ($ctoolintro) { - $introText = $ctoolintro->getIntroText(); - $cleanedHtml = $this->processHtmlContent($introText); - $responseData = [ 'iid' => $ctoolintro->getIid(), - 'introText' => $cleanedHtml, + 'introText' => $ctoolintro->getIntroText(), 'createInSession' => $createInSession, 'cToolId' => $ctool->getIid(), ]; @@ -975,29 +972,4 @@ private function isUserEnrolledInAnySession(User $user, EntityManagerInterface $ return $enrollmentCount > 0; } - - private function processHtmlContent($htmlText): array|string|null - { - $doc = new \DOMDocument(); - libxml_use_internal_errors(true); - $doc->loadHTML(mb_convert_encoding($htmlText, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); - libxml_clear_errors(); - - // Extract everything inside the element, if present, or fallback to the document itself - $htmlContent = ''; - $html = $doc->getElementsByTagName('html')->item(0); - if ($html) { - foreach ($html->childNodes as $child) { - $htmlContent .= $doc->saveHTML($child); - } - } else { - $htmlContent = $doc->saveHTML($doc->documentElement); - } - - // Remove , , and tags manually - $htmlContent = preg_replace('/<\/?(html|head|body)[^>]*>/i', '', $htmlContent); - - return $htmlContent; - } - } From f1c16adf454dd73cd2bec08e7d431f5b2978a224 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 16:24:41 -0500 Subject: [PATCH 023/413] Forum: Include all coaches in session notifications - refs BT#21550 --- public/main/forum/forumfunction.inc.php | 7 +++- public/main/inc/lib/sessionmanager.lib.php | 39 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/public/main/forum/forumfunction.inc.php b/public/main/forum/forumfunction.inc.php index 9e4ad10c691..690d5c37530 100644 --- a/public/main/forum/forumfunction.inc.php +++ b/public/main/forum/forumfunction.inc.php @@ -4362,7 +4362,12 @@ function send_notifications(CForum $forum, CForumThread $thread, $post_id = 0) $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications'); if (1 === $subscribe) { - $users_to_be_notified = CourseManager::get_user_list_from_course_code(api_get_course_id(), api_get_session_id()); + $sessionId = api_get_session_id(); + if (!empty($sessionId)) { + $users_to_be_notified = SessionManager::getAllUserIdsInSession($sessionId); + } else { + $users_to_be_notified = CourseManager::get_user_list_from_course_code(api_get_course_id()); + } } if (is_array($users_to_be_notified)) { diff --git a/public/main/inc/lib/sessionmanager.lib.php b/public/main/inc/lib/sessionmanager.lib.php index d64b9ce6815..7f9533d2269 100644 --- a/public/main/inc/lib/sessionmanager.lib.php +++ b/public/main/inc/lib/sessionmanager.lib.php @@ -10104,4 +10104,43 @@ public static function sessionHasSessionAdmin(int $sessionId, int $userId): bool return in_array($userId, $adminIds); } + + /** + * Retrieves all user IDs associated with a session including coaches and students. + * + * @param int $sessionId The session ID. + * @return array An array of user IDs. + */ + public static function getAllUserIdsInSession(int $sessionId): array + { + $users = []; + $session = api_get_session_entity($sessionId); + if ($session) { + $courses = $session->getCourses(); + if (!empty($courses)) { + foreach ($courses as $sessionRelCourse) { + $course = $sessionRelCourse->getCourse(); + $coachSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::COURSE_COACH); + foreach ($coachSubscriptions as $coachSubscription) { + $users[]['user_id'] = $coachSubscription->getUser()->getId(); + } + + $userCourseSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::STUDENT); + foreach ($userCourseSubscriptions as $courseSubscription) { + $users[]['user_id'] = $courseSubscription->getUser()->getId(); + } + + } + } + + $generalCoachesId = self::getGeneralCoachesIdForSession($sessionId); + if (!empty($generalCoachesId)) { + foreach ($generalCoachesId as $generalCoachId) { + $users[]['user_id'] = $generalCoachId; + } + } + } + + return $users; + } } From 634bdc5395e9ccd46ff6fe19cc9a06e9e7eef439 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 18:14:30 -0500 Subject: [PATCH 024/413] Internal: Fix 'Undefined SYS_COURSE_PATH' error on gradebook import - refs BT#21541 --- public/main/inc/lib/document.lib.php | 1 - 1 file changed, 1 deletion(-) diff --git a/public/main/inc/lib/document.lib.php b/public/main/inc/lib/document.lib.php index 68163cd708e..8c5d07c8634 100644 --- a/public/main/inc/lib/document.lib.php +++ b/public/main/inc/lib/document.lib.php @@ -896,7 +896,6 @@ public static function get_document_data_by_id( $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id; $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id; // Consider storing a relative path or identifier in the database to construct paths - $row['absolute_path_from_document'] = api_get_path(SYS_COURSE_PATH) . $course_info['directory'] . '/document/' . $id; $row['basename'] = $id; // You may store and use titles or another unique identifier // Handling the parent ID should be adjusted if the path isn't available From eeb3a62f86ab16241f17abf6ac8895eeced7ac67 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 18:13:31 -0500 Subject: [PATCH 025/413] Agenda: Move method to determinate event type to the entity class - refs BT#20991 --- public/main/cron/agenda_reminders.php | 8 ++--- .../CalendarEventTransformer.php | 2 +- src/CourseBundle/Entity/CCalendarEvent.php | 26 +++++++++++++++ .../Repository/CCalendarEventRepository.php | 32 ------------------- 4 files changed, 29 insertions(+), 39 deletions(-) diff --git a/public/main/cron/agenda_reminders.php b/public/main/cron/agenda_reminders.php index 090252750ed..50fc2b17829 100644 --- a/public/main/cron/agenda_reminders.php +++ b/public/main/cron/agenda_reminders.php @@ -36,7 +36,7 @@ foreach ($reminders as $reminder) { $event = $reminder->getEvent(); - if ('personal' === $reminder->getType()) { + if ('personal' === $event->determineType()) { if (null === $event) { continue; } @@ -114,11 +114,7 @@ } } - if ('course' === $reminder->getType()) { - if (null === $event) { - continue; - } - + if ('course' === $event->determineType()) { $notificationDate = clone $event->getStartDate(); $notificationDate->sub($reminder->getDateInterval()); diff --git a/src/CoreBundle/DataTransformer/CalendarEventTransformer.php b/src/CoreBundle/DataTransformer/CalendarEventTransformer.php index a0479672ead..cd35eb51a72 100644 --- a/src/CoreBundle/DataTransformer/CalendarEventTransformer.php +++ b/src/CoreBundle/DataTransformer/CalendarEventTransformer.php @@ -53,7 +53,7 @@ private function mapCCalendarToDto(object $object): CalendarEvent $subscriptionItemTitle = $this->usergroupRepository->find($object->getSubscriptionItemId())?->getTitle(); } - $eventType = $this->calendarEventRepository->determineEventType($object); + $eventType = $object->determineType(); $color = $this->determineEventColor($eventType); $calendarEvent = new CalendarEvent( diff --git a/src/CourseBundle/Entity/CCalendarEvent.php b/src/CourseBundle/Entity/CCalendarEvent.php index dfd49432fdc..025b8a8955f 100644 --- a/src/CourseBundle/Entity/CCalendarEvent.php +++ b/src/CourseBundle/Entity/CCalendarEvent.php @@ -22,6 +22,7 @@ use Chamilo\CoreBundle\Entity\Career; use Chamilo\CoreBundle\Entity\Promotion; use Chamilo\CoreBundle\Entity\ResourceInterface; +use Chamilo\CoreBundle\Entity\ResourceLink; use Chamilo\CoreBundle\Entity\Room; use Chamilo\CoreBundle\Filter\CidFilter; use Chamilo\CoreBundle\Filter\GlobalEventFilter; @@ -508,4 +509,29 @@ public function setPromotion(?Promotion $promotion): self return $this; } + + public function determineType(): string + { + $resourceLinks = $this->resourceNode->getResourceLinks(); + + foreach ($resourceLinks as $link) { + if (null === $link->getCourse() + && null === $link->getSession() + && null === $link->getGroup() + && null === $link->getUser() + ) { + return 'global'; + } + + if (null !== $link->getCourse()) { + return 'course'; + } + + if (null !== $link->getSession()) { + return 'session'; + } + } + + return 'personal'; + } } diff --git a/src/CourseBundle/Repository/CCalendarEventRepository.php b/src/CourseBundle/Repository/CCalendarEventRepository.php index 8c0ccd6ad45..49e5388979f 100644 --- a/src/CourseBundle/Repository/CCalendarEventRepository.php +++ b/src/CourseBundle/Repository/CCalendarEventRepository.php @@ -77,36 +77,4 @@ public function createFromAnnouncement( return $event; } - - public function determineEventType(CCalendarEvent $event): string - { - $em = $this->getEntityManager(); - $queryBuilder = $em->createQueryBuilder(); - - $queryBuilder - ->select('rl') - ->from(ResourceLink::class, 'rl') - ->innerJoin('rl.resourceNode', 'rn') - ->where('rn.id = :resourceNodeId') - ->setParameter('resourceNodeId', $event->getResourceNode()->getId()) - ; - - $resourceLinks = $queryBuilder->getQuery()->getResult(); - - foreach ($resourceLinks as $link) { - if (null === $link->getCourse() && null === $link->getSession() && null === $link->getGroup() && null === $link->getUser()) { - return 'global'; - } - - if (null !== $link->getCourse()) { - return 'course'; - } - - if (null !== $link->getSession()) { - return 'session'; - } - } - - return 'personal'; - } } From ae0dbfb4f61a44e81b708f618cd5d92ac08c30e8 Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos Date: Wed, 24 Apr 2024 18:16:52 -0500 Subject: [PATCH 026/413] Agenda: Fix invitee list to send reminder - refs BT#20991 --- public/main/cron/agenda_reminders.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/main/cron/agenda_reminders.php b/public/main/cron/agenda_reminders.php index 50fc2b17829..52002fb45a9 100644 --- a/public/main/cron/agenda_reminders.php +++ b/public/main/cron/agenda_reminders.php @@ -36,11 +36,11 @@ foreach ($reminders as $reminder) { $event = $reminder->getEvent(); - if ('personal' === $event->determineType()) { - if (null === $event) { - continue; - } + if (null === $event) { + continue; + } + if ('personal' === $event->determineType()) { $notificationDate = clone $event->getStartDate(); $notificationDate->sub($reminder->getDateInterval()); @@ -87,7 +87,7 @@ return []; } - $resourceLinks = $event->getResourceLinkEntityList(); + $resourceLinks = $event->getResourceNode()->getResourceLinks(); $inviteeList = []; foreach ($resourceLinks as $resourceLink) { $user = $resourceLink->getUser(); From 7865c2ef94074ffa8b50c2c66257ff8e4033ce5d Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 20:35:23 -0500 Subject: [PATCH 027/413] Internal: Ensure default platform language is used for datetime picker on initial load - refs BT#21532 --- .../lib/formvalidator/Element/DatePicker.php | 56 ++++++++++------ .../formvalidator/Element/DateTimePicker.php | 64 +++++++++++-------- 2 files changed, 73 insertions(+), 47 deletions(-) diff --git a/public/main/inc/lib/formvalidator/Element/DatePicker.php b/public/main/inc/lib/formvalidator/Element/DatePicker.php index 28d19c7aebb..7bfc3806fe7 100644 --- a/public/main/inc/lib/formvalidator/Element/DatePicker.php +++ b/public/main/inc/lib/formvalidator/Element/DatePicker.php @@ -3,6 +3,7 @@ /* For licensing terms, see /license.txt */ use Chamilo\CoreBundle\Component\Utils\ToolIcon; +use Chamilo\CoreBundle\Framework\Container; /** * Form element to select a date. @@ -99,28 +100,38 @@ private function getElementJS(): string $localeCode = $this->getLocaleCode(); $id = $this->getAttribute('id'); - $localeScript = ''; - if ($localeCode !== 'en') { - $localeScript = ''; - } - - return $localeScript . ""; } @@ -135,7 +146,12 @@ private function getElementJS(): string */ private function getLocaleCode(): string { - $locale = api_get_language_isocode(); + $locale = api_get_setting('language.platform_language'); + $request = Container::getRequest(); + if ($request) { + $locale = $request->getLocale(); + } + $userInfo = api_get_user_info(); if (is_array($userInfo) && !empty($userInfo['language']) && ANONYMOUS != $userInfo['status']) { $locale = $userInfo['language']; diff --git a/public/main/inc/lib/formvalidator/Element/DateTimePicker.php b/public/main/inc/lib/formvalidator/Element/DateTimePicker.php index 066742bf042..69212ef43a1 100644 --- a/public/main/inc/lib/formvalidator/Element/DateTimePicker.php +++ b/public/main/inc/lib/formvalidator/Element/DateTimePicker.php @@ -67,40 +67,50 @@ public function setValue($value) * * @return string */ - private function getElementJS() + private function getElementJS(): string { $localeCode = $this->getLocaleCode(); $id = $this->getAttribute('id'); - $localeScript = ''; - if ($localeCode !== 'en') { - $localeScript = ''; - } - - $js = $localeScript . ""; From f9b09ee2dcc762ca1476f7fe070276e4592721f6 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Wed, 24 Apr 2024 23:36:17 -0500 Subject: [PATCH 028/413] Internal: Integrate Gregwar CaptchaBundle for enhanced security in contact form - refs BT#20920 --- composer.json | 1 + config/bundles.php | 1 + config/routes.yaml | 3 +++ src/CoreBundle/Form/ContactType.php | 17 +++++++++++++++++ .../Resources/views/Contact/index.html.twig | 10 ++++++++-- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5276848a16e..1db6cdf745e 100755 --- a/composer.json +++ b/composer.json @@ -82,6 +82,7 @@ "friendsofsymfony/jsrouting-bundle": "*", "graphp/algorithms": "~0.8", "graphp/graphviz": "~0.2", + "gregwar/captcha-bundle": "^2.2", "jbroadway/urlify": "~1.1", "jeroendesloovere/vcard": "~1.7", "jimmiw/php-time-ago": "~3.2", diff --git a/config/bundles.php b/config/bundles.php index 88c5a4b90ca..174031b91a4 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -36,4 +36,5 @@ Hautelook\AliceBundle\HautelookAliceBundle::class => ['dev' => true, 'test' => true], Sonata\Exporter\Bridge\Symfony\SonataExporterBundle::class => ['all' => true], Oh\GoogleMapFormTypeBundle\OhGoogleMapFormTypeBundle::class => ['all' => true], + Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true], ]; diff --git a/config/routes.yaml b/config/routes.yaml index a0342c837ce..4fdfed11daf 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -53,3 +53,6 @@ web_ajax: web_main: path: main/ + +gregwar_captcha_routing: + resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml" diff --git a/src/CoreBundle/Form/ContactType.php b/src/CoreBundle/Form/ContactType.php index fa48dbceec1..d37c6ab2b34 100644 --- a/src/CoreBundle/Form/ContactType.php +++ b/src/CoreBundle/Form/ContactType.php @@ -18,6 +18,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Contracts\Translation\TranslatorInterface; +use Gregwar\CaptchaBundle\Type\CaptchaType; /** * @template-extends AbstractType @@ -76,6 +77,22 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new NotBlank(), ], ]) + ->add('captcha', CaptchaType::class, [ + 'label' => $this->translator->trans('Verification'), + 'width' => 200, + 'height' => 50, + 'length' => 6, + 'quality' => 90, + 'distortion' => true, + 'background_color' => [255, 255, 255], + 'reload' => true, + 'as_url' => true, + 'attr' => [ + 'class' => 'captcha_image', + 'placeholder' => $this->translator->trans('Type the letters'), + ], + 'invalid_message' => $this->translator->trans('The security code entered was incorrect.'), + ]) ->add('submit', SubmitType::class, [ 'label' => $this->translator->trans('Send'), 'attr' => [ diff --git a/src/CoreBundle/Resources/views/Contact/index.html.twig b/src/CoreBundle/Resources/views/Contact/index.html.twig index d478c97d5dd..741d7e51a4a 100644 --- a/src/CoreBundle/Resources/views/Contact/index.html.twig +++ b/src/CoreBundle/Resources/views/Contact/index.html.twig @@ -4,13 +4,13 @@ {% block content %}
-
+

{{ "Contact Us"|get_lang }}

{{ form_start(form, {'attr': {'class': 'bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4'}}) }} {% for message in app.flashes('success') %} - +
+ {{ form_label(form.captcha) }} + {{ form_widget(form.captcha) }} + {{ form_errors(form.captcha) }} +
+ {{ form_end(form) }}
From 90dae3cb2e6eaaaff157c63fb70212885e66f6e9 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Thu, 25 Apr 2024 00:12:28 -0500 Subject: [PATCH 029/413] Internal: Fix access issues for session tutors with resource files in courses - refs BT#21538 --- .../Security/Authorization/Voter/ResourceNodeVoter.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php b/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php index a20e15d649a..930d5254ddd 100644 --- a/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php +++ b/src/CoreBundle/Security/Authorization/Voter/ResourceNodeVoter.php @@ -12,6 +12,7 @@ use Chamilo\CoreBundle\Entity\ResourceRight; use Chamilo\CoreBundle\Entity\Session; use Chamilo\CourseBundle\Entity\CGroup; +use ChamiloSession; use Laminas\Permissions\Acl\Acl; use Laminas\Permissions\Acl\Resource\GenericResource; use Laminas\Permissions\Acl\Role\GenericRole; @@ -149,6 +150,12 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ $courseId = (int) $request->getSession()->get('cid'); $sessionId = (int) $request->getSession()->get('sid'); $groupId = (int) $request->getSession()->get('gid'); + + if (0 === $courseId) { + $courseId = (int) ChamiloSession::read('cid'); + $sessionId = (int) ChamiloSession::read('sid'); + $groupId = (int) ChamiloSession::read('gid'); + } } $links = $resourceNode->getResourceLinks(); From 1da78c93c28de6fa26029e813291d620376e0f82 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Thu, 25 Apr 2024 00:40:16 -0500 Subject: [PATCH 030/413] Internal: Adjust down migration for track_e_downloads schema changes - refs #5412 --- .../Migrations/Schema/V200/Version20240114215900.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240114215900.php b/src/CoreBundle/Migrations/Schema/V200/Version20240114215900.php index 1e7d7ddc50c..11dd668192c 100644 --- a/src/CoreBundle/Migrations/Schema/V200/Version20240114215900.php +++ b/src/CoreBundle/Migrations/Schema/V200/Version20240114215900.php @@ -33,8 +33,8 @@ public function down(Schema $schema): void $this->addSql('DROP INDEX IDX_EEDF4DA6F004E599 ON track_e_downloads;'); $this->addSql('ALTER TABLE track_e_downloads DROP resource_link_id;'); $this->addSql('ALTER TABLE track_e_downloads ADD c_id INT NOT NULL;'); - $this->addSql('ALTER TABLE track_e_downloads ADD down_session_id INT DEFAULT NULL;'); $this->addSql('CREATE INDEX idx_ted_c_id ON track_e_downloads (c_id);'); - $this->addSql('CREATE INDEX down_session_id ON track_e_downloads (down_session_id);'); + $this->addSql('ALTER TABLE track_e_downloads ADD session_id INT DEFAULT NULL;'); + $this->addSql('CREATE INDEX session_id ON track_e_downloads (session_id);'); } } From be36d7ca2c6230525fab27be72284e06ef762c19 Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Thu, 25 Apr 2024 09:25:30 +0200 Subject: [PATCH 031/413] Skill: Language: reimplementing translation as it was in 1.11.x - refs BT#21568 --- src/CoreBundle/Entity/Skill.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/CoreBundle/Entity/Skill.php b/src/CoreBundle/Entity/Skill.php index 9bb6d82c550..9c493e51c43 100644 --- a/src/CoreBundle/Entity/Skill.php +++ b/src/CoreBundle/Entity/Skill.php @@ -10,6 +10,7 @@ use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use Chamilo\CoreBundle\Repository\SkillRepository; +use Chamilo\CoreBundle\Component\Utils\ChamiloApi; use DateTime; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -116,12 +117,26 @@ public function setTitle(string $title): self return $this; } - public function getTitle(): string + public function getTitle($translated = true): string { + if ($translated) { + $variable = ChamiloApi::getLanguageVar($this->title, 'Skill'); + $translation = get_lang($variable); + if ($variable != $translation) { + return $translation; + } + } return $this->title; } - public function getShortCode(): string + public function getShortCode($translated = true): string { + if ($translated) { + $variable = ChamiloApi::getLanguageVar($this->shortCode, 'SkillCode'); + $translation = get_lang($variable); + if ($variable != $translation) { + return $translation; + } + } return $this->shortCode; } public function setShortCode(string $shortCode): self From 221ea44af2e0bdfd5349cd066d21fad318fadc52 Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Thu, 25 Apr 2024 10:12:58 +0200 Subject: [PATCH 032/413] Skill: Language: adaptation of translation method to use gettext - refs BT#21568 --- public/main/inc/lib/SkillModel.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/main/inc/lib/SkillModel.php b/public/main/inc/lib/SkillModel.php index 4139d8db867..20b67e2132f 100644 --- a/public/main/inc/lib/SkillModel.php +++ b/public/main/inc/lib/SkillModel.php @@ -1739,8 +1739,11 @@ public function getStudentSkills($userId, $level = 0) public static function translateName($name) { $variable = ChamiloApi::getLanguageVar($name, 'Skill'); - - return isset($GLOBALS[$variable]) ? $GLOBALS[$variable] : $name; + $translation = get_lang($variable); + if ($variable != $translation) { + return $translation; + } + return $name; } /** @@ -1755,8 +1758,11 @@ public static function translateCode($code) } $variable = ChamiloApi::getLanguageVar($code, 'SkillCode'); - - return isset($GLOBALS[$variable]) ? $GLOBALS[$variable] : $code; + $translation = get_lang($variable); + if ($variable != $translation) { + return $translation; + } + return $code; } /** From e77aa9836736dec193e76243c4a0bdbfdc6bf166 Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Thu, 25 Apr 2024 11:33:38 +0200 Subject: [PATCH 033/413] Ticket: Internal: fix name to title conversion errors in ticket management --- public/main/inc/lib/TicketManager.php | 58 +++++++++++++-------------- public/main/ticket/new_ticket.php | 4 +- public/main/ticket/ticket_details.php | 2 +- public/main/ticket/tickets.php | 4 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/public/main/inc/lib/TicketManager.php b/public/main/inc/lib/TicketManager.php index f785ba6d816..c4fcac8f613 100644 --- a/public/main/inc/lib/TicketManager.php +++ b/public/main/inc/lib/TicketManager.php @@ -87,7 +87,7 @@ public static function get_all_tickets_categories($projectId, $order = '') public static function getCategories($from, $numberItems, $column, $direction) { $table = Database::get_main_table(TABLE_TICKET_CATEGORY); - $sql = "SELECT id, name, description, total_tickets + $sql = "SELECT id, title, description, total_tickets FROM $table"; if (!in_array($direction, ['ASC', 'DESC'])) { @@ -118,7 +118,7 @@ public static function getCategory($id) { $table = Database::get_main_table(TABLE_TICKET_CATEGORY); $id = (int) $id; - $sql = "SELECT id, name, description, total_tickets + $sql = "SELECT id, title, description, total_tickets FROM $table WHERE id = $id"; $result = Database::query($sql); @@ -518,7 +518,7 @@ public static function add( if (empty($usersInCategory)) { $subject = sprintf( get_lang('Warning: No one has been assigned to category %s'), - $categoryInfo['name'] + $categoryInfo['title'] ); if ('true' === api_get_setting('ticket_send_warning_to_all_admins')) { @@ -527,7 +527,7 @@ public static function add( get_lang( 'A notification was sent to the administrators to report this category has no user assigned' ), - $categoryInfo['name'] + $categoryInfo['title'] ), null, false @@ -815,7 +815,7 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, $column = 'ticket_id'; break; case 1: - $column = 'status_name'; + $column = 'status_title'; break; case 2: $column = 'start_date'; @@ -824,7 +824,7 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, $column = 'sys_lastedit_datetime'; break; case 4: - $column = 'category_name'; + $column = 'category_title'; break; case 5: $column = 'sys_insert_user_id'; @@ -845,11 +845,11 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, $sql = "SELECT DISTINCT ticket.*, ticket.id ticket_id, - status.name AS status_name, + status.title AS status_title, ticket.start_date, ticket.sys_lastedit_datetime, - cat.name AS category_name, - priority.name AS priority_name, + cat.title AS category_title, + priority.title AS priority_title, ticket.total_messages AS total_messages, ticket.message AS message, ticket.subject AS subject, @@ -882,9 +882,9 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, ticket.message LIKE '%$keyword%' OR ticket.keyword LIKE '%$keyword%' OR ticket.source LIKE '%$keyword%' OR - cat.name LIKE '%$keyword%' OR - status.name LIKE '%$keyword%' OR - priority.name LIKE '%$keyword%' OR + cat.title LIKE '%$keyword%' OR + status.title LIKE '%$keyword%' OR + priority.title LIKE '%$keyword%' OR ticket.personal_email LIKE '%$keyword%' )"; } @@ -989,10 +989,10 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, if ($isAdmin) { $ticket = [ $icon.' '.Security::remove_XSS($row['subject']), - $row['status_name'], + $row['status_title'], $row['start_date'], $row['sys_lastedit_datetime'], - $row['category_name'], + $row['category_title'], $name, $row['assigned_last_user'], $row['total_messages'], @@ -1000,10 +1000,10 @@ public static function getTicketsByCurrentUser($from, $number_of_items, $column, } else { $ticket = [ $icon.' '.Security::remove_XSS($row['subject']), - $row['status_name'], + $row['status_title'], $row['start_date'], $row['sys_lastedit_datetime'], - $row['category_name'], + $row['category_title'], ]; } if ($isAdmin) { @@ -1189,9 +1189,9 @@ public static function get_ticket_detail_by_id($ticketId) $sql = "SELECT ticket.*, - cat.name, - status.name as status, - priority.name priority + cat.title, + status.title as status, + priority.title priority FROM $table_support_tickets ticket INNER JOIN $table_support_category cat ON (cat.id = ticket.category_id) @@ -1680,9 +1680,9 @@ public static function export_tickets_by_user_id( ticket.code, ticket.sys_insert_datetime, ticket.sys_lastedit_datetime, - cat.name as category, + cat.title as category, CONCAT(user.lastname,' ', user.firstname) AS fullname, - status.name as status, + status.title as status, ticket.total_messages as messages, ticket.assigned_last_user as responsable FROM $table_support_tickets ticket, @@ -1997,7 +1997,7 @@ public static function getProjectsCount() public static function addProject($params) { $project = new TicketProject(); - $project->setTitle($params['name']); + $project->setTitle($params['title']); $project->setDescription($params['description']); $project->setInsertUserId(api_get_user_id()); @@ -2022,7 +2022,7 @@ public static function getProject($id) public static function updateProject($id, $params) { $project = self::getProject($id); - $project->setTitle($params['name']); + $project->setTitle($params['title']); $project->setDescription($params['description']); $project->setLastEditDateTime(new DateTime($params['sys_lastedit_datetime'])); $project->setLastEditUserId($params['sys_lastedit_user_id']); @@ -2117,8 +2117,8 @@ public static function getStatusCount() public static function addStatus($params) { $item = new TicketStatus(); - $item->setCode(URLify::filter($params['name'])); - $item->setTitle($params['name']); + $item->setCode(URLify::filter($params['title'])); + $item->setTitle($params['title']); $item->setDescription($params['description']); Database::getManager()->persist($item); @@ -2142,7 +2142,7 @@ public static function getStatus($id) public static function updateStatus($id, $params) { $item = self::getStatus($id); - $item->setTitle($params['name']); + $item->setTitle($params['title']); $item->setDescription($params['description']); Database::getManager()->persist($item); @@ -2217,8 +2217,8 @@ public static function addPriority($params) { $item = new TicketPriority(); $item - ->setCode(URLify::filter($params['name'])) - ->setTitle($params['name']) + ->setCode(URLify::filter($params['title'])) + ->setTitle($params['title']) ->setDescription($params['description']) ->setColor('') ->setInsertUserId(api_get_user_id()) @@ -2246,7 +2246,7 @@ public static function getPriority($id) public static function updatePriority($id, $params) { $item = self::getPriority($id); - $item->setTitle($params['name']); + $item->setTitle($params['title']); $item->setDescription($params['description']); Database::getManager()->persist($item); diff --git a/public/main/ticket/new_ticket.php b/public/main/ticket/new_ticket.php index 4ea61c09eec..7456df4962e 100644 --- a/public/main/ticket/new_ticket.php +++ b/public/main/ticket/new_ticket.php @@ -105,7 +105,7 @@ class: "controls" $projectId = isset($_GET['project_id']) ? (int) $_GET['project_id'] : 0; -$types = TicketManager::get_all_tickets_categories($projectId, 'category.name ASC'); +$types = TicketManager::get_all_tickets_categories($projectId, 'category.title ASC'); $htmlHeadXtra[] = '"; From a1b617e6b7e4fda2f0099dafd26fd7c5508869f4 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Thu, 25 Apr 2024 23:26:31 -0500 Subject: [PATCH 044/413] Internal: Fix null errors in open course lessons - refs BT#21579 --- public/main/exercise/exercise.class.php | 2 +- src/CoreBundle/Controller/ResourceController.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/main/exercise/exercise.class.php b/public/main/exercise/exercise.class.php index e4632b768e5..a166d5f2c99 100644 --- a/public/main/exercise/exercise.class.php +++ b/public/main/exercise/exercise.class.php @@ -6172,7 +6172,7 @@ public function send_mail_notification_for_exam( ) { $setting = api_get_course_setting('email_alert_manager_on_new_quiz'); - if (empty($setting) && empty($this->getNotifications())) { + if ((empty($setting) || !is_array($setting)) && empty($this->getNotifications())) { return false; } diff --git a/src/CoreBundle/Controller/ResourceController.php b/src/CoreBundle/Controller/ResourceController.php index 51e03be6226..2256f816b00 100644 --- a/src/CoreBundle/Controller/ResourceController.php +++ b/src/CoreBundle/Controller/ResourceController.php @@ -146,7 +146,7 @@ public function view(Request $request, EntityManagerInterface $entityManager): R $user = $this->userHelper->getCurrent(); $firstResourceLink = $resourceNode->getResourceLinks()->first(); - if ($firstResourceLink) { + if ($firstResourceLink && $user) { $resourceLinkId = $firstResourceLink->getId(); $url = $resourceNode->getResourceFile()->getOriginalName(); $downloadRepository = $entityManager->getRepository(TrackEDownloads::class); @@ -156,7 +156,7 @@ public function view(Request $request, EntityManagerInterface $entityManager): R $cid = (int) $request->query->get('cid'); $sid = (int) $request->query->get('sid'); $allUserInfo = null; - if ($cid) { + if ($cid && $user) { $allUserInfo = $this->getAllInfoToCertificate( $user->getId(), $cid, From 84bd767e4ad471d765b92a52b868a00b4f983168 Mon Sep 17 00:00:00 2001 From: NicoDucou Date: Fri, 26 Apr 2024 07:50:39 +0200 Subject: [PATCH 045/413] Language: remove extra text included by error --- translations/messages.fr.po | 1 - 1 file changed, 1 deletion(-) diff --git a/translations/messages.fr.po b/translations/messages.fr.po index 2b4672c3144..e11fb3fb9e0 100644 --- a/translations/messages.fr.po +++ b/translations/messages.fr.po @@ -25997,7 +25997,6 @@ msgstr "Mon profil" msgid "Number of times this answer was selected" msgstr "Nombre de fois que cette réponse a été sélectionnée" -#, fuzzy msgid "Back to tests list" msgstr "Retour à la liste des tests" From 2c223dca098d6190dc19861b2d51ca8d83fd2d10 Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Fri, 26 Apr 2024 09:50:19 +0200 Subject: [PATCH 046/413] Session: Remove 1:1 ratio for session image cropper (no reason for it to be there) to keep same ratio as for course images - refs BT#21551 --- public/main/inc/lib/sessionmanager.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/main/inc/lib/sessionmanager.lib.php b/public/main/inc/lib/sessionmanager.lib.php index bda5836b397..2669cf2b8ac 100644 --- a/public/main/inc/lib/sessionmanager.lib.php +++ b/public/main/inc/lib/sessionmanager.lib.php @@ -8184,7 +8184,7 @@ public static function setForm(FormValidator $form, Session $session = null, $fr $form->addFile( 'picture', get_lang('Add image'), - ['id' => 'picture', 'class' => 'picture-form', 'crop_image' => true, 'crop_ratio' => '1 / 1'] + ['id' => 'picture', 'class' => 'picture-form', 'crop_image' => true] ); $allowedPictureTypes = api_get_supported_image_extensions(false); $form->addRule('picture', get_lang('Only PNG, JPG or GIF images allowed').' ('.implode(',', $allowedPictureTypes).')', 'filetype', $allowedPictureTypes); From a80e93c313dcd703e11b5483e4f97f963f709495 Mon Sep 17 00:00:00 2001 From: Daniel <50702276+daniboygg@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:18:12 +0200 Subject: [PATCH 047/413] Message: Add name and username in user profile + resize user avatar to use full container size Author: @daniboygg --- .../basecomponents/BaseUserAvatar.vue | 11 ++- .../vue/components/social/MyFriendsCard.vue | 2 +- .../vue/components/social/UserProfileCard.vue | 2 +- .../userreluser/UserRelUserRequestsList.vue | 2 +- .../message/MessageCommunicationParty.vue | 35 ++++++++ assets/vue/views/message/MessageCreate.vue | 2 +- assets/vue/views/message/MessageList.vue | 2 +- assets/vue/views/message/MessageShow.vue | 79 ++++++++++--------- 8 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 assets/vue/views/message/MessageCommunicationParty.vue diff --git a/assets/vue/components/basecomponents/BaseUserAvatar.vue b/assets/vue/components/basecomponents/BaseUserAvatar.vue index 99ccae59979..5c6f922301c 100644 --- a/assets/vue/components/basecomponents/BaseUserAvatar.vue +++ b/assets/vue/components/basecomponents/BaseUserAvatar.vue @@ -6,6 +6,11 @@ class="rounded-full" :class="avatarClass" :aria-label="alt" + :pt="{ + image: { + style: 'width: 100%;' + } + }" /> @@ -28,7 +33,7 @@ const props = defineProps({ type: String, require: false, default: "normal", - validator: (value) => ["normal", "large", "xlarge"].includes(value), + validator: (value) => ["small", "normal", "large", "xlarge"].includes(value), }, shape: { type: String, @@ -56,7 +61,9 @@ const avatarClass = computed(() => { } else if (props.size === "large") { clazz += "h-16 w-16 " } else if (props.size === "normal") { - clazz += "h-10 w-10 " + clazz += "h-10 w-10 " // base size 40px + } else if (props.size === "small") { + clazz += "h-8 w-8 " } return clazz }) diff --git a/assets/vue/components/social/MyFriendsCard.vue b/assets/vue/components/social/MyFriendsCard.vue index 6b810528631..a7e62859490 100644 --- a/assets/vue/components/social/MyFriendsCard.vue +++ b/assets/vue/components/social/MyFriendsCard.vue @@ -26,7 +26,7 @@