Passed
Push — dependabot/npm_and_yarn/tar-7.... ( ae31d2...265b94 )
by
unknown
21:55 queued 09:58
created

SocialController::getLegalStatus()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 69
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 36
nc 10
nop 7
dl 0
loc 69
rs 8.0995
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\DateTimeHelper;
18
use Chamilo\CoreBundle\Helpers\UserHelper;
19
use Chamilo\CoreBundle\Repository\ExtraFieldOptionsRepository;
20
use Chamilo\CoreBundle\Repository\ExtraFieldRepository;
21
use Chamilo\CoreBundle\Repository\LanguageRepository;
22
use Chamilo\CoreBundle\Repository\LegalRepository;
23
use Chamilo\CoreBundle\Repository\MessageRepository;
24
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
25
use Chamilo\CoreBundle\Repository\Node\MessageAttachmentRepository;
26
use Chamilo\CoreBundle\Repository\Node\UsergroupRepository;
27
use Chamilo\CoreBundle\Repository\Node\UserRepository;
28
use Chamilo\CoreBundle\Repository\TrackEOnlineRepository;
29
use Chamilo\CoreBundle\Security\Authorization\Voter\UserVoter;
30
use Chamilo\CoreBundle\Serializer\UserToJsonNormalizer;
31
use Chamilo\CoreBundle\Settings\SettingsManager;
32
use Chamilo\CourseBundle\Repository\CForumThreadRepository;
33
use DateTime;
34
use DateTimeInterface;
35
use Doctrine\ORM\EntityManagerInterface;
36
use Exception;
37
use ExtraFieldValue;
38
use MessageManager;
39
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
40
use Symfony\Component\HttpFoundation\File\UploadedFile;
41
use Symfony\Component\HttpFoundation\JsonResponse;
42
use Symfony\Component\HttpFoundation\Request;
43
use Symfony\Component\HttpFoundation\RequestStack;
44
use Symfony\Component\HttpFoundation\Response;
45
use Symfony\Component\Mailer\MailerInterface;
46
use Symfony\Component\Mime\Email;
47
use Symfony\Component\Routing\Attribute\Route;
48
use Symfony\Component\Security\Http\Attribute\CurrentUser;
49
use Symfony\Component\Security\Http\Attribute\IsGranted;
50
use Symfony\Contracts\Translation\TranslatorInterface;
51
use UserManager;
52
53
#[IsGranted('ROLE_USER')]
54
#[Route('/social-network')]
55
class SocialController extends AbstractController
56
{
57
    private const TERMS_SECTIONS = [
58
        0 => ['title' => 'Terms and Conditions', 'subtitle' => null],
59
        1 => ['title' => 'Personal data collection', 'subtitle' => 'Why do we collect this data?'],
60
        2 => ['title' => 'Personal data recording', 'subtitle' => 'Where do we record the data?'],
61
        3 => ['title' => 'Personal data organization', 'subtitle' => 'How is the data structured?'],
62
        4 => ['title' => 'Personal data structure', 'subtitle' => 'How is the data structured in this software?'],
63
        5 => ['title' => 'Personal data conservation', 'subtitle' => 'How long do we save the data?'],
64
        6 => ['title' => 'Personal data adaptation or modification', 'subtitle' => 'What changes can we make to the data?'],
65
        7 => ['title' => 'Personal data extraction', 'subtitle' => 'What do we extract data for and which data is it?'],
66
        8 => ['title' => 'Personal data queries', 'subtitle' => 'Who can consult the personal data? For what purpose?'],
67
        9 => ['title' => 'Personal data use', 'subtitle' => 'How and for what can we use the personal data?'],
68
        10 => ['title' => 'Personal data communication and sharing', 'subtitle' => 'With whom can we share them?'],
69
        11 => ['title' => 'Personal data interconnection', 'subtitle' => 'Do we have another system with which Chamilo interacts?'],
70
        12 => ['title' => 'Personal data limitation', 'subtitle' => 'What are the limits that we will always respect?'],
71
        13 => ['title' => 'Personal data deletion', 'subtitle' => 'After how long do we erase the data?'],
72
        14 => ['title' => 'Personal data destruction', 'subtitle' => 'What happens if the data is destroyed?'],
73
        15 => ['title' => 'Personal data profiling', 'subtitle' => 'For what purpose do we process personal data?'],
74
    ];
75
76
    public function __construct(
77
        private readonly UserHelper $userHelper,
78
        private readonly DateTimeHelper $dateTimeHelper,
79
    ) {}
80
81
    #[Route('/personal-data/{userId}', name: 'chamilo_core_social_personal_data')]
82
    public function getPersonalData(
83
        int $userId,
84
        SettingsManager $settingsManager,
85
        UserToJsonNormalizer $userToJsonNormalizer
86
    ): JsonResponse {
87
        $propertiesToJson = $userToJsonNormalizer->serializeUserData($userId);
88
        $properties = $propertiesToJson ? json_decode($propertiesToJson, true) : [];
89
90
        $officerData = [
91
            ['name' => $settingsManager->getSetting('privacy.data_protection_officer_name')],
92
            ['role' => $settingsManager->getSetting('privacy.data_protection_officer_role')],
93
            ['email' => $settingsManager->getSetting('privacy.data_protection_officer_email')],
94
        ];
95
        $properties['officer_data'] = $officerData;
96
97
        $dataForVue = [
98
            'personalData' => $properties,
99
        ];
100
101
        return $this->json($dataForVue);
102
    }
103
104
    #[Route('/terms-and-conditions/{userId}', name: 'chamilo_core_social_terms')]
105
    public function getLegalTerms(
106
        int $userId,
107
        Request $request,
108
        SettingsManager $settingsManager,
109
        TranslatorInterface $translator,
110
        LegalRepository $legalTermsRepo,
111
        UserRepository $userRepo,
112
        LanguageRepository $languageRepo
113
    ): JsonResponse {
114
        $user = $userRepo->find($userId);
115
        if (!$user) {
116
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
117
        }
118
119
        $isoCode = $user->getLocale();
120
        $showAccepted = '1' === (string) $request->query->get('accepted', '0');
121
122
        $languageId = 0;
123
        $version = 0;
124
125
        if ($showAccepted) {
126
            $extraFieldValue = new ExtraFieldValue('user');
127
            $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
128
129
            if (!empty($value['value'])) {
130
                [$acceptedVersion, $acceptedLanguageId] = explode(':', $value['value']);
131
                $languageId = (int) $acceptedLanguageId;
132
                $version = (int) $acceptedVersion;
133
            } else {
134
                return $this->json(['error' => 'No accepted terms found'], Response::HTTP_NOT_FOUND);
135
            }
136
        } else {
137
            $resolved = $this->resolveLatestTermsVersion($isoCode, $languageRepo, $legalTermsRepo, $settingsManager);
138
            if (!$resolved) {
139
                return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
140
            }
141
            $languageId = (int) $resolved['languageId'];
142
            $version = (int) $resolved['version'];
143
        }
144
145
        $rows = $legalTermsRepo->findByLanguageAndVersion($languageId, $version);
146
        if (empty($rows)) {
147
            return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
148
        }
149
150
        $terms = [];
151
        foreach ($rows as $row) {
152
            $type = $row->getType();
153
            $section = self::TERMS_SECTIONS[$type] ?? self::TERMS_SECTIONS[0];
154
155
            $content = $row->getContent() ?? '';
156
            if ('' === trim($content)) {
157
                continue;
158
            }
159
160
            $terms[] = [
161
                'title' => $translator->trans($section['title'], [], 'messages', $isoCode),
162
                'subtitle' => $section['subtitle']
163
                    ? $translator->trans($section['subtitle'], [], 'messages', $isoCode)
164
                    : null,
165
                'content' => $content,
166
            ];
167
        }
168
169
        $pubDate = $this->dateTimeHelper->localTimeYmdHis($rows[0]->getDate(), null, 'UTC');
170
171
        return $this->json([
172
            'terms' => $terms,
173
            'date_text' => $translator->trans('Publication date', [], 'messages', $isoCode).': '.$pubDate,
174
            'version' => $version,
175
            'language_id' => $languageId,
176
            'showing_accepted' => $showAccepted,
177
        ]);
178
    }
179
180
    #[Route('/legal-status/{userId}', name: 'chamilo_core_social_legal_status')]
181
    public function getLegalStatus(
182
        int $userId,
183
        #[CurrentUser] User $currentUser,
184
        SettingsManager $settingsManager,
185
        TranslatorInterface $translator,
186
        UserRepository $userRepo,
187
        LanguageRepository $languageRepo,
188
        LegalRepository $legalTermsRepo
189
    ): JsonResponse {
190
        $allowTermsConditions = 'true' === $settingsManager->getSetting('registration.allow_terms_conditions');
191
        if (!$allowTermsConditions) {
192
            throw $this->createAccessDeniedException(
193
                $translator->trans('No terms and conditions available')
194
            );
195
        }
196
197
        /*if (!$currentUser) {
198
            throw $this->createAccessDeniedException(
199
                $translator->trans('User not found')
200
            );
201
        }*/
202
203
        if ($userId !== $currentUser->getId()) {
204
            throw $this->createAccessDeniedException();
205
        }
206
207
        $isoCode = $currentUser->getLocale();
208
209
        $resolved = $this->resolveLatestTermsVersion($isoCode, $languageRepo, $legalTermsRepo, $settingsManager);
210
        if (!$resolved) {
211
            return $this->json([
212
                'isAccepted' => false,
213
                'message' => $translator->trans('No terms and conditions available', [], 'messages'),
214
            ], Response::HTTP_NOT_FOUND);
215
        }
216
217
        $latestLanguageId = (int) $resolved['languageId'];
218
        $latestVersion = (int) $resolved['version'];
219
220
        $extraFieldValue = new ExtraFieldValue('user');
221
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'legal_accept');
222
223
        if (empty($value['value'])) {
224
            return $this->json([
225
                'isAccepted' => false,
226
                'message' => $translator->trans('Send legal agreement', [], 'messages'),
227
            ]);
228
        }
229
230
        [$acceptedVersion, $acceptedLanguageId, $acceptedTime] = explode(':', $value['value']);
231
        $dateTime = new DateTime('@'.(int) $acceptedTime);
232
233
        $isLatestAccepted = null !== $latestVersion
234
            && (int) $acceptedLanguageId === (int) $latestLanguageId
235
            && (int) $acceptedVersion === (int) $latestVersion;
236
237
        if (!$isLatestAccepted) {
238
            return $this->json([
239
                'isAccepted' => false,
240
                'acceptDate' => $dateTime->format('Y-m-d H:i:s'),
241
                'message' => $translator->trans('Please accept the latest version of the terms and conditions.', [], 'messages'),
242
            ]);
243
        }
244
245
        return $this->json([
246
            'isAccepted' => true,
247
            'acceptDate' => $dateTime->format('Y-m-d H:i:s'),
248
            'message' => '',
249
        ]);
250
    }
251
252
    #[Route('/send-legal-term', name: 'chamilo_core_social_send_legal_term', methods: ['POST'])]
253
    public function sendLegalTerm(
254
        Request $request,
255
        #[CurrentUser] User $currentUser,
256
        SettingsManager $settingsManager,
257
        TranslatorInterface $translator,
258
        LegalRepository $legalTermsRepo,
259
        UserRepository $userRepo,
260
        LanguageRepository $languageRepo
261
    ): JsonResponse {
262
        $data = json_decode($request->getContent(), true);
263
        if (!is_array($data)) {
264
            return $this->json(['error' => 'Invalid JSON body'], Response::HTTP_BAD_REQUEST);
265
        }
266
267
        $requestedUserId = isset($data['userId']) ? (int) $data['userId'] : 0;
268
269
        // Non-admin users can only accept terms for themselves.
270
        if (!$this->isGranted('ROLE_ADMIN')) {
271
            $targetUserId = $currentUser->getId();
272
            $user = $currentUser;
273
        } else {
274
            $targetUserId = $requestedUserId > 0 ? $requestedUserId : 0;
275
            if (0 === $targetUserId) {
276
                return $this->json(['error' => 'Missing or invalid userId'], Response::HTTP_BAD_REQUEST);
277
            }
278
            $user = $userRepo->find($targetUserId);
279
            if (!$user) {
280
                return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
281
            }
282
        }
283
284
        $isoCode = $user->getLocale();
285
        $resolved = $this->resolveLatestTermsVersion($isoCode, $languageRepo, $legalTermsRepo, $settingsManager);
286
        if (!$resolved) {
287
            return $this->json(['error' => 'Terms not found'], Response::HTTP_NOT_FOUND);
288
        }
289
        $languageId = (int) $resolved['languageId'];
290
        $version = (int) $resolved['version'];
291
292
        $legalAcceptType = $version . ':' . $languageId . ':' . time();
293
        UserManager::update_extra_field_value($targetUserId, 'legal_accept', $legalAcceptType);
294
295
        // Notify bosses (kept as-is, now based on $targetUserId)
296
        $bossList = UserManager::getStudentBossList($targetUserId);
297
        if (!empty($bossList)) {
298
            $bossList = array_column($bossList, 'boss_id');
299
            foreach ($bossList as $bossId) {
300
                $subjectEmail = sprintf(
301
                    $translator->trans('User %s signed the agreement.', [], 'messages'),
302
                    $user->getFullName()
303
                );
304
                $contentEmail = sprintf(
305
                    $translator->trans('User %s signed the agreement the %s.', [], 'messages'),
306
                    $user->getFullName(),
307
                    $this->dateTimeHelper->localTimeYmdHis(null, null, 'UTC')
308
                );
309
310
                MessageManager::send_message_simple(
311
                    (int) $bossId,
312
                    $subjectEmail,
313
                    $contentEmail,
314
                    $targetUserId
315
                );
316
            }
317
        }
318
319
        return $this->json([
320
            'success' => true,
321
            'message' => $translator->trans('Terms accepted successfully.', [], 'messages'),
322
        ]);
323
    }
324
325
    #[Route('/delete-legal', name: 'chamilo_core_social_delete_legal', methods: ['POST'])]
326
    public function deleteLegal(
327
        #[CurrentUser] User $currentUser,
328
        Request $request,
329
        TranslatorInterface $translator
330
    ): JsonResponse {
331
        $data = json_decode($request->getContent(), true);
332
        if (!is_array($data)) {
333
            return $this->json(['error' => 'Invalid JSON body'], Response::HTTP_BAD_REQUEST);
334
        }
335
336
        // Backward compatible: if userId is not provided, default to current user.
337
        $requestedUserId = isset($data['userId']) ? (int) $data['userId'] : 0;
338
339
        // Non-admin users can only revoke their own consent (ignore provided userId if different).
340
        if (!$this->isGranted('ROLE_ADMIN')) {
341
            $targetUserId = $currentUser->getId();
342
        } else {
343
            // Admin must explicitly provide a valid userId.
344
            $targetUserId = $requestedUserId > 0 ? $requestedUserId : 0;
345
            if (0 === $targetUserId) {
346
                return $this->json(['error' => 'Missing or invalid userId'], Response::HTTP_BAD_REQUEST);
347
            }
348
        }
349
350
        $extraFieldValue = new ExtraFieldValue('user');
351
352
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($targetUserId, 'legal_accept');
353
        if (!empty($value['id'])) {
354
            $extraFieldValue->delete($value['id']);
355
        }
356
357
        $value = $extraFieldValue->get_values_by_handler_and_field_variable($targetUserId, 'termactivated');
358
        if (!empty($value['id'])) {
359
            $extraFieldValue->delete($value['id']);
360
        }
361
362
        return $this->json([
363
            'success' => true,
364
            'message' => $translator->trans('Legal consent revoked successfully.'),
365
        ]);
366
    }
367
368
    #[Route('/handle-privacy-request', name: 'chamilo_core_social_handle_privacy_request', methods: ['POST'])]
369
    public function handlePrivacyRequest(
370
        Request $request,
371
        #[CurrentUser] User $currentUser,
372
        SettingsManager $settingsManager,
373
        UserRepository $userRepo,
374
        TranslatorInterface $translator,
375
        MailerInterface $mailer
376
    ): JsonResponse {
377
        $data = json_decode($request->getContent(), true);
378
        if (!is_array($data)) {
379
            return $this->json(['success' => false, 'message' => 'Invalid JSON body'], Response::HTTP_BAD_REQUEST);
380
        }
381
382
        $requestedUserId = isset($data['userId']) ? (int) $data['userId'] : 0;
383
        $explanation = (string) ($data['explanation'] ?? '');
384
        $requestType = (string) ($data['requestType'] ?? '');
385
386
        // Non-admin users can only create requests for themselves
387
        if (!$this->isGranted('ROLE_ADMIN')) {
388
            $targetUserId = $currentUser->getId();
389
            $user = $currentUser;
390
        } else {
391
            // Admin can create requests for another user if explicitly provided
392
            $targetUserId = $requestedUserId > 0 ? $requestedUserId : 0;
393
            if (0 === $targetUserId) {
394
                return $this->json(['success' => false, 'message' => 'Missing or invalid userId'], Response::HTTP_BAD_REQUEST);
395
            }
396
            $user = $userRepo->find($targetUserId);
397
            if (!$user) {
398
                return $this->json(['success' => false, 'message' => 'User not found'], Response::HTTP_NOT_FOUND);
399
            }
400
        }
401
402
        $baseUrl = $request->getSchemeAndHttpHost() . $request->getBasePath();
403
        $link = $baseUrl . '/main/admin/user_list_consent.php';
404
405
        if ('delete_account' === $requestType) {
406
            $fieldToUpdate = 'request_for_delete_account';
407
            $justificationFieldToUpdate = 'request_for_delete_account_justification';
408
            $emailSubject = $translator->trans('Request for account removal');
409
            $emailContent = sprintf(
410
                $translator->trans('User %s asked for the deletion of his/her account, explaining that "%s". You can process the request here: %s'),
411
                $user->getFullName(),
412
                $explanation,
413
                '<a href="'.$link.'">'.$link.'</a>'
414
            );
415
        } elseif ('delete_legal' === $requestType) {
416
            $fieldToUpdate = 'request_for_legal_agreement_consent_removal';
417
            $justificationFieldToUpdate = 'request_for_legal_agreement_consent_removal_justification';
418
            $emailSubject = $translator->trans('Request for consent withdrawal on legal terms');
419
            $emailContent = sprintf(
420
                $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'),
421
                $user->getFullName(),
422
                $explanation,
423
                '<a href="'.$link.'">'.$link.'</a>'
424
            );
425
        } else {
426
            return $this->json(['success' => false, 'message' => 'Invalid action type'], Response::HTTP_BAD_REQUEST);
427
        }
428
429
        UserManager::createDataPrivacyExtraFields();
430
        UserManager::update_extra_field_value($targetUserId, $fieldToUpdate, 1);
431
        UserManager::update_extra_field_value($targetUserId, $justificationFieldToUpdate, $explanation);
432
433
        $emailOfficer = (string) $settingsManager->getSetting('profile.data_protection_officer_email');
434
435
        if ('' !== trim($emailOfficer)) {
436
            $email = (new Email())
437
                ->from($user->getEmail())
438
                ->to($emailOfficer)
439
                ->subject($emailSubject)
440
                ->html($emailContent);
441
442
            $mailer->send($email);
443
        } else {
444
            MessageManager::sendMessageToAllAdminUsers($user->getId(), $emailSubject, $emailContent);
445
        }
446
447
        return $this->json([
448
            'success' => true,
449
            'message' => $translator->trans('Your request has been received.'),
450
        ]);
451
    }
452
453
    #[Route('/groups/{userId}', name: 'chamilo_core_social_groups')]
454
    public function getGroups(
455
        int $userId,
456
        UsergroupRepository $usergroupRepository,
457
        CForumThreadRepository $forumThreadRepository,
458
        SettingsManager $settingsManager,
459
        RequestStack $requestStack
460
    ): JsonResponse {
461
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
462
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');
463
        $items = [];
464
        $goToUrl = '';
465
466
        if (!empty($cid)) {
467
            $threads = $forumThreadRepository->getThreadsBySubscriptions($userId, $cid);
468
            foreach ($threads as $thread) {
469
                $threadId = $thread->getIid();
470
                $forumId = (int) $thread->getForum()->getIid();
471
                $items[] = [
472
                    'id' => $threadId,
473
                    'name' => $thread->getTitle(),
474
                    'description' => '',
475
                    'url' => $baseUrl.'/main/forum/viewthread.php?cid='.$cid.'&sid=0&gid=0&forum='.$forumId.'&thread='.$threadId,
476
                ];
477
            }
478
            $goToUrl = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
479
        } else {
480
            $groups = $usergroupRepository->getGroupsByUser($userId);
481
            foreach ($groups as $group) {
482
                $items[] = [
483
                    'id' => $group->getId(),
484
                    'name' => $group->getTitle(),
485
                    'description' => $group->getDescription(),
486
                    'url' => $baseUrl.'/resources/usergroups/show/'.$group->getId(),
487
                ];
488
            }
489
        }
490
491
        return $this->json([
492
            'items' => $items,
493
            'go_to' => $goToUrl,
494
        ]);
495
    }
496
497
    #[Route('/group/{groupId}/discussion/{discussionId}/messages', name: 'chamilo_core_social_group_discussion_messages')]
498
    public function getDiscussionMessages(
499
        $groupId,
500
        $discussionId,
501
        MessageRepository $messageRepository,
502
        UserRepository $userRepository,
503
        MessageAttachmentRepository $attachmentRepository
504
    ): JsonResponse {
505
        $messages = $messageRepository->getMessagesByGroupAndMessage((int) $groupId, (int) $discussionId);
506
507
        $formattedMessages = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository);
508
509
        return $this->json($formattedMessages);
510
    }
511
512
    #[Route('/get-forum-link', name: 'get_forum_link')]
513
    public function getForumLink(
514
        SettingsManager $settingsManager,
515
        RequestStack $requestStack
516
    ): JsonResponse {
517
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
518
        $cid = (int) $settingsManager->getSetting('forum.global_forums_course_id');
519
520
        $goToLink = '';
521
        if (!empty($cid)) {
522
            $goToLink = $baseUrl.'/main/forum/index.php?cid='.$cid.'&sid=0&gid=0';
523
        }
524
525
        return $this->json(['go_to' => $goToLink]);
526
    }
527
528
    #[Route('/invite-friends/{userId}/{groupId}', name: 'chamilo_core_social_invite_friends')]
529
    public function inviteFriends(
530
        int $userId,
531
        int $groupId,
532
        UserRepository $userRepository,
533
        UsergroupRepository $usergroupRepository,
534
        IllustrationRepository $illustrationRepository
535
    ): JsonResponse {
536
        $user = $userRepository->find($userId);
537
        if (!$user) {
538
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
539
        }
540
541
        $group = $usergroupRepository->find($groupId);
542
        if (!$group) {
543
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
544
        }
545
546
        $friends = $userRepository->getFriendsNotInGroup($userId, $groupId);
547
548
        $friendsList = array_map(function ($friend) use ($illustrationRepository) {
549
            return [
550
                'id' => $friend->getId(),
551
                'name' => $friend->getFirstName().' '.$friend->getLastName(),
552
                'avatar' => $illustrationRepository->getIllustrationUrl($friend),
553
            ];
554
        }, $friends);
555
556
        return $this->json(['friends' => $friendsList]);
557
    }
558
559
    #[Route('/add-users-to-group/{groupId}', name: 'chamilo_core_social_add_users_to_group')]
560
    public function addUsersToGroup(Request $request, int $groupId, UsergroupRepository $usergroupRepository): JsonResponse
561
    {
562
        $data = json_decode($request->getContent(), true);
563
        $userIds = $data['userIds'] ?? [];
564
565
        try {
566
            $usergroupRepository->addUserToGroup($userIds, $groupId);
567
568
            return $this->json(['success' => true, 'message' => 'Users added to group successfully.']);
569
        } catch (Exception $e) {
570
            return $this->json(['success' => false, 'message' => 'An error occurred: '.$e->getMessage()], Response::HTTP_BAD_REQUEST);
571
        }
572
    }
573
574
    #[Route('/group/{groupId}/invited-users', name: 'chamilo_core_social_group_invited_users')]
575
    public function groupInvitedUsers(int $groupId, UsergroupRepository $usergroupRepository, IllustrationRepository $illustrationRepository): JsonResponse
576
    {
577
        $invitedUsers = $usergroupRepository->getInvitedUsersByGroup($groupId);
578
579
        $invitedUsersList = array_map(function ($user) {
580
            return [
581
                'id' => $user['id'],
582
                'name' => $user['username'],
583
                // 'avatar' => $illustrationRepository->getIllustrationUrl($user),
584
            ];
585
        }, $invitedUsers);
586
587
        return $this->json(['invitedUsers' => $invitedUsersList]);
588
    }
589
590
    #[Route('/user-profile/{userId}', name: 'chamilo_core_social_user_profile')]
591
    public function getUserProfile(
592
        int $userId,
593
        SettingsManager $settingsManager,
594
        LanguageRepository $languageRepository,
595
        UserRepository $userRepository,
596
        RequestStack $requestStack,
597
        TrackEOnlineRepository $trackOnlineRepository,
598
        ExtraFieldRepository $extraFieldRepository,
599
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
600
    ): JsonResponse {
601
        $user = $userRepository->find($userId);
602
        if (!$user) {
603
            throw $this->createNotFoundException('User not found');
604
        }
605
606
        $baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
607
        $profileFieldsVisibilityJson = $settingsManager->getSetting('profile.profile_fields_visibility');
608
        $decoded = json_decode($profileFieldsVisibilityJson, true);
609
        $profileFieldsVisibility = (\is_array($decoded) && isset($decoded['options']))
610
            ? $decoded['options']
611
            : [];
612
613
        $vCardUserLink = $profileFieldsVisibility['vcard'] ?? true ? $baseUrl.'/main/social/vcard_export.php?userId='.(int) $userId : '';
614
615
        $languageInfo = null;
616
        if ($profileFieldsVisibility['language'] ?? true) {
617
            $language = $languageRepository->findByIsoCode($user->getLocale());
618
            if ($language) {
619
                $languageInfo = [
620
                    'label' => $language->getOriginalName(),
621
                    'value' => $language->getEnglishName(),
622
                    'code' => $language->getIsocode(),
623
                ];
624
            }
625
        }
626
627
        $isUserOnline = $trackOnlineRepository->isUserOnline($userId);
628
        $userOnlyInChat = $this->checkUserStatus($userId, $userRepository);
629
        $extraFields = $this->getExtraFieldBlock($userId, $userRepository, $settingsManager, $extraFieldRepository, $extraFieldOptionsRepository);
630
631
        $response = [
632
            'vCardUserLink' => $vCardUserLink,
633
            'language' => $languageInfo,
634
            'visibility' => $profileFieldsVisibility,
635
            'isUserOnline' => $isUserOnline,
636
            'userOnlyInChat' => $userOnlyInChat,
637
            'extraFields' => $extraFields,
638
        ];
639
640
        return $this->json($response);
641
    }
642
643
    private function getExtraFieldBlock(
644
        int $userId,
645
        UserRepository $userRepository,
646
        SettingsManager $settingsManager,
647
        ExtraFieldRepository $extraFieldRepository,
648
        ExtraFieldOptionsRepository $extraFieldOptionsRepository
649
    ): array {
650
        $user = $userRepository->find($userId);
651
        if (!$user) {
652
            return [];
653
        }
654
655
        $fieldVisibilityConfig = $settingsManager->getSetting('profile.profile_fields_visibility');
656
        $decoded = json_decode($fieldVisibilityConfig, true);
657
        $fieldVisibility = (\is_array($decoded) && isset($decoded['options']))
658
            ? $decoded['options']
659
            : [];
660
661
        $extraUserData = $userRepository->getExtraUserData($userId);
662
        $extraFieldsFormatted = [];
663
        foreach ($extraUserData as $key => $value) {
664
            $fieldVariable = str_replace('extra_', '', $key);
665
666
            $extraField = $extraFieldRepository->getHandlerFieldInfoByFieldVariable($fieldVariable, ExtraField::USER_FIELD_TYPE);
667
            if (!$extraField || !isset($fieldVisibility[$fieldVariable]) || !$fieldVisibility[$fieldVariable]) {
668
                continue;
669
            }
670
671
            $fieldValue = \is_array($value) ? implode(', ', $value) : $value;
672
673
            switch ($extraField['type']) {
674
                case ExtraField::FIELD_TYPE_RADIO:
675
                case ExtraField::FIELD_TYPE_SELECT:
676
                    $extraFieldOptions = $extraFieldOptionsRepository->getFieldOptionByFieldAndOption(
677
                        $extraField['id'],
678
                        $fieldValue,
679
                        ExtraField::USER_FIELD_TYPE
680
                    );
681
                    if (!empty($extraFieldOptions)) {
682
                        $fieldValue = implode(
683
                            ', ',
684
                            array_map(
685
                                fn (ExtraFieldOptions $opt) => $opt->getDisplayText(),
686
                                $extraFieldOptions
687
                            )
688
                        );
689
                    }
690
691
                    break;
692
693
                case ExtraField::FIELD_TYPE_GEOLOCALIZATION_COORDINATES:
694
                case ExtraField::FIELD_TYPE_GEOLOCALIZATION:
695
                    $geoData = explode('::', $fieldValue ?: '');
696
                    $locationName = $geoData[0];
697
                    $coordinates = $geoData[1] ?? '';
698
                    $fieldValue = $locationName;
699
700
                    break;
701
            }
702
703
            $extraFieldsFormatted[] = [
704
                'variable' => $fieldVariable,
705
                'label' => $extraField['display_text'],
706
                'value' => $fieldValue,
707
            ];
708
        }
709
710
        return $extraFieldsFormatted;
711
    }
712
713
    #[Route('/invitations/{userId}', name: 'chamilo_core_social_invitations')]
714
    public function getInvitations(
715
        int $userId,
716
        MessageRepository $messageRepository,
717
        UsergroupRepository $usergroupRepository,
718
        UserRepository $userRepository,
719
        TranslatorInterface $translator
720
    ): JsonResponse {
721
        $user = $this->userHelper->getCurrent();
722
        if ($userId !== $user->getId()) {
723
            return $this->json(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED);
724
        }
725
726
        $receivedMessages = $messageRepository->findReceivedInvitationsByUser($user);
727
        $receivedInvitations = [];
728
        foreach ($receivedMessages as $message) {
729
            $sender = $message->getSender();
730
            $receivedInvitations[] = [
731
                'id' => $message->getId(),
732
                'itemId' => $sender->getId(),
733
                'itemName' => $sender->getFirstName().' '.$sender->getLastName(),
734
                'itemPicture' => $userRepository->getUserPicture($sender->getId()),
735
                'content' => $message->getContent(),
736
                'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
737
                'canAccept' => true,
738
                'canDeny' => true,
739
            ];
740
        }
741
742
        $sentMessages = $messageRepository->findSentInvitationsByUser($user);
743
        $sentInvitations = [];
744
        foreach ($sentMessages as $message) {
745
            foreach ($message->getReceivers() as $receiver) {
746
                $receiverUser = $receiver->getReceiver();
747
                $sentInvitations[] = [
748
                    'id' => $message->getId(),
749
                    'itemId' => $receiverUser->getId(),
750
                    'itemName' => $receiverUser->getFirstName().' '.$receiverUser->getLastName(),
751
                    'itemPicture' => $userRepository->getUserPicture($receiverUser->getId()),
752
                    'content' => $message->getContent(),
753
                    'date' => $message->getSendDate()->format('Y-m-d H:i:s'),
754
                    'canAccept' => false,
755
                    'canDeny' => false,
756
                ];
757
            }
758
        }
759
760
        $pendingGroupInvitations = [];
761
        $pendingGroups = $usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION);
762
763
        /** @var Usergroup $group */
764
        foreach ($pendingGroups as $group) {
765
            $isGroupVisible = 1 === (int) $group->getVisibility();
766
            $infoVisibility = !$isGroupVisible ? ' - '.$translator->trans('This group is closed.') : '';
767
            $pendingGroupInvitations[] = [
768
                'id' => $group->getId(),
769
                'itemId' => $group->getId(),
770
                'itemName' => $group->getTitle().$infoVisibility,
771
                'itemPicture' => $usergroupRepository->getUsergroupPicture($group->getId()),
772
                'content' => $group->getDescription(),
773
                'date' => $group->getCreatedAt()->format('Y-m-d H:i:s'),
774
                'canAccept' => $isGroupVisible,
775
                'canDeny' => true,
776
            ];
777
        }
778
779
        return $this->json([
780
            'receivedInvitations' => $receivedInvitations,
781
            'sentInvitations' => $sentInvitations,
782
            'pendingGroupInvitations' => $pendingGroupInvitations,
783
        ]);
784
    }
785
786
    #[IsGranted('ROLE_USER')]
787
    #[Route('/invitations/count/{userId}', name: 'chamilo_core_social_invitations_count')]
788
    public function getInvitationsCount(
789
        int $userId,
790
        MessageRepository $messageRepository,
791
        UsergroupRepository $usergroupRepository
792
    ): JsonResponse {
793
        $user = $this->userHelper->getCurrent();
794
        if ($userId !== $user->getId()) {
795
            return $this->json(['error' => 'Unauthorized']);
796
        }
797
798
        $receivedMessagesCount = \count($messageRepository->findReceivedInvitationsByUser($user));
799
        $pendingGroupInvitationsCount = \count($usergroupRepository->getGroupsByUser($userId, Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION));
800
        $totalInvitationsCount = $receivedMessagesCount + $pendingGroupInvitationsCount;
801
802
        return $this->json(['totalInvitationsCount' => $totalInvitationsCount]);
803
    }
804
805
    #[Route('/search', name: 'chamilo_core_social_search')]
806
    public function search(
807
        Request $request,
808
        UserRepository $userRepository,
809
        UsergroupRepository $usergroupRepository,
810
        TrackEOnlineRepository $trackOnlineRepository,
811
        MessageRepository $messageRepository
812
    ): JsonResponse {
813
        $query = $request->query->get('query', '');
814
        $type = $request->query->get('type', 'user');
815
        $from = $request->query->getInt('from', 0);
816
        $numberOfItems = $request->query->getInt('number_of_items', 1000);
817
818
        $formattedResults = [];
819
        if ('user' === $type) {
820
            $user = $this->userHelper->getCurrent();
821
            $results = $userRepository->searchUsersByTags($query, $user->getId(), 0, $from, $numberOfItems);
822
            foreach ($results as $item) {
823
                $isUserOnline = $trackOnlineRepository->isUserOnline($item['id']);
824
                $relation = $userRepository->getUserRelationWithType($user->getId(), $item['id']);
825
                $userReceiver = $userRepository->find($item['id']);
826
                $existingInvitations = $messageRepository->existingInvitations($user, $userReceiver);
827
                $formattedResults[] = [
828
                    'id' => $item['id'],
829
                    'name' => $item['firstname'].' '.$item['lastname'],
830
                    'avatar' => $userRepository->getUserPicture($item['id']),
831
                    'role' => 5 === $item['status'] ? 'student' : 'teacher',
832
                    'status' => $isUserOnline ? 'online' : 'offline',
833
                    'url' => '/social?id='.$item['id'],
834
                    'relationType' => $relation['relationType'] ?? null,
835
                    'existingInvitations' => $existingInvitations,
836
                ];
837
            }
838
        } elseif ('group' === $type) {
839
            // Perform group search
840
            $results = $usergroupRepository->searchGroupsByTags($query, $from, $numberOfItems);
841
            foreach ($results as $item) {
842
                $formattedResults[] = [
843
                    'id' => $item['id'],
844
                    'name' => $item['title'],
845
                    'description' => $item['description'] ?? '',
846
                    'image' => $usergroupRepository->getUsergroupPicture($item['id']),
847
                    'url' => '/resources/usergroups/show/'.$item['id'],
848
                ];
849
            }
850
        }
851
852
        return $this->json(['results' => $formattedResults]);
853
    }
854
855
    #[Route('/group-details/{groupId}', name: 'chamilo_core_social_group_details')]
856
    public function groupDetails(
857
        int $groupId,
858
        UsergroupRepository $usergroupRepository,
859
        TrackEOnlineRepository $trackOnlineRepository
860
    ): JsonResponse {
861
        $user = $this->userHelper->getCurrent();
862
        if (!$user) {
863
            return $this->json(['error' => 'User not authenticated'], Response::HTTP_UNAUTHORIZED);
864
        }
865
866
        /** @var Usergroup $group */
867
        $group = $usergroupRepository->find($groupId);
868
        if (!$group) {
0 ignored issues
show
introduced by
$group is of type Chamilo\CoreBundle\Entity\Usergroup, thus it always evaluated to true.
Loading history...
869
            return $this->json(['error' => 'Group not found'], Response::HTTP_NOT_FOUND);
870
        }
871
872
        $isMember = $usergroupRepository->isGroupMember($groupId, $user);
873
        $role = $usergroupRepository->getUserGroupRole($groupId, $user->getId());
874
        $isUserOnline = $trackOnlineRepository->isUserOnline($user->getId());
875
        $isModerator = $usergroupRepository->isGroupModerator($groupId, $user->getId());
876
877
        $groupDetails = [
878
            'id' => $group->getId(),
879
            'title' => $group->getTitle(),
880
            'description' => $group->getDescription(),
881
            'url' => $group->getUrl(),
882
            'image' => $usergroupRepository->getUsergroupPicture($group->getId()),
883
            'visibility' => (int) $group->getVisibility(),
884
            'allowMembersToLeaveGroup' => $group->getAllowMembersToLeaveGroup(),
885
            'isMember' => $isMember,
886
            'isModerator' => $isModerator,
887
            'role' => $role,
888
            'isUserOnline' => $isUserOnline,
889
            'isAllowedToLeave' => 1 === $group->getAllowMembersToLeaveGroup(),
890
        ];
891
892
        return $this->json($groupDetails);
893
    }
894
895
    #[Route('/group-action', name: 'chamilo_core_social_group_action')]
896
    public function group(
897
        Request $request,
898
        UsergroupRepository $usergroupRepository,
899
        EntityManagerInterface $em,
900
        MessageRepository $messageRepository
901
    ): JsonResponse {
902
        if (str_starts_with($request->headers->get('Content-Type'), 'multipart/form-data')) {
903
            $userId = $request->request->get('userId');
904
            $groupId = $request->request->get('groupId');
905
            $action = $request->request->get('action');
906
            $title = $request->request->get('title', '');
907
            $content = $request->request->get('content', '');
908
            $parentId = $request->request->get('parentId', 0);
909
            $editMessageId = $request->request->get('messageId', 0);
910
911
            $structuredFiles = [];
912
            if ($request->files->has('files')) {
913
                $files = $request->files->get('files');
914
                foreach ($files as $file) {
915
                    $structuredFiles[] = [
916
                        'name' => $file->getClientOriginalName(),
917
                        'full_path' => $file->getRealPath(),
918
                        'type' => $file->getMimeType(),
919
                        'tmp_name' => $file->getPathname(),
920
                        'error' => $file->getError(),
921
                        'size' => $file->getSize(),
922
                    ];
923
                }
924
            }
925
        } else {
926
            $data = json_decode($request->getContent(), true);
927
            $userId = $data['userId'] ?? null;
928
            $groupId = $data['groupId'] ?? null;
929
            $action = $data['action'] ?? null;
930
        }
931
932
        if (!$userId || !$groupId || !$action) {
933
            return $this->json(['error' => 'Missing parameters'], Response::HTTP_BAD_REQUEST);
934
        }
935
936
        try {
937
            switch ($action) {
938
                case 'accept':
939
                    $userRole = $usergroupRepository->getUserGroupRole($groupId, $userId);
940
                    if (\in_array(
941
                        $userRole,
942
                        [
943
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION_SENT_BY_USER,
944
                            Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION,
945
                        ]
946
                    )) {
947
                        $usergroupRepository->updateUserRole($userId, $groupId, Usergroup::GROUP_USER_PERMISSION_READER);
948
                    }
949
950
                    break;
951
952
                case 'join':
953
                    $usergroupRepository->addUserToGroup($userId, $groupId);
954
955
                    break;
956
957
                case 'deny':
958
                    $usergroupRepository->removeUserFromGroup($userId, $groupId, false);
959
960
                    break;
961
962
                case 'leave':
963
                    $usergroupRepository->removeUserFromGroup($userId, $groupId);
964
965
                    break;
966
967
                case 'reply_message_group':
968
                    $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...
969
970
                    // no break
971
                case 'edit_message_group':
972
                case 'add_message_group':
973
                    $res = MessageManager::send_message(
974
                        $userId,
975
                        $title,
976
                        $content,
977
                        $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...
978
                        [],
979
                        $groupId,
980
                        $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...
981
                        $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...
982
                        0,
983
                        $userId,
984
                        false,
985
                        0,
986
                        false,
987
                        false,
988
                        Message::MESSAGE_TYPE_GROUP
989
                    );
990
991
                    break;
992
993
                case 'delete_message_group':
994
                    $messageId = $data['messageId'] ?? null;
995
996
                    if (!$messageId) {
997
                        return $this->json(['error' => 'Missing messageId parameter'], Response::HTTP_BAD_REQUEST);
998
                    }
999
1000
                    $messageRepository->deleteTopicAndChildren($groupId, $messageId);
1001
1002
                    break;
1003
1004
                default:
1005
                    return $this->json(['error' => 'Invalid action'], Response::HTTP_BAD_REQUEST);
1006
            }
1007
1008
            $em->flush();
1009
1010
            return $this->json(['success' => 'Action completed successfully']);
1011
        } catch (Exception $e) {
1012
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
1013
        }
1014
    }
1015
1016
    #[Route('/user-action', name: 'chamilo_core_social_user_action')]
1017
    public function user(
1018
        Request $request,
1019
        UserRepository $userRepository,
1020
        MessageRepository $messageRepository,
1021
        EntityManagerInterface $em
1022
    ): JsonResponse {
1023
        $data = json_decode($request->getContent(), true);
1024
1025
        $userId = $data['userId'] ?? null;
1026
        $targetUserId = $data['targetUserId'] ?? null;
1027
        $action = $data['action'] ?? null;
1028
        $isMyFriend = $data['is_my_friend'] ?? false;
1029
        $subject = $data['subject'] ?? '';
1030
        $content = $data['content'] ?? '';
1031
1032
        if (!$userId || !$targetUserId || !$action) {
1033
            return $this->json(['error' => 'Missing parameters']);
1034
        }
1035
1036
        $currentUser = $userRepository->find($userId);
1037
        $friendUser = $userRepository->find($targetUserId);
1038
1039
        if (null === $currentUser || null === $friendUser) {
1040
            return $this->json(['error' => 'User not found']);
1041
        }
1042
1043
        try {
1044
            switch ($action) {
1045
                case 'send_invitation':
1046
                    $result = $messageRepository->sendInvitationToFriend($currentUser, $friendUser, $subject, $content);
1047
                    if (!$result) {
1048
                        return $this->json(['error' => 'Invitation already exists or could not be sent']);
1049
                    }
1050
1051
                    break;
1052
1053
                case 'send_message':
1054
                    $result = MessageManager::send_message($friendUser->getId(), $subject, $content);
1055
1056
                    break;
1057
1058
                case 'add_friend':
1059
                    $relationType = $isMyFriend ? UserRelUser::USER_RELATION_TYPE_FRIEND : UserRelUser::USER_UNKNOWN;
1060
1061
                    $userRepository->relateUsers($currentUser, $friendUser, $relationType);
1062
                    $userRepository->relateUsers($friendUser, $currentUser, $relationType);
1063
1064
                    $messageRepository->invitationAccepted($friendUser, $currentUser);
1065
1066
                    break;
1067
1068
                case 'deny_friend':
1069
                    $messageRepository->invitationDenied($friendUser, $currentUser);
1070
1071
                    break;
1072
1073
                default:
1074
                    return $this->json(['error' => 'Invalid action']);
1075
            }
1076
1077
            $em->flush();
1078
1079
            return $this->json(['success' => 'Action completed successfully']);
1080
        } catch (Exception $e) {
1081
            return $this->json(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
1082
        }
1083
    }
1084
1085
    #[Route('/user-relation/{currentUserId}/{profileUserId}', name: 'chamilo_core_social_get_user_relation')]
1086
    public function getUserRelation(int $currentUserId, int $profileUserId, EntityManagerInterface $em): JsonResponse
1087
    {
1088
        $isAllowed = $this->checkUserRelationship($currentUserId, $profileUserId, $em);
1089
1090
        return $this->json([
1091
            'isAllowed' => $isAllowed,
1092
        ]);
1093
    }
1094
1095
    #[Route('/online-status', name: 'chamilo_core_social_get_online_status', methods: ['POST'])]
1096
    public function getOnlineStatus(Request $request, TrackEOnlineRepository $trackOnlineRepository): JsonResponse
1097
    {
1098
        $data = json_decode($request->getContent(), true);
1099
        $userIds = $data['userIds'] ?? [];
1100
1101
        $onlineStatuses = [];
1102
        foreach ($userIds as $userId) {
1103
            $onlineStatuses[$userId] = $trackOnlineRepository->isUserOnline($userId);
1104
        }
1105
1106
        return $this->json($onlineStatuses);
1107
    }
1108
1109
    #[Route('/upload-group-picture/{groupId}', name: 'chamilo_core_social_upload_group_picture')]
1110
    public function uploadGroupPicture(
1111
        Request $request,
1112
        int $groupId,
1113
        UsergroupRepository $usergroupRepository,
1114
        IllustrationRepository $illustrationRepository
1115
    ): JsonResponse {
1116
        $file = $request->files->get('picture');
1117
        if ($file instanceof UploadedFile) {
1118
            $userGroup = $usergroupRepository->find($groupId);
1119
            $illustrationRepository->addIllustration($userGroup, $this->userHelper->getCurrent(), $file);
1120
        }
1121
1122
        return new JsonResponse(['success' => 'Group and image saved successfully'], Response::HTTP_OK);
1123
    }
1124
1125
    #[Route('/terms-restrictions/{userId}', name: 'chamilo_core_social_terms_restrictions')]
1126
    public function checkTermsRestrictions(
1127
        int $userId,
1128
        UserRepository $userRepo,
1129
        ExtraFieldRepository $extraFieldRepository,
1130
        TranslatorInterface $translator,
1131
        SettingsManager $settingsManager
1132
    ): JsonResponse {
1133
        /** @var User $user */
1134
        $user = $userRepo->find($userId);
1135
1136
        if (!$user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\CoreBundle\Entity\User, thus it always evaluated to true.
Loading history...
1137
            return $this->json(['error' => 'User not found'], Response::HTTP_NOT_FOUND);
1138
        }
1139
1140
        $isAdmin = $user->isAdmin() || $user->isSuperAdmin();
1141
1142
        $termActivated = false;
1143
        $blockButton = false;
1144
        $infoMessage = '';
1145
1146
        if (!$isAdmin) {
1147
            if ('true' === $settingsManager->getSetting('profile.show_terms_if_profile_completed')) {
1148
                $extraFieldValue = new ExtraFieldValue('user');
1149
                $value = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'termactivated');
1150
                if (isset($value['value'])) {
1151
                    $termActivated = !empty($value['value']) && 1 === (int) $value['value'];
1152
                }
1153
1154
                if (false === $termActivated) {
1155
                    $blockButton = true;
1156
                    $infoMessage .= $translator->trans('The terms and conditions have not yet been validated by your tutor.').'&nbsp;';
1157
                }
1158
1159
                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...
1160
                    $blockButton = true;
1161
                    $infoMessage .= $translator->trans('You must first fill your profile to enable the terms and conditions validation.');
1162
                }
1163
            }
1164
        }
1165
1166
        return $this->json([
1167
            'blockButton' => $blockButton,
1168
            'infoMessage' => $infoMessage,
1169
        ]);
1170
    }
1171
1172
    /**
1173
     * Formats a hierarchical structure of messages for display.
1174
     *
1175
     * This function takes an array of Message entities and recursively formats them into a hierarchical structure.
1176
     * Each message is formatted with details such as user information, creation date, content, and attachments.
1177
     * The function also assigns a level to each message based on its depth in the hierarchy for display purposes.
1178
     */
1179
    private function formatMessagesHierarchy(array $messages, UserRepository $userRepository, MessageAttachmentRepository $attachmentRepository, ?int $parentId = null, int $level = 0): array
1180
    {
1181
        $formattedMessages = [];
1182
1183
        /** @var Message $message */
1184
        foreach ($messages as $message) {
1185
            if (($message->getParent() ? $message->getParent()->getId() : null) === $parentId) {
1186
                $attachments = $message->getAttachments();
1187
                $attachmentsUrls = [];
1188
                $attachmentSize = 0;
1189
                if ($attachments) {
1190
                    /** @var MessageAttachment $attachment */
1191
                    foreach ($attachments as $attachment) {
1192
                        $attachmentsUrls[] = [
1193
                            'link' => $attachmentRepository->getResourceFileDownloadUrl($attachment),
1194
                            'filename' => $attachment->getFilename(),
1195
                            'size' => $attachment->getSize(),
1196
                        ];
1197
                        $attachmentSize += $attachment->getSize();
1198
                    }
1199
                }
1200
                $formattedMessage = [
1201
                    'id' => $message->getId(),
1202
                    'user' => $message->getSender()->getFullName(),
1203
                    'created' => $message->getSendDate()->format(DateTimeInterface::ATOM),
1204
                    'title' => $message->getTitle(),
1205
                    'content' => $message->getContent(),
1206
                    'parentId' => $message->getParent() ? $message->getParent()->getId() : null,
1207
                    'avatar' => $userRepository->getUserPicture($message->getSender()->getId()),
1208
                    'senderId' => $message->getSender()->getId(),
1209
                    'attachment' => $attachmentsUrls ?? null,
1210
                    'attachmentSize' => $attachmentSize > 0 ? $attachmentSize : null,
1211
                    'level' => $level,
1212
                ];
1213
1214
                $children = $this->formatMessagesHierarchy($messages, $userRepository, $attachmentRepository, $message->getId(), $level + 1);
1215
                if (!empty($children)) {
1216
                    $formattedMessage['children'] = $children;
1217
                }
1218
1219
                $formattedMessages[] = $formattedMessage;
1220
            }
1221
        }
1222
1223
        return $formattedMessages;
1224
    }
1225
1226
    /**
1227
     * Checks the relationship between the current user and another user.
1228
     *
1229
     * This method first checks for a direct relationship between the two users. If no direct relationship is found,
1230
     * it then checks for indirect relationships through common friends (friends of friends).
1231
     */
1232
    private function checkUserRelationship(int $currentUserId, int $otherUserId, EntityManagerInterface $em): bool
1233
    {
1234
        if ($currentUserId === $otherUserId) {
1235
            return true;
1236
        }
1237
1238
        $relation = $em->getRepository(UserRelUser::class)
1239
            ->findOneBy([
1240
                'relationType' => [
1241
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
1242
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1243
                ],
1244
                'friend' => $otherUserId,
1245
                'user' => $currentUserId,
1246
            ])
1247
        ;
1248
1249
        if (null !== $relation) {
1250
            return true;
1251
        }
1252
1253
        $friendsOfCurrentUser = $em->getRepository(UserRelUser::class)
1254
            ->findBy([
1255
                'relationType' => [
1256
                    UserRelUser::USER_RELATION_TYPE_FRIEND,
1257
                    UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1258
                ],
1259
                'user' => $currentUserId,
1260
            ])
1261
        ;
1262
1263
        foreach ($friendsOfCurrentUser as $friendRelation) {
1264
            $friendId = $friendRelation->getFriend()->getId();
1265
            $relationThroughFriend = $em->getRepository(UserRelUser::class)
1266
                ->findOneBy([
1267
                    'relationType' => [
1268
                        UserRelUser::USER_RELATION_TYPE_FRIEND,
1269
                        UserRelUser::USER_RELATION_TYPE_GOODFRIEND,
1270
                    ],
1271
                    'friend' => $otherUserId,
1272
                    'user' => $friendId,
1273
                ])
1274
            ;
1275
1276
            if (null !== $relationThroughFriend) {
1277
                return true;
1278
            }
1279
        }
1280
1281
        return false;
1282
    }
1283
1284
    /**
1285
     * Checks the chat status of a user based on their user ID. It verifies if the user's chat status
1286
     * is active (indicated by a status of 1).
1287
     */
1288
    private function checkUserStatus(int $userId, UserRepository $userRepository): bool
1289
    {
1290
        $userStatus = $userRepository->getExtraUserDataByField($userId, 'user_chat_status');
1291
1292
        return !empty($userStatus) && isset($userStatus['user_chat_status']) && 1 === (int) $userStatus['user_chat_status'];
1293
    }
1294
1295
    /**
1296
     * Retrieves the most recent legal terms for a specified language. If no terms are found for the given language,
1297
     * the function attempts to retrieve terms for the platform's default language. If terms are still not found,
1298
     * it defaults to English ('en_US').
1299
     */
1300
    private function getLastConditionByLanguage(LanguageRepository $languageRepo, string $isoCode, LegalRepository $legalTermsRepo, SettingsManager $settingsManager): ?Legal
1301
    {
1302
        $language = $languageRepo->findByIsoCode($isoCode);
1303
        $languageId = (int) $language->getId();
1304
        $term = $legalTermsRepo->getLastConditionByLanguage($languageId);
1305
        if (!$term) {
1306
            $defaultLanguage = $settingsManager->getSetting('language.platform_language');
1307
            $language = $languageRepo->findByIsoCode($defaultLanguage);
1308
            $languageId = (int) $language->getId();
1309
            $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
1310
            if (!$term) {
1311
                $language = $languageRepo->findByIsoCode('en_US');
1312
                $languageId = (int) $language->getId();
1313
                $term = $legalTermsRepo->getLastConditionByLanguage((int) $languageId);
1314
            }
1315
        }
1316
1317
        return $term;
1318
    }
1319
1320
    private function resolveLegalLanguageId(
1321
        LanguageRepository $languageRepo,
1322
        string $isoCode,
1323
        SettingsManager $settingsManager
1324
    ): int {
1325
        $candidates = [
1326
            $isoCode,
1327
            (string) $settingsManager->getSetting('language.platform_language'),
1328
            'en_US',
1329
        ];
1330
1331
        foreach ($candidates as $code) {
1332
            if ('' === trim((string) $code)) {
1333
                continue;
1334
            }
1335
1336
            $language = $languageRepo->findByIsoCode($code);
1337
            if ($language) {
1338
                return (int) $language->getId();
1339
            }
1340
        }
1341
1342
        return 0;
1343
    }
1344
1345
    private function resolveLatestTermsVersion(
1346
        string $isoCode,
1347
        LanguageRepository $languageRepo,
1348
        LegalRepository $legalRepo,
1349
        SettingsManager $settingsManager
1350
    ): ?array {
1351
        $candidates = [];
1352
1353
        $lang = $languageRepo->findByIsoCode($isoCode);
1354
        if ($lang) {
1355
            $candidates[] = (int) $lang->getId();
1356
        }
1357
1358
        $platformIso = (string) $settingsManager->getSetting('language.platform_language');
1359
        if ('' !== $platformIso) {
1360
            $platformLang = $languageRepo->findByIsoCode($platformIso);
1361
            if ($platformLang) {
1362
                $candidates[] = (int) $platformLang->getId();
1363
            }
1364
        }
1365
1366
        $en = $languageRepo->findByIsoCode('en_US');
1367
        if ($en) {
1368
            $candidates[] = (int) $en->getId();
1369
        }
1370
1371
        $candidates = array_values(array_unique(array_filter($candidates)));
1372
1373
        foreach ($candidates as $languageId) {
1374
            $version = $legalRepo->findLatestVersionByLanguage($languageId);
1375
            if ($version > 0) {
1376
                return ['languageId' => $languageId, 'version' => $version];
1377
            }
1378
        }
1379
1380
        return null;
1381
    }
1382
}
1383