Passed
Push — dependabot/github_actions/code... ( 154cc6...3e3012 )
by
unknown
26:36 queued 15:59
created

UserRepository::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
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 course user relationship based in the course_rel_user table.
247
     *
248
     * @return Course[]
249
     */
250
    public function getCourses(User $user, AccessUrl $url, int $status, string $keyword = '')
251
    {
252
        $qb = $this->createQueryBuilder('u');
253
254
        $qb
255
            // ->select('DISTINCT course')
256
            ->innerJoin('u.courses', 'courseRelUser')
257
            ->innerJoin('courseRelUser.course', 'course')
258
            ->innerJoin('course.urls', 'accessUrlRelCourse')
259
            ->innerJoin('accessUrlRelCourse.url', 'url')
260
            ->where('url = :url')
261
            ->andWhere('courseRelUser.user = :user')
262
            ->andWhere('courseRelUser.status = :status')
263
            ->setParameters(
264
                [
265
                    'user' => $user,
266
                    'url' => $url,
267
                    'status' => $status,
268
                ]
269
            )
270
        //    ->addSelect('courseRelUser')
271
        ;
272
273
        if (!empty($keyword)) {
274
            $qb
275
                ->andWhere('course.title like = :keyword OR course.code like = :keyword')
276
                ->setParameter('keyword', $keyword)
277
            ;
278
        }
279
280
        $qb->orderBy('course.title', Criteria::DESC);
281
282
        $query = $qb->getQuery();
283
284
        return $query->getResult();
285
    }
286
287
    /**
288
     * Get the coaches for a course within a session.
289
     *
290
     * @return Collection|array
291
     */
292
    public function getCoachesForSessionCourse(Session $session, Course $course)
293
    {
294
        $qb = $this->createQueryBuilder('u');
295
296
        $qb->select('u')
297
            ->innerJoin(
298
                'ChamiloCoreBundle:SessionRelCourseRelUser',
299
                'scu',
300
                Join::WITH,
301
                'scu.user = u'
302
            )
303
            ->where(
304
                $qb->expr()->andX(
305
                    $qb->expr()->eq('scu.session', $session->getId()),
306
                    $qb->expr()->eq('scu.course', $course->getId()),
307
                    $qb->expr()->eq('scu.status', Session::COURSE_COACH)
308
                )
309
            )
310
        ;
311
312
        return $qb->getQuery()->getResult();
313
    }
314
315
    /**
316
     * Get the sessions admins for a user.
317
     *
318
     * @return array
319
     */
320
    public function getSessionAdmins(User $user)
321
    {
322
        $qb = $this->createQueryBuilder('u');
323
        $qb
324
            ->distinct()
325
            ->innerJoin(
326
                'ChamiloCoreBundle:SessionRelUser',
327
                'su',
328
                Join::WITH,
329
                'u = su.user'
330
            )
331
            ->innerJoin(
332
                'ChamiloCoreBundle:SessionRelCourseRelUser',
333
                'scu',
334
                Join::WITH,
335
                'su.session = scu.session'
336
            )
337
            ->where(
338
                $qb->expr()->eq('scu.user', $user->getId())
339
            )
340
            ->andWhere(
341
                $qb->expr()->eq('su.relationType', Session::DRH)
342
            )
343
        ;
344
345
        return $qb->getQuery()->getResult();
346
    }
347
348
    /**
349
     * Get number of users in URL.
350
     *
351
     * @return int
352
     */
353
    public function getCountUsersByUrl(AccessUrl $url)
354
    {
355
        return $this->createQueryBuilder('u')
356
            ->select('COUNT(u)')
357
            ->innerJoin('u.portals', 'p')
358
            ->where('p.url = :url')
359
            ->setParameters([
360
                'url' => $url,
361
            ])
362
            ->getQuery()
363
            ->getSingleScalarResult()
364
        ;
365
    }
366
367
    /**
368
     * Get number of users in URL.
369
     *
370
     * @return int
371
     */
372
    public function getCountTeachersByUrl(AccessUrl $url)
373
    {
374
        $qb = $this->createQueryBuilder('u');
375
376
        $qb
377
            ->select('COUNT(u)')
378
            ->innerJoin('u.portals', 'p')
379
            ->where('p.url = :url')
380
            ->setParameters([
381
                'url' => $url,
382
            ])
383
        ;
384
385
        $this->addRoleListQueryBuilder(['ROLE_TEACHER'], $qb);
386
387
        return (int) $qb->getQuery()->getSingleScalarResult();
388
    }
389
390
    /**
391
     * Find potential users to send a message.
392
     *
393
     * @todo remove  api_is_platform_admin
394
     *
395
     * @param int    $currentUserId The current user ID
396
     * @param string $searchFilter  Optional. The search text to filter the user list
397
     * @param int    $limit         Optional. Sets the maximum number of results to retrieve
398
     *
399
     * @return User[]
400
     */
401
    public function findUsersToSendMessage(int $currentUserId, ?string $searchFilter = null, int $limit = 10)
402
    {
403
        $allowSendMessageToAllUsers = api_get_setting('allow_send_message_to_all_platform_users');
404
        $accessUrlId = api_get_multiple_access_url() ? api_get_current_access_url_id() : 1;
405
406
        $messageTool = 'true' === api_get_setting('allow_message_tool');
407
        if (!$messageTool) {
408
            return [];
409
        }
410
411
        $qb = $this->createQueryBuilder('u');
412
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
413
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
414
415
        $dql = null;
416
        if ('true' === api_get_setting('allow_social_tool')) {
417
            // All users
418
            if ('true' === $allowSendMessageToAllUsers || api_is_platform_admin()) {
419
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
420
            /*$dql = "SELECT DISTINCT U
421
                    FROM ChamiloCoreBundle:User U
422
                    LEFT JOIN ChamiloCoreBundle:AccessUrlRelUser R
423
                    WITH U = R.user
424
                    WHERE
425
                        U.active = 1 AND
426
                        U.status != 6  AND
427
                        U.id != {$currentUserId} AND
428
                        R.url = {$accessUrlId}";*/
429
            } else {
430
                $this->addOnlyMyFriendsQueryBuilder($currentUserId, $qb);
431
                /*$dql = 'SELECT DISTINCT U
432
                        FROM ChamiloCoreBundle:AccessUrlRelUser R, ChamiloCoreBundle:UserRelUser UF
433
                        INNER JOIN ChamiloCoreBundle:User AS U
434
                        WITH UF.friendUserId = U
435
                        WHERE
436
                            U.active = 1 AND
437
                            U.status != 6 AND
438
                            UF.relationType NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.") AND
439
                            UF.user = {$currentUserId} AND
440
                            UF.friendUserId != {$currentUserId} AND
441
                            U = R.user AND
442
                            R.url = {$accessUrlId}";*/
443
            }
444
        } else {
445
            if ('true' === $allowSendMessageToAllUsers) {
446
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
447
            } else {
448
                return [];
449
            }
450
451
            /*else {
452
                $time_limit = (int) api_get_setting('time_limit_whosonline');
453
                $online_time = time() - ($time_limit * 60);
454
                $limit_date = api_get_utc_datetime($online_time);
455
                $dql = "SELECT DISTINCT U
456
                        FROM ChamiloCoreBundle:User U
457
                        INNER JOIN ChamiloCoreBundle:TrackEOnline T
458
                        WITH U.id = T.loginUserId
459
                        WHERE
460
                          U.active = 1 AND
461
                          T.loginDate >= '".$limit_date."'";
462
            }*/
463
        }
464
465
        if (!empty($searchFilter)) {
466
            $this->addSearchByKeywordQueryBuilder($searchFilter, $qb);
467
        }
468
469
        return $qb->getQuery()->getResult();
470
    }
471
472
    /**
473
     * Get the list of HRM who have assigned this user.
474
     *
475
     * @return User[]
476
     */
477
    public function getAssignedHrmUserList(int $userId, int $urlId)
478
    {
479
        $qb = $this->createQueryBuilder('u');
480
        $this->addAccessUrlQueryBuilder($urlId, $qb);
481
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
482
        $this->addUserRelUserQueryBuilder($userId, UserRelUser::USER_RELATION_TYPE_RRHH, $qb);
483
484
        return $qb->getQuery()->getResult();
485
    }
486
487
    /**
488
     * Get the last login from the track_e_login table.
489
     * This might be different from user.last_login in the case of legacy users
490
     * as user.last_login was only implemented in 1.10 version with a default
491
     * value of NULL (not the last record from track_e_login).
492
     *
493
     * @return null|TrackELogin
494
     */
495
    public function getLastLogin(User $user)
496
    {
497
        $qb = $this->createQueryBuilder('u');
498
499
        return $qb
500
            ->select('l')
501
            ->innerJoin('u.logins', 'l')
502
            ->where(
503
                $qb->expr()->eq('l.user', $user)
504
            )
505
            ->setMaxResults(1)
506
            ->orderBy('u.loginDate', Criteria::DESC)
507
            ->getQuery()
508
            ->getOneOrNullResult()
509
        ;
510
    }
511
512
    public function addAccessUrlQueryBuilder(int $accessUrlId, ?QueryBuilder $qb = null): QueryBuilder
513
    {
514
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
515
        $qb
516
            ->innerJoin('u.portals', 'p')
517
            ->andWhere('p.url = :url')
518
            ->setParameter('url', $accessUrlId, Types::INTEGER)
519
        ;
520
521
        return $qb;
522
    }
523
524
    public function addActiveAndNotAnonUserQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
525
    {
526
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
527
        $qb
528
            ->andWhere('u.active = 1')
529
            ->andWhere('u.status <> :status')
530
            ->setParameter('status', User::ANONYMOUS, Types::INTEGER)
531
        ;
532
533
        return $qb;
534
    }
535
536
    public function addExpirationDateQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
537
    {
538
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
539
        $qb
540
            ->andWhere('u.expirationDate IS NULL OR u.expirationDate > :now')
541
            ->setParameter('now', new Datetime(), Types::DATETIME_MUTABLE)
542
        ;
543
544
        return $qb;
545
    }
546
547
    private function addRoleQueryBuilder(string $role, ?QueryBuilder $qb = null): QueryBuilder
548
    {
549
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
550
        $qb
551
            ->andWhere('u.roles LIKE :roles')
552
            ->setParameter('roles', '%"'.$role.'"%', Types::STRING)
553
        ;
554
555
        return $qb;
556
    }
557
558
    private function addSearchByKeywordQueryBuilder(string $keyword, ?QueryBuilder $qb = null): QueryBuilder
559
    {
560
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
561
        $qb
562
            ->andWhere('
563
                u.firstname LIKE :keyword OR
564
                u.lastname LIKE :keyword OR
565
                u.email LIKE :keyword OR
566
                u.username LIKE :keyword
567
            ')
568
            ->setParameter('keyword', "%$keyword%", Types::STRING)
569
            ->orderBy('u.firstname', Criteria::ASC)
570
        ;
571
572
        return $qb;
573
    }
574
575
    private function addUserRelUserQueryBuilder(int $userId, int $relationType, ?QueryBuilder $qb = null): QueryBuilder
576
    {
577
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
578
        $qb->leftJoin('u.friends', 'relations');
579
        $qb
580
            ->andWhere('relations.relationType = :relationType')
581
            ->andWhere('relations.user = :userRelation AND relations.friend <> :userRelation')
582
            ->setParameter('relationType', $relationType)
583
            ->setParameter('userRelation', $userId)
584
        ;
585
586
        return $qb;
587
    }
588
589
    private function addOnlyMyFriendsQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
590
    {
591
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
592
        $qb
593
            ->leftJoin('u.friends', 'relations')
594
            ->andWhere(
595
                $qb->expr()->notIn(
596
                    'relations.relationType',
597
                    [UserRelUser::USER_RELATION_TYPE_DELETED, UserRelUser::USER_RELATION_TYPE_RRHH]
598
                )
599
            )
600
            ->andWhere('relations.user = :user AND relations.friend <> :user')
601
            ->setParameter('user', $userId, Types::INTEGER)
602
        ;
603
604
        return $qb;
605
    }
606
607
    private function addNotCurrentUserQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
608
    {
609
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
610
        $qb
611
            ->andWhere('u.id <> :id')
612
            ->setParameter('id', $userId, Types::INTEGER)
613
        ;
614
615
        return $qb;
616
    }
617
618
    public function getFriendsNotInGroup(int $userId, int $groupId)
619
    {
620
        $entityManager = $this->getEntityManager();
621
622
        $subQueryBuilder = $entityManager->createQueryBuilder();
623
        $subQuery = $subQueryBuilder
624
            ->select('IDENTITY(ugr.user)')
625
            ->from(UsergroupRelUser::class, 'ugr')
626
            ->where('ugr.usergroup = :subGroupId')
627
            ->andWhere('ugr.relationType IN (:subRelationTypes)')
628
            ->getDQL()
629
        ;
630
631
        $queryBuilder = $entityManager->createQueryBuilder();
632
        $query = $queryBuilder
633
            ->select('u')
634
            ->from(User::class, 'u')
635
            ->leftJoin('u.friendsWithMe', 'uruf')
636
            ->leftJoin('u.friends', 'urut')
637
            ->where('uruf.friend = :userId OR urut.user = :userId')
638
            ->andWhere($queryBuilder->expr()->notIn('u.id', $subQuery))
639
            ->setParameter('userId', $userId)
640
            ->setParameter('subGroupId', $groupId)
641
            ->setParameter('subRelationTypes', [Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION])
642
            ->getQuery()
643
        ;
644
645
        return $query->getResult();
646
    }
647
648
    public function getExtraUserData(int $userId, bool $prefix = false, bool $allVisibility = true, bool $splitMultiple = false, ?int $fieldFilter = null): array
649
    {
650
        $qb = $this->getEntityManager()->createQueryBuilder();
651
652
        // Start building the query
653
        $qb->select('ef.id', 'ef.variable as fvar', 'ef.valueType as type', 'efv.fieldValue as fval', 'ef.defaultValue as fval_df')
654
            ->from(ExtraField::class, 'ef')
655
            ->leftJoin(ExtraFieldValues::class, 'efv', Join::WITH, 'efv.field = ef.id AND efv.itemId = :userId')
656
            ->where('ef.itemType = :itemType')
657
            ->setParameter('userId', $userId)
658
            ->setParameter('itemType', ExtraField::USER_FIELD_TYPE)
659
        ;
660
661
        // Apply visibility filters
662
        if (!$allVisibility) {
663
            $qb->andWhere('ef.visibleToSelf = true');
664
        }
665
666
        // Apply field filter if provided
667
        if (null !== $fieldFilter) {
668
            $qb->andWhere('ef.id = :fieldFilter')
669
                ->setParameter('fieldFilter', $fieldFilter)
670
            ;
671
        }
672
673
        // Order by field order
674
        $qb->orderBy('ef.fieldOrder', 'ASC');
675
676
        // Execute the query
677
        $results = $qb->getQuery()->getResult();
678
679
        // Process results
680
        $extraData = [];
681
        foreach ($results as $row) {
682
            $value = $row['fval'] ?? $row['fval_df'];
683
684
            // Handle multiple values if necessary
685
            if ($splitMultiple && \in_array($row['type'], [ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE], true)) {
686
                $value = explode(';', $value);
687
            }
688
689
            // Handle prefix if needed
690
            $key = $prefix ? 'extra_'.$row['fvar'] : $row['fvar'];
691
692
            // Special handling for certain field types
693
            if (ExtraField::USER_FIELD_TYPE_TAG == $row['type']) {
694
                // Implement your logic to handle tags
695
            } elseif (ExtraField::USER_FIELD_TYPE_RADIO == $row['type'] && $prefix) {
696
                $extraData[$key][$key] = $value;
697
            } else {
698
                $extraData[$key] = $value;
699
            }
700
        }
701
702
        return $extraData;
703
    }
704
705
    public function getExtraUserDataByField(int $userId, string $fieldVariable, bool $allVisibility = true): array
706
    {
707
        $qb = $this->getEntityManager()->createQueryBuilder();
708
709
        $qb->select('e.id, e.variable, e.valueType, v.fieldValue')
710
            ->from(ExtraFieldValues::class, 'v')
711
            ->innerJoin('v.field', 'e')
712
            ->where('v.itemId = :userId')
713
            ->andWhere('e.variable = :fieldVariable')
714
            ->andWhere('e.itemType = :itemType')
715
            ->setParameters([
716
                'userId' => $userId,
717
                'fieldVariable' => $fieldVariable,
718
                'itemType' => ExtraField::USER_FIELD_TYPE,
719
            ])
720
        ;
721
722
        if (!$allVisibility) {
723
            $qb->andWhere('e.visibleToSelf = true');
724
        }
725
726
        $qb->orderBy('e.fieldOrder', 'ASC');
727
728
        $result = $qb->getQuery()->getResult();
729
730
        $extraData = [];
731
        foreach ($result as $row) {
732
            $value = $row['fieldValue'];
733
            if (ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['valueType']) {
734
                $value = explode(';', $row['fieldValue']);
735
            }
736
737
            $extraData[$row['variable']] = $value;
738
        }
739
740
        return $extraData;
741
    }
742
743
    public function searchUsersByTags(
744
        string $tag,
745
        ?int $excludeUserId = null,
746
        int $fieldId = 0,
747
        int $from = 0,
748
        int $number_of_items = 10,
749
        bool $getCount = false
750
    ): array {
751
        $qb = $this->createQueryBuilder('u');
752
753
        if ($getCount) {
754
            $qb->select('COUNT(DISTINCT u.id)');
755
        } else {
756
            $qb->select('DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.pictureUri, u.status');
757
        }
758
759
        $qb->innerJoin('u.portals', 'urlRelUser')
760
            ->leftJoin(UserRelTag::class, 'uv', 'WITH', 'u = uv.user')
761
            ->leftJoin(Tag::class, 'ut', 'WITH', 'uv.tag = ut')
762
        ;
763
764
        if (0 !== $fieldId) {
765
            $qb->andWhere('ut.field = :fieldId')
766
                ->setParameter('fieldId', $fieldId)
767
            ;
768
        }
769
770
        if (null !== $excludeUserId) {
771
            $qb->andWhere('u.id != :excludeUserId')
772
                ->setParameter('excludeUserId', $excludeUserId)
773
            ;
774
        }
775
776
        $qb->andWhere(
777
            $qb->expr()->orX(
778
                $qb->expr()->like('ut.tag', ':tag'),
779
                $qb->expr()->like('u.firstname', ':likeTag'),
780
                $qb->expr()->like('u.lastname', ':likeTag'),
781
                $qb->expr()->like('u.username', ':likeTag'),
782
                $qb->expr()->like(
783
                    $qb->expr()->concat('u.firstname', $qb->expr()->literal(' '), 'u.lastname'),
784
                    ':likeTag'
785
                ),
786
                $qb->expr()->like(
787
                    $qb->expr()->concat('u.lastname', $qb->expr()->literal(' '), 'u.firstname'),
788
                    ':likeTag'
789
                )
790
            )
791
        )
792
            ->setParameter('tag', $tag.'%')
793
            ->setParameter('likeTag', '%'.$tag.'%')
794
        ;
795
796
        // Only active users and not anonymous
797
        $qb->andWhere('u.active = :active')
798
            ->andWhere('u.status != :anonymous')
799
            ->setParameter('active', true)
800
            ->setParameter('anonymous', 6)
801
        ;
802
803
        if (!$getCount) {
804
            $qb->orderBy('u.username')
805
                ->setFirstResult($from)
806
                ->setMaxResults($number_of_items)
807
            ;
808
        }
809
810
        return $getCount ? $qb->getQuery()->getSingleScalarResult() : $qb->getQuery()->getResult();
811
    }
812
813
    public function getUserRelationWithType(int $userId, int $friendId): ?array
814
    {
815
        $qb = $this->createQueryBuilder('u');
816
        $qb->select('u.id AS userId', 'u.username AS userName', 'ur.relationType', 'f.id AS friendId', 'f.username AS friendName')
817
            ->innerJoin('u.friends', 'ur')
818
            ->innerJoin('ur.friend', 'f')
819
            ->where('u.id = :userId AND f.id = :friendId')
820
            ->setParameter('userId', $userId)
821
            ->setParameter('friendId', $friendId)
822
            ->setMaxResults(1)
823
        ;
824
825
        return $qb->getQuery()->getOneOrNullResult();
826
    }
827
828
    public function relateUsers(User $user1, User $user2, int $relationType): void
829
    {
830
        $em = $this->getEntityManager();
831
832
        $existingRelation = $em->getRepository(UserRelUser::class)->findOneBy([
833
            'user' => $user1,
834
            'friend' => $user2,
835
        ]);
836
837
        if (!$existingRelation) {
838
            $newRelation = new UserRelUser();
839
            $newRelation->setUser($user1);
840
            $newRelation->setFriend($user2);
841
            $newRelation->setRelationType($relationType);
842
            $em->persist($newRelation);
843
        } else {
844
            $existingRelation->setRelationType($relationType);
845
        }
846
847
        $existingRelationInverse = $em->getRepository(UserRelUser::class)->findOneBy([
848
            'user' => $user2,
849
            'friend' => $user1,
850
        ]);
851
852
        if (!$existingRelationInverse) {
853
            $newRelationInverse = new UserRelUser();
854
            $newRelationInverse->setUser($user2);
855
            $newRelationInverse->setFriend($user1);
856
            $newRelationInverse->setRelationType($relationType);
857
            $em->persist($newRelationInverse);
858
        } else {
859
            $existingRelationInverse->setRelationType($relationType);
860
        }
861
862
        $em->flush();
863
    }
864
865
    public function getUserPicture(
866
        $userId,
867
        int $size = self::USER_IMAGE_SIZE_MEDIUM,
868
        $addRandomId = true,
869
    ) {
870
        $user = $this->find($userId);
871
        if (!$user) {
872
            return '/img/icons/64/unknown.png';
873
        }
874
875
        switch ($size) {
876
            case self::USER_IMAGE_SIZE_SMALL:
877
                $width = 32;
878
879
                break;
880
881
            case self::USER_IMAGE_SIZE_MEDIUM:
882
                $width = 64;
883
884
                break;
885
886
            case self::USER_IMAGE_SIZE_BIG:
887
                $width = 128;
888
889
                break;
890
891
            case self::USER_IMAGE_SIZE_ORIGINAL:
892
            default:
893
                $width = 0;
894
895
                break;
896
        }
897
898
        $url = $this->illustrationRepository->getIllustrationUrl($user);
899
        $params = [];
900
        if (!empty($width)) {
901
            $params['w'] = $width;
902
        }
903
904
        if ($addRandomId) {
905
            $params['rand'] = uniqid('u_', true);
906
        }
907
908
        $paramsToString = '';
909
        if (!empty($params)) {
910
            $paramsToString = '?'.http_build_query($params);
911
        }
912
913
        return $url.$paramsToString;
914
    }
915
}
916