Passed
Push — master ( 785c70...b3d31e )
by Angel Fernando Quiroz
09:13
created

UserRepository::isUsernameAvailable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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