Passed
Pull Request — master (#7182)
by
unknown
10:12
created

SocialController::groupInvitedUsers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 3
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use Chamilo\CoreBundle\Entity\ExtraField;
10
use Chamilo\CoreBundle\Entity\ExtraFieldOptions;
11
use Chamilo\CoreBundle\Entity\Legal;
12
use Chamilo\CoreBundle\Entity\Message;
13
use Chamilo\CoreBundle\Entity\MessageAttachment;
14
use Chamilo\CoreBundle\Entity\User;
15
use Chamilo\CoreBundle\Entity\Usergroup;
16
use Chamilo\CoreBundle\Entity\UserRelUser;
17
use Chamilo\CoreBundle\Helpers\UserHelper;
18
use Chamilo\CoreBundle\Repository\ExtraFieldOptionsRepository;
19
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
20
use Chamilo\CoreBundle\Repository\LanguageRepository;
21
use Chamilo\CoreBundle\Repository\LegalRepository;
22
use Chamilo\CoreBundle\Repository\MessageRepository;
23
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
24
use Chamilo\CoreBundle\Repository\Node\MessageAttachmentRepository;
25
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository;
26
use Chamilo\CoreBundle\Repository\Node\UserRepository;
27
use Chamilo\CoreBundle\Repository\TrackEOnlineRepository;
28
use Chamilo\CoreBundle\Serializer\UserToJsonNormalizer;
29
use Chamilo\CoreBundle\Settings\SettingsManager;
30
use Chamilo\CourseBundle\Repository\CForumThreadRepository;
31
use DateTime;
32
use DateTimeInterface;
33
use Doctrine\ORM\EntityManagerInterface;
34
use Exception;
35
use ExtraFieldValue;
36
use MessageManager;
37
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
38
use Symfony\Component\HttpFoundation\File\UploadedFile;
39
use Symfony\Component\HttpFoundation\JsonResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\HttpFoundation\RequestStack;
42
use Symfony\Component\HttpFoundation\Response;
43
use Symfony\Component\Mailer\MailerInterface;
44
use Symfony\Component\Mime\Email;
45
use Symfony\Component\Routing\Attribute\Route;
46
use Symfony\Component\Security\Http\Attribute\IsGranted;
47
use Symfony\Contracts\Translation\TranslatorInterface;
48
use UserManager;
49
50
#[Route('/social-network')]
51
class SocialController extends AbstractController
52
{
53
    private const TERMS_SECTIONS = [
54
        0 => ['title' => 'Terms and Conditions', 'subtitle' => null],
55
        1 => ['title' => 'Personal data collection', 'subtitle' => 'Why do we collect this data?'],
56
        2 => ['title' => 'Personal data recording', 'subtitle' => 'Where do we record the data?'],
57
        3 => ['title' => 'Personal data organization', 'subtitle' => 'How is the data structured?'],
58
        4 => ['title' => 'Personal data structure', 'subtitle' => 'How is the data structured in this software?'],
59
        5 => ['title' => 'Personal data conservation', 'subtitle' => 'How long do we save the data?'],
60
        6 => ['title' => 'Personal data adaptation or modification', 'subtitle' => 'What changes can we make to the data?'],
61
        7 => ['title' => 'Personal data extraction', 'subtitle' => 'What do we extract data for and which data is it?'],
62
        8 => ['title' => 'Personal data queries', 'subtitle' => 'Who can consult the personal data? For what purpose?'],
63
        9 => ['title' => 'Personal data use', 'subtitle' => 'How and for what can we use the personal data?'],
64
        10 => ['title' => 'Personal data communication and sharing', 'subtitle' => 'With whom can we share them?'],
65
        11 => ['title' => 'Personal data interconnection', 'subtitle' => 'Do we have another system with which Chamilo interacts?'],
66
        12 => ['title' => 'Personal data limitation', 'subtitle' => 'What are the limits that we will always respect?'],
67
        13 => ['title' => 'Personal data deletion', 'subtitle' => 'After how long do we erase the data?'],
68
        14 => ['title' => 'Personal data destruction', 'subtitle' => 'What happens if the data is destroyed?'],
69
        15 => ['title' => 'Personal data profiling', 'subtitle' => 'For what purpose do we process personal data?'],
70
    ];
71
72
    public function __construct(
73
        private readonly UserHelper $userHelper,
74
    ) {}
75
76
    #[Route('/personal-data/{userId}', name: 'chamilo_core_social_personal_data')]
77
    public function getPersonalData(
78
        int $userId,
79
        SettingsManager $settingsManager,
80
        UserToJsonNormalizer $userToJsonNormalizer
81
    ): JsonResponse {
82
        $propertiesToJson = $userToJsonNormalizer->serializeUserData($userId);
83
        $properties = $propertiesToJson ? json_decode($propertiesToJson, true) : [];
84
85
        $officerData = [
86
            ['name' => $settingsManager->getSetting('privacy.data_protection_officer_name')],
87
            ['role' => $settingsManager->getSetting('privacy.data_protection_officer_role')],
88
            ['email' => $settingsManager->getSetting('privacy.data_protection_officer_email')],
89
        ];
90
        $properties['officer_data'] = $officerData;
91
92
        $dataForVue = [
93
            'personalData' => $properties,
94
        ];
95
96
        return $this->json($dataForVue);
97
    }
98
99
    #[Route('/terms-and-conditions/{userId}', name: 'chamilo_core_social_terms')]
100
    public function getLegalTerms(
101
        int $userId,
102
        Request $request,
103
        SettingsManager $settingsManager,
104
        TranslatorInterface $translator,
105
        LegalRepository $legalTermsRepo,
106
        UserRepository $userRepo,
107
        LanguageRepository $languageRepo
108
    ): JsonResponse {
109
        $user = $userRepo->find($userId);
110
        if (!$user) {
111
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
112
        }
113
114
        $isoCode = $user->getLocale();
115
        $showAccepted = '1' === (string) $request->query->get('accepted', '0');
116
117
        $languageId = 0;
118
        $version = 0;
119
120
        if ($showAccepted) {
121
            $extraFieldValue = new ExtraFieldValue('user');
122
            $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
123
124
            if (!empty($value['value'])) {
125
                [$acceptedVersion, $acceptedLanguageId] = explode(':', $value['value']);
126
                $languageId = (int) $acceptedLanguageId;
127
                $version = (int) $acceptedVersion;
128
            } else {
129
                return $this->json(['error' => 'No accepted terms found'], Response::HTTP_NOT_FOUND);
130
            }
131
        } else {
132
            $resolved = $this->resolveLatestTermsVersion($isoCode, $languageRepo, $legalTermsRepo, $settingsManager);
133
            if (!$resolved) {
134
                return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
135
            }
136
            $languageId = (int) $resolved['languageId'];
137
            $version = (int) $resolved['version'];
138
        }
139
140
        $rows = $legalTermsRepo->findByLanguageAndVersion($languageId, $version);
141
        if (empty($rows)) {
142
            return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
143
        }
144
145
        $terms = [];
146
        foreach ($rows as $row) {
147
            $type = $row->getType();
148
            $section = self::TERMS_SECTIONS[$type] ?? self::TERMS_SECTIONS[0];
149
150
            $content = $row->getContent() ?? '';
151
            if ('' === trim($content)) {
152
                continue;
153
            }
154
155
            $terms[] = [
156
                'title' => $translator->trans($section['title'], [], 'messages', $isoCode),
157
                'subtitle' => $section['subtitle']
158
                    ? $translator->trans($section['subtitle'], [], 'messages', $isoCode)
159
                    : null,
160
                'content' => $content,
161
            ];
162
        }
163
164
        $date = new DateTime('@'.$rows[0]->getDate());
165
166
        return $this->json([
167
            'terms' => $terms,
168
            'date_text' => $translator->trans('Publication date', [], 'messages', $isoCode).': '.$date->format('Y-m-d H:i:s'),
169
            'version' => $version,
170
            'language_id' => $languageId,
171
            'showing_accepted' => $showAccepted,
172
        ]);
173
    }
174
175
    #[Route('/legal-status/{userId}', name: 'chamilo_core_social_legal_status')]
176
    public function getLegalStatus(
177
        int $userId,
178
        SettingsManager $settingsManager,
179
        TranslatorInterface $translator,
180
        UserRepository $userRepo,
181
        LanguageRepository $languageRepo,
182
        LegalRepository $legalTermsRepo
183
    ): JsonResponse {
184
        $allowTermsConditions = 'true' === $settingsManager->getSetting('registration.allow_terms_conditions');
185
        if (!$allowTermsConditions) {
186
            return $this->json([
187
                'message' => $translator->trans('No terms and conditions available', [], 'messages'),
188
            ]);
189
        }
190
191
        $user = $userRepo->find($userId);
192
        if (!$user) {
193
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
194
        }
195
196
        $isoCode = $user->getLocale();
197
        $latestLanguageId = $this->resolveLegalLanguageId($languageRepo, $isoCode, $settingsManager);
198
        $latestVersion = 0 !== $latestLanguageId ? $legalTermsRepo->getLastVersionByLanguage($latestLanguageId) : null;
199
200
        $extraFieldValue = new ExtraFieldValue('user');
201
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
202
203
        if (empty($value['value'])) {
204
            return $this->json([
205
                'isAccepted' => false,
206
                'message' => $translator->trans('Send legal agreement', [], 'messages'),
207
            ]);
208
        }
209
210
        [$acceptedVersion, $acceptedLanguageId, $acceptedTime] = explode(':', $value['value']);
211
        $dateTime = new DateTime('@'.(int) $acceptedTime);
212
213
        $isLatestAccepted = null !== $latestVersion
214
            && (int) $acceptedLanguageId === (int) $latestLanguageId
215
            && (int) $acceptedVersion === (int) $latestVersion;
216
217
        if (!$isLatestAccepted) {
218
            return $this->json([
219
                'isAccepted' => false,
220
                'acceptDate' => $dateTime->format('Y-m-d H:i:s'),
221
                'message' => $translator->trans('Please accept the latest version of the terms and conditions.', [], 'messages'),
222
            ]);
223
        }
224
225
        return $this->json([
226
            'isAccepted' => true,
227
            'acceptDate' => $dateTime->format('Y-m-d H:i:s'),
228
            'message' => '',
229
        ]);
230
    }
231
232
    #[Route('/send-legal-term', name: 'chamilo_core_social_send_legal_term')]
233
    public function sendLegalTerm(
234
        Request $request,
235
        SettingsManager $settingsManager,
236
        TranslatorInterface $translator,
237
        LegalRepository $legalTermsRepo,
238
        UserRepository $userRepo,
239
        LanguageRepository $languageRepo
240
    ): JsonResponse {
241
        $data = json_decode($request->getContent(), true);
242
        $userId = $data['userId'] ?? null;
243
244
        $user = $userRepo->find($userId);
245
        if (!$user) {
246
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
247
        }
248
249
        $isoCode = $user->getLocale();
250
        $languageId = $this->resolveLegalLanguageId($languageRepo, $isoCode, $settingsManager);
251
        if (0 === $languageId) {
252
            return $this->json(['error' => 'Language not found'], Response::HTTP_BAD_REQUEST);
253
        }
254
255
        $version = $legalTermsRepo->getLastVersionByLanguage($languageId);
256
        if (null === $version) {
257
            return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
258
        }
259
260
        $legalAcceptType = $version.':'.$languageId.':'.time();
261
        UserManager::update_extra_field_value((int) $userId, 'legal_accept', $legalAcceptType);
262
263
        $bossList = UserManager::getStudentBossList((int) $userId);
264
        if (!empty($bossList)) {
265
            $bossList = array_column($bossList, 'boss_id');
266
            foreach ($bossList as $bossId) {
267
                $subjectEmail = \sprintf(
268
                    $translator->trans('User %s signed the agreement.', [], 'messages'),
269
                    $user->getFullName()
270
                );
271
                $contentEmail = \sprintf(
272
                    $translator->trans('User %s signed the agreement the %s.', [], 'messages'),
273
                    $user->getFullName(),
274
                    api_get_local_time()
275
                );
276
277
                MessageManager::send_message_simple(
278
                    (int) $bossId,
279
                    $subjectEmail,
280
                    $contentEmail,
281
                    (int) $userId
282
                );
283
            }
284
        }
285
286
        return $this->json([
287
            'success' => true,
288
            'message' => $translator->trans('Terms accepted successfully.', [], 'messages'),
289
        ]);
290
    }
291
292
    #[Route('/delete-legal', name: 'chamilo_core_social_delete_legal')]
293
    public function deleteLegal(Request $request, TranslatorInterface $translator): JsonResponse
294
    {
295
        $data = json_decode($request->getContent(), true);
296
        $userId = $data['userId'] ?? null;
297
298
        if (!$userId) {
299
            return $this->json(['error' => $translator->trans('User ID not provided')], Response::HTTP_BAD_REQUEST);
300
        }
301
302
        $extraFieldValue = new ExtraFieldValue('user');
303
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
304
        if ($value && isset($value['id'])) {
305
            $extraFieldValue->delete($value['id']);
306
        }
307
308
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'termactivated');
309
        if ($value && isset($value['id'])) {
310
            $extraFieldValue->delete($value['id']);
311
        }
312
313
        return $this->json(['success' => true, 'message' => $translator->trans('Legal consent revoked successfully.')]);
314
    }
315
316
    #[Route('/handle-privacy-request', name: 'chamilo_core_social_handle_privacy_request')]
317
    public function handlePrivacyRequest(
318
        Request $request,
319
        SettingsManager $settingsManager,
320
        UserRepository $userRepo,
321
        TranslatorInterface $translator,
322
        MailerInterface $mailer,
323
        RequestStack $requestStack
324
    ): JsonResponse {
325
        $data = json_decode($request->getContent(), true);
326
        $userId = $data['userId'] ? (int) $data['userId'] : null;
327
        $explanation = $data['explanation'] ?? '';
328
        $requestType = $data['requestType'] ?? '';
329
330
        /** @var User $user */
331
        $user = $userRepo->find($userId);
332
333
        if (!$user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\CoreBundle\Entity\User, thus it always evaluated to true.
Loading history...
334
            return $this->json(['success' => false, 'message' => 'User not found']);
335
        }
336
337
        $request = $requestStack->getCurrentRequest();
338
        $baseUrl = $request->getSchemeAndHttpHost().$request->getBasePath();
339
        $specificPath = '/main/admin/user_list_consent.php';
340
        $link = $baseUrl.$specificPath;
341
342
        if ('delete_account' === $requestType) {
343
            $fieldToUpdate = 'request_for_delete_account';
344
            $justificationFieldToUpdate = 'request_for_delete_account_justification';
345
            $emailSubject = $translator->trans('Request for account removal');
346
            $emailContent = \sprintf($translator->trans('User %s asked for the deletion of his/her account, explaining that "%s". You can process the request here: %s'), $user->getFullName(), $explanation, '<a href="'.$link.'">'.$link.'</a>');
347
        } elseif ('delete_legal' === $requestType) {
348
            $fieldToUpdate = 'request_for_legal_agreement_consent_removal';
349
            $justificationFieldToUpdate = 'request_for_legal_agreement_consent_removal_justification';
350
            $emailSubject = $translator->trans('Request for consent withdrawal on legal terms');
351
            $emailContent = \sprintf($translator->trans('User %s asked for the removal of his/her consent to our legal terms, explaining that "%s". You can process the request here: %s'), $user->getFullName(), $explanation, '<a href="'.$link.'">'.$link.'</a>');
352
        } else {
353
            return $this->json(['success' => false, 'message' => 'Invalid action type']);
354
        }
355
356
        UserManager::createDataPrivacyExtraFields();
357
        UserManager::update_extra_field_value($userId, $fieldToUpdate, 1);
358
        UserManager::update_extra_field_value($userId, $justificationFieldToUpdate, $explanation);
359
360
        $emailOfficer = $settingsManager->getSetting('profile.data_protection_officer_email');
361
        if (!empty($emailOfficer)) {
362
            $email = new Email();
363
            $email
364
                ->from($user->getEmail())
365
                ->to($emailOfficer)
366
                ->subject($emailSubject)
367
                ->html($emailContent)
368
            ;
369
            $mailer->send($email);
370
        } else {
371
            MessageManager::sendMessageToAllAdminUsers($user->getId(), $emailSubject, $emailContent);
372
        }
373
374
        return $this->json([
375
            'success' => true,
376
            'message' => $translator->trans('Your request has been received.'),
377
        ]);
378
    }
379
380
    #[Route('/groups/{userId}', name: 'chamilo_core_social_groups')]
381
    public function getGroups(
382
        int $userId,
383
        UsergroupRepository $usergroupRepository,
384
        CForumThreadRepository $forumThreadRepository,
385
        SettingsManager $settingsManager,
386
        RequestStack $requestStack
387
    ): JsonResponse {
388
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
389
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');
390
        $items = [];
391
        $goToUrl = '';
392
393
        if (!empty($cid)) {
394
            $threads = $forumThreadRepository->getThreadsBySubscriptions($userId, $cid);
395
            foreach ($threads as $thread) {
396
                $threadId = $thread->getIid();
397
                $forumId = (int) $thread->getForum()->getIid();
398
                $items[] = [
399
                    'id' => $threadId,
400
                    'name' => $thread->getTitle(),
401
                    'description' => '',
402
                    'url' => $baseUrl.'/main/forum/viewthread.php?cid='.$cid.'&sid=0&gid=0&forum='.$forumId.'&thread='.$threadId,
403
                ];
404
            }
405
            $goToUrl = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
406
        } else {
407
            $groups = $usergroupRepository->getGroupsByUser($userId);
408
            foreach ($groups as $group) {
409
                $items[] = [
410
                    'id' => $group->getId(),
411
                    'name' => $group->getTitle(),
412
                    'description' => $group->getDescription(),
413
                    'url' => $baseUrl.'/resources/usergroups/show/'.$group->getId(),
414
                ];
415
            }
416
        }
417
418
        return $this->json([
419
            'items' => $items,
420
            'go_to' => $goToUrl,
421
        ]);
422
    }
423
424
    #[Route('/group/{groupId}/discussion/{discussionId}/messages', name: 'chamilo_core_social_group_discussion_messages')]
425
    public function getDiscussionMessages(
426
        $groupId,
427
        $discussionId,
428
        MessageRepository $messageRepository,
429
        UserRepository $userRepository,
430
        MessageAttachmentRepository $attachmentRepository
431
    ): JsonResponse {
432
        $messages = $messageRepository->getMessagesByGroupAndMessage((int) $groupId, (int) $discussionId);
433
434
        $formattedMessages = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository);
435
436
        return $this->json($formattedMessages);
437
    }
438
439
    #[Route('/get-forum-link', name: 'get_forum_link')]
440
    public function getForumLink(
441
        SettingsManager $settingsManager,
442
        RequestStack $requestStack
443
    ): JsonResponse {
444
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
445
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');
446
447
        $goToLink = '';
448
        if (!empty($cid)) {
449
            $goToLink = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
450
        }
451
452
        return $this->json(['go_to' => $goToLink]);
453
    }
454
455
    #[Route('/invite-friends/{userId}/{groupId}', name: 'chamilo_core_social_invite_friends')]
456
    public function inviteFriends(
457
        int $userId,
458
        int $groupId,
459
        UserRepository $userRepository,
460
        UsergroupRepository $usergroupRepository,
461
        IllustrationRepository $illustrationRepository
462
    ): JsonResponse {
463
        $user = $userRepository->find($userId);
464
        if (!$user) {
465
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
466
        }
467
468
        $group = $usergroupRepository->find($groupId);
469
        if (!$group) {
470
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
471
        }
472
473
        $friends = $userRepository->getFriendsNotInGroup($userId, $groupId);
474
475
        $friendsList = array_map(function ($friend) use ($illustrationRepository) {
476
            return [
477
                'id' => $friend->getId(),
478
                'name' => $friend->getFirstName().' '.$friend->getLastName(),
479
                'avatar' => $illustrationRepository->getIllustrationUrl($friend),
480
            ];
481
        }, $friends);
482
483
        return $this->json(['friends' => $friendsList]);
484
    }
485
486
    #[Route('/add-users-to-group/{groupId}', name: 'chamilo_core_social_add_users_to_group')]
487
    public function addUsersToGroup(Request $request, int $groupId, UsergroupRepository $usergroupRepository): JsonResponse
488
    {
489
        $data = json_decode($request->getContent(), true);
490
        $userIds = $data['userIds'] ?? [];
491
492
        try {
493
            $usergroupRepository->addUserToGroup($userIds, $groupId);
494
495
            return $this->json(['success' => true, 'message' => 'Users added to group successfully.']);
496
        } catch (Exception $e) {
497
            return $this->json(['success' => false, 'message' => 'An error occurred: '.$e->getMessage()], Response::HTTP_BAD_REQUEST);
498
        }
499
    }
500
501
    #[Route('/group/{groupId}/invited-users', name: 'chamilo_core_social_group_invited_users')]
502
    public function groupInvitedUsers(int $groupId, UsergroupRepository $usergroupRepository, IllustrationRepository $illustrationRepository): JsonResponse
503
    {
504
        $invitedUsers = $usergroupRepository->getInvitedUsersByGroup($groupId);
505
506
        $invitedUsersList = array_map(function ($user) {
507
            return [
508
                'id' => $user['id'],
509
                'name' => $user['username'],
510
                // 'avatar' => $illustrationRepository->getIllustrationUrl($user),
511
            ];
512
        }, $invitedUsers);
513
514
        return $this->json(['invitedUsers' => $invitedUsersList]);
515
    }
516
517
    #[IsGranted('ROLE_USER')]
518
    #[Route('/user-profile/{userId}', name: 'chamilo_core_social_user_profile')]
519
    public function getUserProfile(
520
        int $userId,
521
        SettingsManager $settingsManager,
522
        LanguageRepository $languageRepository,
523
        UserRepository $userRepository,
524
        RequestStack $requestStack,
525
        TrackEOnlineRepository $trackOnlineRepository,
526
        ExtraFieldRepository $extraFieldRepository,
527
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
528
    ): JsonResponse {
529
        $user = $userRepository->find($userId);
530
        if (!$user) {
531
            throw $this->createNotFoundException('User not found');
532
        }
533
534
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
535
        $profileFieldsVisibilityJson = $settingsManager->getSetting('profile.profile_fields_visibility');
536
        $decoded = json_decode($profileFieldsVisibilityJson, true);
537
        $profileFieldsVisibility = (\is_array($decoded) && isset($decoded['options']))
538
            ? $decoded['options']
539
            : [];
540
541
        $vCardUserLink = $profileFieldsVisibility['vcard'] ?? true ? $baseUrl.'/main/social/vcard_export.php?userId='.(int) $userId : '';
542
543
        $languageInfo = null;
544
        if ($profileFieldsVisibility['language'] ?? true) {
545
            $language = $languageRepository->findByIsoCode($user->getLocale());
546
            if ($language) {
547
                $languageInfo = [
548
                    'label' => $language->getOriginalName(),
549
                    'value' => $language->getEnglishName(),
550
                    'code' => $language->getIsocode(),
551
                ];
552
            }
553
        }
554
555
        $isUserOnline = $trackOnlineRepository->isUserOnline($userId);
556
        $userOnlyInChat = $this->checkUserStatus($userId, $userRepository);
557
        $extraFields = $this->getExtraFieldBlock($userId, $userRepository, $settingsManager, $extraFieldRepository, $extraFieldOptionsRepository);
558
559
        $response = [
560
            'vCardUserLink' => $vCardUserLink,
561
            'language' => $languageInfo,
562
            'visibility' => $profileFieldsVisibility,
563
            'isUserOnline' => $isUserOnline,
564
            'userOnlyInChat' => $userOnlyInChat,
565
            'extraFields' => $extraFields,
566
        ];
567
568
        return $this->json($response);
569
    }
570
571
    private function getExtraFieldBlock(
572
        int $userId,
573
        UserRepository $userRepository,
574
        SettingsManager $settingsManager,
575
        ExtraFieldRepository $extraFieldRepository,
576
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
577
    ): array {
578
        $user = $userRepository->find($userId);
579
        if (!$user) {
580
            return [];
581
        }
582
583
        $fieldVisibilityConfig = $settingsManager->getSetting('profile.profile_fields_visibility');
584
        $decoded = json_decode($fieldVisibilityConfig, true);
585
        $fieldVisibility = (\is_array($decoded) && isset($decoded['options']))
586
            ? $decoded['options']
587
            : [];
588
589
        $extraUserData = $userRepository->getExtraUserData($userId);
590
        $extraFieldsFormatted = [];
591
        foreach ($extraUserData as $key => $value) {
592
            $fieldVariable = str_replace('extra_', '', $key);
593
594
            $extraField = $extraFieldRepository->getHandlerFieldInfoByFieldVariable($fieldVariable, ExtraField::USER_FIELD_TYPE);
595
            if (!$extraField || !isset($fieldVisibility[$fieldVariable]) || !$fieldVisibility[$fieldVariable]) {
596
                continue;
597
            }
598
599
            $fieldValue = \is_array($value) ? implode(', ', $value) : $value;
600
601
            switch ($extraField['type']) {
602
                case ExtraField::FIELD_TYPE_RADIO:
603
                case ExtraField::FIELD_TYPE_SELECT:
604
                    $extraFieldOptions = $extraFieldOptionsRepository->getFieldOptionByFieldAndOption(
605
                        $extraField['id'],
606
                        $fieldValue,
607
                        ExtraField::USER_FIELD_TYPE
608
                    );
609
                    if (!empty($extraFieldOptions)) {
610
                        $fieldValue = implode(
611
                            ', ',
612
                            array_map(
613
                                fn (ExtraFieldOptions $opt) => $opt->getDisplayText(),
614
                                $extraFieldOptions
615
                            )
616
                        );
617
                    }
618
619
                    break;
620
621
                case ExtraField::FIELD_TYPE_GEOLOCALIZATION_COORDINATES:
622
                case ExtraField::FIELD_TYPE_GEOLOCALIZATION:
623
                    $geoData = explode('::', $fieldValue);
624
                    $locationName = $geoData[0];
625
                    $coordinates = $geoData[1] ?? '';
626
                    $fieldValue = $locationName;
627
628
                    break;
629
            }
630
631
            $extraFieldsFormatted[] = [
632
                'variable' => $fieldVariable,
633
                'label' => $extraField['display_text'],
634
                'value' => $fieldValue,
635
            ];
636
        }
637
638
        return $extraFieldsFormatted;
639
    }
640
641
    #[Route('/invitations/{userId}', name: 'chamilo_core_social_invitations')]
642
    public function getInvitations(
643
        int $userId,
644
        MessageRepository $messageRepository,
645
        UsergroupRepository $usergroupRepository,
646
        UserRepository $userRepository,
647
        TranslatorInterface $translator
648
    ): JsonResponse {
649
        $user = $this->userHelper->getCurrent();
650
        if ($userId !== $user->getId()) {
651
            return $this->json(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED);
652
        }
653
654
        $receivedMessages = $messageRepository->findReceivedInvitationsByUser($user);
655
        $receivedInvitations = [];
656
        foreach ($receivedMessages as $message) {
657
            $sender = $message->getSender();
658
            $receivedInvitations[] = [
659
                'id' => $message->getId(),
660
                'itemId' => $sender->getId(),
661
                'itemName' => $sender->getFirstName().' '.$sender->getLastName(),
662
                'itemPicture' => $userRepository->getUserPicture($sender->getId()),
663
                'content' => $message->getContent(),
664
                'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
665
                'canAccept' => true,
666
                'canDeny' => true,
667
            ];
668
        }
669
670
        $sentMessages = $messageRepository->findSentInvitationsByUser($user);
671
        $sentInvitations = [];
672
        foreach ($sentMessages as $message) {
673
            foreach ($message->getReceivers() as $receiver) {
674
                $receiverUser = $receiver->getReceiver();
675
                $sentInvitations[] = [
676
                    'id' => $message->getId(),
677
                    'itemId' => $receiverUser->getId(),
678
                    'itemName' => $receiverUser->getFirstName().' '.$receiverUser->getLastName(),
679
                    'itemPicture' => $userRepository->getUserPicture($receiverUser->getId()),
680
                    'content' => $message->getContent(),
681
                    'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
682
                    'canAccept' => false,
683
                    'canDeny' => false,
684
                ];
685
            }
686
        }
687
688
        $pendingGroupInvitations = [];
689
        $pendingGroups = $usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION);
690
691
        /** @var Usergroup $group */
692
        foreach ($pendingGroups as $group) {
693
            $isGroupVisible = 1 === (int) $group->getVisibility();
694
            $infoVisibility = !$isGroupVisible ? ' - '.$translator->trans('This group is closed.') : '';
695
            $pendingGroupInvitations[] = [
696
                'id' => $group->getId(),
697
                'itemId' => $group->getId(),
698
                'itemName' => $group->getTitle().$infoVisibility,
699
                'itemPicture' => $usergroupRepository->getUsergroupPicture($group->getId()),
700
                'content' => $group->getDescription(),
701
                'date' => $group->getCreatedAt()->format('Y-m-d H:i:s'),
702
                'canAccept' => $isGroupVisible,
703
                'canDeny' => true,
704
            ];
705
        }
706
707
        return $this->json([
708
            'receivedInvitations' => $receivedInvitations,
709
            'sentInvitations' => $sentInvitations,
710
            'pendingGroupInvitations' => $pendingGroupInvitations,
711
        ]);
712
    }
713
714
    #[IsGranted('ROLE_USER')]
715
    #[Route('/invitations/count/{userId}', name: 'chamilo_core_social_invitations_count')]
716
    public function getInvitationsCount(
717
        int $userId,
718
        MessageRepository $messageRepository,
719
        UsergroupRepository $usergroupRepository
720
    ): JsonResponse {
721
        $user = $this->userHelper->getCurrent();
722
        if ($userId !== $user->getId()) {
723
            return $this->json(['error' => 'Unauthorized']);
724
        }
725
726
        $receivedMessagesCount = \count($messageRepository->findReceivedInvitationsByUser($user));
727
        $pendingGroupInvitationsCount = \count($usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION));
728
        $totalInvitationsCount = $receivedMessagesCount + $pendingGroupInvitationsCount;
729
730
        return $this->json(['totalInvitationsCount' => $totalInvitationsCount]);
731
    }
732
733
    #[Route('/search', name: 'chamilo_core_social_search')]
734
    public function search(
735
        Request $request,
736
        UserRepository $userRepository,
737
        UsergroupRepository $usergroupRepository,
738
        TrackEOnlineRepository $trackOnlineRepository,
739
        MessageRepository $messageRepository
740
    ): JsonResponse {
741
        $query = $request->query->get('query', '');
742
        $type = $request->query->get('type', 'user');
743
        $from = $request->query->getInt('from', 0);
744
        $numberOfItems = $request->query->getInt('number_of_items', 1000);
745
746
        $formattedResults = [];
747
        if ('user' === $type) {
748
            $user = $this->userHelper->getCurrent();
749
            $results = $userRepository->searchUsersByTags($query, $user->getId(), 0, $from, $numberOfItems);
750
            foreach ($results as $item) {
751
                $isUserOnline = $trackOnlineRepository->isUserOnline($item['id']);
752
                $relation = $userRepository->getUserRelationWithType($user->getId(), $item['id']);
753
                $userReceiver = $userRepository->find($item['id']);
754
                $existingInvitations = $messageRepository->existingInvitations($user, $userReceiver);
755
                $formattedResults[] = [
756
                    'id' => $item['id'],
757
                    'name' => $item['firstname'].' '.$item['lastname'],
758
                    'avatar' => $userRepository->getUserPicture($item['id']),
759
                    'role' => 5 === $item['status'] ? 'student' : 'teacher',
760
                    'status' => $isUserOnline ? 'online' : 'offline',
761
                    'url' => '/social?id='.$item['id'],
762
                    'relationType' => $relation['relationType'] ?? null,
763
                    'existingInvitations' => $existingInvitations,
764
                ];
765
            }
766
        } elseif ('group' === $type) {
767
            // Perform group search
768
            $results = $usergroupRepository->searchGroupsByTags($query, $from, $numberOfItems);
769
            foreach ($results as $item) {
770
                $formattedResults[] = [
771
                    'id' => $item['id'],
772
                    'name' => $item['title'],
773
                    'description' => $item['description'] ?? '',
774
                    'image' => $usergroupRepository->getUsergroupPicture($item['id']),
775
                    'url' => '/resources/usergroups/show/'.$item['id'],
776
                ];
777
            }
778
        }
779
780
        return $this->json(['results' => $formattedResults]);
781
    }
782
783
    #[Route('/group-details/{groupId}', name: 'chamilo_core_social_group_details')]
784
    public function groupDetails(
785
        int $groupId,
786
        UsergroupRepository $usergroupRepository,
787
        TrackEOnlineRepository $trackOnlineRepository
788
    ): JsonResponse {
789
        $user = $this->userHelper->getCurrent();
790
        if (!$user) {
791
            return $this->json(['error' => 'User not authenticated'], Response::HTTP_UNAUTHORIZED);
792
        }
793
794
        /** @var Usergroup $group */
795
        $group = $usergroupRepository->find($groupId);
796
        if (!$group) {
0 ignored issues
show
introduced by
$group is of type Chamilo\CoreBundle\Entity\Usergroup, thus it always evaluated to true.
Loading history...
797
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
798
        }
799
800
        $isMember = $usergroupRepository->isGroupMember($groupId, $user);
801
        $role = $usergroupRepository->getUserGroupRole($groupId, $user->getId());
802
        $isUserOnline = $trackOnlineRepository->isUserOnline($user->getId());
803
        $isModerator = $usergroupRepository->isGroupModerator($groupId, $user->getId());
804
805
        $groupDetails = [
806
            'id' => $group->getId(),
807
            'title' => $group->getTitle(),
808
            'description' => $group->getDescription(),
809
            'url' => $group->getUrl(),
810
            'image' => $usergroupRepository->getUsergroupPicture($group->getId()),
811
            'visibility' => (int) $group->getVisibility(),
812
            'allowMembersToLeaveGroup' => $group->getAllowMembersToLeaveGroup(),
813
            'isMember' => $isMember,
814
            'isModerator' => $isModerator,
815
            'role' => $role,
816
            'isUserOnline' => $isUserOnline,
817
            'isAllowedToLeave' => 1 === $group->getAllowMembersToLeaveGroup(),
818
        ];
819
820
        return $this->json($groupDetails);
821
    }
822
823
    #[Route('/group-action', name: 'chamilo_core_social_group_action')]
824
    public function group(
825
        Request $request,
826
        UsergroupRepository $usergroupRepository,
827
        EntityManagerInterface $em,
828
        MessageRepository $messageRepository
829
    ): JsonResponse {
830
        if (str_starts_with($request->headers->get('Content-Type'), 'multipart/form-data')) {
831
            $userId = $request->request->get('userId');
832
            $groupId = $request->request->get('groupId');
833
            $action = $request->request->get('action');
834
            $title = $request->request->get('title', '');
835
            $content = $request->request->get('content', '');
836
            $parentId = $request->request->get('parentId', 0);
837
            $editMessageId = $request->request->get('messageId', 0);
838
839
            $structuredFiles = [];
840
            if ($request->files->has('files')) {
841
                $files = $request->files->get('files');
842
                foreach ($files as $file) {
843
                    $structuredFiles[] = [
844
                        'name' => $file->getClientOriginalName(),
845
                        'full_path' => $file->getRealPath(),
846
                        'type' => $file->getMimeType(),
847
                        'tmp_name' => $file->getPathname(),
848
                        'error' => $file->getError(),
849
                        'size' => $file->getSize(),
850
                    ];
851
                }
852
            }
853
        } else {
854
            $data = json_decode($request->getContent(), true);
855
            $userId = $data['userId'] ?? null;
856
            $groupId = $data['groupId'] ?? null;
857
            $action = $data['action'] ?? null;
858
        }
859
860
        if (!$userId || !$groupId || !$action) {
861
            return $this->json(['error' => 'Missing parameters'], Response::HTTP_BAD_REQUEST);
862
        }
863
864
        try {
865
            switch ($action) {
866
                case 'accept':
867
                    $userRole = $usergroupRepository->getUserGroupRole($groupId, $userId);
868
                    if (\in_array(
869
                        $userRole,
870
                        [
871
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION_SENT_BY_USER,
872
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION,
873
                        ]
874
                    )) {
875
                        $usergroupRepository->updateUserRole($userId, $groupId, Usergroup::GROUP_USER_PERMISSION_READER);
876
                    }
877
878
                    break;
879
880
                case 'join':
881
                    $usergroupRepository->addUserToGroup($userId, $groupId);
882
883
                    break;
884
885
                case 'deny':
886
                    $usergroupRepository->removeUserFromGroup($userId, $groupId, false);
887
888
                    break;
889
890
                case 'leave':
891
                    $usergroupRepository->removeUserFromGroup($userId, $groupId);
892
893
                    break;
894
895
                case 'reply_message_group':
896
                    $title = $title ?: substr(strip_tags($content), 0, 50);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $content does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $title does not seem to be defined for all execution paths leading up to this point.
Loading history...
897
898
                    // no break
899
                case 'edit_message_group':
900
                case 'add_message_group':
901
                    $res = MessageManager::send_message(
902
                        $userId,
903
                        $title,
904
                        $content,
905
                        $structuredFiles,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $structuredFiles does not seem to be defined for all execution paths leading up to this point.
Loading history...
906
                        [],
907
                        $groupId,
908
                        $parentId,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parentId does not seem to be defined for all execution paths leading up to this point.
Loading history...
909
                        $editMessageId,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $editMessageId does not seem to be defined for all execution paths leading up to this point.
Loading history...
910
                        0,
911
                        $userId,
912
                        false,
913
                        0,
914
                        false,
915
                        false,
916
                        Message::MESSAGE_TYPE_GROUP
917
                    );
918
919
                    break;
920
921
                case 'delete_message_group':
922
                    $messageId = $data['messageId'] ?? null;
923
924
                    if (!$messageId) {
925
                        return $this->json(['error' => 'Missing messageId parameter'], Response::HTTP_BAD_REQUEST);
926
                    }
927
928
                    $messageRepository->deleteTopicAndChildren($groupId, $messageId);
929
930
                    break;
931
932
                default:
933
                    return $this->json(['error' => 'Invalid action'], Response::HTTP_BAD_REQUEST);
934
            }
935
936
            $em->flush();
937
938
            return $this->json(['success' => 'Action completed successfully']);
939
        } catch (Exception $e) {
940
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
941
        }
942
    }
943
944
    #[Route('/user-action', name: 'chamilo_core_social_user_action')]
945
    public function user(
946
        Request $request,
947
        UserRepository $userRepository,
948
        MessageRepository $messageRepository,
949
        EntityManagerInterface $em
950
    ): JsonResponse {
951
        $data = json_decode($request->getContent(), true);
952
953
        $userId = $data['userId'] ?? null;
954
        $targetUserId = $data['targetUserId'] ?? null;
955
        $action = $data['action'] ?? null;
956
        $isMyFriend = $data['is_my_friend'] ?? false;
957
        $subject = $data['subject'] ?? '';
958
        $content = $data['content'] ?? '';
959
960
        if (!$userId || !$targetUserId || !$action) {
961
            return $this->json(['error' => 'Missing parameters']);
962
        }
963
964
        $currentUser = $userRepository->find($userId);
965
        $friendUser = $userRepository->find($targetUserId);
966
967
        if (null === $currentUser || null === $friendUser) {
968
            return $this->json(['error' => 'User not found']);
969
        }
970
971
        try {
972
            switch ($action) {
973
                case 'send_invitation':
974
                    $result = $messageRepository->sendInvitationToFriend($currentUser, $friendUser, $subject, $content);
975
                    if (!$result) {
976
                        return $this->json(['error' => 'Invitation already exists or could not be sent']);
977
                    }
978
979
                    break;
980
981
                case 'send_message':
982
                    $result = MessageManager::send_message($friendUser->getId(), $subject, $content);
983
984
                    break;
985
986
                case 'add_friend':
987
                    $relationType = $isMyFriend ? UserRelUser::USER_RELATION_TYPE_FRIEND : UserRelUser::USER_UNKNOWN;
988
989
                    $userRepository->relateUsers($currentUser, $friendUser, $relationType);
990
                    $userRepository->relateUsers($friendUser, $currentUser, $relationType);
991
992
                    $messageRepository->invitationAccepted($friendUser, $currentUser);
993
994
                    break;
995
996
                case 'deny_friend':
997
                    $messageRepository->invitationDenied($friendUser, $currentUser);
998
999
                    break;
1000
1001
                default:
1002
                    return $this->json(['error' => 'Invalid action']);
1003
            }
1004
1005
            $em->flush();
1006
1007
            return $this->json(['success' => 'Action completed successfully']);
1008
        } catch (Exception $e) {
1009
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
1010
        }
1011
    }
1012
1013
    #[Route('/user-relation/{currentUserId}/{profileUserId}', name: 'chamilo_core_social_get_user_relation')]
1014
    public function getUserRelation(int $currentUserId, int $profileUserId, EntityManagerInterface $em): JsonResponse
1015
    {
1016
        $isAllowed = $this->checkUserRelationship($currentUserId, $profileUserId, $em);
1017
1018
        return $this->json([
1019
            'isAllowed' => $isAllowed,
1020
        ]);
1021
    }
1022
1023
    #[Route('/online-status', name: 'chamilo_core_social_get_online_status', methods: ['POST'])]
1024
    public function getOnlineStatus(Request $request, TrackEOnlineRepository $trackOnlineRepository): JsonResponse
1025
    {
1026
        $data = json_decode($request->getContent(), true);
1027
        $userIds = $data['userIds'] ?? [];
1028
1029
        $onlineStatuses = [];
1030
        foreach ($userIds as $userId) {
1031
            $onlineStatuses[$userId] = $trackOnlineRepository->isUserOnline($userId);
1032
        }
1033
1034
        return $this->json($onlineStatuses);
1035
    }
1036
1037
    #[Route('/upload-group-picture/{groupId}', name: 'chamilo_core_social_upload_group_picture')]
1038
    public function uploadGroupPicture(
1039
        Request $request,
1040
        int $groupId,
1041
        UsergroupRepository $usergroupRepository,
1042
        IllustrationRepository $illustrationRepository
1043
    ): JsonResponse {
1044
        $file = $request->files->get('picture');
1045
        if ($file instanceof UploadedFile) {
1046
            $userGroup = $usergroupRepository->find($groupId);
1047
            $illustrationRepository->addIllustration($userGroup, $this->userHelper->getCurrent(), $file);
1048
        }
1049
1050
        return new JsonResponse(['success' => 'Group and image saved successfully'], Response::HTTP_OK);
1051
    }
1052
1053
    #[Route('/terms-restrictions/{userId}', name: 'chamilo_core_social_terms_restrictions')]
1054
    public function checkTermsRestrictions(
1055
        int $userId,
1056
        UserRepository $userRepo,
1057
        ExtraFieldRepository $extraFieldRepository,
1058
        TranslatorInterface $translator,
1059
        SettingsManager $settingsManager
1060
    ): JsonResponse {
1061
        /** @var User $user */
1062
        $user = $userRepo->find($userId);
1063
1064
        if (!$user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\CoreBundle\Entity\User, thus it always evaluated to true.
Loading history...
1065
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
1066
        }
1067
1068
        $isAdmin = $user->isAdmin() || $user->isSuperAdmin();
1069
1070
        $termActivated = false;
1071
        $blockButton = false;
1072
        $infoMessage = '';
1073
1074
        if (!$isAdmin) {
1075
            if ('true' === $settingsManager->getSetting('profile.show_terms_if_profile_completed')) {
1076
                $extraFieldValue = new ExtraFieldValue('user');
1077
                $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'termactivated');
1078
                if (isset($value['value'])) {
1079
                    $termActivated = !empty($value['value']) && 1 === (int) $value['value'];
1080
                }
1081
1082
                if (false === $termActivated) {
1083
                    $blockButton = true;
1084
                    $infoMessage .= $translator->trans('The terms and conditions have not yet been validated by your tutor.').'&nbsp;';
1085
                }
1086
1087
                if (!$user->isProfileCompleted()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user->isProfileCompleted() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1088
                    $blockButton = true;
1089
                    $infoMessage .= $translator->trans('You must first fill your profile to enable the terms and conditions validation.');
1090
                }
1091
            }
1092
        }
1093
1094
        return $this->json([
1095
            'blockButton' => $blockButton,
1096
            'infoMessage' => $infoMessage,
1097
        ]);
1098
    }
1099
1100
    /**
1101
     * Formats a hierarchical structure of messages for display.
1102
     *
1103
     * This function takes an array of Message entities and recursively formats them into a hierarchical structure.
1104
     * Each message is formatted with details such as user information, creation date, content, and attachments.
1105
     * The function also assigns a level to each message based on its depth in the hierarchy for display purposes.
1106
     */
1107
    private function formatMessagesHierarchy(array $messages, UserRepository $userRepository, MessageAttachmentRepository $attachmentRepository, ?int $parentId = null, int $level = 0): array
1108
    {
1109
        $formattedMessages = [];
1110
1111
        /** @var Message $message */
1112
        foreach ($messages as $message) {
1113
            if (($message->getParent() ? $message->getParent()->getId() : null) === $parentId) {
1114
                $attachments = $message->getAttachments();
1115
                $attachmentsUrls = [];
1116
                $attachmentSize = 0;
1117
                if ($attachments) {
1118
                    /** @var MessageAttachment $attachment */
1119
                    foreach ($attachments as $attachment) {
1120
                        $attachmentsUrls[] = [
1121
                            'link' => $attachmentRepository->getResourceFileDownloadUrl($attachment),
1122
                            'filename' => $attachment->getFilename(),
1123
                            'size' => $attachment->getSize(),
1124
                        ];
1125
                        $attachmentSize += $attachment->getSize();
1126
                    }
1127
                }
1128
                $formattedMessage = [
1129
                    'id' => $message->getId(),
1130
                    'user' => $message->getSender()->getFullName(),
1131
                    'created' => $message->getSendDate()->format(DateTimeInterface::ATOM),
1132
                    'title' => $message->getTitle(),
1133
                    'content' => $message->getContent(),
1134
                    'parentId' => $message->getParent() ? $message->getParent()->getId() : null,
1135
                    'avatar' => $userRepository->getUserPicture($message->getSender()->getId()),
1136
                    'senderId' => $message->getSender()->getId(),
1137
                    'attachment' => $attachmentsUrls ?? null,
1138
                    'attachmentSize' => $attachmentSize > 0 ? $attachmentSize : null,
1139
                    'level' => $level,
1140
                ];
1141
1142
                $children = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository, $message->getId(), $level + 1);
1143
                if (!empty($children)) {
1144
                    $formattedMessage['children'] = $children;
1145
                }
1146
1147
                $formattedMessages[] = $formattedMessage;
1148
            }
1149
        }
1150
1151
        return $formattedMessages;
1152
    }
1153
1154
    /**
1155
     * Checks the relationship between the current user and another user.
1156
     *
1157
     * This method first checks for a direct relationship between the two users. If no direct relationship is found,
1158
     * it then checks for indirect relationships through common friends (friends of friends).
1159
     */
1160
    private function checkUserRelationship(int $currentUserId, int $otherUserId, EntityManagerInterface $em): bool
1161
    {
1162
        if ($currentUserId === $otherUserId) {
1163
            return true;
1164
        }
1165
1166
        $relation = $em->getRepository(UserRelUser::class)
1167
            ->findOneBy([
1168
                'relationType' => [
1169
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
1170
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1171
                ],
1172
                'friend' => $otherUserId,
1173
                'user' => $currentUserId,
1174
            ])
1175
        ;
1176
1177
        if (null !== $relation) {
1178
            return true;
1179
        }
1180
1181
        $friendsOfCurrentUser = $em->getRepository(UserRelUser::class)
1182
            ->findBy([
1183
                'relationType' => [
1184
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
1185
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1186
                ],
1187
                'user' => $currentUserId,
1188
            ])
1189
        ;
1190
1191
        foreach ($friendsOfCurrentUser as $friendRelation) {
1192
            $friendId = $friendRelation->getFriend()->getId();
1193
            $relationThroughFriend = $em->getRepository(UserRelUser::class)
1194
                ->findOneBy([
1195
                    'relationType' => [
1196
                        UserRelUser::USER_RELATION_TYPE_FRIEND,
1197
                        UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1198
                    ],
1199
                    'friend' => $otherUserId,
1200
                    'user' => $friendId,
1201
                ])
1202
            ;
1203
1204
            if (null !== $relationThroughFriend) {
1205
                return true;
1206
            }
1207
        }
1208
1209
        return false;
1210
    }
1211
1212
    /**
1213
     * Checks the chat status of a user based on their user ID. It verifies if the user's chat status
1214
     * is active (indicated by a status of 1).
1215
     */
1216
    private function checkUserStatus(int $userId, UserRepository $userRepository): bool
1217
    {
1218
        $userStatus = $userRepository->getExtraUserDataByField($userId, 'user_chat_status');
1219
1220
        return !empty($userStatus) && isset($userStatus['user_chat_status']) && 1 === (int) $userStatus['user_chat_status'];
1221
    }
1222
1223
    /**
1224
     * Retrieves the most recent legal terms for a specified language. If no terms are found for the given language,
1225
     * the function attempts to retrieve terms for the platform's default language. If terms are still not found,
1226
     * it defaults to English ('en_US').
1227
     */
1228
    private function getLastConditionByLanguage(LanguageRepository $languageRepo, string $isoCode, LegalRepository $legalTermsRepo, SettingsManager $settingsManager): ?Legal
1229
    {
1230
        $language = $languageRepo->findByIsoCode($isoCode);
1231
        $languageId = (int) $language->getId();
1232
        $term = $legalTermsRepo->getLastConditionByLanguage($languageId);
1233
        if (!$term) {
1234
            $defaultLanguage = $settingsManager->getSetting('language.platform_language');
1235
            $language = $languageRepo->findByIsoCode($defaultLanguage);
1236
            $languageId = (int) $language->getId();
1237
            $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
1238
            if (!$term) {
1239
                $language = $languageRepo->findByIsoCode('en_US');
1240
                $languageId = (int) $language->getId();
1241
                $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
1242
            }
1243
        }
1244
1245
        return $term;
1246
    }
1247
1248
    private function resolveLegalLanguageId(
1249
        LanguageRepository $languageRepo,
1250
        string $isoCode,
1251
        SettingsManager $settingsManager
1252
    ): int {
1253
        $candidates = [
1254
            $isoCode,
1255
            (string) $settingsManager->getSetting('language.platform_language'),
1256
            'en_US',
1257
        ];
1258
1259
        foreach ($candidates as $code) {
1260
            if ('' === trim((string) $code)) {
1261
                continue;
1262
            }
1263
1264
            $language = $languageRepo->findByIsoCode($code);
1265
            if ($language) {
1266
                return (int) $language->getId();
1267
            }
1268
        }
1269
1270
        return 0;
1271
    }
1272
1273
    private function resolveLatestTermsVersion(
1274
        string $isoCode,
1275
        LanguageRepository $languageRepo,
1276
        LegalRepository $legalRepo,
1277
        SettingsManager $settingsManager
1278
    ): ?array {
1279
        $candidates = [];
1280
1281
        $lang = $languageRepo->findByIsoCode($isoCode);
1282
        if ($lang) {
1283
            $candidates[] = (int) $lang->getId();
1284
        }
1285
1286
        $platformIso = (string) $settingsManager->getSetting('language.platform_language');
1287
        if ($platformIso !== '') {
1288
            $platformLang = $languageRepo->findByIsoCode($platformIso);
1289
            if ($platformLang) {
1290
                $candidates[] = (int) $platformLang->getId();
1291
            }
1292
        }
1293
1294
        $en = $languageRepo->findByIsoCode('en_US');
1295
        if ($en) {
1296
            $candidates[] = (int) $en->getId();
1297
        }
1298
1299
        $candidates = array_values(array_unique(array_filter($candidates)));
1300
1301
        foreach ($candidates as $languageId) {
1302
            $version = $legalRepo->findLatestVersionByLanguage($languageId);
1303
            if ($version > 0) {
1304
                return ['languageId' => $languageId, 'version' => $version];
1305
            }
1306
        }
1307
1308
        return null;
1309
    }
1310
}
1311