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

SocialController::getUserProfile()   B

Complexity

Conditions 7
Paths 25

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 29
nc 25
nop 8
dl 0
loc 51
rs 8.5226
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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