chamilo/chamilo-lms

View on GitHub
src/CoreBundle/Controller/SocialController.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

/* For licensing terms, see /license.txt */

namespace Chamilo\CoreBundle\Controller;

use Chamilo\CoreBundle\Entity\ExtraField;
use Chamilo\CoreBundle\Entity\Legal;
use Chamilo\CoreBundle\Entity\Message;
use Chamilo\CoreBundle\Entity\MessageAttachment;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Entity\Usergroup;
use Chamilo\CoreBundle\Entity\UserRelUser;
use Chamilo\CoreBundle\Repository\ExtraFieldOptionsRepository;
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
use Chamilo\CoreBundle\Repository\LanguageRepository;
use Chamilo\CoreBundle\Repository\LegalRepository;
use Chamilo\CoreBundle\Repository\MessageRepository;
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
use Chamilo\CoreBundle\Repository\Node\MessageAttachmentRepository;
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository;
use Chamilo\CoreBundle\Repository\Node\UserRepository;
use Chamilo\CoreBundle\Repository\TrackEOnlineRepository;
use Chamilo\CoreBundle\Serializer\UserToJsonNormalizer;
use Chamilo\CoreBundle\ServiceHelper\UserHelper;
use Chamilo\CoreBundle\Settings\SettingsManager;
use Chamilo\CourseBundle\Repository\CForumThreadRepository;
use DateTime;
use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use ExtraFieldValue;
use MessageManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
use UserManager;

#[Route('/social-network')]
class SocialController extends AbstractController
{
    public function __construct(
        private readonly UserHelper $userHelper,
    ) {}

    #[Route('/personal-data/{userId}', name: 'chamilo_core_social_personal_data')]
    public function getPersonalData(
        int $userId,
        SettingsManager $settingsManager,
        UserToJsonNormalizer $userToJsonNormalizer
    ): JsonResponse {
        $propertiesToJson = $userToJsonNormalizer->serializeUserData($userId);
        $properties = $propertiesToJson ? json_decode($propertiesToJson, true) : [];

        $officerData = [
            ['name' => $settingsManager->getSetting('profile.data_protection_officer_name')],
            ['role' => $settingsManager->getSetting('profile.data_protection_officer_role')],
            ['email' => $settingsManager->getSetting('profile.data_protection_officer_email')],
        ];
        $properties['officer_data'] = $officerData;

        $dataForVue = [
            'personalData' => $properties,
        ];

        return $this->json($dataForVue);
    }

    #[Route('/terms-and-conditions/{userId}', name: 'chamilo_core_social_terms')]
    public function getLegalTerms(
        int $userId,
        SettingsManager $settingsManager,
        TranslatorInterface $translator,
        LegalRepository $legalTermsRepo,
        UserRepository $userRepo,
        LanguageRepository $languageRepo
    ): JsonResponse {
        $user = $userRepo->find($userId);
        if (!$user) {
            return $this->json(['error' => 'User not found']);
        }

        $isoCode = $user->getLocale();
        $extraFieldValue = new ExtraFieldValue('user');
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
        if ($value && !empty($value['value'])) {
            [$legalId, $legalLanguageId, $legalTime] = explode(':', $value['value']);
            $term = $legalTermsRepo->find($legalId);
        } else {
            $term = $this->getLastConditionByLanguage($languageRepo, $isoCode, $legalTermsRepo, $settingsManager);
        }

        if (!$term) {
            return $this->json(['error' => 'Terms not found']);
        }

        $termExtraFields = new ExtraFieldValue('terms_and_condition');
        $values = $termExtraFields->getAllValuesByItem($term->getId());

        $termsContent = [];
        foreach ($values as $value) {
            if (!empty($value['value'])) {
                $termsContent[] = [
                    'title' => $translator->trans($value['display_text'], [], 'messages', $isoCode),
                    'content' => $value['value'],
                ];
            }
        }

        if (empty($termsContent)) {
            $termsContent[] = [
                'title' => $translator->trans('Terms and Conditions', [], 'messages', $isoCode),
                'content' => $term->getContent(),
            ];
        }

        $formattedDate = new DateTime('@'.$term->getDate());

        $dataForVue = [
            'terms' => $termsContent,
            'date_text' => $translator->trans('PublicationDate', [], 'messages', $isoCode).': '.$formattedDate->format('Y-m-d H:i:s'),
        ];

        return $this->json($dataForVue);
    }

    #[Route('/legal-status/{userId}', name: 'chamilo_core_social_legal_status')]
    public function getLegalStatus(
        int $userId,
        SettingsManager $settingsManager,
        TranslatorInterface $translator,
        UserRepository $userRepo,
    ): JsonResponse {
        $allowTermsConditions = 'true' === $settingsManager->getSetting('registration.allow_terms_conditions');

        if (!$allowTermsConditions) {
            return $this->json([
                'message' => $translator->trans('No terms and conditions available', [], 'messages'),
            ]);
        }

        $user = $userRepo->find($userId);
        if (!$user) {
            return $this->json(['error' => 'User not found']);
        }

        $extraFieldValue = new ExtraFieldValue('user');
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');

        if (empty($value['value'])) {
            return $this->json([
                'isAccepted' => false,
                'message' => $translator->trans('Send legal agreement', [], 'messages'),
            ]);
        }

        [$legalId, $legalLanguageId, $legalTime] = explode(':', $value['value']);
        $dateTime = new DateTime("@$legalTime");
        $response = [
            'isAccepted' => true,
            'acceptDate' => $dateTime->format('Y-m-d H:i:s'),
            'message' => '',
        ];

        return $this->json($response);
    }

    #[Route('/send-legal-term', name: 'chamilo_core_social_send_legal_term')]
    public function sendLegalTerm(
        Request $request,
        SettingsManager $settingsManager,
        TranslatorInterface $translator,
        LegalRepository $legalTermsRepo,
        UserRepository $userRepo,
        LanguageRepository $languageRepo
    ): JsonResponse {
        $data = json_decode($request->getContent(), true);
        $userId = $data['userId'] ?? null;

        /** @var User $user */
        $user = $userRepo->find($userId);
        if (!$user) {
            return $this->json(['error' => 'User not found']);
        }

        $isoCode = $user->getLocale();

        /** @var Legal $term */
        $term = $this->getLastConditionByLanguage($languageRepo, $isoCode, $legalTermsRepo, $settingsManager);

        if (!$term) {
            return $this->json(['error' => 'Terms not found']);
        }

        $legalAcceptType = $term->getVersion().':'.$term->getLanguageId().':'.time();
        UserManager::update_extra_field_value(
            $userId,
            'legal_accept',
            $legalAcceptType
        );

        $bossList = UserManager::getStudentBossList($userId);
        if (!empty($bossList)) {
            $bossList = array_column($bossList, 'boss_id');
            foreach ($bossList as $bossId) {
                $subjectEmail = \sprintf(
                    $translator->trans('User %s signed the agreement'),
                    $user->getFullname()
                );
                $contentEmail = \sprintf(
                    $translator->trans('User %s signed the agreement.TheDateY'),
                    $user->getFullname(),
                    api_get_local_time()
                );

                MessageManager::send_message_simple(
                    $bossId,
                    $subjectEmail,
                    $contentEmail,
                    $userId
                );
            }
        }

        return $this->json([
            'success' => true,
            'message' => $translator->trans('Terms accepted successfully.'),
        ]);
    }

    #[Route('/delete-legal', name: 'chamilo_core_social_delete_legal')]
    public function deleteLegal(Request $request, TranslatorInterface $translator): JsonResponse
    {
        $data = json_decode($request->getContent(), true);
        $userId = $data['userId'] ?? null;

        if (!$userId) {
            return $this->json(['error' => $translator->trans('User ID not provided')], Response::HTTP_BAD_REQUEST);
        }

        $extraFieldValue = new ExtraFieldValue('user');
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
        if ($value && isset($value['id'])) {
            $extraFieldValue->delete($value['id']);
        }

        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'termactivated');
        if ($value && isset($value['id'])) {
            $extraFieldValue->delete($value['id']);
        }

        return $this->json(['success' => true, 'message' => $translator->trans('Legal acceptance revoked successfully.')]);
    }

    #[Route('/handle-privacy-request', name: 'chamilo_core_social_handle_privacy_request')]
    public function handlePrivacyRequest(
        Request $request,
        SettingsManager $settingsManager,
        UserRepository $userRepo,
        TranslatorInterface $translator,
        MailerInterface $mailer,
        RequestStack $requestStack
    ): JsonResponse {
        $data = json_decode($request->getContent(), true);
        $userId = $data['userId'] ? (int) $data['userId'] : null;
        $explanation = $data['explanation'] ?? '';
        $requestType = $data['requestType'] ?? '';

        /** @var User $user */
        $user = $userRepo->find($userId);

        if (!$user) {
            return $this->json(['success' => false, 'message' => 'User not found']);
        }

        if ('delete_account' === $requestType) {
            $fieldToUpdate = 'request_for_delete_account';
            $justificationFieldToUpdate = 'request_for_delete_account_justification';
            $emailSubject = $translator->trans('Request for account deletion');
            $emailContent = \sprintf($translator->trans('User %s asked for the deletion of his/her account, explaining that : ').$explanation, $user->getFullName());
        } elseif ('delete_legal' === $requestType) {
            $fieldToUpdate = 'request_for_legal_agreement_consent_removal';
            $justificationFieldToUpdate = 'request_for_legal_agreement_consent_removal_justification';
            $emailSubject = $translator->trans('Request for consent withdrawal on legal terms');
            $emailContent = \sprintf($translator->trans('User %s asked for the removal of his/her consent to our legal terms, explaining that: ').$explanation, $user->getFullName());
        } else {
            return $this->json(['success' => false, 'message' => 'Invalid action type']);
        }

        UserManager::createDataPrivacyExtraFields();
        UserManager::update_extra_field_value($userId, $fieldToUpdate, 1);
        UserManager::update_extra_field_value($userId, $justificationFieldToUpdate, $explanation);

        $request = $requestStack->getCurrentRequest();
        $baseUrl = $request->getSchemeAndHttpHost().$request->getBasePath();
        $specificPath = '/main/admin/user_list_consent.php';
        $link = $baseUrl.$specificPath;
        $emailContent .= $translator->trans('Go here : ').'<a href="'.$link.'">'.$link.'</a>';

        $emailOfficer = $settingsManager->getSetting('profile.data_protection_officer_email');
        if (!empty($emailOfficer)) {
            $email = new Email();
            $email
                ->from($user->getEmail())
                ->to($emailOfficer)
                ->subject($emailSubject)
                ->html($emailContent)
            ;
            $mailer->send($email);
        } else {
            MessageManager::sendMessageToAllAdminUsers($user->getId(), $emailSubject, $emailContent);
        }

        return $this->json([
            'success' => true,
            'message' => $translator->trans('Your request has been received.'),
        ]);
    }

    #[Route('/groups/{userId}', name: 'chamilo_core_social_groups')]
    public function getGroups(
        int $userId,
        UsergroupRepository $usergroupRepository,
        CForumThreadRepository $forumThreadRepository,
        SettingsManager $settingsManager,
        RequestStack $requestStack
    ): JsonResponse {
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');
        $items = [];
        $goToUrl = '';

        if (!empty($cid)) {
            $threads = $forumThreadRepository->getThreadsBySubscriptions($userId, $cid);
            foreach ($threads as $thread) {
                $threadId = $thread->getIid();
                $forumId = (int) $thread->getForum()->getIid();
                $items[] = [
                    'id' => $threadId,
                    'name' => $thread->getTitle(),
                    'description' => '',
                    'url' => $baseUrl.'/main/forum/viewthread.php?cid='.$cid.'&sid=0&gid=0&forum='.$forumId.'&thread='.$threadId,
                ];
            }
            $goToUrl = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
        } else {
            $groups = $usergroupRepository->getGroupsByUser($userId);
            foreach ($groups as $group) {
                $items[] = [
                    'id' => $group->getId(),
                    'name' => $group->getTitle(),
                    'description' => $group->getDescription(),
                    'url' => $baseUrl.'/resources/usergroups/show/'.$group->getId(),
                ];
            }
        }

        return $this->json([
            'items' => $items,
            'go_to' => $goToUrl,
        ]);
    }

    #[Route('/group/{groupId}/discussion/{discussionId}/messages', name: 'chamilo_core_social_group_discussion_messages')]
    public function getDiscussionMessages(
        $groupId,
        $discussionId,
        MessageRepository $messageRepository,
        UserRepository $userRepository,
        MessageAttachmentRepository $attachmentRepository
    ): JsonResponse {
        $messages = $messageRepository->getMessagesByGroupAndMessage((int) $groupId, (int) $discussionId);

        $formattedMessages = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository);

        return $this->json($formattedMessages);
    }

    #[Route('/get-forum-link', name: 'get_forum_link')]
    public function getForumLink(
        SettingsManager $settingsManager,
        RequestStack $requestStack
    ): JsonResponse {
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');

        $goToLink = '';
        if (!empty($cid)) {
            $goToLink = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
        }

        return $this->json(['go_to' => $goToLink]);
    }

    #[Route('/invite-friends/{userId}/{groupId}', name: 'chamilo_core_social_invite_friends')]
    public function inviteFriends(
        int $userId,
        int $groupId,
        UserRepository $userRepository,
        UsergroupRepository $usergroupRepository,
        IllustrationRepository $illustrationRepository
    ): JsonResponse {
        $user = $userRepository->find($userId);
        if (!$user) {
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
        }

        $group = $usergroupRepository->find($groupId);
        if (!$group) {
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
        }

        $friends = $userRepository->getFriendsNotInGroup($userId, $groupId);

        $friendsList = array_map(function ($friend) use ($illustrationRepository) {
            return [
                'id' => $friend->getId(),
                'name' => $friend->getFirstName().' '.$friend->getLastName(),
                'avatar' => $illustrationRepository->getIllustrationUrl($friend),
            ];
        }, $friends);

        return $this->json(['friends' => $friendsList]);
    }

    #[Route('/add-users-to-group/{groupId}', name: 'chamilo_core_social_add_users_to_group')]
    public function addUsersToGroup(Request $request, int $groupId, UsergroupRepository $usergroupRepository): JsonResponse
    {
        $data = json_decode($request->getContent(), true);
        $userIds = $data['userIds'] ?? [];

        try {
            $usergroupRepository->addUserToGroup($userIds, $groupId);

            return $this->json(['success' => true, 'message' => 'Users added to group successfully.']);
        } catch (Exception $e) {
            return $this->json(['success' => false, 'message' => 'An error occurred: '.$e->getMessage()], Response::HTTP_BAD_REQUEST);
        }
    }

    #[Route('/group/{groupId}/invited-users', name: 'chamilo_core_social_group_invited_users')]
    public function groupInvitedUsers(int $groupId, UsergroupRepository $usergroupRepository, IllustrationRepository $illustrationRepository): JsonResponse
    {
        $invitedUsers = $usergroupRepository->getInvitedUsersByGroup($groupId);

        $invitedUsersList = array_map(function ($user) {
            return [
                'id' => $user['id'],
                'name' => $user['username'],
                // 'avatar' => $illustrationRepository->getIllustrationUrl($user),
            ];
        }, $invitedUsers);

        return $this->json(['invitedUsers' => $invitedUsersList]);
    }

    #[IsGranted('ROLE_USER')]
    #[Route('/user-profile/{userId}', name: 'chamilo_core_social_user_profile')]
    public function getUserProfile(
        int $userId,
        SettingsManager $settingsManager,
        LanguageRepository $languageRepository,
        UserRepository $userRepository,
        RequestStack $requestStack,
        TrackEOnlineRepository $trackOnlineRepository,
        ExtraFieldRepository $extraFieldRepository,
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
    ): JsonResponse {
        $user = $userRepository->find($userId);
        if (!$user) {
            return $this->createNotFoundException('User not found');
        }

        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
        $profileFieldsVisibilityJson = $settingsManager->getSetting('profile.profile_fields_visibility');
        $profileFieldsVisibility = json_decode($profileFieldsVisibilityJson, true)['options'] ?? [];

        $vCardUserLink = $profileFieldsVisibility['vcard'] ?? true ? $baseUrl.'/main/social/vcard_export.php?userId='.(int) $userId : '';

        $languageInfo = null;
        if ($profileFieldsVisibility['language'] ?? true) {
            $language = $languageRepository->findByIsoCode($user->getLocale());
            if ($language) {
                $languageInfo = [
                    'label' => $language->getOriginalName(),
                    'value' => $language->getEnglishName(),
                    'code' => $language->getIsocode(),
                ];
            }
        }

        $isUserOnline = $trackOnlineRepository->isUserOnline($userId);
        $userOnlyInChat = $this->checkUserStatus($userId, $userRepository);
        $extraFields = $this->getExtraFieldBlock($userId, $userRepository, $settingsManager, $extraFieldRepository, $extraFieldOptionsRepository);

        $response = [
            'vCardUserLink' => $vCardUserLink,
            'language' => $languageInfo,
            'visibility' => $profileFieldsVisibility,
            'isUserOnline' => $isUserOnline,
            'userOnlyInChat' => $userOnlyInChat,
            'extraFields' => $extraFields,
        ];

        return $this->json($response);
    }

    private function getExtraFieldBlock(
        int $userId,
        UserRepository $userRepository,
        SettingsManager $settingsManager,
        ExtraFieldRepository $extraFieldRepository,
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
    ): array {
        $user = $userRepository->find($userId);
        if (!$user) {
            return [];
        }

        $fieldVisibilityConfig = $settingsManager->getSetting('profile.profile_fields_visibility');
        $fieldVisibility = ($fieldVisibilityConfig && 'false' !== $fieldVisibilityConfig) ? json_decode($fieldVisibilityConfig, true)['options'] : [];

        $extraUserData = $userRepository->getExtraUserData($userId);
        $extraFieldsFormatted = [];
        foreach ($extraUserData as $key => $value) {
            $fieldVariable = str_replace('extra_', '', $key);

            $extraField = $extraFieldRepository->getHandlerFieldInfoByFieldVariable($fieldVariable, ExtraField::USER_FIELD_TYPE);
            if (!$extraField || !isset($fieldVisibility[$fieldVariable]) || !$fieldVisibility[$fieldVariable]) {
                continue;
            }

            $fieldValue = \is_array($value) ? implode(', ', $value) : $value;

            switch ($extraField['type']) {
                case ExtraField::FIELD_TYPE_RADIO:
                case ExtraField::FIELD_TYPE_SELECT:
                    $extraFieldOptions = $extraFieldOptionsRepository->getFieldOptionByFieldAndOption($extraField['id'], $fieldValue, ExtraField::USER_FIELD_TYPE);
                    if (!empty($extraFieldOptions)) {
                        $optionTexts = array_map(function ($option) {
                            return $option['display_text'];
                        }, $extraFieldOptions);
                        $fieldValue = implode(', ', $optionTexts);
                    }

                    break;

                case ExtraField::FIELD_TYPE_GEOLOCALIZATION_COORDINATES:
                case ExtraField::FIELD_TYPE_GEOLOCALIZATION:
                    $geoData = explode('::', $fieldValue);
                    $locationName = $geoData[0];
                    $coordinates = $geoData[1] ?? '';
                    $fieldValue = $locationName;

                    break;
            }

            $extraFieldsFormatted[] = [
                'variable' => $fieldVariable,
                'label' => $extraField['display_text'],
                'value' => $fieldValue,
            ];
        }

        return $extraFieldsFormatted;
    }

    #[Route('/invitations/{userId}', name: 'chamilo_core_social_invitations')]
    public function getInvitations(
        int $userId,
        MessageRepository $messageRepository,
        UsergroupRepository $usergroupRepository,
        UserRepository $userRepository,
        TranslatorInterface $translator
    ): JsonResponse {
        $user = $this->userHelper->getCurrent();
        if ($userId !== $user->getId()) {
            return $this->json(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED);
        }

        $receivedMessages = $messageRepository->findReceivedInvitationsByUser($user);
        $receivedInvitations = [];
        foreach ($receivedMessages as $message) {
            $sender = $message->getSender();
            $receivedInvitations[] = [
                'id' => $message->getId(),
                'itemId' => $sender->getId(),
                'itemName' => $sender->getFirstName().' '.$sender->getLastName(),
                'itemPicture' => $userRepository->getUserPicture($sender->getId()),
                'content' => $message->getContent(),
                'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
                'canAccept' => true,
                'canDeny' => true,
            ];
        }

        $sentMessages = $messageRepository->findSentInvitationsByUser($user);
        $sentInvitations = [];
        foreach ($sentMessages as $message) {
            foreach ($message->getReceivers() as $receiver) {
                $receiverUser = $receiver->getReceiver();
                $sentInvitations[] = [
                    'id' => $message->getId(),
                    'itemId' => $receiverUser->getId(),
                    'itemName' => $receiverUser->getFirstName().' '.$receiverUser->getLastName(),
                    'itemPicture' => $userRepository->getUserPicture($receiverUser->getId()),
                    'content' => $message->getContent(),
                    'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
                    'canAccept' => false,
                    'canDeny' => false,
                ];
            }
        }

        $pendingGroupInvitations = [];
        $pendingGroups = $usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION);

        /** @var Usergroup $group */
        foreach ($pendingGroups as $group) {
            $isGroupVisible = 1 === (int) $group->getVisibility();
            $infoVisibility = !$isGroupVisible ? ' - '.$translator->trans('This group is closed.') : '';
            $pendingGroupInvitations[] = [
                'id' => $group->getId(),
                'itemId' => $group->getId(),
                'itemName' => $group->getTitle().$infoVisibility,
                'itemPicture' => $usergroupRepository->getUsergroupPicture($group->getId()),
                'content' => $group->getDescription(),
                'date' => $group->getCreatedAt()->format('Y-m-d H:i:s'),
                'canAccept' => $isGroupVisible,
                'canDeny' => true,
            ];
        }

        return $this->json([
            'receivedInvitations' => $receivedInvitations,
            'sentInvitations' => $sentInvitations,
            'pendingGroupInvitations' => $pendingGroupInvitations,
        ]);
    }

    #[IsGranted('ROLE_USER')]
    #[Route('/invitations/count/{userId}', name: 'chamilo_core_social_invitations_count')]
    public function getInvitationsCount(
        int $userId,
        MessageRepository $messageRepository,
        UsergroupRepository $usergroupRepository
    ): JsonResponse {
        $user = $this->userHelper->getCurrent();
        if ($userId !== $user->getId()) {
            return $this->json(['error' => 'Unauthorized']);
        }

        $receivedMessagesCount = \count($messageRepository->findReceivedInvitationsByUser($user));
        $pendingGroupInvitationsCount = \count($usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION));
        $totalInvitationsCount = $receivedMessagesCount + $pendingGroupInvitationsCount;

        return $this->json(['totalInvitationsCount' => $totalInvitationsCount]);
    }

    #[Route('/search', name: 'chamilo_core_social_search')]
    public function search(
        Request $request,
        UserRepository $userRepository,
        UsergroupRepository $usergroupRepository,
        TrackEOnlineRepository $trackOnlineRepository,
        MessageRepository $messageRepository
    ): JsonResponse {
        $query = $request->query->get('query', '');
        $type = $request->query->get('type', 'user');
        $from = $request->query->getInt('from', 0);
        $numberOfItems = $request->query->getInt('number_of_items', 1000);

        $formattedResults = [];
        if ('user' === $type) {
            $user = $this->userHelper->getCurrent();
            $results = $userRepository->searchUsersByTags($query, $user->getId(), 0, $from, $numberOfItems);
            foreach ($results as $item) {
                $isUserOnline = $trackOnlineRepository->isUserOnline($item['id']);
                $relation = $userRepository->getUserRelationWithType($user->getId(), $item['id']);
                $userReceiver = $userRepository->find($item['id']);
                $existingInvitations = $messageRepository->existingInvitations($user, $userReceiver);
                $formattedResults[] = [
                    'id' => $item['id'],
                    'name' => $item['firstname'].' '.$item['lastname'],
                    'avatar' => $userRepository->getUserPicture($item['id']),
                    'role' => 5 === $item['status'] ? 'student' : 'teacher',
                    'status' => $isUserOnline ? 'online' : 'offline',
                    'url' => '/social?id='.$item['id'],
                    'relationType' => $relation['relationType'] ?? null,
                    'existingInvitations' => $existingInvitations,
                ];
            }
        } elseif ('group' === $type) {
            // Perform group search
            $results = $usergroupRepository->searchGroupsByTags($query, $from, $numberOfItems);
            foreach ($results as $item) {
                $formattedResults[] = [
                    'id' => $item['id'],
                    'name' => $item['title'],
                    'description' => $item['description'] ?? '',
                    'image' => $usergroupRepository->getUsergroupPicture($item['id']),
                    'url' => '/resources/usergroups/show/'.$item['id'],
                ];
            }
        }

        return $this->json(['results' => $formattedResults]);
    }

    #[Route('/group-details/{groupId}', name: 'chamilo_core_social_group_details')]
    public function groupDetails(
        int $groupId,
        UsergroupRepository $usergroupRepository,
        TrackEOnlineRepository $trackOnlineRepository
    ): JsonResponse {
        $user = $this->userHelper->getCurrent();
        if (!$user) {
            return $this->json(['error' => 'User not authenticated'], Response::HTTP_UNAUTHORIZED);
        }

        /** @var Usergroup $group */
        $group = $usergroupRepository->find($groupId);
        if (!$group) {
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
        }

        $isMember = $usergroupRepository->isGroupMember($groupId, $user);
        $role = $usergroupRepository->getUserGroupRole($groupId, $user->getId());
        $isUserOnline = $trackOnlineRepository->isUserOnline($user->getId());
        $isModerator = $usergroupRepository->isGroupModerator($groupId, $user->getId());

        $groupDetails = [
            'id' => $group->getId(),
            'title' => $group->getTitle(),
            'description' => $group->getDescription(),
            'url' => $group->getUrl(),
            'image' => $usergroupRepository->getUsergroupPicture($group->getId()),
            'visibility' => (int) $group->getVisibility(),
            'allowMembersToLeaveGroup' => $group->getAllowMembersToLeaveGroup(),
            'isMember' => $isMember,
            'isModerator' => $isModerator,
            'role' => $role,
            'isUserOnline' => $isUserOnline,
            'isAllowedToLeave' => 1 === $group->getAllowMembersToLeaveGroup(),
        ];

        return $this->json($groupDetails);
    }

    #[Route('/group-action', name: 'chamilo_core_social_group_action')]
    public function group(
        Request $request,
        UsergroupRepository $usergroupRepository,
        EntityManagerInterface $em,
        MessageRepository $messageRepository
    ): JsonResponse {
        if (str_starts_with($request->headers->get('Content-Type'), 'multipart/form-data')) {
            $userId = $request->request->get('userId');
            $groupId = $request->request->get('groupId');
            $action = $request->request->get('action');
            $title = $request->request->get('title', '');
            $content = $request->request->get('content', '');
            $parentId = $request->request->get('parentId', 0);
            $editMessageId = $request->request->get('messageId', 0);

            $structuredFiles = [];
            if ($request->files->has('files')) {
                $files = $request->files->get('files');
                foreach ($files as $file) {
                    $structuredFiles[] = [
                        'name' => $file->getClientOriginalName(),
                        'full_path' => $file->getRealPath(),
                        'type' => $file->getMimeType(),
                        'tmp_name' => $file->getPathname(),
                        'error' => $file->getError(),
                        'size' => $file->getSize(),
                    ];
                }
            }
        } else {
            $data = json_decode($request->getContent(), true);
            $userId = $data['userId'] ?? null;
            $groupId = $data['groupId'] ?? null;
            $action = $data['action'] ?? null;
        }

        if (!$userId || !$groupId || !$action) {
            return $this->json(['error' => 'Missing parameters'], Response::HTTP_BAD_REQUEST);
        }

        try {
            switch ($action) {
                case 'accept':
                    $userRole = $usergroupRepository->getUserGroupRole($groupId, $userId);
                    if (\in_array(
                        $userRole,
                        [
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION_SENT_BY_USER,
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION,
                        ]
                    )) {
                        $usergroupRepository->updateUserRole($userId, $groupId, Usergroup::GROUP_USER_PERMISSION_READER);
                    }

                    break;

                case 'join':
                    $usergroupRepository->addUserToGroup($userId, $groupId);

                    break;

                case 'deny':
                    $usergroupRepository->removeUserFromGroup($userId, $groupId, false);

                    break;

                case 'leave':
                    $usergroupRepository->removeUserFromGroup($userId, $groupId);

                    break;

                case 'reply_message_group':
                    $title = $title ?: substr(strip_tags($content), 0, 50);

                    // no break
                case 'edit_message_group':
                case 'add_message_group':
                    $res = MessageManager::send_message(
                        $userId,
                        $title,
                        $content,
                        $structuredFiles,
                        [],
                        $groupId,
                        $parentId,
                        $editMessageId,
                        0,
                        $userId,
                        false,
                        0,
                        false,
                        false,
                        Message::MESSAGE_TYPE_GROUP
                    );

                    break;

                case 'delete_message_group':
                    $messageId = $data['messageId'] ?? null;

                    if (!$messageId) {
                        return $this->json(['error' => 'Missing messageId parameter'], Response::HTTP_BAD_REQUEST);
                    }

                    $messageRepository->deleteTopicAndChildren($groupId, $messageId);

                    break;

                default:
                    return $this->json(['error' => 'Invalid action'], Response::HTTP_BAD_REQUEST);
            }

            $em->flush();

            return $this->json(['success' => 'Action completed successfully']);
        } catch (Exception $e) {
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    #[Route('/user-action', name: 'chamilo_core_social_user_action')]
    public function user(
        Request $request,
        UserRepository $userRepository,
        MessageRepository $messageRepository,
        EntityManagerInterface $em
    ): JsonResponse {
        $data = json_decode($request->getContent(), true);

        $userId = $data['userId'] ?? null;
        $targetUserId = $data['targetUserId'] ?? null;
        $action = $data['action'] ?? null;
        $isMyFriend = $data['is_my_friend'] ?? false;
        $subject = $data['subject'] ?? '';
        $content = $data['content'] ?? '';

        if (!$userId || !$targetUserId || !$action) {
            return $this->json(['error' => 'Missing parameters']);
        }

        $currentUser = $userRepository->find($userId);
        $friendUser = $userRepository->find($targetUserId);

        if (null === $currentUser || null === $friendUser) {
            return $this->json(['error' => 'User not found']);
        }

        try {
            switch ($action) {
                case 'send_invitation':
                    $result = $messageRepository->sendInvitationToFriend($currentUser, $friendUser, $subject, $content);
                    if (!$result) {
                        return $this->json(['error' => 'Invitation already exists or could not be sent']);
                    }

                    break;

                case 'send_message':
                    $result = MessageManager::send_message($friendUser->getId(), $subject, $content);

                    break;

                case 'add_friend':
                    $relationType = $isMyFriend ? UserRelUser::USER_RELATION_TYPE_FRIEND : UserRelUser::USER_UNKNOWN;

                    $userRepository->relateUsers($currentUser, $friendUser, $relationType);
                    $userRepository->relateUsers($friendUser, $currentUser, $relationType);

                    $messageRepository->invitationAccepted($friendUser, $currentUser);

                    break;

                case 'deny_friend':
                    $messageRepository->invitationDenied($friendUser, $currentUser);

                    break;

                default:
                    return $this->json(['error' => 'Invalid action']);
            }

            $em->flush();

            return $this->json(['success' => 'Action completed successfully']);
        } catch (Exception $e) {
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    #[Route('/user-relation/{currentUserId}/{profileUserId}', name: 'chamilo_core_social_get_user_relation')]
    public function getUserRelation(int $currentUserId, int $profileUserId, EntityManagerInterface $em): JsonResponse
    {
        $isAllowed = $this->checkUserRelationship($currentUserId, $profileUserId, $em);

        return $this->json([
            'isAllowed' => $isAllowed,
        ]);
    }

    #[Route('/online-status', name: 'chamilo_core_social_get_online_status', methods: ['POST'])]
    public function getOnlineStatus(Request $request, TrackEOnlineRepository $trackOnlineRepository): JsonResponse
    {
        $data = json_decode($request->getContent(), true);
        $userIds = $data['userIds'] ?? [];

        $onlineStatuses = [];
        foreach ($userIds as $userId) {
            $onlineStatuses[$userId] = $trackOnlineRepository->isUserOnline($userId);
        }

        return $this->json($onlineStatuses);
    }

    #[Route('/upload-group-picture/{groupId}', name: 'chamilo_core_social_upload_group_picture')]
    public function uploadGroupPicture(
        Request $request,
        int $groupId,
        UsergroupRepository $usergroupRepository,
        IllustrationRepository $illustrationRepository
    ): JsonResponse {
        $file = $request->files->get('picture');
        if ($file instanceof UploadedFile) {
            $userGroup = $usergroupRepository->find($groupId);
            $illustrationRepository->addIllustration($userGroup, $this->userHelper->getCurrent(), $file);
        }

        return new JsonResponse(['success' => 'Group and image saved successfully'], Response::HTTP_OK);
    }

    #[Route('/terms-restrictions/{userId}', name: 'chamilo_core_social_terms_restrictions')]
    public function checkTermsRestrictions(
        int $userId,
        UserRepository $userRepo,
        ExtraFieldRepository $extraFieldRepository,
        TranslatorInterface $translator,
        SettingsManager $settingsManager
    ): JsonResponse {
        /** @var User $user */
        $user = $userRepo->find($userId);

        if (!$user) {
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
        }

        $isAdmin = $user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_SUPER_ADMIN');

        $termActivated = false;
        $blockButton = false;
        $infoMessage = '';

        if (!$isAdmin) {
            if ('true' === $settingsManager->getSetting('ticket.show_terms_if_profile_completed')) {
                $extraFieldValue = new ExtraFieldValue('user');
                $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'termactivated');
                if (isset($value['value'])) {
                    $termActivated = !empty($value['value']) && 1 === (int) $value['value'];
                }

                if (false === $termActivated) {
                    $blockButton = true;
                    $infoMessage .= $translator->trans('The terms and conditions have not yet been validated by your tutor.').'&nbsp;';
                }

                if (!$user->isProfileCompleted()) {
                    $blockButton = true;
                    $infoMessage .= $translator->trans('You must first fill your profile to enable the terms and conditions validation.');
                }
            }
        }

        return $this->json([
            'blockButton' => $blockButton,
            'infoMessage' => $infoMessage,
        ]);
    }

    /**
     * Formats a hierarchical structure of messages for display.
     *
     * This function takes an array of Message entities and recursively formats them into a hierarchical structure.
     * Each message is formatted with details such as user information, creation date, content, and attachments.
     * The function also assigns a level to each message based on its depth in the hierarchy for display purposes.
     */
    private function formatMessagesHierarchy(array $messages, UserRepository $userRepository, MessageAttachmentRepository $attachmentRepository, ?int $parentId = null, int $level = 0): array
    {
        $formattedMessages = [];

        /** @var Message $message */
        foreach ($messages as $message) {
            if (($message->getParent() ? $message->getParent()->getId() : null) === $parentId) {
                $attachments = $message->getAttachments();
                $attachmentsUrls = [];
                $attachmentSize = 0;
                if ($attachments) {
                    /** @var MessageAttachment $attachment */
                    foreach ($attachments as $attachment) {
                        $attachmentsUrls[] = [
                            'link' => $attachmentRepository->getResourceFileDownloadUrl($attachment),
                            'filename' => $attachment->getFilename(),
                            'size' => $attachment->getSize(),
                        ];
                        $attachmentSize += $attachment->getSize();
                    }
                }
                $formattedMessage = [
                    'id' => $message->getId(),
                    'user' => $message->getSender()->getFullName(),
                    'created' => $message->getSendDate()->format(DateTimeInterface::ATOM),
                    'title' => $message->getTitle(),
                    'content' => $message->getContent(),
                    'parentId' => $message->getParent() ? $message->getParent()->getId() : null,
                    'avatar' => $userRepository->getUserPicture($message->getSender()->getId()),
                    'senderId' => $message->getSender()->getId(),
                    'attachment' => $attachmentsUrls ?? null,
                    'attachmentSize' => $attachmentSize > 0 ? $attachmentSize : null,
                    'level' => $level,
                ];

                $children = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository, $message->getId(), $level + 1);
                if (!empty($children)) {
                    $formattedMessage['children'] = $children;
                }

                $formattedMessages[] = $formattedMessage;
            }
        }

        return $formattedMessages;
    }

    /**
     * Checks the relationship between the current user and another user.
     *
     * This method first checks for a direct relationship between the two users. If no direct relationship is found,
     * it then checks for indirect relationships through common friends (friends of friends).
     */
    private function checkUserRelationship(int $currentUserId, int $otherUserId, EntityManagerInterface $em): bool
    {
        if ($currentUserId === $otherUserId) {
            return true;
        }

        $relation = $em->getRepository(UserRelUser::class)
            ->findOneBy([
                'relationType' => [
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
                ],
                'friend' => $otherUserId,
                'user' => $currentUserId,
            ])
        ;

        if (null !== $relation) {
            return true;
        }

        $friendsOfCurrentUser = $em->getRepository(UserRelUser::class)
            ->findBy([
                'relationType' => [
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
                ],
                'user' => $currentUserId,
            ])
        ;

        foreach ($friendsOfCurrentUser as $friendRelation) {
            $friendId = $friendRelation->getFriend()->getId();
            $relationThroughFriend = $em->getRepository(UserRelUser::class)
                ->findOneBy([
                    'relationType' => [
                        UserRelUser::USER_RELATION_TYPE_FRIEND,
                        UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
                    ],
                    'friend' => $otherUserId,
                    'user' => $friendId,
                ])
            ;

            if (null !== $relationThroughFriend) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks the chat status of a user based on their user ID. It verifies if the user's chat status
     * is active (indicated by a status of 1).
     */
    private function checkUserStatus(int $userId, UserRepository $userRepository): bool
    {
        $userStatus = $userRepository->getExtraUserDataByField($userId, 'user_chat_status');

        return !empty($userStatus) && isset($userStatus['user_chat_status']) && 1 === (int) $userStatus['user_chat_status'];
    }

    /**
     * Retrieves the most recent legal terms for a specified language. If no terms are found for the given language,
     * the function attempts to retrieve terms for the platform's default language. If terms are still not found,
     * it defaults to English ('en_US').
     */
    private function getLastConditionByLanguage(LanguageRepository $languageRepo, string $isoCode, LegalRepository $legalTermsRepo, SettingsManager $settingsManager): ?Legal
    {
        $language = $languageRepo->findByIsoCode($isoCode);
        $languageId = (int) $language->getId();
        $term = $legalTermsRepo->getLastConditionByLanguage($languageId);
        if (!$term) {
            $defaultLanguage = $settingsManager->getSetting('language.platform_language');
            $language = $languageRepo->findByIsoCode($defaultLanguage);
            $languageId = (int) $language->getId();
            $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
            if (!$term) {
                $language = $languageRepo->findByIsoCode('en_US');
                $languageId = (int) $language->getId();
                $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
            }
        }

        return $term;
    }
}