Passed
Push — master ( 769c74...60f6e4 )
by Yannick
07:30 queued 29s
created

UserRepository::loadUserByIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Repository\Node;
8
9
use Chamilo\CoreBundle\Entity\AccessUrl;
10
use Chamilo\CoreBundle\Entity\Course;
11
use Chamilo\CoreBundle\Entity\ExtraField;
12
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
13
use Chamilo\CoreBundle\Entity\Message;
14
use Chamilo\CoreBundle\Entity\ResourceNode;
15
use Chamilo\CoreBundle\Entity\Session;
16
use Chamilo\CoreBundle\Entity\Tag;
17
use Chamilo\CoreBundle\Entity\TrackELogin;
18
use Chamilo\CoreBundle\Entity\TrackEOnline;
19
use Chamilo\CoreBundle\Entity\User;
20
use Chamilo\CoreBundle\Entity\Usergroup;
21
use Chamilo\CoreBundle\Entity\UsergroupRelUser;
22
use Chamilo\CoreBundle\Entity\UserRelTag;
23
use Chamilo\CoreBundle\Entity\UserRelUser;
24
use Chamilo\CoreBundle\Repository\ResourceRepository;
25
use Datetime;
26
use Doctrine\Common\Collections\Collection;
27
use Doctrine\Common\Collections\Criteria;
28
use Doctrine\DBAL\Types\Types;
29
use Doctrine\ORM\Query\Expr\Join;
30
use Doctrine\ORM\QueryBuilder;
31
use Doctrine\Persistence\ManagerRegistry;
32
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
33
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
34
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
35
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
36
37
use const MB_CASE_LOWER;
38
39
class UserRepository extends ResourceRepository implements PasswordUpgraderInterface
40
{
41
    protected ?UserPasswordHasherInterface $hasher = null;
42
43
    public const USER_IMAGE_SIZE_SMALL = 1;
44
    public const USER_IMAGE_SIZE_MEDIUM = 2;
45
    public const USER_IMAGE_SIZE_BIG = 3;
46
    public const USER_IMAGE_SIZE_ORIGINAL = 4;
47
48
    public function __construct(
49
        ManagerRegistry $registry,
50
        private readonly IllustrationRepository $illustrationRepository
51
    ) {
52
        parent::__construct($registry, User::class);
53
    }
54
55
    public function loadUserByIdentifier(string $identifier): ?User
56
    {
57
        return $this->findOneBy([
58
            'username' => $identifier,
59
        ]);
60
    }
61
62
    public function setHasher(UserPasswordHasherInterface $hasher): void
63
    {
64
        $this->hasher = $hasher;
65
    }
66
67
    public function createUser(): User
68
    {
69
        return new User();
70
    }
71
72
    public function updateUser(User $user, bool $andFlush = true): void
73
    {
74
        $this->updateCanonicalFields($user);
75
        $this->updatePassword($user);
76
        $this->getEntityManager()->persist($user);
77
        if ($andFlush) {
78
            $this->getEntityManager()->flush();
79
        }
80
    }
81
82
    public function canonicalize(string $string): string
83
    {
84
        $encoding = mb_detect_encoding($string, mb_detect_order(), true);
85
86
        return $encoding
87
            ? mb_convert_case($string, MB_CASE_LOWER, $encoding)
88
            : mb_convert_case($string, MB_CASE_LOWER);
89
    }
90
91
    public function updateCanonicalFields(User $user): void
92
    {
93
        $user->setUsernameCanonical($this->canonicalize($user->getUsername()));
94
        $user->setEmailCanonical($this->canonicalize($user->getEmail()));
95
    }
96
97
    public function updatePassword(User $user): void
98
    {
99
        $password = (string) $user->getPlainPassword();
100
        if ('' !== $password) {
101
            $password = $this->hasher->hashPassword($user, $password);
0 ignored issues
show
Bug introduced by
The method hashPassword() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

101
            /** @scrutinizer ignore-call */ 
102
            $password = $this->hasher->hashPassword($user, $password);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
102
            $user->setPassword($password);
103
            $user->eraseCredentials();
104
        }
105
    }
106
107
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
108
    {
109
        /** @var User $user */
110
        $user->setPassword($newHashedPassword);
111
        $this->getEntityManager()->persist($user);
112
        $this->getEntityManager()->flush();
113
    }
114
115
    public function getRootUser(): User
116
    {
117
        $qb = $this->createQueryBuilder('u');
118
        $qb
119
            ->innerJoin(
120
                'u.resourceNode',
121
                'r'
122
            )
123
        ;
124
        $qb
125
            ->where('r.creator = u')
126
            ->andWhere('r.parent IS NULL')
127
            ->getFirstResult()
128
        ;
129
130
        $rootUser = $qb->getQuery()->getSingleResult();
131
132
        if (null === $rootUser) {
133
            throw new UserNotFoundException('Root user not found');
134
        }
135
136
        return $rootUser;
137
    }
138
139
    public function deleteUser(User $user): void
140
    {
141
        $em = $this->getEntityManager();
142
        $type = $user->getResourceNode()->getResourceType();
143
        $rootUser = $this->getRootUser();
144
145
        // User children will be set to the root user.
146
        $criteria = Criteria::create()->where(Criteria::expr()->eq('resourceType', $type));
147
        $userNodeCreatedList = $user->getResourceNodes()->matching($criteria);
148
149
        /** @var ResourceNode $userCreated */
150
        foreach ($userNodeCreatedList as $userCreated) {
151
            $userCreated->setCreator($rootUser);
152
        }
153
154
        $em->remove($user->getResourceNode());
155
156
        foreach ($user->getGroups() as $group) {
157
            $user->removeGroup($group);
158
        }
159
160
        $em->remove($user);
161
        $em->flush();
162
    }
163
164
    public function addUserToResourceNode(int $userId, int $creatorId): ResourceNode
165
    {
166
        /** @var User $user */
167
        $user = $this->find($userId);
168
        $creator = $this->find($creatorId);
169
170
        $resourceNode = (new ResourceNode())
171
            ->setTitle($user->getUsername())
172
            ->setCreator($creator)
173
            ->setResourceType($this->getResourceType())
174
            // ->setParent($resourceNode)
175
        ;
176
177
        $user->setResourceNode($resourceNode);
178
179
        $this->getEntityManager()->persist($resourceNode);
180
        $this->getEntityManager()->persist($user);
181
182
        return $resourceNode;
183
    }
184
185
    public function addRoleListQueryBuilder(array $roles, ?QueryBuilder $qb = null): QueryBuilder
186
    {
187
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
188
        if (!empty($roles)) {
189
            $orX = $qb->expr()->orX();
190
            foreach ($roles as $role) {
191
                $orX->add($qb->expr()->like('u.roles', ':'.$role));
192
                $qb->setParameter($role, '%'.$role.'%');
193
            }
194
            $qb->andWhere($orX);
195
        }
196
197
        return $qb;
198
    }
199
200
    public function findByUsername(string $username): ?User
201
    {
202
        $user = $this->findOneBy([
203
            'username' => $username,
204
        ]);
205
206
        if (null === $user) {
207
            throw new UserNotFoundException(sprintf("User with id '%s' not found.", $username));
208
        }
209
210
        return $user;
211
    }
212
213
    /**
214
     * Get a filtered list of user by role and (optionally) access url.
215
     *
216
     * @param string $keyword     The query to filter
217
     * @param int    $accessUrlId The access URL ID
218
     *
219
     * @return User[]
220
     */
221
    public function findByRole(string $role, string $keyword, int $accessUrlId = 0)
222
    {
223
        $qb = $this->createQueryBuilder('u');
224
225
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
226
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
227
        $this->addRoleQueryBuilder($role, $qb);
228
        $this->addSearchByKeywordQueryBuilder($keyword, $qb);
229
230
        return $qb->getQuery()->getResult();
231
    }
232
233
    public function findByRoleList(array $roleList, string $keyword, int $accessUrlId = 0)
234
    {
235
        $qb = $this->createQueryBuilder('u');
236
237
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
238
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
239
        $this->addRoleListQueryBuilder($roleList, $qb);
240
        $this->addSearchByKeywordQueryBuilder($keyword, $qb);
241
242
        return $qb->getQuery()->getResult();
243
    }
244
245
    /**
246
     * Get the coaches for a course within a session.
247
     *
248
     * @return Collection|array
249
     */
250
    public function getCoachesForSessionCourse(Session $session, Course $course)
251
    {
252
        $qb = $this->createQueryBuilder('u');
253
254
        $qb->select('u')
255
            ->innerJoin(
256
                'ChamiloCoreBundle:SessionRelCourseRelUser',
257
                'scu',
258
                Join::WITH,
259
                'scu.user = u'
260
            )
261
            ->where(
262
                $qb->expr()->andX(
263
                    $qb->expr()->eq('scu.session', $session->getId()),
264
                    $qb->expr()->eq('scu.course', $course->getId()),
265
                    $qb->expr()->eq('scu.status', Session::COURSE_COACH)
266
                )
267
            )
268
        ;
269
270
        return $qb->getQuery()->getResult();
271
    }
272
273
    /**
274
     * Get the sessions admins for a user.
275
     *
276
     * @return array
277
     */
278
    public function getSessionAdmins(User $user)
279
    {
280
        $qb = $this->createQueryBuilder('u');
281
        $qb
282
            ->distinct()
283
            ->innerJoin(
284
                'ChamiloCoreBundle:SessionRelUser',
285
                'su',
286
                Join::WITH,
287
                'u = su.user'
288
            )
289
            ->innerJoin(
290
                'ChamiloCoreBundle:SessionRelCourseRelUser',
291
                'scu',
292
                Join::WITH,
293
                'su.session = scu.session'
294
            )
295
            ->where(
296
                $qb->expr()->eq('scu.user', $user->getId())
297
            )
298
            ->andWhere(
299
                $qb->expr()->eq('su.relationType', Session::DRH)
300
            )
301
        ;
302
303
        return $qb->getQuery()->getResult();
304
    }
305
306
    /**
307
     * Get number of users in URL.
308
     *
309
     * @return int
310
     */
311
    public function getCountUsersByUrl(AccessUrl $url)
312
    {
313
        return $this->createQueryBuilder('u')
314
            ->select('COUNT(u)')
315
            ->innerJoin('u.portals', 'p')
316
            ->where('p.url = :url')
317
            ->setParameters([
318
                'url' => $url,
319
            ])
320
            ->getQuery()
321
            ->getSingleScalarResult()
322
        ;
323
    }
324
325
    /**
326
     * Get number of users in URL.
327
     *
328
     * @return int
329
     */
330
    public function getCountTeachersByUrl(AccessUrl $url)
331
    {
332
        $qb = $this->createQueryBuilder('u');
333
334
        $qb
335
            ->select('COUNT(u)')
336
            ->innerJoin('u.portals', 'p')
337
            ->where('p.url = :url')
338
            ->setParameters([
339
                'url' => $url,
340
            ])
341
        ;
342
343
        $this->addRoleListQueryBuilder(['ROLE_TEACHER'], $qb);
344
345
        return (int) $qb->getQuery()->getSingleScalarResult();
346
    }
347
348
    /**
349
     * Find potential users to send a message.
350
     *
351
     * @todo remove  api_is_platform_admin
352
     *
353
     * @param int    $currentUserId The current user ID
354
     * @param string $searchFilter  Optional. The search text to filter the user list
355
     * @param int    $limit         Optional. Sets the maximum number of results to retrieve
356
     *
357
     * @return User[]
358
     */
359
    public function findUsersToSendMessage(int $currentUserId, ?string $searchFilter = null, int $limit = 10)
360
    {
361
        $allowSendMessageToAllUsers = api_get_setting('allow_send_message_to_all_platform_users');
362
        $accessUrlId = api_get_multiple_access_url() ? api_get_current_access_url_id() : 1;
363
364
        $messageTool = 'true' === api_get_setting('allow_message_tool');
365
        if (!$messageTool) {
366
            return [];
367
        }
368
369
        $qb = $this->createQueryBuilder('u');
370
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
371
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
372
373
        $dql = null;
374
        if ('true' === api_get_setting('allow_social_tool')) {
375
            // All users
376
            if ('true' === $allowSendMessageToAllUsers || api_is_platform_admin()) {
377
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
378
            /*$dql = "SELECT DISTINCT U
379
                    FROM ChamiloCoreBundle:User U
380
                    LEFT JOIN ChamiloCoreBundle:AccessUrlRelUser R
381
                    WITH U = R.user
382
                    WHERE
383
                        U.active = 1 AND
384
                        U.status != 6  AND
385
                        U.id != {$currentUserId} AND
386
                        R.url = {$accessUrlId}";*/
387
            } else {
388
                $this->addOnlyMyFriendsQueryBuilder($currentUserId, $qb);
389
                /*$dql = 'SELECT DISTINCT U
390
                        FROM ChamiloCoreBundle:AccessUrlRelUser R, ChamiloCoreBundle:UserRelUser UF
391
                        INNER JOIN ChamiloCoreBundle:User AS U
392
                        WITH UF.friendUserId = U
393
                        WHERE
394
                            U.active = 1 AND
395
                            U.status != 6 AND
396
                            UF.relationType NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.") AND
397
                            UF.user = {$currentUserId} AND
398
                            UF.friendUserId != {$currentUserId} AND
399
                            U = R.user AND
400
                            R.url = {$accessUrlId}";*/
401
            }
402
        } else {
403
            if ('true' === $allowSendMessageToAllUsers) {
404
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
405
            } else {
406
                return [];
407
            }
408
409
            /*else {
410
                $time_limit = (int) api_get_setting('time_limit_whosonline');
411
                $online_time = time() - ($time_limit * 60);
412
                $limit_date = api_get_utc_datetime($online_time);
413
                $dql = "SELECT DISTINCT U
414
                        FROM ChamiloCoreBundle:User U
415
                        INNER JOIN ChamiloCoreBundle:TrackEOnline T
416
                        WITH U.id = T.loginUserId
417
                        WHERE
418
                          U.active = 1 AND
419
                          T.loginDate >= '".$limit_date."'";
420
            }*/
421
        }
422
423
        if (!empty($searchFilter)) {
424
            $this->addSearchByKeywordQueryBuilder($searchFilter, $qb);
425
        }
426
427
        return $qb->getQuery()->getResult();
428
    }
429
430
    /**
431
     * Get the list of HRM who have assigned this user.
432
     *
433
     * @return User[]
434
     */
435
    public function getAssignedHrmUserList(int $userId, int $urlId)
436
    {
437
        $qb = $this->createQueryBuilder('u');
438
        $this->addAccessUrlQueryBuilder($urlId, $qb);
439
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
440
        $this->addUserRelUserQueryBuilder($userId, UserRelUser::USER_RELATION_TYPE_RRHH, $qb);
441
442
        return $qb->getQuery()->getResult();
443
    }
444
445
    /**
446
     * Get the last login from the track_e_login table.
447
     * This might be different from user.last_login in the case of legacy users
448
     * as user.last_login was only implemented in 1.10 version with a default
449
     * value of NULL (not the last record from track_e_login).
450
     *
451
     * @return null|TrackELogin
452
     */
453
    public function getLastLogin(User $user)
454
    {
455
        $qb = $this->createQueryBuilder('u');
456
457
        return $qb
458
            ->select('l')
459
            ->innerJoin('u.logins', 'l')
460
            ->where(
461
                $qb->expr()->eq('l.user', $user)
462
            )
463
            ->setMaxResults(1)
464
            ->orderBy('u.loginDate', Criteria::DESC)
465
            ->getQuery()
466
            ->getOneOrNullResult()
467
        ;
468
    }
469
470
    public function addAccessUrlQueryBuilder(int $accessUrlId, ?QueryBuilder $qb = null): QueryBuilder
471
    {
472
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
473
        $qb
474
            ->innerJoin('u.portals', 'p')
475
            ->andWhere('p.url = :url')
476
            ->setParameter('url', $accessUrlId, Types::INTEGER)
477
        ;
478
479
        return $qb;
480
    }
481
482
    public function addActiveAndNotAnonUserQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
483
    {
484
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
485
        $qb
486
            ->andWhere('u.active = 1')
487
            ->andWhere('u.status <> :status')
488
            ->setParameter('status', User::ANONYMOUS, Types::INTEGER)
489
        ;
490
491
        return $qb;
492
    }
493
494
    public function addExpirationDateQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
495
    {
496
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
497
        $qb
498
            ->andWhere('u.expirationDate IS NULL OR u.expirationDate > :now')
499
            ->setParameter('now', new Datetime(), Types::DATETIME_MUTABLE)
500
        ;
501
502
        return $qb;
503
    }
504
505
    private function addRoleQueryBuilder(string $role, ?QueryBuilder $qb = null): QueryBuilder
506
    {
507
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
508
        $qb
509
            ->andWhere('u.roles LIKE :roles')
510
            ->setParameter('roles', '%"'.$role.'"%', Types::STRING)
511
        ;
512
513
        return $qb;
514
    }
515
516
    private function addSearchByKeywordQueryBuilder(string $keyword, ?QueryBuilder $qb = null): QueryBuilder
517
    {
518
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
519
        $qb
520
            ->andWhere('
521
                u.firstname LIKE :keyword OR
522
                u.lastname LIKE :keyword OR
523
                u.email LIKE :keyword OR
524
                u.username LIKE :keyword
525
            ')
526
            ->setParameter('keyword', "%$keyword%", Types::STRING)
527
            ->orderBy('u.firstname', Criteria::ASC)
528
        ;
529
530
        return $qb;
531
    }
532
533
    private function addUserRelUserQueryBuilder(int $userId, int $relationType, ?QueryBuilder $qb = null): QueryBuilder
534
    {
535
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
536
        $qb->leftJoin('u.friends', 'relations');
537
        $qb
538
            ->andWhere('relations.relationType = :relationType')
539
            ->andWhere('relations.user = :userRelation AND relations.friend <> :userRelation')
540
            ->setParameter('relationType', $relationType)
541
            ->setParameter('userRelation', $userId)
542
        ;
543
544
        return $qb;
545
    }
546
547
    private function addOnlyMyFriendsQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
548
    {
549
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
550
        $qb
551
            ->leftJoin('u.friends', 'relations')
552
            ->andWhere(
553
                $qb->expr()->notIn(
554
                    'relations.relationType',
555
                    [UserRelUser::USER_RELATION_TYPE_DELETED, UserRelUser::USER_RELATION_TYPE_RRHH]
556
                )
557
            )
558
            ->andWhere('relations.user = :user AND relations.friend <> :user')
559
            ->setParameter('user', $userId, Types::INTEGER)
560
        ;
561
562
        return $qb;
563
    }
564
565
    private function addNotCurrentUserQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
566
    {
567
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
568
        $qb
569
            ->andWhere('u.id <> :id')
570
            ->setParameter('id', $userId, Types::INTEGER)
571
        ;
572
573
        return $qb;
574
    }
575
576
    public function getFriendsNotInGroup(int $userId, int $groupId)
577
    {
578
        $entityManager = $this->getEntityManager();
579
580
        $subQueryBuilder = $entityManager->createQueryBuilder();
581
        $subQuery = $subQueryBuilder
582
            ->select('IDENTITY(ugr.user)')
583
            ->from(UsergroupRelUser::class, 'ugr')
584
            ->where('ugr.usergroup = :subGroupId')
585
            ->andWhere('ugr.relationType IN (:subRelationTypes)')
586
            ->getDQL()
587
        ;
588
589
        $queryBuilder = $entityManager->createQueryBuilder();
590
        $query = $queryBuilder
591
            ->select('u')
592
            ->from(User::class, 'u')
593
            ->leftJoin('u.friendsWithMe', 'uruf')
594
            ->leftJoin('u.friends', 'urut')
595
            ->where('uruf.friend = :userId OR urut.user = :userId')
596
            ->andWhere($queryBuilder->expr()->notIn('u.id', $subQuery))
597
            ->setParameter('userId', $userId)
598
            ->setParameter('subGroupId', $groupId)
599
            ->setParameter('subRelationTypes', [Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION])
600
            ->getQuery()
601
        ;
602
603
        return $query->getResult();
604
    }
605
606
    public function getExtraUserData(int $userId, bool $prefix = false, bool $allVisibility = true, bool $splitMultiple = false, ?int $fieldFilter = null): array
607
    {
608
        $qb = $this->getEntityManager()->createQueryBuilder();
609
610
        // Start building the query
611
        $qb->select('ef.id', 'ef.variable as fvar', 'ef.valueType as type', 'efv.fieldValue as fval', 'ef.defaultValue as fval_df')
612
            ->from(ExtraField::class, 'ef')
613
            ->leftJoin(ExtraFieldValues::class, 'efv', Join::WITH, 'efv.field = ef.id AND efv.itemId = :userId')
614
            ->where('ef.itemType = :itemType')
615
            ->setParameter('userId', $userId)
616
            ->setParameter('itemType', ExtraField::USER_FIELD_TYPE)
617
        ;
618
619
        // Apply visibility filters
620
        if (!$allVisibility) {
621
            $qb->andWhere('ef.visibleToSelf = true');
622
        }
623
624
        // Apply field filter if provided
625
        if (null !== $fieldFilter) {
626
            $qb->andWhere('ef.id = :fieldFilter')
627
                ->setParameter('fieldFilter', $fieldFilter)
628
            ;
629
        }
630
631
        // Order by field order
632
        $qb->orderBy('ef.fieldOrder', 'ASC');
633
634
        // Execute the query
635
        $results = $qb->getQuery()->getResult();
636
637
        // Process results
638
        $extraData = [];
639
        foreach ($results as $row) {
640
            $value = $row['fval'] ?? $row['fval_df'];
641
642
            // Handle multiple values if necessary
643
            if ($splitMultiple && \in_array($row['type'], [ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE], true)) {
644
                $value = explode(';', $value);
645
            }
646
647
            // Handle prefix if needed
648
            $key = $prefix ? 'extra_'.$row['fvar'] : $row['fvar'];
649
650
            // Special handling for certain field types
651
            if (ExtraField::USER_FIELD_TYPE_TAG == $row['type']) {
652
                // Implement your logic to handle tags
653
            } elseif (ExtraField::USER_FIELD_TYPE_RADIO == $row['type'] && $prefix) {
654
                $extraData[$key][$key] = $value;
655
            } else {
656
                $extraData[$key] = $value;
657
            }
658
        }
659
660
        return $extraData;
661
    }
662
663
    public function getExtraUserDataByField(int $userId, string $fieldVariable, bool $allVisibility = true): array
664
    {
665
        $qb = $this->getEntityManager()->createQueryBuilder();
666
667
        $qb->select('e.id, e.variable, e.valueType, v.fieldValue')
668
            ->from(ExtraFieldValues::class, 'v')
669
            ->innerJoin('v.field', 'e')
670
            ->where('v.itemId = :userId')
671
            ->andWhere('e.variable = :fieldVariable')
672
            ->andWhere('e.itemType = :itemType')
673
            ->setParameters([
674
                'userId' => $userId,
675
                'fieldVariable' => $fieldVariable,
676
                'itemType' => ExtraField::USER_FIELD_TYPE,
677
            ])
678
        ;
679
680
        if (!$allVisibility) {
681
            $qb->andWhere('e.visibleToSelf = true');
682
        }
683
684
        $qb->orderBy('e.fieldOrder', 'ASC');
685
686
        $result = $qb->getQuery()->getResult();
687
688
        $extraData = [];
689
        foreach ($result as $row) {
690
            $value = $row['fieldValue'];
691
            if (ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['valueType']) {
692
                $value = explode(';', $row['fieldValue']);
693
            }
694
695
            $extraData[$row['variable']] = $value;
696
        }
697
698
        return $extraData;
699
    }
700
701
    public function searchUsersByTags(
702
        string $tag,
703
        ?int $excludeUserId = null,
704
        int $fieldId = 0,
705
        int $from = 0,
706
        int $number_of_items = 10,
707
        bool $getCount = false
708
    ): array {
709
        $qb = $this->createQueryBuilder('u');
710
711
        if ($getCount) {
712
            $qb->select('COUNT(DISTINCT u.id)');
713
        } else {
714
            $qb->select('DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.pictureUri, u.status');
715
        }
716
717
        $qb->innerJoin('u.portals', 'urlRelUser')
718
            ->leftJoin(UserRelTag::class, 'uv', 'WITH', 'u = uv.user')
719
            ->leftJoin(Tag::class, 'ut', 'WITH', 'uv.tag = ut')
720
        ;
721
722
        if (0 !== $fieldId) {
723
            $qb->andWhere('ut.field = :fieldId')
724
                ->setParameter('fieldId', $fieldId)
725
            ;
726
        }
727
728
        if (null !== $excludeUserId) {
729
            $qb->andWhere('u.id != :excludeUserId')
730
                ->setParameter('excludeUserId', $excludeUserId)
731
            ;
732
        }
733
734
        $qb->andWhere(
735
            $qb->expr()->orX(
736
                $qb->expr()->like('ut.tag', ':tag'),
737
                $qb->expr()->like('u.firstname', ':likeTag'),
738
                $qb->expr()->like('u.lastname', ':likeTag'),
739
                $qb->expr()->like('u.username', ':likeTag'),
740
                $qb->expr()->like(
741
                    $qb->expr()->concat('u.firstname', $qb->expr()->literal(' '), 'u.lastname'),
742
                    ':likeTag'
743
                ),
744
                $qb->expr()->like(
745
                    $qb->expr()->concat('u.lastname', $qb->expr()->literal(' '), 'u.firstname'),
746
                    ':likeTag'
747
                )
748
            )
749
        )
750
            ->setParameter('tag', $tag.'%')
751
            ->setParameter('likeTag', '%'.$tag.'%')
752
        ;
753
754
        // Only active users and not anonymous
755
        $qb->andWhere('u.active = :active')
756
            ->andWhere('u.status != :anonymous')
757
            ->setParameter('active', true)
758
            ->setParameter('anonymous', 6)
759
        ;
760
761
        if (!$getCount) {
762
            $qb->orderBy('u.username')
763
                ->setFirstResult($from)
764
                ->setMaxResults($number_of_items)
765
            ;
766
        }
767
768
        return $getCount ? $qb->getQuery()->getSingleScalarResult() : $qb->getQuery()->getResult();
769
    }
770
771
    public function getUserRelationWithType(int $userId, int $friendId): ?array
772
    {
773
        $qb = $this->createQueryBuilder('u');
774
        $qb->select('u.id AS userId', 'u.username AS userName', 'ur.relationType', 'f.id AS friendId', 'f.username AS friendName')
775
            ->innerJoin('u.friends', 'ur')
776
            ->innerJoin('ur.friend', 'f')
777
            ->where('u.id = :userId AND f.id = :friendId')
778
            ->setParameter('userId', $userId)
779
            ->setParameter('friendId', $friendId)
780
            ->setMaxResults(1)
781
        ;
782
783
        return $qb->getQuery()->getOneOrNullResult();
784
    }
785
786
    public function relateUsers(User $user1, User $user2, int $relationType): void
787
    {
788
        $em = $this->getEntityManager();
789
790
        $existingRelation = $em->getRepository(UserRelUser::class)->findOneBy([
791
            'user' => $user1,
792
            'friend' => $user2,
793
        ]);
794
795
        if (!$existingRelation) {
796
            $newRelation = new UserRelUser();
797
            $newRelation->setUser($user1);
798
            $newRelation->setFriend($user2);
799
            $newRelation->setRelationType($relationType);
800
            $em->persist($newRelation);
801
        } else {
802
            $existingRelation->setRelationType($relationType);
803
        }
804
805
        $existingRelationInverse = $em->getRepository(UserRelUser::class)->findOneBy([
806
            'user' => $user2,
807
            'friend' => $user1,
808
        ]);
809
810
        if (!$existingRelationInverse) {
811
            $newRelationInverse = new UserRelUser();
812
            $newRelationInverse->setUser($user2);
813
            $newRelationInverse->setFriend($user1);
814
            $newRelationInverse->setRelationType($relationType);
815
            $em->persist($newRelationInverse);
816
        } else {
817
            $existingRelationInverse->setRelationType($relationType);
818
        }
819
820
        $em->flush();
821
    }
822
823
    public function getUserPicture(
824
        $userId,
825
        int $size = self::USER_IMAGE_SIZE_MEDIUM,
826
        $addRandomId = true,
827
    ) {
828
        $user = $this->find($userId);
829
        if (!$user) {
830
            return '/img/icons/64/unknown.png';
831
        }
832
833
        switch ($size) {
834
            case self::USER_IMAGE_SIZE_SMALL:
835
                $width = 32;
836
837
                break;
838
839
            case self::USER_IMAGE_SIZE_MEDIUM:
840
                $width = 64;
841
842
                break;
843
844
            case self::USER_IMAGE_SIZE_BIG:
845
                $width = 128;
846
847
                break;
848
849
            case self::USER_IMAGE_SIZE_ORIGINAL:
850
            default:
851
                $width = 0;
852
853
                break;
854
        }
855
856
        $url = $this->illustrationRepository->getIllustrationUrl($user);
857
        $params = [];
858
        if (!empty($width)) {
859
            $params['w'] = $width;
860
        }
861
862
        if ($addRandomId) {
863
            $params['rand'] = uniqid('u_', true);
864
        }
865
866
        $paramsToString = '';
867
        if (!empty($params)) {
868
            $paramsToString = '?'.http_build_query($params);
869
        }
870
871
        return $url.$paramsToString;
872
    }
873
}
874