Passed
Push — master ( b3a6f6...c6237d )
by
unknown
20:07 queued 10:16
created

UserRepository::addAccessUrlQueryBuilder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 13
rs 10
c 1
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\SessionRelCourseRelUser;
17
use Chamilo\CoreBundle\Entity\SessionRelUser;
18
use Chamilo\CoreBundle\Entity\Tag;
19
use Chamilo\CoreBundle\Entity\TrackELogin;
20
use Chamilo\CoreBundle\Entity\TrackEOnline;
21
use Chamilo\CoreBundle\Entity\User;
22
use Chamilo\CoreBundle\Entity\Usergroup;
23
use Chamilo\CoreBundle\Entity\UsergroupRelUser;
24
use Chamilo\CoreBundle\Entity\UserRelTag;
25
use Chamilo\CoreBundle\Entity\UserRelUser;
26
use Chamilo\CoreBundle\Helpers\QueryCacheHelper;
27
use Chamilo\CoreBundle\Repository\ResourceRepository;
28
use Chamilo\CourseBundle\Entity\CGroupRelUser;
29
use Datetime;
30
use Doctrine\Common\Collections\Collection;
31
use Doctrine\Common\Collections\Criteria;
32
use Doctrine\DBAL\Types\Types;
33
use Doctrine\ORM\Query\Expr\Join;
34
use Doctrine\ORM\QueryBuilder;
35
use Doctrine\Persistence\ManagerRegistry;
36
use Exception;
37
use InvalidArgumentException;
38
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
39
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
40
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
41
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
42
use Symfony\Contracts\Translation\TranslatorInterface;
43
44
use const MB_CASE_LOWER;
45
46
class UserRepository extends ResourceRepository implements PasswordUpgraderInterface
47
{
48
    protected ?UserPasswordHasherInterface $hasher = null;
49
50
    public const USER_IMAGE_SIZE_SMALL = 1;
51
    public const USER_IMAGE_SIZE_MEDIUM = 2;
52
    public const USER_IMAGE_SIZE_BIG = 3;
53
    public const USER_IMAGE_SIZE_ORIGINAL = 4;
54
55
    public function __construct(
56
        ManagerRegistry $registry,
57
        private readonly IllustrationRepository $illustrationRepository,
58
        private readonly TranslatorInterface $translator,
59
        private readonly QueryCacheHelper $queryCacheHelper
60
    ) {
61
        parent::__construct($registry, User::class);
62
    }
63
64
    public function loadUserByIdentifier(string $identifier): ?User
65
    {
66
        return $this->findOneBy([
67
            'username' => $identifier,
68
        ]);
69
    }
70
71
    public function setHasher(UserPasswordHasherInterface $hasher): void
72
    {
73
        $this->hasher = $hasher;
74
    }
75
76
    public function createUser(): User
77
    {
78
        return new User();
79
    }
80
81
    public function updateUser(User $user, bool $andFlush = true): void
82
    {
83
        $this->updateCanonicalFields($user);
84
        $this->updatePassword($user);
85
        $this->getEntityManager()->persist($user);
86
        if ($andFlush) {
87
            $this->getEntityManager()->flush();
88
        }
89
    }
90
91
    public function canonicalize(string $string): string
92
    {
93
        $encoding = mb_detect_encoding($string, mb_detect_order(), true);
94
95
        return $encoding
96
            ? mb_convert_case($string, MB_CASE_LOWER, $encoding)
97
            : mb_convert_case($string, MB_CASE_LOWER);
98
    }
99
100
    public function updateCanonicalFields(User $user): void
101
    {
102
        $user->setUsernameCanonical($this->canonicalize($user->getUsername()));
103
        $user->setEmailCanonical($this->canonicalize($user->getEmail()));
104
    }
105
106
    public function updatePassword(User $user): void
107
    {
108
        $password = (string) $user->getPlainPassword();
109
        if ('' !== $password) {
110
            $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

110
            /** @scrutinizer ignore-call */ 
111
            $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...
111
            $user->setPassword($password);
112
            $user->eraseCredentials();
113
        }
114
    }
115
116
    public function isPasswordValid(User $user, string $plainPassword): bool
117
    {
118
        return $this->hasher->isPasswordValid($user, $plainPassword);
119
    }
120
121
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
122
    {
123
        /** @var User $user */
124
        $user->setPassword($newHashedPassword);
125
        $this->getEntityManager()->persist($user);
126
        $this->getEntityManager()->flush();
127
    }
128
129
    public function getRootUser(): User
130
    {
131
        $qb = $this->createQueryBuilder('u');
132
        $qb
133
            ->innerJoin(
134
                'u.resourceNode',
135
                'r'
136
            )
137
        ;
138
        $qb
139
            ->where('r.creator = u')
140
            ->andWhere('r.parent IS NULL')
141
            ->getFirstResult()
142
        ;
143
144
        $rootUser = $qb->getQuery()->getSingleResult();
145
146
        if (null === $rootUser) {
147
            throw new UserNotFoundException('Root user not found');
148
        }
149
150
        return $rootUser;
151
    }
152
153
    public function deleteUser(User $user, bool $destroy = false): void
154
    {
155
        $connection = $this->getEntityManager()->getConnection();
156
        $connection->beginTransaction();
157
158
        try {
159
            if ($destroy) {
160
                // Call method to delete messages and attachments
161
                $this->deleteUserMessagesAndAttachments($user);
162
163
                $fallbackUser = $this->getFallbackUser();
164
165
                if ($fallbackUser) {
166
                    $this->reassignUserResourcesToFallbackSQL($user, $fallbackUser, $connection);
167
                }
168
169
                // Remove group relationships
170
                $connection->executeStatement(
171
                    'DELETE FROM usergroup_rel_user WHERE user_id = :userId',
172
                    ['userId' => $user->getId()]
173
                );
174
175
                // Remove resource node if exists
176
                $connection->executeStatement(
177
                    'DELETE FROM resource_node WHERE id = :nodeId',
178
                    ['nodeId' => $user->getResourceNode()->getId()]
179
                );
180
181
                // Remove the user itself
182
                $connection->executeStatement(
183
                    'DELETE FROM user WHERE id = :userId',
184
                    ['userId' => $user->getId()]
185
                );
186
            } else {
187
                // Soft delete the user
188
                $connection->executeStatement(
189
                    'UPDATE user SET active = :softDeleted WHERE id = :userId',
190
                    ['softDeleted' => User::SOFT_DELETED, 'userId' => $user->getId()]
191
                );
192
            }
193
194
            $connection->commit();
195
        } catch (Exception $e) {
196
            $connection->rollBack();
197
198
            throw $e;
199
        }
200
    }
201
202
    /**
203
     * Reassigns resources and related data from a deleted user to a fallback user in the database.
204
     *
205
     * @param mixed $connection
206
     */
207
    protected function reassignUserResourcesToFallbackSQL(User $userToDelete, User $fallbackUser, $connection): void
208
    {
209
        // Update resource nodes created by the user
210
        $connection->executeStatement(
211
            'UPDATE resource_node SET creator_id = :fallbackUserId WHERE creator_id = :userId',
212
            ['fallbackUserId' => $fallbackUser->getId(), 'userId' => $userToDelete->getId()]
213
        );
214
215
        // Update child resource nodes
216
        $connection->executeStatement(
217
            'UPDATE resource_node SET parent_id = :fallbackParentId WHERE parent_id = :userParentId',
218
            [
219
                'fallbackParentId' => $fallbackUser->getResourceNode()?->getId(),
220
                'userParentId' => $userToDelete->getResourceNode()->getId(),
221
            ]
222
        );
223
224
        // Relations to update or delete
225
        $relations = $this->getRelations();
226
227
        foreach ($relations as $relation) {
228
            $table = $relation['table'];
229
            $field = $relation['field'];
230
            $action = $relation['action'];
231
232
            if ('delete' === $action) {
233
                $connection->executeStatement(
234
                    "DELETE FROM $table WHERE $field = :userId",
235
                    ['userId' => $userToDelete->getId()]
236
                );
237
            } elseif ('update' === $action) {
238
                $connection->executeStatement(
239
                    "UPDATE $table SET $field = :fallbackUserId WHERE $field = :userId",
240
                    [
241
                        'fallbackUserId' => $fallbackUser->getId(),
242
                        'userId' => $userToDelete->getId(),
243
                    ]
244
                );
245
            }
246
        }
247
    }
248
249
    /**
250
     * Provides a list of database table relations and their respective actions
251
     * (update or delete) for handling user resource reassignment or deletion.
252
     *
253
     * Any new database table that stores references to users and requires updates
254
     * or deletions when a user is removed should be added to this list. This ensures
255
     * proper handling of dependencies and avoids orphaned data.
256
     */
257
    protected function getRelations(): array
258
    {
259
        return [
260
            ['table' => 'access_url_rel_user', 'field' => 'user_id', 'action' => 'delete'],
261
            ['table' => 'admin', 'field' => 'user_id', 'action' => 'delete'],
262
            ['table' => 'attempt_feedback', 'field' => 'user_id', 'action' => 'update'],
263
            ['table' => 'chat', 'field' => 'to_user', 'action' => 'update'],
264
            ['table' => 'chat_video', 'field' => 'to_user', 'action' => 'update'],
265
            ['table' => 'course_rel_user', 'field' => 'user_id', 'action' => 'delete'],
266
            ['table' => 'course_rel_user_catalogue', 'field' => 'user_id', 'action' => 'delete'],
267
            ['table' => 'course_request', 'field' => 'user_id', 'action' => 'update'],
268
            ['table' => 'c_attendance_result', 'field' => 'user_id', 'action' => 'delete'],
269
            ['table' => 'c_attendance_result_comment', 'field' => 'user_id', 'action' => 'update'],
270
            ['table' => 'c_attendance_sheet', 'field' => 'user_id', 'action' => 'delete'],
271
            ['table' => 'c_attendance_sheet_log', 'field' => 'lastedit_user_id', 'action' => 'delete'],
272
            ['table' => 'c_chat_connected', 'field' => 'user_id', 'action' => 'delete'],
273
            ['table' => 'c_dropbox_category', 'field' => 'user_id', 'action' => 'update'],
274
            ['table' => 'c_dropbox_feedback', 'field' => 'author_user_id', 'action' => 'update'],
275
            ['table' => 'c_dropbox_person', 'field' => 'user_id', 'action' => 'update'],
276
            ['table' => 'c_dropbox_post', 'field' => 'dest_user_id', 'action' => 'update'],
277
            ['table' => 'c_forum_mailcue', 'field' => 'user_id', 'action' => 'delete'],
278
            ['table' => 'c_forum_notification', 'field' => 'user_id', 'action' => 'delete'],
279
            ['table' => 'c_forum_post', 'field' => 'poster_id', 'action' => 'update'],
280
            ['table' => 'c_forum_thread', 'field' => 'thread_poster_id', 'action' => 'update'],
281
            ['table' => 'c_forum_thread_qualify', 'field' => 'user_id', 'action' => 'update'],
282
            ['table' => 'c_forum_thread_qualify_log', 'field' => 'user_id', 'action' => 'update'],
283
            ['table' => 'c_group_rel_tutor', 'field' => 'user_id', 'action' => 'update'],
284
            ['table' => 'c_group_rel_user', 'field' => 'user_id', 'action' => 'update'],
285
            ['table' => 'c_lp_category_rel_user', 'field' => 'user_id', 'action' => 'delete'],
286
            ['table' => 'c_lp_rel_user', 'field' => 'user_id', 'action' => 'delete'],
287
            ['table' => 'c_lp_view', 'field' => 'user_id', 'action' => 'delete'],
288
            ['table' => 'c_student_publication_comment', 'field' => 'user_id', 'action' => 'delete'],
289
            ['table' => 'c_student_publication_rel_user', 'field' => 'user_id', 'action' => 'delete'],
290
            ['table' => 'c_survey_invitation', 'field' => 'user_id', 'action' => 'update'],
291
            ['table' => 'c_wiki', 'field' => 'user_id', 'action' => 'update'],
292
            ['table' => 'c_wiki_mailcue', 'field' => 'user_id', 'action' => 'delete'],
293
            ['table' => 'extra_field_saved_search', 'field' => 'user_id', 'action' => 'delete'],
294
            ['table' => 'gradebook_category', 'field' => 'user_id', 'action' => 'update'],
295
            ['table' => 'gradebook_certificate', 'field' => 'user_id', 'action' => 'delete'],
296
            ['table' => 'gradebook_comment', 'field' => 'user_id', 'action' => 'update'],
297
            ['table' => 'gradebook_linkeval_log', 'field' => 'user_id_log', 'action' => 'delete'],
298
            ['table' => 'gradebook_result', 'field' => 'user_id', 'action' => 'delete'],
299
            ['table' => 'gradebook_result_log', 'field' => 'user_id', 'action' => 'delete'],
300
            ['table' => 'gradebook_score_log', 'field' => 'user_id', 'action' => 'delete'],
301
            ['table' => 'message', 'field' => 'user_sender_id', 'action' => 'update'],
302
            ['table' => 'message_rel_user', 'field' => 'user_id', 'action' => 'delete'],
303
            ['table' => 'message_tag', 'field' => 'user_id', 'action' => 'delete'],
304
            ['table' => 'notification', 'field' => 'dest_user_id', 'action' => 'delete'],
305
            ['table' => 'page_category', 'field' => 'creator_id', 'action' => 'update'],
306
            ['table' => 'portfolio', 'field' => 'user_id', 'action' => 'update'],
307
            ['table' => 'portfolio_category', 'field' => 'user_id', 'action' => 'update'],
308
            ['table' => 'portfolio_comment', 'field' => 'author_id', 'action' => 'update'],
309
            ['table' => 'resource_comment', 'field' => 'author_id', 'action' => 'update'],
310
            ['table' => 'sequence_value', 'field' => 'user_id', 'action' => 'update'],
311
            ['table' => 'session_rel_course_rel_user', 'field' => 'user_id', 'action' => 'delete'],
312
            ['table' => 'session_rel_user', 'field' => 'user_id', 'action' => 'delete'],
313
            ['table' => 'skill_rel_item_rel_user', 'field' => 'user_id', 'action' => 'delete'],
314
            ['table' => 'skill_rel_user', 'field' => 'user_id', 'action' => 'delete'],
315
            ['table' => 'skill_rel_user_comment', 'field' => 'feedback_giver_id', 'action' => 'delete'],
316
            ['table' => 'social_post', 'field' => 'sender_id', 'action' => 'update'],
317
            ['table' => 'social_post', 'field' => 'user_receiver_id', 'action' => 'update'],
318
            ['table' => 'social_post_attachments', 'field' => 'sys_insert_user_id', 'action' => 'update'],
319
            ['table' => 'social_post_attachments', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
320
            ['table' => 'social_post_feedback', 'field' => 'user_id', 'action' => 'update'],
321
            ['table' => 'templates', 'field' => 'user_id', 'action' => 'update'],
322
            ['table' => 'ticket_assigned_log', 'field' => 'user_id', 'action' => 'update'],
323
            ['table' => 'ticket_assigned_log', 'field' => 'sys_insert_user_id', 'action' => 'update'],
324
            ['table' => 'ticket_category', 'field' => 'sys_insert_user_id', 'action' => 'update'],
325
            ['table' => 'ticket_category', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
326
            ['table' => 'ticket_category_rel_user', 'field' => 'user_id', 'action' => 'delete'],
327
            ['table' => 'ticket_message', 'field' => 'sys_insert_user_id', 'action' => 'update'],
328
            ['table' => 'ticket_message', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
329
            ['table' => 'ticket_message_attachments', 'field' => 'sys_insert_user_id', 'action' => 'update'],
330
            ['table' => 'ticket_message_attachments', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
331
            ['table' => 'ticket_priority', 'field' => 'sys_insert_user_id', 'action' => 'update'],
332
            ['table' => 'ticket_priority', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
333
            ['table' => 'ticket_project', 'field' => 'sys_insert_user_id', 'action' => 'update'],
334
            ['table' => 'ticket_project', 'field' => 'sys_lastedit_user_id', 'action' => 'update'],
335
            ['table' => 'track_e_access', 'field' => 'access_user_id', 'action' => 'delete'],
336
            ['table' => 'track_e_access_complete', 'field' => 'user_id', 'action' => 'delete'],
337
            ['table' => 'track_e_attempt', 'field' => 'user_id', 'action' => 'delete'],
338
            ['table' => 'track_e_course_access', 'field' => 'user_id', 'action' => 'delete'],
339
            ['table' => 'track_e_default', 'field' => 'default_user_id', 'action' => 'update'],
340
            ['table' => 'track_e_downloads', 'field' => 'down_user_id', 'action' => 'delete'],
341
            ['table' => 'track_e_exercises', 'field' => 'exe_user_id', 'action' => 'delete'],
342
            ['table' => 'track_e_exercise_confirmation', 'field' => 'user_id', 'action' => 'delete'],
343
            ['table' => 'track_e_hotpotatoes', 'field' => 'exe_user_id', 'action' => 'delete'],
344
            ['table' => 'track_e_hotspot', 'field' => 'hotspot_user_id', 'action' => 'delete'],
345
            ['table' => 'track_e_lastaccess', 'field' => 'access_user_id', 'action' => 'delete'],
346
            ['table' => 'track_e_links', 'field' => 'links_user_id', 'action' => 'delete'],
347
            ['table' => 'track_e_login', 'field' => 'login_user_id', 'action' => 'delete'],
348
            ['table' => 'track_e_online', 'field' => 'login_user_id', 'action' => 'delete'],
349
            ['table' => 'track_e_uploads', 'field' => 'upload_user_id', 'action' => 'delete'],
350
            ['table' => 'usergroup_rel_user', 'field' => 'user_id', 'action' => 'update'],
351
            ['table' => 'user_rel_tag', 'field' => 'user_id', 'action' => 'delete'],
352
            ['table' => 'user_rel_user', 'field' => 'user_id', 'action' => 'delete'],
353
        ];
354
    }
355
356
    /**
357
     * Deletes a user's messages and their attachments, updates the message content,
358
     * and detaches the user as the sender.
359
     */
360
    public function deleteUserMessagesAndAttachments(User $user): void
361
    {
362
        $em = $this->getEntityManager();
363
        $connection = $em->getConnection();
364
365
        $currentDate = (new Datetime())->format('Y-m-d H:i:s');
366
        $updatedContent = \sprintf(
367
            $this->translator->trans('This message was deleted when the user was removed from the platform on %s'),
368
            $currentDate
369
        );
370
371
        $connection->executeStatement(
372
            'UPDATE message m
373
         SET m.content = :content, m.user_sender_id = NULL
374
         WHERE m.user_sender_id = :userId',
375
            [
376
                'content' => $updatedContent,
377
                'userId' => $user->getId(),
378
            ]
379
        );
380
381
        $connection->executeStatement(
382
            'DELETE ma
383
         FROM message_attachment ma
384
         INNER JOIN message m ON ma.message_id = m.id
385
         WHERE m.user_sender_id IS NULL',
386
            [
387
                'userId' => $user->getId(),
388
            ]
389
        );
390
391
        $em->clear();
392
    }
393
394
    public function getFallbackUser(): ?User
395
    {
396
        return $this->findOneBy(['status' => User::ROLE_FALLBACK], ['id' => 'ASC']);
397
    }
398
399
    public function addUserToResourceNode(int $userId, int $creatorId): ResourceNode
400
    {
401
        /** @var User $user */
402
        $user = $this->find($userId);
403
        $creator = $this->find($creatorId);
404
405
        $resourceNode = (new ResourceNode())
406
            ->setTitle($user->getUsername())
407
            ->setCreator($creator)
408
            ->setResourceType($this->getResourceType())
409
            // ->setParent($resourceNode)
410
        ;
411
412
        $user->setResourceNode($resourceNode);
413
414
        $this->getEntityManager()->persist($resourceNode);
415
        $this->getEntityManager()->persist($user);
416
417
        return $resourceNode;
418
    }
419
420
    public function addRoleListQueryBuilder(array $roles, ?QueryBuilder $qb = null): QueryBuilder
421
    {
422
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
423
        if (!empty($roles)) {
424
            $orX = $qb->expr()->orX();
425
            foreach ($roles as $role) {
426
                $orX->add($qb->expr()->like('u.roles', ':'.$role));
427
                $qb->setParameter($role, '%'.$role.'%');
428
            }
429
            $qb->andWhere($orX);
430
        }
431
432
        return $qb;
433
    }
434
435
    public function findByUsername(string $username): ?User
436
    {
437
        $user = $this->findOneBy([
438
            'username' => $username,
439
        ]);
440
441
        if (null === $user) {
442
            throw new UserNotFoundException(\sprintf("User with id '%s' not found.", $username));
443
        }
444
445
        return $user;
446
    }
447
448
    public function findAllUsers(bool $useCache = false): array
449
    {
450
        $qb = $this->createQueryBuilder('u');
451
452
        if ($useCache) {
453
            return $this->queryCacheHelper->run(
454
                $qb,
455
                'findAllUsers',
456
                []
457
            );
458
        }
459
460
        return $qb->getQuery()->getResult();
461
    }
462
463
    /**
464
     * Get a filtered list of user by role and (optionally) access url.
465
     *
466
     * @param string $keyword     The query to filter
467
     * @param int    $accessUrlId The access URL ID
468
     *
469
     * @return User[]
470
     */
471
    public function findByRole(
472
        string $role,
473
        string $keyword,
474
        int $accessUrlId = 0,
475
        bool $useCache = false
476
    ) {
477
        $qb = $this->createQueryBuilder('u');
478
479
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
480
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
481
        $this->addRoleQueryBuilder($role, $qb);
482
        $this->addSearchByKeywordQueryBuilder($keyword, $qb);
483
484
        if ($useCache) {
485
            return $this->queryCacheHelper->run(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->queryCache...rlId' => $accessUrlId)) returns an array which contains values of type string which are incompatible with the documented value type Chamilo\CoreBundle\Entity\User.
Loading history...
486
                $qb,
487
                'findByRole',
488
                [
489
                    'role' => $role,
490
                    'keyword' => $keyword,
491
                    'accessUrlId' => $accessUrlId,
492
                ]
493
            );
494
        }
495
496
        return $qb->getQuery()->getResult();
497
    }
498
499
    public function findByRoleList(
500
        array $roleList,
501
        string $keyword,
502
        int $accessUrlId = 0,
503
        bool $useCache = false
504
    ) {
505
        $qb = $this->createQueryBuilder('u');
506
507
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
508
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
509
        $this->addRoleListQueryBuilder($roleList, $qb);
510
        $this->addSearchByKeywordQueryBuilder($keyword, $qb);
511
512
        if ($useCache) {
513
            return $this->queryCacheHelper->run(
514
                $qb,
515
                'findByRoleList',
516
                [
517
                    'roles' => $roleList,
518
                    'keyword' => $keyword,
519
                    'accessUrlId' => $accessUrlId,
520
                ]
521
            );
522
        }
523
524
        return $qb->getQuery()->getResult();
525
    }
526
527
    /**
528
     * Get the coaches for a course within a session.
529
     *
530
     * @return Collection|array
531
     */
532
    public function getCoachesForSessionCourse(Session $session, Course $course)
533
    {
534
        $qb = $this->createQueryBuilder('u');
535
536
        $qb->select('u')
537
            ->innerJoin(
538
                SessionRelCourseRelUser::class,
539
                'scu',
540
                Join::WITH,
541
                'scu.user = u'
542
            )
543
            ->where(
544
                $qb->expr()->andX(
545
                    $qb->expr()->eq('scu.session', $session->getId()),
546
                    $qb->expr()->eq('scu.course', $course->getId()),
547
                    $qb->expr()->eq('scu.status', Session::COURSE_COACH)
548
                )
549
            )
550
        ;
551
552
        return $qb->getQuery()->getResult();
553
    }
554
555
    /**
556
     * Get the sessions admins for a user.
557
     *
558
     * @return array
559
     */
560
    public function getSessionAdmins(User $user)
561
    {
562
        $qb = $this->createQueryBuilder('u');
563
        $qb
564
            ->distinct()
565
            ->innerJoin(
566
                SessionRelUser::class,
567
                'su',
568
                Join::WITH,
569
                'u = su.user'
570
            )
571
            ->innerJoin(
572
                SessionRelCourseRelUser::class,
573
                'scu',
574
                Join::WITH,
575
                'su.session = scu.session'
576
            )
577
            ->where(
578
                $qb->expr()->eq('scu.user', $user->getId())
579
            )
580
            ->andWhere(
581
                $qb->expr()->eq('su.relationType', Session::DRH)
582
            )
583
        ;
584
585
        return $qb->getQuery()->getResult();
586
    }
587
588
    /**
589
     * Get number of users in URL.
590
     *
591
     * @return int
592
     */
593
    public function getCountUsersByUrl(AccessUrl $url)
594
    {
595
        return $this->createQueryBuilder('u')
596
            ->select('COUNT(u)')
597
            ->innerJoin('u.portals', 'p')
598
            ->where('p.url = :url')
599
            ->setParameters([
600
                'url' => $url,
601
            ])
602
            ->getQuery()
603
            ->getSingleScalarResult()
604
        ;
605
    }
606
607
    /**
608
     * Get number of users in URL.
609
     *
610
     * @return int
611
     */
612
    public function getCountTeachersByUrl(AccessUrl $url)
613
    {
614
        $qb = $this->createQueryBuilder('u');
615
616
        $qb
617
            ->select('COUNT(u)')
618
            ->innerJoin('u.portals', 'p')
619
            ->where('p.url = :url')
620
            ->setParameters([
621
                'url' => $url,
622
            ])
623
        ;
624
625
        $this->addRoleListQueryBuilder(['ROLE_TEACHER'], $qb);
626
627
        return (int) $qb->getQuery()->getSingleScalarResult();
628
    }
629
630
    /**
631
     * Find potential users to send a message.
632
     *
633
     * @todo remove  api_is_platform_admin
634
     *
635
     * @param int    $currentUserId The current user ID
636
     * @param string $searchFilter  Optional. The search text to filter the user list
637
     * @param int    $limit         Optional. Sets the maximum number of results to retrieve
638
     *
639
     * @return User[]
640
     */
641
    public function findUsersToSendMessage(int $currentUserId, ?string $searchFilter = null, int $limit = 10)
642
    {
643
        $allowSendMessageToAllUsers = api_get_setting('allow_send_message_to_all_platform_users');
644
        $accessUrlId = api_get_multiple_access_url() ? api_get_current_access_url_id() : 1;
645
646
        $messageTool = 'true' === api_get_setting('allow_message_tool');
647
        if (!$messageTool) {
648
            return [];
649
        }
650
651
        $qb = $this->createQueryBuilder('u');
652
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
653
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
654
655
        $dql = null;
656
        if ('true' === api_get_setting('allow_social_tool')) {
657
            // All users
658
            if ('true' === $allowSendMessageToAllUsers || api_is_platform_admin()) {
659
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
660
            /*$dql = "SELECT DISTINCT U
661
                    FROM ChamiloCoreBundle:User U
662
                    LEFT JOIN ChamiloCoreBundle:AccessUrlRelUser R
663
                    WITH U = R.user
664
                    WHERE
665
                        U.active = 1 AND
666
                        U.status != 6  AND
667
                        U.id != {$currentUserId} AND
668
                        R.url = {$accessUrlId}";*/
669
            } else {
670
                $this->addOnlyMyFriendsQueryBuilder($currentUserId, $qb);
671
                /*$dql = 'SELECT DISTINCT U
672
                        FROM ChamiloCoreBundle:AccessUrlRelUser R, ChamiloCoreBundle:UserRelUser UF
673
                        INNER JOIN ChamiloCoreBundle:User AS U
674
                        WITH UF.friendUserId = U
675
                        WHERE
676
                            U.active = 1 AND
677
                            U.status != 6 AND
678
                            UF.relationType NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.") AND
679
                            UF.user = {$currentUserId} AND
680
                            UF.friendUserId != {$currentUserId} AND
681
                            U = R.user AND
682
                            R.url = {$accessUrlId}";*/
683
            }
684
        } else {
685
            if ('true' === $allowSendMessageToAllUsers) {
686
                $this->addNotCurrentUserQueryBuilder($currentUserId, $qb);
687
            } else {
688
                return [];
689
            }
690
691
            /*else {
692
                $time_limit = (int) api_get_setting('time_limit_whosonline');
693
                $online_time = time() - ($time_limit * 60);
694
                $limit_date = api_get_utc_datetime($online_time);
695
                $dql = "SELECT DISTINCT U
696
                        FROM ChamiloCoreBundle:User U
697
                        INNER JOIN ChamiloCoreBundle:TrackEOnline T
698
                        WITH U.id = T.loginUserId
699
                        WHERE
700
                          U.active = 1 AND
701
                          T.loginDate >= '".$limit_date."'";
702
            }*/
703
        }
704
705
        if (!empty($searchFilter)) {
706
            $this->addSearchByKeywordQueryBuilder($searchFilter, $qb);
707
        }
708
709
        return $qb->getQuery()->getResult();
710
    }
711
712
    /**
713
     * Get the list of HRM who have assigned this user.
714
     *
715
     * @return User[]
716
     */
717
    public function getAssignedHrmUserList(int $userId, int $urlId)
718
    {
719
        $qb = $this->createQueryBuilder('u');
720
        $this->addAccessUrlQueryBuilder($urlId, $qb);
721
        $this->addActiveAndNotAnonUserQueryBuilder($qb);
722
        $this->addUserRelUserQueryBuilder($userId, UserRelUser::USER_RELATION_TYPE_RRHH, $qb);
723
724
        return $qb->getQuery()->getResult();
725
    }
726
727
    /**
728
     * Get the last login from the track_e_login table.
729
     * This might be different from user.last_login in the case of legacy users
730
     * as user.last_login was only implemented in 1.10 version with a default
731
     * value of NULL (not the last record from track_e_login).
732
     *
733
     * @return null|TrackELogin
734
     */
735
    public function getLastLogin(User $user)
736
    {
737
        $qb = $this->createQueryBuilder('u');
738
739
        return $qb
740
            ->select('l')
741
            ->innerJoin('u.logins', 'l')
742
            ->where(
743
                $qb->expr()->eq('l.user', $user)
744
            )
745
            ->setMaxResults(1)
746
            ->orderBy('u.loginDate', Criteria::DESC)
747
            ->getQuery()
748
            ->getOneOrNullResult()
749
        ;
750
    }
751
752
    public function addAccessUrlQueryBuilder(int $accessUrlId, ?QueryBuilder $qb = null): QueryBuilder
753
    {
754
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
755
756
        if ($accessUrlId > 0) {
757
            $qb
758
                ->innerJoin('u.portals', 'p')
759
                ->andWhere('p.url = :url')
760
                ->setParameter('url', $accessUrlId, Types::INTEGER)
761
            ;
762
        }
763
764
        return $qb;
765
    }
766
767
    public function addActiveAndNotAnonUserQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
768
    {
769
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
770
        $qb
771
            ->andWhere('u.active = 1')
772
            ->andWhere('u.status <> :status')
773
            ->setParameter('status', User::ANONYMOUS, Types::INTEGER)
774
        ;
775
776
        return $qb;
777
    }
778
779
    public function addExpirationDateQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
780
    {
781
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
782
        $qb
783
            ->andWhere('u.expirationDate IS NULL OR u.expirationDate > :now')
784
            ->setParameter('now', new Datetime(), Types::DATETIME_MUTABLE)
785
        ;
786
787
        return $qb;
788
    }
789
790
    private function addRoleQueryBuilder(string $role, ?QueryBuilder $qb = null): QueryBuilder
791
    {
792
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
793
        $qb
794
            ->andWhere('u.roles LIKE :roles')
795
            ->setParameter('roles', '%"'.$role.'"%', Types::STRING)
796
        ;
797
798
        return $qb;
799
    }
800
801
    private function addSearchByKeywordQueryBuilder(string $keyword, ?QueryBuilder $qb = null): QueryBuilder
802
    {
803
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
804
        $qb
805
            ->andWhere('
806
                u.firstname LIKE :keyword OR
807
                u.lastname LIKE :keyword OR
808
                u.email LIKE :keyword OR
809
                u.username LIKE :keyword
810
            ')
811
            ->setParameter('keyword', "%$keyword%", Types::STRING)
812
            ->orderBy('u.firstname', Criteria::ASC)
813
        ;
814
815
        return $qb;
816
    }
817
818
    private function addUserRelUserQueryBuilder(int $userId, int $relationType, ?QueryBuilder $qb = null): QueryBuilder
819
    {
820
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
821
        $qb->leftJoin('u.friends', 'relations');
822
        $qb
823
            ->andWhere('relations.relationType = :relationType')
824
            ->andWhere('relations.user = :userRelation AND relations.friend <> :userRelation')
825
            ->setParameter('relationType', $relationType)
826
            ->setParameter('userRelation', $userId)
827
        ;
828
829
        return $qb;
830
    }
831
832
    private function addOnlyMyFriendsQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
833
    {
834
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
835
        $qb
836
            ->leftJoin('u.friends', 'relations')
837
            ->andWhere(
838
                $qb->expr()->notIn(
839
                    'relations.relationType',
840
                    [UserRelUser::USER_RELATION_TYPE_DELETED, UserRelUser::USER_RELATION_TYPE_RRHH]
841
                )
842
            )
843
            ->andWhere('relations.user = :user AND relations.friend <> :user')
844
            ->setParameter('user', $userId, Types::INTEGER)
845
        ;
846
847
        return $qb;
848
    }
849
850
    private function addNotCurrentUserQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
851
    {
852
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
853
        $qb
854
            ->andWhere('u.id <> :id')
855
            ->setParameter('id', $userId, Types::INTEGER)
856
        ;
857
858
        return $qb;
859
    }
860
861
    public function getFriendsNotInGroup(int $userId, int $groupId)
862
    {
863
        $entityManager = $this->getEntityManager();
864
865
        $subQueryBuilder = $entityManager->createQueryBuilder();
866
        $subQuery = $subQueryBuilder
867
            ->select('IDENTITY(ugr.user)')
868
            ->from(UsergroupRelUser::class, 'ugr')
869
            ->where('ugr.usergroup = :subGroupId')
870
            ->andWhere('ugr.relationType IN (:subRelationTypes)')
871
            ->getDQL()
872
        ;
873
874
        $queryBuilder = $entityManager->createQueryBuilder();
875
        $query = $queryBuilder
876
            ->select('u')
877
            ->from(User::class, 'u')
878
            ->leftJoin('u.friendsWithMe', 'uruf')
879
            ->leftJoin('u.friends', 'urut')
880
            ->where('uruf.friend = :userId OR urut.user = :userId')
881
            ->andWhere($queryBuilder->expr()->notIn('u.id', $subQuery))
882
            ->setParameter('userId', $userId)
883
            ->setParameter('subGroupId', $groupId)
884
            ->setParameter('subRelationTypes', [Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION])
885
            ->getQuery()
886
        ;
887
888
        return $query->getResult();
889
    }
890
891
    public function getExtraUserData(int $userId, bool $prefix = false, bool $allVisibility = true, bool $splitMultiple = false, ?int $fieldFilter = null): array
892
    {
893
        $qb = $this->getEntityManager()->createQueryBuilder();
894
895
        // Start building the query
896
        $qb->select('ef.id', 'ef.variable as fvar', 'ef.valueType as type', 'efv.fieldValue as fval', 'ef.defaultValue as fval_df')
897
            ->from(ExtraField::class, 'ef')
898
            ->leftJoin(ExtraFieldValues::class, 'efv', Join::WITH, 'efv.field = ef.id AND efv.itemId = :userId')
899
            ->where('ef.itemType = :itemType')
900
            ->setParameter('userId', $userId)
901
            ->setParameter('itemType', ExtraField::USER_FIELD_TYPE)
902
        ;
903
904
        // Apply visibility filters
905
        if (!$allVisibility) {
906
            $qb->andWhere('ef.visibleToSelf = true');
907
        }
908
909
        // Apply field filter if provided
910
        if (null !== $fieldFilter) {
911
            $qb->andWhere('ef.id = :fieldFilter')
912
                ->setParameter('fieldFilter', $fieldFilter)
913
            ;
914
        }
915
916
        // Order by field order
917
        $qb->orderBy('ef.fieldOrder', 'ASC');
918
919
        // Execute the query
920
        $results = $qb->getQuery()->getResult();
921
922
        // Process results
923
        $extraData = [];
924
        foreach ($results as $row) {
925
            $value = $row['fval'] ?? $row['fval_df'];
926
927
            // Handle multiple values if necessary
928
            if ($splitMultiple && \in_array($row['type'], [ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE], true)) {
929
                $value = explode(';', $value);
930
            }
931
932
            // Handle prefix if needed
933
            $key = $prefix ? 'extra_'.$row['fvar'] : $row['fvar'];
934
935
            // Special handling for certain field types
936
            if (ExtraField::USER_FIELD_TYPE_TAG == $row['type']) {
937
                // Implement your logic to handle tags
938
            } elseif (ExtraField::USER_FIELD_TYPE_RADIO == $row['type'] && $prefix) {
939
                $extraData[$key][$key] = $value;
940
            } else {
941
                $extraData[$key] = $value;
942
            }
943
        }
944
945
        return $extraData;
946
    }
947
948
    public function getExtraUserDataByField(int $userId, string $fieldVariable, bool $allVisibility = true): array
949
    {
950
        $qb = $this->getEntityManager()->createQueryBuilder();
951
952
        $qb->select('e.id, e.variable, e.valueType, v.fieldValue')
953
            ->from(ExtraFieldValues::class, 'v')
954
            ->innerJoin('v.field', 'e')
955
            ->where('v.itemId = :userId')
956
            ->andWhere('e.variable = :fieldVariable')
957
            ->andWhere('e.itemType = :itemType')
958
            ->setParameters([
959
                'userId' => $userId,
960
                'fieldVariable' => $fieldVariable,
961
                'itemType' => ExtraField::USER_FIELD_TYPE,
962
            ])
963
        ;
964
965
        if (!$allVisibility) {
966
            $qb->andWhere('e.visibleToSelf = true');
967
        }
968
969
        $qb->orderBy('e.fieldOrder', 'ASC');
970
971
        $result = $qb->getQuery()->getResult();
972
973
        $extraData = [];
974
        foreach ($result as $row) {
975
            $value = $row['fieldValue'];
976
            if (ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['valueType']) {
977
                $value = explode(';', $row['fieldValue']);
978
            }
979
980
            $extraData[$row['variable']] = $value;
981
        }
982
983
        return $extraData;
984
    }
985
986
    public function searchUsersByTags(
987
        string $tag,
988
        ?int $excludeUserId = null,
989
        int $fieldId = 0,
990
        int $from = 0,
991
        int $number_of_items = 10,
992
        bool $getCount = false
993
    ): array {
994
        $qb = $this->createQueryBuilder('u');
995
996
        if ($getCount) {
997
            $qb->select('COUNT(DISTINCT u.id)');
998
        } else {
999
            $qb->select('DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.pictureUri, u.status');
1000
        }
1001
1002
        $qb->innerJoin('u.portals', 'urlRelUser')
1003
            ->leftJoin(UserRelTag::class, 'uv', 'WITH', 'u = uv.user')
1004
            ->leftJoin(Tag::class, 'ut', 'WITH', 'uv.tag = ut')
1005
        ;
1006
1007
        if (0 !== $fieldId) {
1008
            $qb->andWhere('ut.field = :fieldId')
1009
                ->setParameter('fieldId', $fieldId)
1010
            ;
1011
        }
1012
1013
        if (null !== $excludeUserId) {
1014
            $qb->andWhere('u.id != :excludeUserId')
1015
                ->setParameter('excludeUserId', $excludeUserId)
1016
            ;
1017
        }
1018
1019
        $qb->andWhere(
1020
            $qb->expr()->orX(
1021
                $qb->expr()->like('ut.tag', ':tag'),
1022
                $qb->expr()->like('u.firstname', ':likeTag'),
1023
                $qb->expr()->like('u.lastname', ':likeTag'),
1024
                $qb->expr()->like('u.username', ':likeTag'),
1025
                $qb->expr()->like(
1026
                    $qb->expr()->concat('u.firstname', $qb->expr()->literal(' '), 'u.lastname'),
1027
                    ':likeTag'
1028
                ),
1029
                $qb->expr()->like(
1030
                    $qb->expr()->concat('u.lastname', $qb->expr()->literal(' '), 'u.firstname'),
1031
                    ':likeTag'
1032
                )
1033
            )
1034
        )
1035
            ->setParameter('tag', $tag.'%')
1036
            ->setParameter('likeTag', '%'.$tag.'%')
1037
        ;
1038
1039
        // Only active users and not anonymous
1040
        $qb->andWhere('u.active = :active')
1041
            ->andWhere('u.status != :anonymous')
1042
            ->setParameter('active', true)
1043
            ->setParameter('anonymous', 6)
1044
        ;
1045
1046
        if (!$getCount) {
1047
            $qb->orderBy('u.username')
1048
                ->setFirstResult($from)
1049
                ->setMaxResults($number_of_items)
1050
            ;
1051
        }
1052
1053
        return $getCount ? $qb->getQuery()->getSingleScalarResult() : $qb->getQuery()->getResult();
1054
    }
1055
1056
    public function getUserRelationWithType(int $userId, int $friendId): ?array
1057
    {
1058
        $qb = $this->createQueryBuilder('u');
1059
        $qb->select('u.id AS userId', 'u.username AS userName', 'ur.relationType', 'f.id AS friendId', 'f.username AS friendName')
1060
            ->innerJoin('u.friends', 'ur')
1061
            ->innerJoin('ur.friend', 'f')
1062
            ->where('u.id = :userId AND f.id = :friendId')
1063
            ->setParameter('userId', $userId)
1064
            ->setParameter('friendId', $friendId)
1065
            ->setMaxResults(1)
1066
        ;
1067
1068
        return $qb->getQuery()->getOneOrNullResult();
1069
    }
1070
1071
    public function relateUsers(User $user1, User $user2, int $relationType): void
1072
    {
1073
        $em = $this->getEntityManager();
1074
1075
        $existingRelation = $em->getRepository(UserRelUser::class)->findOneBy([
1076
            'user' => $user1,
1077
            'friend' => $user2,
1078
        ]);
1079
1080
        if (!$existingRelation) {
1081
            $newRelation = new UserRelUser();
1082
            $newRelation->setUser($user1);
1083
            $newRelation->setFriend($user2);
1084
            $newRelation->setRelationType($relationType);
1085
            $em->persist($newRelation);
1086
        } else {
1087
            $existingRelation->setRelationType($relationType);
1088
        }
1089
1090
        $existingRelationInverse = $em->getRepository(UserRelUser::class)->findOneBy([
1091
            'user' => $user2,
1092
            'friend' => $user1,
1093
        ]);
1094
1095
        if (!$existingRelationInverse) {
1096
            $newRelationInverse = new UserRelUser();
1097
            $newRelationInverse->setUser($user2);
1098
            $newRelationInverse->setFriend($user1);
1099
            $newRelationInverse->setRelationType($relationType);
1100
            $em->persist($newRelationInverse);
1101
        } else {
1102
            $existingRelationInverse->setRelationType($relationType);
1103
        }
1104
1105
        $em->flush();
1106
    }
1107
1108
    public function getUserPicture(
1109
        $userId,
1110
        int $size = self::USER_IMAGE_SIZE_MEDIUM,
1111
        $addRandomId = true,
1112
    ) {
1113
        $user = $this->find($userId);
1114
        if (!$user) {
1115
            return '/img/icons/64/unknown.png';
1116
        }
1117
1118
        switch ($size) {
1119
            case self::USER_IMAGE_SIZE_SMALL:
1120
                $width = 32;
1121
1122
                break;
1123
1124
            case self::USER_IMAGE_SIZE_MEDIUM:
1125
                $width = 64;
1126
1127
                break;
1128
1129
            case self::USER_IMAGE_SIZE_BIG:
1130
                $width = 128;
1131
1132
                break;
1133
1134
            case self::USER_IMAGE_SIZE_ORIGINAL:
1135
            default:
1136
                $width = 0;
1137
1138
                break;
1139
        }
1140
1141
        $url = $this->illustrationRepository->getIllustrationUrl($user);
1142
        $params = [];
1143
        if (!empty($width)) {
1144
            $params['w'] = $width;
1145
        }
1146
1147
        if ($addRandomId) {
1148
            $params['rand'] = uniqid('u_', true);
1149
        }
1150
1151
        $paramsToString = '';
1152
        if (!empty($params)) {
1153
            $paramsToString = '?'.http_build_query($params);
1154
        }
1155
1156
        return $url.$paramsToString;
1157
    }
1158
1159
    /**
1160
     * Retrieves the list of DRH (HR) users related to a specific user and access URL.
1161
     */
1162
    public function getDrhListFromUser(int $userId, int $accessUrlId): array
1163
    {
1164
        $qb = $this->createQueryBuilder('u');
1165
1166
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
1167
1168
        $qb->select('u.id, u.username, u.firstname, u.lastname')
1169
            ->innerJoin('u.friends', 'uru', Join::WITH, 'uru.friend = u.id')
1170
            ->where('uru.user = :userId')
1171
            ->andWhere('uru.relationType = :relationType')
1172
            ->setParameter('userId', $userId)
1173
            ->setParameter('relationType', UserRelUser::USER_RELATION_TYPE_RRHH)
1174
        ;
1175
1176
        $qb->orderBy('u.lastname', 'ASC')
1177
            ->addOrderBy('u.firstname', 'ASC')
1178
        ;
1179
1180
        return $qb->getQuery()->getResult();
1181
    }
1182
1183
    public function findUsersByContext(int $courseId, ?int $sessionId = null, ?int $groupId = null): array
1184
    {
1185
        $course = $this->_em->getRepository(Course::class)->find($courseId);
1186
        if (!$course) {
1187
            throw new InvalidArgumentException('Course not found.');
1188
        }
1189
1190
        if (null !== $sessionId) {
1191
            $session = $this->_em->getRepository(Session::class)->find($sessionId);
1192
            if (!$session) {
1193
                throw new InvalidArgumentException('Session not found.');
1194
            }
1195
1196
            $list = $session->getSessionRelCourseRelUsersByStatus($course, Session::STUDENT);
1197
            $users = [];
1198
1199
            if ($list) {
1200
                foreach ($list as $sessionCourseUser) {
1201
                    $users[$sessionCourseUser->getUser()->getId()] = $sessionCourseUser->getUser();
1202
                }
1203
            }
1204
1205
            return array_values($users);
1206
        }
1207
1208
        if (null !== $groupId) {
1209
            $qb = $this->_em->createQueryBuilder();
1210
            $qb->select('u')
1211
                ->from(CGroupRelUser::class, 'cgru')
1212
                ->innerJoin('cgru.user', 'u')
1213
                ->where('cgru.cId = :courseId')
1214
                ->andWhere('cgru.group = :groupId')
1215
                ->setParameters([
1216
                    'courseId' => $courseId,
1217
                    'groupId' => $groupId,
1218
                ])
1219
                ->orderBy('u.lastname', 'ASC')
1220
                ->addOrderBy('u.firstname', 'ASC')
1221
            ;
1222
1223
            return $qb->getQuery()->getResult();
1224
        }
1225
1226
        $queryBuilder = $this->_em->getRepository(Course::class)->getSubscribedStudents($course);
1227
1228
        return $queryBuilder->getQuery()->getResult();
1229
    }
1230
1231
    public function findUsersForSessionAdmin(
1232
        ?string $lastname,
1233
        ?string $firstname,
1234
        ?array $extraFilters,
1235
        ?AccessUrl $accessUrl
1236
    ): array {
1237
        $qb = $this->createQueryBuilder('u');
1238
1239
        if ($accessUrl !== null) {
1240
            $qb->join('u.portals', 'p')
1241
                ->andWhere('p.url = :url')
1242
                ->setParameter('url', $accessUrl);
1243
        }
1244
1245
        $qb->andWhere('u.active != :softDeleted')
1246
            ->setParameter('softDeleted', -2);
1247
1248
        if (!empty($lastname)) {
1249
            $qb->andWhere('u.lastname LIKE :lastname')
1250
                ->setParameter('lastname', '%'.$lastname.'%');
1251
        }
1252
1253
        if (!empty($firstname)) {
1254
            $qb->andWhere('u.firstname LIKE :firstname')
1255
                ->setParameter('firstname', '%'.$firstname.'%');
1256
        }
1257
1258
        if (!empty($extraFilters)) {
1259
            foreach ($extraFilters as $field => $value) {
1260
                $qb->andWhere(\sprintf(
1261
                    'EXISTS (
1262
                    SELECT 1
1263
                    FROM Chamilo\CoreBundle\Entity\ExtraFieldValues efv
1264
                    JOIN efv.field ef
1265
                    WHERE efv.itemId = u.id
1266
                      AND ef.variable = :field_%s
1267
                      AND efv.fieldValue LIKE :value_%s
1268
                )',
1269
                    $field,
1270
                    $field
1271
                ));
1272
                $qb->setParameter('field_'.$field, $field);
1273
                $qb->setParameter('value_'.$field, '%'.$value.'%');
1274
            }
1275
        }
1276
1277
        return $qb->getQuery()->getResult();
1278
    }
1279
1280
    /**
1281
     * Returns the number of users registered with a given email.
1282
     */
1283
    public function countUsersByEmail(string $email): int
1284
    {
1285
        return (int) $this->createQueryBuilder('u')
1286
            ->select('COUNT(u.id)')
1287
            ->where('u.email = :email')
1288
            ->setParameter('email', $email)
1289
            ->getQuery()
1290
            ->getSingleScalarResult()
1291
        ;
1292
    }
1293
1294
    public function findByAuthsource(string $authentication): array
1295
    {
1296
        $qb = $this->getOrCreateQueryBuilder(null, 'u');
1297
1298
        $qb
1299
            ->innerJoin('u.authSources', 'as')
1300
            ->where(
1301
                $qb->expr()->eq('as.authentication', ':authentication')
1302
            )
1303
            ->setParameter('authentication', $authentication)
1304
            ->getQuery()
1305
            ->getResult()
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
1306
        ;
1307
    }
1308
1309
    public function deactivateUsers(array $ids): void
1310
    {
1311
        $qb = $this->getEntityManager()->createQueryBuilder();
1312
1313
        $qb
1314
            ->update(User::class, 'u')
1315
            ->set('u.active', ':active')
1316
            ->where(
1317
                $qb->expr()->in('u.id', ':ids')
1318
            )
1319
            ->setParameters([
1320
                'active' => false,
1321
                'ids' => $ids,
1322
            ])
1323
        ;
1324
    }
1325
}
1326