Passed
Pull Request — master (#6357)
by
unknown
10:25
created

UserRepository::getRootUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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