Passed
Push — master ( 7a508d...4b0712 )
by Angel Fernando Quiroz
10:57
created

UserRepository::deactivateUsers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 13
rs 9.9666
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
        return $qb;
764
    }
765
766
    public function addActiveAndNotAnonUserQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
767
    {
768
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
769
        $qb
770
            ->andWhere('u.active = 1')
771
            ->andWhere('u.status <> :status')
772
            ->setParameter('status', User::ANONYMOUS, Types::INTEGER)
773
        ;
774
775
        return $qb;
776
    }
777
778
    public function addExpirationDateQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
779
    {
780
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
781
        $qb
782
            ->andWhere('u.expirationDate IS NULL OR u.expirationDate > :now')
783
            ->setParameter('now', new Datetime(), Types::DATETIME_MUTABLE)
784
        ;
785
786
        return $qb;
787
    }
788
789
    private function addRoleQueryBuilder(string $role, ?QueryBuilder $qb = null): QueryBuilder
790
    {
791
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
792
        $qb
793
            ->andWhere('u.roles LIKE :roles')
794
            ->setParameter('roles', '%"'.$role.'"%', Types::STRING)
795
        ;
796
797
        return $qb;
798
    }
799
800
    private function addSearchByKeywordQueryBuilder(string $keyword, ?QueryBuilder $qb = null): QueryBuilder
801
    {
802
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
803
        $qb
804
            ->andWhere('
805
                u.firstname LIKE :keyword OR
806
                u.lastname LIKE :keyword OR
807
                u.email LIKE :keyword OR
808
                u.username LIKE :keyword
809
            ')
810
            ->setParameter('keyword', "%$keyword%", Types::STRING)
811
            ->orderBy('u.firstname', Criteria::ASC)
812
        ;
813
814
        return $qb;
815
    }
816
817
    private function addUserRelUserQueryBuilder(int $userId, int $relationType, ?QueryBuilder $qb = null): QueryBuilder
818
    {
819
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
820
        $qb->leftJoin('u.friends', 'relations');
821
        $qb
822
            ->andWhere('relations.relationType = :relationType')
823
            ->andWhere('relations.user = :userRelation AND relations.friend <> :userRelation')
824
            ->setParameter('relationType', $relationType)
825
            ->setParameter('userRelation', $userId)
826
        ;
827
828
        return $qb;
829
    }
830
831
    private function addOnlyMyFriendsQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
832
    {
833
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
834
        $qb
835
            ->leftJoin('u.friends', 'relations')
836
            ->andWhere(
837
                $qb->expr()->notIn(
838
                    'relations.relationType',
839
                    [UserRelUser::USER_RELATION_TYPE_DELETED, UserRelUser::USER_RELATION_TYPE_RRHH]
840
                )
841
            )
842
            ->andWhere('relations.user = :user AND relations.friend <> :user')
843
            ->setParameter('user', $userId, Types::INTEGER)
844
        ;
845
846
        return $qb;
847
    }
848
849
    private function addNotCurrentUserQueryBuilder(int $userId, ?QueryBuilder $qb = null): QueryBuilder
850
    {
851
        $qb = $this->getOrCreateQueryBuilder($qb, 'u');
852
        $qb
853
            ->andWhere('u.id <> :id')
854
            ->setParameter('id', $userId, Types::INTEGER)
855
        ;
856
857
        return $qb;
858
    }
859
860
    public function getFriendsNotInGroup(int $userId, int $groupId)
861
    {
862
        $entityManager = $this->getEntityManager();
863
864
        $subQueryBuilder = $entityManager->createQueryBuilder();
865
        $subQuery = $subQueryBuilder
866
            ->select('IDENTITY(ugr.user)')
867
            ->from(UsergroupRelUser::class, 'ugr')
868
            ->where('ugr.usergroup = :subGroupId')
869
            ->andWhere('ugr.relationType IN (:subRelationTypes)')
870
            ->getDQL()
871
        ;
872
873
        $queryBuilder = $entityManager->createQueryBuilder();
874
        $query = $queryBuilder
875
            ->select('u')
876
            ->from(User::class, 'u')
877
            ->leftJoin('u.friendsWithMe', 'uruf')
878
            ->leftJoin('u.friends', 'urut')
879
            ->where('uruf.friend = :userId OR urut.user = :userId')
880
            ->andWhere($queryBuilder->expr()->notIn('u.id', $subQuery))
881
            ->setParameter('userId', $userId)
882
            ->setParameter('subGroupId', $groupId)
883
            ->setParameter('subRelationTypes', [Usergroup::GROUP_USER_PERMISSION_PENDING_INVITATION])
884
            ->getQuery()
885
        ;
886
887
        return $query->getResult();
888
    }
889
890
    public function getExtraUserData(int $userId, bool $prefix = false, bool $allVisibility = true, bool $splitMultiple = false, ?int $fieldFilter = null): array
891
    {
892
        $qb = $this->getEntityManager()->createQueryBuilder();
893
894
        // Start building the query
895
        $qb->select('ef.id', 'ef.variable as fvar', 'ef.valueType as type', 'efv.fieldValue as fval', 'ef.defaultValue as fval_df')
896
            ->from(ExtraField::class, 'ef')
897
            ->leftJoin(ExtraFieldValues::class, 'efv', Join::WITH, 'efv.field = ef.id AND efv.itemId = :userId')
898
            ->where('ef.itemType = :itemType')
899
            ->setParameter('userId', $userId)
900
            ->setParameter('itemType', ExtraField::USER_FIELD_TYPE)
901
        ;
902
903
        // Apply visibility filters
904
        if (!$allVisibility) {
905
            $qb->andWhere('ef.visibleToSelf = true');
906
        }
907
908
        // Apply field filter if provided
909
        if (null !== $fieldFilter) {
910
            $qb->andWhere('ef.id = :fieldFilter')
911
                ->setParameter('fieldFilter', $fieldFilter)
912
            ;
913
        }
914
915
        // Order by field order
916
        $qb->orderBy('ef.fieldOrder', 'ASC');
917
918
        // Execute the query
919
        $results = $qb->getQuery()->getResult();
920
921
        // Process results
922
        $extraData = [];
923
        foreach ($results as $row) {
924
            $value = $row['fval'] ?? $row['fval_df'];
925
926
            // Handle multiple values if necessary
927
            if ($splitMultiple && \in_array($row['type'], [ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE], true)) {
928
                $value = explode(';', $value);
929
            }
930
931
            // Handle prefix if needed
932
            $key = $prefix ? 'extra_'.$row['fvar'] : $row['fvar'];
933
934
            // Special handling for certain field types
935
            if (ExtraField::USER_FIELD_TYPE_TAG == $row['type']) {
936
                // Implement your logic to handle tags
937
            } elseif (ExtraField::USER_FIELD_TYPE_RADIO == $row['type'] && $prefix) {
938
                $extraData[$key][$key] = $value;
939
            } else {
940
                $extraData[$key] = $value;
941
            }
942
        }
943
944
        return $extraData;
945
    }
946
947
    public function getExtraUserDataByField(int $userId, string $fieldVariable, bool $allVisibility = true): array
948
    {
949
        $qb = $this->getEntityManager()->createQueryBuilder();
950
951
        $qb->select('e.id, e.variable, e.valueType, v.fieldValue')
952
            ->from(ExtraFieldValues::class, 'v')
953
            ->innerJoin('v.field', 'e')
954
            ->where('v.itemId = :userId')
955
            ->andWhere('e.variable = :fieldVariable')
956
            ->andWhere('e.itemType = :itemType')
957
            ->setParameters([
958
                'userId' => $userId,
959
                'fieldVariable' => $fieldVariable,
960
                'itemType' => ExtraField::USER_FIELD_TYPE,
961
            ])
962
        ;
963
964
        if (!$allVisibility) {
965
            $qb->andWhere('e.visibleToSelf = true');
966
        }
967
968
        $qb->orderBy('e.fieldOrder', 'ASC');
969
970
        $result = $qb->getQuery()->getResult();
971
972
        $extraData = [];
973
        foreach ($result as $row) {
974
            $value = $row['fieldValue'];
975
            if (ExtraField::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['valueType']) {
976
                $value = explode(';', $row['fieldValue']);
977
            }
978
979
            $extraData[$row['variable']] = $value;
980
        }
981
982
        return $extraData;
983
    }
984
985
    public function searchUsersByTags(
986
        string $tag,
987
        ?int $excludeUserId = null,
988
        int $fieldId = 0,
989
        int $from = 0,
990
        int $number_of_items = 10,
991
        bool $getCount = false
992
    ): array {
993
        $qb = $this->createQueryBuilder('u');
994
995
        if ($getCount) {
996
            $qb->select('COUNT(DISTINCT u.id)');
997
        } else {
998
            $qb->select('DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.pictureUri, u.status');
999
        }
1000
1001
        $qb->innerJoin('u.portals', 'urlRelUser')
1002
            ->leftJoin(UserRelTag::class, 'uv', 'WITH', 'u = uv.user')
1003
            ->leftJoin(Tag::class, 'ut', 'WITH', 'uv.tag = ut')
1004
        ;
1005
1006
        if (0 !== $fieldId) {
1007
            $qb->andWhere('ut.field = :fieldId')
1008
                ->setParameter('fieldId', $fieldId)
1009
            ;
1010
        }
1011
1012
        if (null !== $excludeUserId) {
1013
            $qb->andWhere('u.id != :excludeUserId')
1014
                ->setParameter('excludeUserId', $excludeUserId)
1015
            ;
1016
        }
1017
1018
        $qb->andWhere(
1019
            $qb->expr()->orX(
1020
                $qb->expr()->like('ut.tag', ':tag'),
1021
                $qb->expr()->like('u.firstname', ':likeTag'),
1022
                $qb->expr()->like('u.lastname', ':likeTag'),
1023
                $qb->expr()->like('u.username', ':likeTag'),
1024
                $qb->expr()->like(
1025
                    $qb->expr()->concat('u.firstname', $qb->expr()->literal(' '), 'u.lastname'),
1026
                    ':likeTag'
1027
                ),
1028
                $qb->expr()->like(
1029
                    $qb->expr()->concat('u.lastname', $qb->expr()->literal(' '), 'u.firstname'),
1030
                    ':likeTag'
1031
                )
1032
            )
1033
        )
1034
            ->setParameter('tag', $tag.'%')
1035
            ->setParameter('likeTag', '%'.$tag.'%')
1036
        ;
1037
1038
        // Only active users and not anonymous
1039
        $qb->andWhere('u.active = :active')
1040
            ->andWhere('u.status != :anonymous')
1041
            ->setParameter('active', true)
1042
            ->setParameter('anonymous', 6)
1043
        ;
1044
1045
        if (!$getCount) {
1046
            $qb->orderBy('u.username')
1047
                ->setFirstResult($from)
1048
                ->setMaxResults($number_of_items)
1049
            ;
1050
        }
1051
1052
        return $getCount ? $qb->getQuery()->getSingleScalarResult() : $qb->getQuery()->getResult();
1053
    }
1054
1055
    public function getUserRelationWithType(int $userId, int $friendId): ?array
1056
    {
1057
        $qb = $this->createQueryBuilder('u');
1058
        $qb->select('u.id AS userId', 'u.username AS userName', 'ur.relationType', 'f.id AS friendId', 'f.username AS friendName')
1059
            ->innerJoin('u.friends', 'ur')
1060
            ->innerJoin('ur.friend', 'f')
1061
            ->where('u.id = :userId AND f.id = :friendId')
1062
            ->setParameter('userId', $userId)
1063
            ->setParameter('friendId', $friendId)
1064
            ->setMaxResults(1)
1065
        ;
1066
1067
        return $qb->getQuery()->getOneOrNullResult();
1068
    }
1069
1070
    public function relateUsers(User $user1, User $user2, int $relationType): void
1071
    {
1072
        $em = $this->getEntityManager();
1073
1074
        $existingRelation = $em->getRepository(UserRelUser::class)->findOneBy([
1075
            'user' => $user1,
1076
            'friend' => $user2,
1077
        ]);
1078
1079
        if (!$existingRelation) {
1080
            $newRelation = new UserRelUser();
1081
            $newRelation->setUser($user1);
1082
            $newRelation->setFriend($user2);
1083
            $newRelation->setRelationType($relationType);
1084
            $em->persist($newRelation);
1085
        } else {
1086
            $existingRelation->setRelationType($relationType);
1087
        }
1088
1089
        $existingRelationInverse = $em->getRepository(UserRelUser::class)->findOneBy([
1090
            'user' => $user2,
1091
            'friend' => $user1,
1092
        ]);
1093
1094
        if (!$existingRelationInverse) {
1095
            $newRelationInverse = new UserRelUser();
1096
            $newRelationInverse->setUser($user2);
1097
            $newRelationInverse->setFriend($user1);
1098
            $newRelationInverse->setRelationType($relationType);
1099
            $em->persist($newRelationInverse);
1100
        } else {
1101
            $existingRelationInverse->setRelationType($relationType);
1102
        }
1103
1104
        $em->flush();
1105
    }
1106
1107
    public function getUserPicture(
1108
        $userId,
1109
        int $size = self::USER_IMAGE_SIZE_MEDIUM,
1110
        $addRandomId = true,
1111
    ) {
1112
        $user = $this->find($userId);
1113
        if (!$user) {
1114
            return '/img/icons/64/unknown.png';
1115
        }
1116
1117
        switch ($size) {
1118
            case self::USER_IMAGE_SIZE_SMALL:
1119
                $width = 32;
1120
1121
                break;
1122
1123
            case self::USER_IMAGE_SIZE_MEDIUM:
1124
                $width = 64;
1125
1126
                break;
1127
1128
            case self::USER_IMAGE_SIZE_BIG:
1129
                $width = 128;
1130
1131
                break;
1132
1133
            case self::USER_IMAGE_SIZE_ORIGINAL:
1134
            default:
1135
                $width = 0;
1136
1137
                break;
1138
        }
1139
1140
        $url = $this->illustrationRepository->getIllustrationUrl($user);
1141
        $params = [];
1142
        if (!empty($width)) {
1143
            $params['w'] = $width;
1144
        }
1145
1146
        if ($addRandomId) {
1147
            $params['rand'] = uniqid('u_', true);
1148
        }
1149
1150
        $paramsToString = '';
1151
        if (!empty($params)) {
1152
            $paramsToString = '?'.http_build_query($params);
1153
        }
1154
1155
        return $url.$paramsToString;
1156
    }
1157
1158
    /**
1159
     * Retrieves the list of DRH (HR) users related to a specific user and access URL.
1160
     */
1161
    public function getDrhListFromUser(int $userId, int $accessUrlId): array
1162
    {
1163
        $qb = $this->createQueryBuilder('u');
1164
1165
        $this->addAccessUrlQueryBuilder($accessUrlId, $qb);
1166
1167
        $qb->select('u.id, u.username, u.firstname, u.lastname')
1168
            ->innerJoin('u.friends', 'uru', Join::WITH, 'uru.friend = u.id')
1169
            ->where('uru.user = :userId')
1170
            ->andWhere('uru.relationType = :relationType')
1171
            ->setParameter('userId', $userId)
1172
            ->setParameter('relationType', UserRelUser::USER_RELATION_TYPE_RRHH)
1173
        ;
1174
1175
        $qb->orderBy('u.lastname', 'ASC')
1176
            ->addOrderBy('u.firstname', 'ASC')
1177
        ;
1178
1179
        return $qb->getQuery()->getResult();
1180
    }
1181
1182
    public function findUsersByContext(int $courseId, ?int $sessionId = null, ?int $groupId = null): array
1183
    {
1184
        $course = $this->_em->getRepository(Course::class)->find($courseId);
1185
        if (!$course) {
1186
            throw new InvalidArgumentException('Course not found.');
1187
        }
1188
1189
        if (null !== $sessionId) {
1190
            $session = $this->_em->getRepository(Session::class)->find($sessionId);
1191
            if (!$session) {
1192
                throw new InvalidArgumentException('Session not found.');
1193
            }
1194
1195
            $list = $session->getSessionRelCourseRelUsersByStatus($course, Session::STUDENT);
1196
            $users = [];
1197
1198
            if ($list) {
1199
                foreach ($list as $sessionCourseUser) {
1200
                    $users[$sessionCourseUser->getUser()->getId()] = $sessionCourseUser->getUser();
1201
                }
1202
            }
1203
1204
            return array_values($users);
1205
        }
1206
1207
        if (null !== $groupId) {
1208
            $qb = $this->_em->createQueryBuilder();
1209
            $qb->select('u')
1210
                ->from(CGroupRelUser::class, 'cgru')
1211
                ->innerJoin('cgru.user', 'u')
1212
                ->where('cgru.cId = :courseId')
1213
                ->andWhere('cgru.group = :groupId')
1214
                ->setParameters([
1215
                    'courseId' => $courseId,
1216
                    'groupId' => $groupId,
1217
                ])
1218
                ->orderBy('u.lastname', 'ASC')
1219
                ->addOrderBy('u.firstname', 'ASC')
1220
            ;
1221
1222
            return $qb->getQuery()->getResult();
1223
        }
1224
1225
        $queryBuilder = $this->_em->getRepository(Course::class)->getSubscribedStudents($course);
1226
1227
        return $queryBuilder->getQuery()->getResult();
1228
    }
1229
1230
    public function findUsersForSessionAdmin(
1231
        ?string $lastname,
1232
        ?string $firstname,
1233
        ?array $extraFilters,
1234
        AccessUrl $accessUrl
1235
    ): array {
1236
        $qb = $this->createQueryBuilder('u');
1237
1238
        $qb->join('u.portals', 'p')
1239
            ->andWhere('p.url = :url')
1240
            ->setParameter('url', $accessUrl);
1241
1242
        if (!empty($lastname)) {
1243
            $qb->andWhere('u.lastname LIKE :lastname')
1244
                ->setParameter('lastname', '%' . $lastname . '%');
1245
        }
1246
1247
        if (!empty($firstname)) {
1248
            $qb->andWhere('u.firstname LIKE :firstname')
1249
                ->setParameter('firstname', '%' . $firstname . '%');
1250
        }
1251
1252
        if (!empty($extraFilters)) {
1253
            foreach ($extraFilters as $field => $value) {
1254
                $qb->andWhere(sprintf(
1255
                    'EXISTS (
1256
                    SELECT 1
1257
                    FROM Chamilo\CoreBundle\Entity\ExtraFieldValues efv
1258
                    JOIN efv.field ef
1259
                    WHERE efv.itemId = u.id
1260
                      AND ef.variable = :field_%s
1261
                      AND efv.fieldValue LIKE :value_%s
1262
                )',
1263
                    $field,
1264
                    $field
1265
                ));
1266
                $qb->setParameter('field_' . $field, $field);
1267
                $qb->setParameter('value_' . $field, '%' . $value . '%');
1268
            }
1269
        }
1270
1271
        return $qb->getQuery()->getResult();
1272
    }
1273
1274
    /**
1275
     * Returns the number of users registered with a given email.
1276
     *
1277
     * @param string $email
1278
     *
1279
     * @return int
1280
     */
1281
    public function countUsersByEmail(string $email): int
1282
    {
1283
        return (int) $this->createQueryBuilder('u')
1284
            ->select('COUNT(u.id)')
1285
            ->where('u.email = :email')
1286
            ->setParameter('email', $email)
1287
            ->getQuery()
1288
            ->getSingleScalarResult();
1289
    }
1290
1291
    public function findByAuthsource(string $authentication): array
1292
    {
1293
        $qb = $this->getOrCreateQueryBuilder(null, 'u');
1294
1295
        $qb
1296
            ->innerJoin('u.authSources', 'as')
1297
            ->where(
1298
                $qb->expr()->eq('as.authentication', ':authentication')
1299
            )
1300
            ->setParameter('authentication', $authentication)
1301
            ->getQuery()
1302
            ->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...
1303
        ;
1304
    }
1305
1306
    public function deactivateUsers(array $ids)
1307
    {
1308
        $qb = $this->getEntityManager()->createQueryBuilder();
1309
1310
        $qb
1311
            ->update(User::class, 'u')
1312
            ->set('u.active', ':active')
1313
            ->where(
1314
                $qb->expr()->in('u.id', ':ids')
1315
            )
1316
            ->setParameters([
1317
                'active' => false,
1318
                'ids' => $ids,
1319
            ])
1320
        ;
1321
    }
1322
}
1323