UserManager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField;
5
use Chamilo\CoreBundle\Entity\ExtraFieldValues as EntityExtraFieldValues;
6
use Chamilo\CoreBundle\Entity\GradebookCategory;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Entity\SessionRelCourse;
9
use Chamilo\CoreBundle\Entity\User;
10
use Chamilo\CoreBundle\Entity\UserAuthSource;
11
use Chamilo\CoreBundle\Entity\UserRelUser;
12
use Chamilo\CoreBundle\Event\AbstractEvent;
13
use Chamilo\CoreBundle\Event\Events;
14
use Chamilo\CoreBundle\Event\UserCreatedEvent;
15
use Chamilo\CoreBundle\Event\UserUpdatedEvent;
16
use Chamilo\CoreBundle\Framework\Container;
17
use Chamilo\CoreBundle\Repository\LanguageRepository;
18
use Chamilo\CoreBundle\Repository\Node\UserRepository;
19
use Chamilo\CoreBundle\Helpers\NameConventionHelper;
20
use ChamiloSession as Session;
21
use Symfony\Component\HttpFoundation\File\UploadedFile;
22
use Symfony\Contracts\Translation\TranslatorInterface;
23
24
/**
25
 * This library provides functions for user management.
26
 * Include/require it in your code to use its functionality.
27
 *
28
 * @author Julio Montoya <[email protected]> Social network groups added 2009/12
29
 */
30
class UserManager
31
{
32
    // This constants are deprecated use the constants located in ExtraField
33
    public const USER_FIELD_TYPE_TEXT = 1;
34
    public const USER_FIELD_TYPE_TEXTAREA = 2;
35
    public const USER_FIELD_TYPE_RADIO = 3;
36
    public const USER_FIELD_TYPE_SELECT = 4;
37
    public const USER_FIELD_TYPE_SELECT_MULTIPLE = 5;
38
    public const USER_FIELD_TYPE_DATE = 6;
39
    public const USER_FIELD_TYPE_DATETIME = 7;
40
    public const USER_FIELD_TYPE_DOUBLE_SELECT = 8;
41
    public const USER_FIELD_TYPE_DIVIDER = 9;
42
    public const USER_FIELD_TYPE_TAG = 10;
43
    public const USER_FIELD_TYPE_TIMEZONE = 11;
44
    public const USER_FIELD_TYPE_SOCIAL_PROFILE = 12;
45
    public const USER_FIELD_TYPE_FILE = 13;
46
    public const USER_FIELD_TYPE_MOBILE_PHONE_NUMBER = 14;
47
48
    public function __construct()
49
    {
50
    }
51
52
    /**
53
     * Repository is use to query the DB, selects, etc.
54
     *
55
     * @return UserRepository
56
     */
57
    public static function getRepository()
58
    {
59
        return Container::getUserRepository();
60
    }
61
62
    /**
63
     * Validates the password.
64
     */
65
    public static function isPasswordValid(User $user, string $plainPassword): bool
66
    {
67
        /**
68
         * @psalm-suppress PrivateService
69
         */
70
        $hasher = Container::$container->get('security.user_password_hasher');
0 ignored issues
show
Bug introduced by
The method get() 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

70
        /** @scrutinizer ignore-call */ 
71
        $hasher = Container::$container->get('security.user_password_hasher');

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...
71
72
        return $hasher->isPasswordValid($user, $plainPassword);
73
    }
74
75
    /**
76
     * @param int    $userId
77
     * @param string $password
78
     */
79
    public static function updatePassword($userId, $password)
80
    {
81
        $user = api_get_user_entity($userId);
82
        $userManager = self::getRepository();
83
        $user->setPlainPassword($password);
84
        $userManager->updateUser($user, true);
85
    }
86
87
    /**
88
     * Creates a new user for the platform.
89
     *
90
     * @param string        $firstName
91
     * @param string        $lastName
92
     * @param int           $status                  (1 for course tutor, 5 for student, 6 for anonymous)
93
     * @param string        $email
94
     * @param string        $loginName
95
     * @param string        $password
96
     * @param string        $officialCode           Any official code (optional)
97
     * @param string        $language                User language    (optional)
98
     * @param string        $phone                   Phone number    (optional)
99
     * @param string        $pictureUri             Picture URI        (optional)
100
     * @param string        $authSources              Authentication source (defaults to 'platform', dependind on constant)
101
     * @param string $expirationDate          Account expiration date (optional, defaults to null)
102
     * @param int           $active                  Whether the account is enabled or disabled by default
103
     * @param int           $hrDeptId              The department of HR in which the user is registered (defaults to 0)
104
     * @param array         $extra                   Extra fields (labels must be prefixed by "extra_")
105
     * @param string        $encryptMethod          Used if password is given encrypted. Set to an empty string by default
106
     * @param bool          $sendMail
107
     * @param bool          $isAdmin
108
     * @param string        $address
109
     * @param bool          $sendEmailToAllAdmins
110
     * @param FormValidator $form
111
     * @param int           $creatorId
112
     * @param array         $emailTemplate
113
     * @param string        $redirectToURLAfterLogin
114
     *
115
     * @return mixed new user id - if the new user creation succeeds, false otherwise
116
     * @desc The function tries to retrieve user id from the session.
117
     * If it exists, the current user id is the creator id. If a problem arises,
118
     * @assert ('Sam','Gamegie',5,'[email protected]','jo','jo') > 1
119
     * @assert ('Pippin','Took',null,null,'jo','jo') === false
120
     *@author Hugues Peeters <[email protected]>,
121
     * @author Roan Embrechts <[email protected]>
122
     *
123
     */
124
    public static function create_user(
125
        $firstName,
126
        $lastName,
127
        $status,
128
        $email,
129
        $loginName,
130
        $password,
131
        $officialCode = '',
132
        $language = '',
133
        $phone = '',
134
        $pictureUri = '',
135
        ?array $authSources = [],
136
        $expirationDate = null,
137
        $active = 1,
138
        $hrDeptId = 0,
139
        $extra = [],
140
        $encryptMethod = '',
141
        $sendMail = false,
142
        $isAdmin = false,
143
        $address = '',
144
        $sendEmailToAllAdmins = false,
145
        $form = null,
146
        $creatorId = 0,
147
        $emailTemplate = [],
148
        $redirectToURLAfterLogin = ''
149
    ) {
150
        $authSources = !empty($authSources) ? $authSources : [UserAuthSource::PLATFORM];
151
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
152
153
        if (0 === $creatorId) {
154
            Display::addFlash(
155
                Display::return_message(get_lang('A user creator is needed'))
156
            );
157
158
            return false;
159
        }
160
161
        $creatorInfo = api_get_user_info($creatorId);
162
        $creatorEmail = $creatorInfo['email'] ?? '';
163
164
        // First check if the login exists.
165
        if (!self::is_username_available($loginName)) {
166
            Display::addFlash(
167
                Display::return_message(get_lang('This login is already taken !'))
168
            );
169
170
            return false;
171
        }
172
173
        Container::getEventDispatcher()
174
            ->dispatch(
175
                new UserCreatedEvent([], AbstractEvent::TYPE_PRE),
176
                Events::USER_CREATED
177
            )
178
        ;
179
180
        $original_password = $password;
181
182
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
183
        $access_url_id = $accessUrl->getId();
184
185
        $hostingLimitUsers = get_hosting_limit($access_url_id, 'users');
186
187
        if ($hostingLimitUsers !== null && $hostingLimitUsers > 0) {
188
            $num = self::get_number_of_users();
189
            if ($num >= $hostingLimitUsers) {
190
                api_warn_hosting_contact('users');
191
                Display::addFlash(
192
                    Display::return_message(
193
                        get_lang('Sorry, this installation has a users limit, which has now been reached. To increase the number of users allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.'),
194
                        'warning'
195
                    )
196
                );
197
198
                return false;
199
            }
200
        }
201
202
        if (1 === $status) {
203
            $hostingLimitTeachers = get_hosting_limit($access_url_id, 'teachers');
204
205
            if ($hostingLimitTeachers !== null && $hostingLimitTeachers > 0) {
206
                $num = self::get_number_of_users(1);
207
                if ($num >= $hostingLimitTeachers) {
208
                    Display::addFlash(
209
                        Display::return_message(
210
                            get_lang('Sorry, this installation has a teachers limit, which has now been reached. To increase the number of teachers allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.'),
211
                            'warning'
212
                        )
213
                    );
214
                    api_warn_hosting_contact('hosting_limit_teachers');
215
216
                    return false;
217
                }
218
            }
219
        }
220
221
        if (empty($password)) {
222
            if (in_array(UserAuthSource::PLATFORM, $authSources)) {
223
                Display::addFlash(
224
                    Display::return_message(
225
                        get_lang('Required field').': '.get_lang(
226
                            'Password'
227
                        ),
228
                        'warning'
229
                    )
230
                );
231
232
                return false;
233
            }
234
235
            // We use the authSource as password.
236
            // The real validation will be by processed by the auth
237
            // source not Chamilo
238
            $password = $authSources;
239
        }
240
241
        // Checking the user language
242
        $languages = api_get_languages();
243
244
        // Default to english
245
        if (!in_array($language, array_keys($languages), true)) {
246
            $language = 'en_US';
247
        }
248
249
        $now = new DateTime();
250
        if (empty($expirationDate) || '0000-00-00 00:00:00' === $expirationDate) {
251
            $expirationDate = null;
252
        // Default expiration date
253
            // if there is a default duration of a valid account then
254
            // we have to change the expiration_date accordingly
255
            // Accept 0000-00-00 00:00:00 as a null value to avoid issues with
256
            // third party code using this method with the previous (pre-1.10)
257
            // value of 0000...
258
            /*if ('' != api_get_setting('account_valid_duration')) {
259
                $expirationDate = new DateTime($currentDate);
260
                $days = (int) api_get_setting('account_valid_duration');
261
                $expirationDate->modify('+'.$days.' day');
262
            }*/
263
        } else {
264
            $expirationDate = api_get_utc_datetime($expirationDate, true, true);
265
        }
266
267
        $repo = Container::getUserRepository();
268
        $user = $repo->createUser()
269
            ->setLastname($lastName)
270
            ->setFirstname($firstName)
271
            ->setUsername($loginName)
272
            ->setStatus($status)
273
            ->setPlainPassword($password)
274
            ->setEmail($email)
275
            ->setOfficialCode($officialCode)
276
            ->setCreatorId($creatorId)
277
            ->setPhone($phone)
278
            ->setAddress($address)
279
            ->setLocale($language)
280
            ->setHrDeptId($hrDeptId)
281
            ->setActive($active)
282
            ->setTimezone(api_get_timezone())
283
        ;
284
285
        foreach ($authSources as $authSource) {
286
            $user->addAuthSourceByAuthentication($authSource, $accessUrl);
287
        }
288
289
        if (null !== $expirationDate) {
290
            $user->setExpirationDate($expirationDate);
291
        }
292
293
        $user->setRoleFromStatus($status);
294
295
        // Add user to a group
296
        /*$statusToGroup = [
297
            COURSEMANAGER => 'TEACHER',
298
            STUDENT => 'STUDENT',
299
            DRH => 'RRHH',
300
            SESSIONADMIN => 'SESSION_ADMIN',
301
            STUDENT_BOSS => 'STUDENT_BOSS',
302
            INVITEE => 'INVITEE',
303
        ];
304
305
        if (isset($statusToGroup[$status])) {
306
            $group = Container::$container->get(GroupRepository::class)->findOneBy(['code' => $statusToGroup[$status]]);
307
            if ($group) {
308
                $user->addGroup($group);
309
            }
310
        }*/
311
312
        $repo->updateUser($user, true);
313
314
        $userId = $user->getId();
315
316
        if (!empty($userId)) {
317
            $userLocale = $user->getLocale();
318
            if ($isAdmin) {
319
                self::addUserAsAdmin($user);
320
            }
321
322
            if (api_get_multiple_access_url()) {
323
                UrlManager::add_user_to_url($userId, api_get_current_access_url_id());
324
            } else {
325
                //we are adding by default the access_url_user table with access_url_id = 1
326
                UrlManager::add_user_to_url($userId, 1);
327
            }
328
329
            if (is_array($extra) && count($extra) > 0) {
330
                $extra['item_id'] = $userId;
331
                $userFieldValue = new ExtraFieldValue('user');
332
                /* Force saving of extra fields (otherwise, if the current
333
                user is not admin, fields not visible to the user - most
334
                of them - are just ignored) */
335
                $userFieldValue->saveFieldValues(
336
                    $extra,
337
                    true,
338
                    false,
339
                    [],
340
                    [],
341
                    true
342
                );
343
            } else {
344
                // Create notify settings by default
345
                self::update_extra_field_value(
346
                    $userId,
347
                    'mail_notify_invitation',
348
                    '1'
349
                );
350
                self::update_extra_field_value(
351
                    $userId,
352
                    'mail_notify_message',
353
                    '1'
354
                );
355
                self::update_extra_field_value(
356
                    $userId,
357
                    'mail_notify_group_message',
358
                    '1'
359
                );
360
            }
361
362
            self::update_extra_field_value(
363
                $userId,
364
                'already_logged_in',
365
                'false'
366
            );
367
368
            if (!empty($redirectToURLAfterLogin) && ('true' === api_get_setting('admin.plugin_redirection_enabled'))) {
369
                RedirectionPlugin::insert($userId, $redirectToURLAfterLogin);
370
            }
371
372
            if (!empty($email) && $sendMail) {
373
                $recipient_name = api_get_person_name(
374
                    $firstName,
375
                    $lastName,
376
                    null,
377
                    PERSON_NAME_EMAIL_ADDRESS
378
                );
379
                $tpl = Container::getTwig();
380
                $emailSubject = $tpl->render('@ChamiloCore/Mailer/Legacy/subject_registration_platform.html.twig', ['locale' => $userLocale]);
381
                $sender_name = api_get_person_name(
382
                    api_get_setting('administratorName'),
383
                    api_get_setting('administratorSurname'),
384
                    null,
385
                    PERSON_NAME_EMAIL_ADDRESS
386
                );
387
                $email_admin = api_get_setting('emailAdministrator');
388
389
                $url = api_get_path(WEB_PATH);
390
                if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

390
                if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
391
                    $access_url_id = api_get_current_access_url_id();
392
                    if (-1 != $access_url_id) {
393
                        $urlInfo = api_get_access_url($access_url_id);
394
                        if ($urlInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
395
                            $url = $urlInfo['url'];
396
                        }
397
                    }
398
                }
399
400
                // variables for the default template
401
                $params = [
402
                    'complete_name' => stripslashes(api_get_person_name($firstName, $lastName)),
403
                    'login_name' => $loginName,
404
                    'original_password' => stripslashes($original_password),
405
                    'mailWebPath' => $url,
406
                    'new_user' => $user,
407
                    'search_link' => $url,
408
                    'locale' => $userLocale,
409
                ];
410
411
                // ofaj
412
                if ('true' === api_get_setting('session.allow_search_diagnostic')) {
413
                    $urlSearch = api_get_path(WEB_CODE_PATH).'search/search.php';
414
                    $linkSearch = Display::url($urlSearch, $urlSearch);
415
                    $params['search_link'] = $linkSearch;
416
                }
417
418
                $emailBody = $tpl->render(
419
                    '@ChamiloCore/Mailer/Legacy/content_registration_platform.html.twig',
420
                    $params
421
                );
422
423
                $userInfo = api_get_user_info($userId);
424
                $mailTemplateManager = new MailTemplateManager();
425
                $phoneNumber = $extra['mobile_phone_number'] ?? null;
426
427
                $emailBodyTemplate = '';
428
                if (!empty($emailTemplate)) {
429
                    if (isset($emailTemplate['content_registration_platform.tpl']) &&
430
                        !empty($emailTemplate['content_registration_platform.tpl'])
431
                    ) {
432
                        $emailBodyTemplate = $mailTemplateManager->parseTemplate(
433
                            $emailTemplate['content_registration_platform.tpl'],
434
                            $userInfo
435
                        );
436
                    }
437
                }
438
439
                $twoEmail = ('true' === api_get_setting('mail.send_two_inscription_confirmation_mail'));
440
                if (true === $twoEmail) {
441
                    $emailBody = $tpl->render(
442
                        '@ChamiloCore/Mailer/Legacy/new_user_first_email_confirmation.html.twig'
443
                    );
444
445
                    if (!empty($emailBodyTemplate) &&
446
                        isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
447
                        !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
448
                    ) {
449
                        $emailBody = $mailTemplateManager->parseTemplate(
450
                            $emailTemplate['new_user_first_email_confirmation.tpl'],
451
                            $userInfo
452
                        );
453
                    }
454
455
                    api_mail_html(
456
                        $recipient_name,
457
                        $email,
458
                        $emailSubject,
459
                        $emailBody,
460
                        $sender_name,
461
                        $email_admin,
462
                        [],
463
                        [],
464
                        false,
465
                        [],
466
                        $creatorEmail
467
                    );
468
469
                    $emailBody = $tpl->render('@ChamiloCore/Mailer/Legacy/new_user_second_email_confirmation.html.twig');
470
471
                    if (!empty($emailBodyTemplate) &&
472
                        isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
473
                        !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
474
                    ) {
475
                        $emailBody = $mailTemplateManager->parseTemplate(
476
                            $emailTemplate['new_user_second_email_confirmation.tpl'],
477
                            $userInfo
478
                        );
479
                    }
480
481
                    api_mail_html(
482
                        $recipient_name,
483
                        $email,
484
                        $emailSubject,
485
                        $emailBody,
486
                        $sender_name,
487
                        $email_admin,
488
                        null,
489
                        null,
490
                        null,
491
                        [],
492
                        $creatorEmail
493
                    );
494
                } else {
495
                    if (!empty($emailBodyTemplate)) {
496
                        $emailBody = $emailBodyTemplate;
497
                    }
498
                    $sendToInbox = ('true' === api_get_setting('registration.send_inscription_msg_to_inbox'));
499
                    if ($sendToInbox) {
500
                        $adminList = self::get_all_administrators();
501
                        $senderId = 1;
502
                        if (!empty($adminList)) {
503
                            $adminInfo = current($adminList);
504
                            $senderId = $adminInfo['user_id'];
505
                        }
506
507
                        MessageManager::send_message_simple(
508
                            $userId,
509
                            $emailSubject,
510
                            $emailBody,
511
                            $senderId
512
                        );
513
                    } else {
514
                        api_mail_html(
515
                            $recipient_name,
516
                            $email,
517
                            $emailSubject,
518
                            $emailBody,
519
                            $sender_name,
520
                            $email_admin,
521
                            [],
522
                            [],
523
                            false,
524
                            [],
525
                            $creatorEmail
526
                        );
527
                    }
528
                }
529
530
                $notification = api_get_setting('profile.send_notification_when_user_added', true);
531
                if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {
532
                    foreach ($notification['admins'] as $adminId) {
533
                        $emailSubjectToAdmin = get_lang('The user has been added').': '.
534
                            api_get_person_name($firstName, $lastName);
535
                        MessageManager::send_message_simple($adminId, $emailSubjectToAdmin, $emailBody, $userId);
536
                    }
537
                }
538
539
                /** @var TranslatorInterface $translator */
540
                $translator   = Container::$container->get('translator');
541
                $currentLocale = $translator->getLocale();
542
543
                if ($sendEmailToAllAdmins) {
544
                    $adminList = self::get_all_administrators();
545
                    $url = api_get_path(WEB_CODE_PATH).'admin/user_information.php?user_id='.$user->getId();
546
547
                    foreach ($adminList as $adminId => $adminData) {
548
                        $adminLocale = $adminData['locale'] ?? 'en_US';
549
                        $translator->setLocale($adminLocale);
550
551
                        $profileHtml      = self::renderRegistrationProfileHtml($user, $extra ?? [], $adminLocale);
552
                        $userLanguageName = self::resolveLanguageName($user->getLocale());
553
554
                        $params = [
555
                            'complete_name' => stripslashes(api_get_person_name($firstName, $lastName)),
556
                            'user_added'    => $user,
557
                            'link'          => Display::url($url, $url),
558
                            'form'          => $profileHtml,
559
                            'user_language' => $userLanguageName,
560
                        ];
561
562
                        $emailBody = $tpl->render(
563
                            '@ChamiloCore/Mailer/Legacy/content_registration_platform_to_admin.html.twig',
564
                            $params
565
                        );
566
567
                        $subject = get_lang('The user has been added', $adminLocale);
568
569
                        MessageManager::send_message_simple(
570
                            $adminId,
571
                            $subject,
572
                            $emailBody,
573
                            $userId
574
                        );
575
576
                        $translator->setLocale($currentLocale);
577
                    }
578
                }
579
            }
580
581
            Container::getEventDispatcher()
582
                ->dispatch(
583
                    new UserCreatedEvent(
584
                        ['return' => $user, 'originalPassword' => $original_password],
585
                        AbstractEvent::TYPE_POST
586
                    ),
587
                    Events::USER_CREATED
588
                )
589
            ;
590
591
            Event::addEvent(LOG_USER_CREATE, LOG_USER_ID, $userId, null, $creatorId);
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

591
            Event::/** @scrutinizer ignore-call */ 
592
                   addEvent(LOG_USER_CREATE, LOG_USER_ID, $userId, null, $creatorId);

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...
592
        } else {
593
            Display::addFlash(
594
                Display::return_message(
595
                    get_lang('There happened an unknown error. Please contact the platform administrator.')
596
                )
597
            );
598
599
            return false;
600
        }
601
602
        return $userId;
603
    }
604
605
    /**
606
     * Returns the human-readable language name for a given ISO code.
607
     *
608
     * Accepts ISO 639-1/2 codes (e.g. "en", "es", "eng"). If $iso is null or the
609
     * code is unknown, an empty string is returned.
610
     *
611
     * @param string|null $iso Two- or three-letter ISO 639 language code.
612
     * @return string          Language name in English (e.g. "English", "Spanish").
613
     */
614
    private static function resolveLanguageName(?string $iso): string
615
    {
616
        if (empty($iso)) {
617
            return '';
618
        }
619
620
        /** @var LanguageRepository $langRepo */
621
        $langRepo = Container::$container->get(LanguageRepository::class);
622
        $entity   = $langRepo->findOneBy(['isocode' => $iso]);
623
624
        return $entity ? $entity->getOriginalName() : $iso;
625
    }
626
627
    /**
628
     * Build the “profile” HTML (core + dynamic extra fields) for the admin email, localized in $adminLocale.
629
     *
630
     * @param User $user
631
     * @param array  $extraParams  Raw POST values from registration (keys: "extra_*").
632
     * @param string $adminLocale  e.g. "es_ES", "fr_FR".
633
     */
634
    private static function renderRegistrationProfileHtml(User $user, array $extraParams, string $adminLocale): string
635
    {
636
        $statusLabel = ((int) $user->getStatus() === COURSEMANAGER)
637
            ? get_lang('Teach courses', $adminLocale)
638
            : get_lang('Follow courses', $adminLocale);
639
640
        $languageName = $user->getLocale();
641
        $langRepo = Container::$container->get(LanguageRepository::class);
642
        if ($langRepo && ($lang = $langRepo->findOneBy(['isocode' => $user->getLocale()]))) {
643
            $languageName = $lang->getOriginalName();
644
        }
645
646
        $corePairs = [
647
            get_lang('e-mail',        $adminLocale) => (string) $user->getEmail(),
648
            get_lang('First name',    $adminLocale) => (string) $user->getFirstname(),
649
            get_lang('Last name',     $adminLocale) => (string) $user->getLastname(),
650
            get_lang('Username',      $adminLocale) => (string) $user->getUsername(),
651
            get_lang('Official code', $adminLocale) => (string) ($user->getOfficialCode() ?? ''),
652
            get_lang('Phone',         $adminLocale) => (string) ($user->getPhone() ?? ''),
653
            get_lang('User address',  $adminLocale) => (string) ($user->getAddress() ?? ''),
654
            get_lang('Language',      $adminLocale) => (string) $languageName,
655
            get_lang('What do you want to do?', $adminLocale) => $statusLabel,
656
        ];
657
658
        if ($user->getDateOfBirth() instanceof \DateTimeInterface) {
659
            $corePairs[get_lang('Date of birth', $adminLocale)] = $user->getDateOfBirth()->format('Y-m-d');
660
        }
661
662
        $efv = new \ExtraFieldValue('user');
663
        $ef  = new \ExtraField('user');
664
665
        $extraPairs = [];
666
        $presentVars = [];
667
        $rows = $efv->getAllValuesByItem((int) $user->getId());
668
669
        if (is_array($rows)) {
670
            foreach ($rows as $row) {
671
                $fieldId   = (int)$row['id'];
672
                $variable  = (string)$row['variable'];
673
                $presentVars[$variable] = true;
674
675
                $tr = $efv->get_values_by_handler_and_field_id((int) $user->getId(), $fieldId, true);
676
                $val = null;
677
678
                if ($tr && array_key_exists('value', $tr)) {
679
                    $val = $tr['value'];
680
                } else {
681
                    $val = $row['value'];
682
                }
683
684
                $type = (int)$row['value_type'];
685
                if ($type === \ExtraField::FIELD_TYPE_CHECKBOX) {
686
                    $val = ((string)$val === '1') ? get_lang('Yes', $adminLocale) : get_lang('No', $adminLocale);
687
                }
688
                if ($type === \ExtraField::FIELD_TYPE_TAG && is_array($val)) {
689
                    $val = implode(', ', array_map(fn($t) => is_array($t) ? (string)($t['value'] ?? '') : (string)$t, $val));
690
                }
691
                if (is_string($val)) {
692
                    $val = str_replace(['<br />','<br>','<br/>'], ', ', $val);
693
                }
694
695
                $label = !empty($row['display_text'])
696
                    ? get_lang($row['display_text'], $adminLocale)
697
                    : get_lang(ucwords(str_replace('_',' ',$variable)), $adminLocale);
698
699
                if ($val !== '' && $val !== null) {
700
                    $extraPairs[$label] = (string)$val;
701
                }
702
            }
703
        }
704
705
        foreach ($extraParams as $k => $v) {
706
            if (strpos($k, 'extra_') !== 0) continue;
707
            $variable = substr($k, 6);
708
            if (isset($presentVars[$variable])) continue;
709
710
            $def = $ef->get_handler_field_info_by_field_variable($variable);
711
            $label = $def && !empty($def['display_text'])
712
                ? get_lang($def['display_text'], $adminLocale)
713
                : get_lang(ucwords(str_replace('_',' ',$variable)), $adminLocale);
714
715
            $val = $v;
716
            if (is_array($val)) {
717
                $val = implode(', ', array_map('strval', $val));
718
            } elseif ($val === '1') {
719
                $val = get_lang('Yes', $adminLocale);
720
            } elseif ($val === '0') {
721
                $val = get_lang('No', $adminLocale);
722
            }
723
724
            if ($val !== '' && $val !== null) {
725
                $extraPairs[$label] = (string)$val;
726
            }
727
        }
728
729
        $html = '<div class="form-horizontal">';
730
        foreach ($corePairs as $k => $v) {
731
            if ($v === '' || $v === null) continue;
732
            $html .= '<div>'.$k.': '.\Security::remove_XSS((string)$v).'</div>';
733
        }
734
        foreach ($extraPairs as $k => $v) {
735
            $html .= '<div>'.$k.': '.\Security::remove_XSS((string)$v).'</div>';
736
        }
737
        $html .= '</div>';
738
739
        return $html;
740
    }
741
742
    /**
743
     * Can user be deleted? This function checks whether there's a course
744
     * in which the given user is the
745
     * only course administrator. If that is the case, the user can't be
746
     * deleted because the course would remain without a course admin.
747
     *
748
     * @param int $user_id The user id
749
     *
750
     * @return bool true if user can be deleted
751
     *
752
     * @assert (null) === false
753
     * @assert (-1) === false
754
     * @assert ('abc') === false
755
     */
756
    public static function canDeleteUser($user_id)
757
    {
758
        $deny = api_get_env_variable('DENY_DELETE_USERS', false);
759
760
        if ($deny) {
761
            return false;
762
        }
763
764
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
765
        $user_id = (int) $user_id;
766
767
        if (empty($user_id)) {
768
            return false;
769
        }
770
771
        $sql = "SELECT * FROM $table_course_user
772
                WHERE status = 1 AND user_id = ".$user_id;
773
        $res = Database::query($sql);
774
        while ($course = Database::fetch_object($res)) {
775
            $sql = "SELECT id FROM $table_course_user
776
                    WHERE status=1 AND c_id = ".intval($course->c_id);
777
            $res2 = Database::query($sql);
778
            if (1 == Database::num_rows($res2)) {
779
                return false;
780
            }
781
        }
782
783
        return true;
784
    }
785
786
    /**
787
     * Deletes a user from the system or marks the user as deleted based on the $destroy flag.
788
     * If $destroy is false, the user is only marked as deleted (e.g., active = -1) but not actually removed from the database.
789
     * This allows for the possibility of restoring the user at a later time. If $destroy is true, the user and all their relations
790
     * are permanently removed from the database.
791
     *
792
     * Note: When $destroy is false, the user's relations are not removed, allowing for potential restoration. When $destroy is true,
793
     * the function proceeds to remove all the user's relations, effectively cleaning up all references to the user in the system.
794
     */
795
    public static function delete_user(int $user_id, bool $destroy = false): bool
796
    {
797
        $user_id = (int) $user_id;
798
799
        if (empty($user_id)) {
800
            return false;
801
        }
802
803
        if (!self::canDeleteUser($user_id)) {
804
            return false;
805
        }
806
807
        $repository = Container::getUserRepository();
808
809
        /** @var User $user */
810
        $user = $repository->find($user_id);
811
812
        if (!$user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\CoreBundle\Entity\User, thus it always evaluated to true.
Loading history...
813
            return false;
814
        }
815
816
        $repository->deleteUser($user, $destroy);
817
818
        if ($destroy) {
819
            Event::addEvent(
820
                LOG_USER_DELETE,
821
                LOG_USER_ID,
822
                $user_id,
823
                api_get_utc_datetime(),
824
                api_get_user_id()
825
            );
826
827
            Event::addEvent(
828
                LOG_USER_DELETE,
829
                LOG_USER_OBJECT,
830
                api_get_user_info($user_id),
831
                api_get_utc_datetime(),
832
                api_get_user_id()
833
            );
834
        }
835
836
        return true;
837
    }
838
839
    /**
840
     * Deletes users completely. Can be called either as:
841
     * - UserManager::delete_users(1, 2, 3); or
842
     * - UserManager::delete_users(array(1, 2, 3));.
843
     *
844
     * @param array|int $ids
845
     *
846
     * @return bool True if at least one user was successfuly deleted. False otherwise.
847
     *
848
     * @author Laurent Opprecht
849
     *
850
     * @uses \UserManager::delete_user() to actually delete each user
851
     * @assert (null) === false
852
     * @assert (-1) === false
853
     * @assert (array(-1)) === false
854
     */
855
    public static function delete_users($ids = [])
856
    {
857
        $result = false;
858
        $ids = is_array($ids) ? $ids : func_get_args();
859
        if (!is_array($ids) || 0 == count($ids)) {
860
            return false;
861
        }
862
        $ids = array_map('intval', $ids);
863
        foreach ($ids as $id) {
864
            if (empty($id) || $id < 1) {
865
                continue;
866
            }
867
            $deleted = self::delete_user($id);
868
            $result = $deleted || $result;
869
        }
870
871
        return $result;
872
    }
873
874
    /**
875
     * Disable users. Can be called either as:
876
     * - UserManager::deactivate_users(1, 2, 3);
877
     * - UserManager::deactivate_users(array(1, 2, 3));.
878
     *
879
     * @param array|int $ids
880
     *
881
     * @return bool
882
     *
883
     * @author Laurent Opprecht
884
     * @assert (null) === false
885
     * @assert (array(-1)) === false
886
     */
887
    public static function deactivate_users($ids = [])
888
    {
889
        if (empty($ids)) {
890
            return false;
891
        }
892
893
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
894
895
        $ids = is_array($ids) ? $ids : func_get_args();
896
        $ids = array_map('intval', $ids);
897
        $ids = implode(',', $ids);
898
899
        $sql = "UPDATE $table_user SET active = 0 WHERE id IN ($ids)";
900
        $r = Database::query($sql);
901
        if (false !== $r) {
902
            Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $ids);
903
904
            return true;
905
        }
906
907
        return false;
908
    }
909
910
    /**
911
     * Enable users. Can be called either as:
912
     * - UserManager::activate_users(1, 2, 3);
913
     * - UserManager::activate_users(array(1, 2, 3));.
914
     *
915
     * @param array|int IDs of the users to enable
916
     *
917
     * @return bool
918
     *
919
     * @author Laurent Opprecht
920
     * @assert (null) === false
921
     * @assert (array(-1)) === false
922
     */
923
    public static function activate_users($ids = [])
924
    {
925
        if (empty($ids)) {
926
            return false;
927
        }
928
929
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
930
931
        $ids = is_array($ids) ? $ids : func_get_args();
932
        $ids = array_map('intval', $ids);
933
        $ids = implode(',', $ids);
934
935
        $sql = "UPDATE $table_user SET active = 1 WHERE id IN ($ids)";
936
        $r = Database::query($sql);
937
        if (false !== $r) {
938
            Event::addEvent(LOG_USER_ENABLE, LOG_USER_ID, $ids);
939
940
            return true;
941
        }
942
943
        return false;
944
    }
945
946
    /**
947
     * Update user information with all the parameters passed to this function.
948
     *
949
     * @param int    $user_id         The ID of the user to be updated
950
     * @param string $firstname       The user's firstname
951
     * @param string $lastname        The user's lastname
952
     * @param string $username        The user's username (login)
953
     * @param string $password        The user's password
954
     * @param string $auth_sources     The authentication source (default: "platform")
955
     * @param string $email           The user's e-mail address
956
     * @param int    $status          The user's status
957
     * @param string $official_code   The user's official code (usually just an internal institutional code)
958
     * @param string $phone           The user's phone number
959
     * @param string $picture_uri     The user's picture URL (internal to the Chamilo directory)
960
     * @param string $expiration_date The date at which this user will be automatically disabled
961
     * @param int    $active          Whether this account needs to be enabled (1) or disabled (0)
962
     * @param int    $creator_id      The user ID of the person who registered this user (optional, defaults to null)
963
     * @param int    $hr_dept_id      The department of HR in which the user is registered (optional, defaults to 0)
964
     * @param array  $extra           Additional fields to add to this user as extra fields (defaults to null)
965
     * @param string $language        The language to which the user account will be set
966
     * @param string $encrypt_method  The cipher method. This parameter is deprecated. It will use the system's default
967
     * @param bool   $send_email      Whether to send an e-mail to the user after the update is complete
968
     * @param int    $reset_password  Method used to reset password (0, 1, 2 or 3 - see usage examples for details)
969
     * @param string $address
970
     * @param array  $emailTemplate
971
     *
972
     * @return bool|int False on error, or the user ID if the user information was updated
973
     * @assert (false, false, false, false, false, false, false, false, false, false, false, false, false) === false
974
     */
975
    public static function update_user(
976
        $user_id,
977
        $firstname,
978
        $lastname,
979
        $username,
980
        $password,
981
        array $auth_sources,
982
        $email,
983
        $status,
984
        $official_code,
985
        $phone,
986
        $picture_uri,
987
        $expiration_date,
988
        $active,
989
        $creator_id = null,
990
        $hr_dept_id = 0,
991
        $extra = null,
992
        $language = 'en_US',
993
        $encrypt_method = '',
994
        $send_email = false,
995
        $reset_password = 0,
996
        $address = null,
997
        $emailTemplate = []
998
    ) {
999
        $eventDispatcher = Container::getEventDispatcher();
1000
1001
        $eventDispatcher->dispatch(
1002
            new UserUpdatedEvent([], AbstractEvent::TYPE_PRE),
1003
            Events::USER_UPDATED
1004
        );
1005
1006
        $original_password = $password;
1007
        $user_id = (int) $user_id;
1008
        $creator_id = (int) $creator_id;
1009
1010
        if (empty($user_id)) {
1011
            return false;
1012
        }
1013
1014
        $userManager = self::getRepository();
1015
        $user = api_get_user_entity($user_id);
1016
1017
        if (null === $user) {
1018
            return false;
1019
        }
1020
1021
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
1022
1023
        if (0 == $reset_password) {
1024
            $password = null;
1025
            $auth_sources = $user->getAuthSourcesAuthentications($accessUrl);
1026
        } elseif (1 == $reset_password) {
1027
            $original_password = $password = api_generate_password();
1028
            $auth_sources = [UserAuthSource::PLATFORM];
1029
        } elseif (2 == $reset_password) {
1030
            $auth_sources = [UserAuthSource::PLATFORM];
1031
        }
1032
1033
        // Checking the user language
1034
        $languages = array_keys(api_get_languages());
1035
        if (!in_array($language, $languages)) {
1036
            $language = api_get_setting('platformLanguage');
1037
        }
1038
1039
        $change_active = 0;
1040
        $isUserActive = $user->isActive();
1041
        if ($active != USER_SOFT_DELETED) {
1042
            if ($isUserActive != $active) {
1043
                $change_active = 1;
1044
            }
1045
        }
1046
1047
        $originalUsername = $user->getUsername();
1048
1049
        // If username is different from original then check if it exists.
1050
        if ($originalUsername !== $username) {
1051
            $available = self::is_username_available($username);
1052
            if (false === $available) {
1053
                return false;
1054
            }
1055
        }
1056
1057
        if (!empty($expiration_date)) {
1058
            $expiration_date = api_get_utc_datetime($expiration_date);
1059
            $expiration_date = new \DateTime($expiration_date, new DateTimeZone('UTC'));
1060
        }
1061
1062
        $previousStatus = $user->getStatus();
1063
        $previousRole = $user->getRoleFromStatus($previousStatus);
1064
1065
        $user
1066
            ->removeRole($previousRole)
1067
            ->setLastname($lastname)
1068
            ->setFirstname($firstname)
1069
            ->setUsername($username)
1070
            ->setStatus($status)
1071
            ->setLocale($language)
1072
            ->setEmail($email)
1073
            ->setOfficialCode($official_code)
1074
            ->setPhone($phone)
1075
            ->setAddress($address)
1076
            ->setExpirationDate($expiration_date)
1077
            ->setActive($active)
1078
            ->setHrDeptId((int) $hr_dept_id)
1079
            ->removeAuthSources()
1080
        ;
1081
1082
        foreach ($auth_sources as $authSource) {
1083
            $user->addAuthSourceByAuthentication($authSource, $accessUrl);
1084
        }
1085
1086
        if (!is_null($password)) {
1087
            $user->setPlainPassword($password);
1088
        }
1089
1090
        $user->setRoleFromStatus($status);
1091
        $userManager->updateUser($user, true);
1092
        Event::addEvent(LOG_USER_UPDATE, LOG_USER_ID, $user_id);
1093
1094
        if (1 == $change_active) {
1095
            $event_title = LOG_USER_DISABLE;
1096
            if (1 == $active) {
1097
                $event_title = LOG_USER_ENABLE;
1098
            }
1099
            Event::addEvent($event_title, LOG_USER_ID, $user_id);
1100
        }
1101
1102
        if (is_array($extra) && count($extra) > 0) {
1103
            $res = true;
1104
            foreach ($extra as $fname => $fvalue) {
1105
                $res = $res && self::update_extra_field_value(
1106
                    $user_id,
1107
                    $fname,
1108
                    $fvalue
1109
                );
1110
            }
1111
        }
1112
1113
        if (!empty($email) && $send_email) {
1114
            $recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1115
            $emailsubject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
0 ignored issues
show
Bug introduced by
Are you sure api_get_setting('siteName') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1115
            $emailsubject = '['./** @scrutinizer ignore-type */ api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
Loading history...
1116
            $sender_name = api_get_person_name(
1117
                api_get_setting('administratorName'),
1118
                api_get_setting('administratorSurname'),
1119
                null,
1120
                PERSON_NAME_EMAIL_ADDRESS
1121
            );
1122
            $email_admin = api_get_setting('emailAdministrator');
1123
            $url = api_get_path(WEB_PATH);
1124
            if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1124
            if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1125
                $access_url_id = api_get_current_access_url_id();
1126
                if (-1 != $access_url_id) {
1127
                    $url = api_get_access_url($access_url_id);
1128
                    $url = $url['url'];
1129
                }
1130
            }
1131
1132
            $tplContent = new Template(
1133
                null,
1134
                false,
1135
                false,
1136
                false,
1137
                false,
1138
                false
1139
            );
1140
            // variables for the default template
1141
            $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstname, $lastname)));
1142
            $tplContent->assign('login_name', $username);
1143
1144
            $originalPassword = '';
1145
            if ($reset_password > 0) {
1146
                $originalPassword = stripslashes($original_password);
1147
            }
1148
            $tplContent->assign('original_password', $originalPassword);
1149
            $tplContent->assign('portal_url', $url);
1150
1151
            $emailBody = $tplContent->fetch('@ChamiloCore/Mailer/Legacy/user_edit_content.html.twig');
1152
1153
            $mailTemplateManager = new MailTemplateManager();
1154
1155
            if (!empty($emailTemplate) &&
1156
                isset($emailTemplate['user_edit_content.tpl']) &&
1157
                !empty($emailTemplate['user_edit_content.tpl'])
1158
            ) {
1159
                $userInfo = api_get_user_info($user_id);
1160
                $emailBody = $mailTemplateManager->parseTemplate($emailTemplate['user_edit_content.tpl'], $userInfo);
1161
            }
1162
1163
            $creatorInfo = api_get_user_info($creator_id);
1164
            $creatorEmail = $creatorInfo['email'] ?? '';
1165
1166
            api_mail_html(
1167
                $recipient_name,
1168
                $email,
1169
                $emailsubject,
1170
                $emailBody,
1171
                $sender_name,
1172
                $email_admin,
1173
                null,
1174
                null,
1175
                null,
1176
                null,
1177
                $creatorEmail
1178
            );
1179
        }
1180
1181
        $eventDispatcher->dispatch(
1182
            new UserUpdatedEvent(['user' => $user], AbstractEvent::TYPE_POST),
1183
            Events::USER_UPDATED
1184
        );
1185
1186
        return $user->getId();
1187
    }
1188
1189
    /**
1190
     * Disables a user.
1191
     *
1192
     * @param int User id
1193
     *
1194
     * @return bool
1195
     *
1196
     * @uses \UserManager::change_active_state() to actually disable the user
1197
     * @assert (0) === false
1198
     */
1199
    public static function disable($user_id)
1200
    {
1201
        if (empty($user_id)) {
1202
            return false;
1203
        }
1204
        self::change_active_state($user_id, 0);
1205
1206
        return true;
1207
    }
1208
1209
    /**
1210
     * Enable a user.
1211
     *
1212
     * @param int User id
1213
     *
1214
     * @return bool
1215
     *
1216
     * @uses \UserManager::change_active_state() to actually disable the user
1217
     * @assert (0) === false
1218
     */
1219
    public static function enable($user_id)
1220
    {
1221
        if (empty($user_id)) {
1222
            return false;
1223
        }
1224
        self::change_active_state($user_id, 1);
1225
1226
        return true;
1227
    }
1228
1229
    /**
1230
     * Returns the user's id based on the original id and field name in
1231
     * the extra fields. Returns 0 if no user was found. This function is
1232
     * mostly useful in the context of a web services-based sinchronization.
1233
     *
1234
     * @param string Original user id
1235
     * @param string Original field name
1236
     *
1237
     * @return int User id
1238
     * @assert ('0','---') === 0
1239
     */
1240
    public static function get_user_id_from_original_id(
1241
        $original_user_id_value,
1242
        $original_user_id_name
1243
    ) {
1244
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
1245
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1246
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
1247
1248
        $original_user_id_name = Database::escape_string($original_user_id_name);
1249
        $original_user_id_value = Database::escape_string($original_user_id_value);
1250
1251
        $sql = "SELECT item_id as user_id
1252
                FROM $t_uf uf
1253
                INNER JOIN $t_ufv ufv
1254
                ON ufv.field_id = uf.id
1255
                WHERE
1256
                    variable = '$original_user_id_name' AND
1257
                    field_value = '$original_user_id_value' AND
1258
                    item_type = $extraFieldType
1259
                ";
1260
        $res = Database::query($sql);
1261
        $row = Database::fetch_object($res);
1262
        if ($row) {
1263
            return $row->user_id;
1264
        }
1265
1266
        return 0;
1267
    }
1268
1269
    /**
1270
     * Check if a username is available.
1271
     *
1272
     * @param string $username the wanted username
1273
     *
1274
     * @return bool true if the wanted username is available
1275
     * @assert ('') === false
1276
     * @assert ('xyzxyzxyz') === true
1277
     */
1278
    public static function is_username_available($username)
1279
    {
1280
        if (empty($username)) {
1281
            return false;
1282
        }
1283
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1284
        $sql = "SELECT username FROM $table_user
1285
                WHERE username = '".Database::escape_string($username)."'";
1286
        $res = Database::query($sql);
1287
1288
        return 0 == Database::num_rows($res);
1289
    }
1290
1291
    /**
1292
     * Creates a username using person's names, i.e. creates jmontoya from Julio Montoya.
1293
     *
1294
     * @param string $firstname the first name of the user
1295
     * @param string $lastname  the last name of the user
1296
     *
1297
     * @return string suggests a username that contains only ASCII-letters and digits,
1298
     *                without check for uniqueness within the system
1299
     *
1300
     * @author Julio Montoya Armas
1301
     * @author Ivan Tcholakov, 2009 - rework about internationalization.
1302
     * @assert ('','') === false
1303
     * @assert ('a','b') === 'ab'
1304
     */
1305
    public static function create_username($firstname, $lastname)
1306
    {
1307
        if (empty($firstname) && empty($lastname)) {
1308
            return false;
1309
        }
1310
1311
        // The first letter only.
1312
        $firstname = api_substr(
1313
            preg_replace(USERNAME_PURIFIER, '', $firstname),
1314
            0,
1315
            1
1316
        );
1317
        //Looking for a space in the lastname
1318
        $pos = api_strpos($lastname, ' ');
1319
        if (false !== $pos) {
1320
            $lastname = api_substr($lastname, 0, $pos);
1321
        }
1322
1323
        $lastname = preg_replace(USERNAME_PURIFIER, '', $lastname);
1324
        $username = $firstname.$lastname;
1325
        if (empty($username)) {
1326
            $username = 'user';
1327
        }
1328
1329
        $username = URLify::transliterate($username);
1330
1331
        return strtolower(substr($username, 0, User::USERNAME_MAX_LENGTH - 3));
1332
    }
1333
1334
    /**
1335
     * Creates a unique username, using:
1336
     * 1. the first name and the last name of a user;
1337
     * 2. an already created username but not checked for uniqueness yet.
1338
     *
1339
     * @param string $firstname The first name of a given user. If the second parameter $lastname is NULL, then this
1340
     *                          parameter is treated as username which is to be checked f
1341
     *                          or uniqueness and to be modified when it is necessary.
1342
     * @param string $lastname  the last name of the user
1343
     *
1344
     * @return string Returns a username that contains only ASCII-letters and digits and that is unique in the system.
1345
     *                Note: When the method is called several times with same parameters,
1346
     *                its results look like the following sequence: ivan, ivan2, ivan3, ivan4, ...
1347
     *
1348
     * @author Ivan Tcholakov, 2009
1349
     */
1350
    public static function create_unique_username($firstname, $lastname = null)
1351
    {
1352
        if (is_null($lastname)) {
1353
            // In this case the actual input parameter $firstname should contain ASCII-letters and digits only.
1354
            // For making this method tolerant of mistakes,
1355
            // let us transliterate and purify the suggested input username anyway.
1356
            // So, instead of the sentence $username = $firstname; we place the following:
1357
            $username = strtolower(preg_replace(USERNAME_PURIFIER, '', $firstname));
1358
        } else {
1359
            $username = self::create_username($firstname, $lastname);
1360
        }
1361
        if (!self::is_username_available($username)) {
1362
            $i = 2;
1363
            $temp_username = substr($username, 0, User::USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
1364
            while (!self::is_username_available($temp_username)) {
1365
                $i++;
1366
                $temp_username = substr($username, 0, User::USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
1367
            }
1368
            $username = $temp_username;
1369
        }
1370
1371
        $username = URLify::transliterate($username);
1372
1373
        return $username;
1374
    }
1375
1376
    /**
1377
     * Modifies a given username accordingly to the specification for valid characters and length.
1378
     *
1379
     * @param $username string          The input username
1380
     * @param bool $strict (optional)   When this flag is TRUE, the result is guaranteed for full compliance,
1381
     *                     otherwise compliance may be partial. The default value is FALSE.
1382
     *
1383
     * @return string the resulting purified username
1384
     */
1385
    public static function purify_username($username, $strict = false)
1386
    {
1387
        if ($strict) {
1388
            // 1. Conversion of unacceptable letters (latinian letters with accents for example)
1389
            // into ASCII letters in order they not to be totally removed.
1390
            // 2. Applying the strict purifier.
1391
            // 3. Length limitation.
1392
            $return = 'true' === api_get_setting('login_is_email') ? substr(preg_replace(USERNAME_PURIFIER_MAIL, '', $username), 0, User::USERNAME_MAX_LENGTH) : substr(preg_replace(USERNAME_PURIFIER, '', $username), 0, User::USERNAME_MAX_LENGTH);
1393
            $return = URLify::transliterate($return);
1394
1395
            // We want everything transliterate() does except converting @ to '(at)'. This is a hack to avoid this.
1396
            $return = str_replace(' (at) ', '@', $return);
1397
1398
            return $return;
1399
        }
1400
1401
        // 1. Applying the shallow purifier.
1402
        // 2. Length limitation.
1403
        return substr(
1404
            preg_replace(USERNAME_PURIFIER_SHALLOW, '', $username),
1405
            0,
1406
            User::USERNAME_MAX_LENGTH
1407
        );
1408
    }
1409
1410
    /**
1411
     * Checks whether the user id exists in the database.
1412
     *
1413
     * @param int $userId User id
1414
     *
1415
     * @return bool True if user id was found, false otherwise
1416
     */
1417
    public static function is_user_id_valid($userId)
1418
    {
1419
        $resultData = Database::select(
1420
            'COUNT(1) AS count',
1421
            Database::get_main_table(TABLE_MAIN_USER),
1422
            [
1423
                'where' => ['id = ?' => (int) $userId],
1424
            ],
1425
            'first'
1426
        );
1427
1428
        if (false === $resultData) {
1429
            return false;
1430
        }
1431
1432
        return $resultData['count'] > 0;
1433
    }
1434
1435
    /**
1436
     * Checks whether a given username matches to the specification strictly.
1437
     * The empty username is assumed here as invalid.
1438
     * Mostly this function is to be used in the user interface built-in validation routines
1439
     * for providing feedback while usernames are enterd manually.
1440
     *
1441
     * @param string $username the input username
1442
     *
1443
     * @return bool returns TRUE if the username is valid, FALSE otherwise
1444
     */
1445
    public static function is_username_valid($username)
1446
    {
1447
        return !empty($username) && $username == self::purify_username($username, true);
1448
    }
1449
1450
    /**
1451
     * Checks whether a username is empty. If the username contains whitespace characters,
1452
     * such as spaces, tabulators, newlines, etc.,
1453
     * it is assumed as empty too. This function is safe for validation unpurified data (during importing).
1454
     *
1455
     * @param string $username the given username
1456
     *
1457
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
1458
     */
1459
    public static function is_username_empty($username)
1460
    {
1461
        return 0 == strlen(self::purify_username($username, false));
1462
    }
1463
1464
    /**
1465
     * Checks whether a username is too long or not.
1466
     *
1467
     * @param string $username the given username, it should contain only ASCII-letters and digits
1468
     *
1469
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
1470
     */
1471
    public static function is_username_too_long($username)
1472
    {
1473
        return strlen($username) > User::USERNAME_MAX_LENGTH;
1474
    }
1475
1476
    /**
1477
     * Get the users by ID.
1478
     *
1479
     * @param array  $ids    student ids
1480
     * @param bool   $active
1481
     * @param string $order
1482
     * @param string $limit
1483
     *
1484
     * @return array $result student information
1485
     */
1486
    public static function get_user_list_by_ids($ids = [], $active = null, $order = null, $limit = null)
1487
    {
1488
        if (empty($ids)) {
1489
            return [];
1490
        }
1491
1492
        $ids = is_array($ids) ? $ids : [$ids];
1493
        $ids = array_map('intval', $ids);
1494
        $ids = implode(',', $ids);
1495
1496
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
1497
        $sql = "SELECT * FROM $tbl_user WHERE id IN ($ids)";
1498
        if (!is_null($active)) {
1499
            $sql .= ' AND active='.($active ? '1' : '0');
1500
        }
1501
1502
        if (!is_null($order)) {
1503
            $order = Database::escape_string($order);
1504
            $sql .= ' ORDER BY '.$order;
1505
        }
1506
1507
        if (!is_null($limit)) {
1508
            $limit = Database::escape_string($limit);
1509
            $sql .= ' LIMIT '.$limit;
1510
        }
1511
1512
        $rs = Database::query($sql);
1513
        $result = [];
1514
        while ($row = Database::fetch_array($rs)) {
1515
            $result[] = $row;
1516
        }
1517
1518
        return $result;
1519
    }
1520
1521
    /**
1522
     * Get a list of users of which the given conditions match with an = 'cond'.
1523
     *
1524
     * @param array $conditions a list of condition (example : status=>STUDENT)
1525
     * @param array $order_by   a list of fields on which sort
1526
     *
1527
     * @return array an array with all users of the platform
1528
     *
1529
     * @todo security filter order by
1530
     */
1531
    public static function get_user_list(
1532
        $conditions = [],
1533
        $order_by = [],
1534
        $limit_from = false,
1535
        $limit_to = false,
1536
        $idCampus = null
1537
    ) {
1538
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1539
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1540
        $return_array = [];
1541
        $sql = "SELECT user.*, user.id as user_id FROM $user_table user ";
1542
1543
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1543
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1544
            if ($idCampus) {
1545
                $urlId = $idCampus;
1546
            } else {
1547
                $urlId = api_get_current_access_url_id();
1548
            }
1549
            $sql .= " INNER JOIN $userUrlTable url_user
1550
                      ON (user.id = url_user.user_id)
1551
                      WHERE url_user.access_url_id = $urlId";
1552
        } else {
1553
            $sql .= " WHERE 1=1 ";
1554
        }
1555
1556
        if (count($conditions) > 0) {
1557
            foreach ($conditions as $field => $value) {
1558
                $field = Database::escape_string($field);
1559
                $value = Database::escape_string($value);
1560
                $sql .= " AND $field = '$value'";
1561
            }
1562
        }
1563
1564
        if (count($order_by) > 0) {
1565
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1566
        }
1567
1568
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1569
            $limit_from = (int) $limit_from;
1570
            $limit_to = (int) $limit_to;
1571
            $sql .= " LIMIT $limit_from, $limit_to";
1572
        }
1573
        $sql_result = Database::query($sql);
1574
        while ($result = Database::fetch_array($sql_result)) {
1575
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1576
            $return_array[] = $result;
1577
        }
1578
1579
        return $return_array;
1580
    }
1581
1582
    public static function getUserListExtraConditions(
1583
        $conditions = [],
1584
        $order_by = [],
1585
        $limit_from = false,
1586
        $limit_to = false,
1587
        $idCampus = null,
1588
        $extraConditions = '',
1589
        $getCount = false
1590
    ) {
1591
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1592
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1593
        $return_array = [];
1594
        $sql = "SELECT user.*, user.id as user_id FROM $user_table user ";
1595
1596
        if ($getCount) {
1597
            $sql = "SELECT count(user.id) count FROM $user_table user ";
1598
        }
1599
1600
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1600
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1601
            if ($idCampus) {
1602
                $urlId = $idCampus;
1603
            } else {
1604
                $urlId = api_get_current_access_url_id();
1605
            }
1606
            $sql .= " INNER JOIN $userUrlTable url_user
1607
                      ON (user.id = url_user.user_id)
1608
                      WHERE url_user.access_url_id = $urlId";
1609
        } else {
1610
            $sql .= " WHERE 1=1 ";
1611
        }
1612
1613
        $sql .= " AND status <> ".ANONYMOUS." ";
1614
1615
        if (count($conditions) > 0) {
1616
            foreach ($conditions as $field => $value) {
1617
                $field = Database::escape_string($field);
1618
                $value = Database::escape_string($value);
1619
                $sql .= " AND $field = '$value'";
1620
            }
1621
        }
1622
1623
        $sql .= str_replace("\'", "'", Database::escape_string($extraConditions));
1624
1625
        if (!empty($order_by) && count($order_by) > 0) {
1626
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1627
        }
1628
1629
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1630
            $limit_from = (int) $limit_from;
1631
            $limit_to = (int) $limit_to;
1632
            $sql .= " LIMIT $limit_from, $limit_to";
1633
        }
1634
1635
        $sql_result = Database::query($sql);
1636
1637
        if ($getCount) {
1638
            $result = Database::fetch_array($sql_result);
1639
1640
            return $result['count'];
1641
        }
1642
1643
        while ($result = Database::fetch_array($sql_result)) {
1644
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1645
            $return_array[] = $result;
1646
        }
1647
1648
        return $return_array;
1649
    }
1650
1651
    /**
1652
     * Get a list of users of which the given conditions match with a LIKE '%cond%'.
1653
     *
1654
     * @param array  $conditions       a list of condition (exemple : status=>STUDENT)
1655
     * @param array  $order_by         a list of fields on which sort
1656
     * @param bool   $simple_like      Whether we want a simple LIKE 'abc' or a LIKE '%abc%'
1657
     * @param string $condition        Whether we want the filters to be combined by AND or OR
1658
     * @param array  $onlyThisUserList
1659
     *
1660
     * @return array an array with all users of the platform
1661
     *
1662
     * @todo optional course code parameter, optional sorting parameters...
1663
     * @todo security filter order_by
1664
     */
1665
    public static function getUserListLike(
1666
        $conditions = [],
1667
        $order_by = [],
1668
        $simple_like = false,
1669
        $condition = 'AND',
1670
        $onlyThisUserList = [],
1671
        int $limit = 0,
1672
        int $offset = 0
1673
    ) {
1674
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1675
        $tblAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1676
        $return_array = [];
1677
        $sql_query = "SELECT user.id, user.username, user.firstname, user.lastname, user.official_code, user.status
1678
                  FROM $user_table user ";
1679
1680
        if (api_get_multiple_access_url()) {
1681
            $sql_query .= " INNER JOIN $tblAccessUrlRelUser auru ON auru.user_id = user.id ";
1682
        }
1683
1684
        $sql_query .= ' WHERE 1 = 1 ';
1685
        if (count($conditions) > 0) {
1686
            $temp_conditions = [];
1687
            foreach ($conditions as $field => $value) {
1688
                $field = Database::escape_string($field);
1689
                $value = Database::escape_string($value);
1690
                if ($simple_like) {
1691
                    $temp_conditions[] = "$field LIKE '$value%'";
1692
                } else {
1693
                    if (in_array($field, ['user.id', 'user.status'])) {
1694
                        $temp_conditions[] = "$field = '$value'";
1695
                    } else {
1696
                        $temp_conditions[] = "$field LIKE '%$value%'";
1697
                    }
1698
                }
1699
            }
1700
            if (!empty($temp_conditions)) {
1701
                $sql_query .= ' AND '.implode(" $condition ", $temp_conditions);
1702
            }
1703
1704
            if (api_get_multiple_access_url()) {
1705
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1706
            }
1707
        } else {
1708
            if (api_get_multiple_access_url()) {
1709
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1710
            }
1711
        }
1712
1713
        if (!empty($onlyThisUserList)) {
1714
            $onlyThisUserListToString = implode("','", array_map('intval', $onlyThisUserList));
1715
            $sql_query .= " AND user.id IN ('$onlyThisUserListToString') ";
1716
        }
1717
1718
        if (!empty($order_by)) {
1719
            $sql_query .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1720
        }
1721
1722
        if ($limit > 0) {
1723
            $sql_query .= ' LIMIT '.intval($limit);
1724
            if ($offset > 0) {
1725
                $sql_query .= ' OFFSET '.intval($offset);
1726
            }
1727
        }
1728
1729
        $sql_result = Database::query($sql_query);
1730
        while ($result = Database::fetch_array($sql_result)) {
1731
            $return_array[] = $result;
1732
        }
1733
1734
        return $return_array;
1735
    }
1736
1737
    /**
1738
     * Get user path from user ID (returns an array).
1739
     * The return format is a complete path to a folder ending with "/"
1740
     * In case the first level of subdirectory of users/ does not exist, the
1741
     * function will attempt to create it. Probably not the right place to do it
1742
     * but at least it avoids headaches in many other places.
1743
     *
1744
     * @param int    $id   User ID
1745
     * @param string $type Type of path to return (can be 'system', 'web', 'last')
1746
     *
1747
     * @return string User folder path (i.e. /var/www/chamilo/app/upload/users/1/1/)
1748
     */
1749
    public static function getUserPathById($id, $type)
1750
    {
1751
        $id = (int) $id;
1752
        if (!$id) {
1753
            return null;
1754
        }
1755
1756
        $userPath = "users/$id/";
1757
        if (api_get_setting('split_users_upload_directory') === 'true') {
1758
            $userPath = 'users/'.substr((string) $id, 0, 1).'/'.$id.'/';
1759
            // In exceptional cases, on some portals, the intermediate base user
1760
            // directory might not have been created. Make sure it is before
1761
            // going further.
1762
1763
            $rootPath = api_get_path(SYS_PATH).'../app/upload/users/'.substr((string) $id, 0, 1);
1764
            if (!is_dir($rootPath)) {
1765
                $perm = api_get_permissions_for_new_directories();
1766
                try {
1767
                    mkdir($rootPath, $perm);
1768
                } catch (Exception $e) {
1769
                    error_log($e->getMessage());
1770
                }
1771
            }
1772
        }
1773
        switch ($type) {
1774
            case 'system': // Base: absolute system path.
1775
                $userPath = api_get_path(SYS_PATH).'../app/upload/'.$userPath;
1776
                break;
1777
            case 'web': // Base: absolute web path.
1778
                $userPath = api_get_path(WEB_PATH).'../app/upload/'.$userPath;
1779
                break;
1780
            case 'last': // Only the last part starting with users/
1781
                break;
1782
        }
1783
1784
        return $userPath;
1785
    }
1786
1787
    /**
1788
     * Gets the current user image.
1789
     *
1790
     * @param string $userId
1791
     * @param int    $size        it can be USER_IMAGE_SIZE_SMALL,
1792
     *                            USER_IMAGE_SIZE_MEDIUM, USER_IMAGE_SIZE_BIG or  USER_IMAGE_SIZE_ORIGINAL
1793
     * @param bool   $addRandomId
1794
     * @param array  $userInfo    to avoid query the DB
1795
     *
1796
     * @todo add gravatar support
1797
     * @todo replace $userId with User entity
1798
     *
1799
     * @return string
1800
     */
1801
    public static function getUserPicture(
1802
        $userId,
1803
        int $size = USER_IMAGE_SIZE_MEDIUM,
1804
        $addRandomId = true,
1805
        $userInfo = []
1806
    ) {
1807
        $user = api_get_user_entity($userId);
1808
        $illustrationRepo = Container::getIllustrationRepository();
1809
1810
        switch ($size) {
1811
            case USER_IMAGE_SIZE_SMALL:
1812
                $width = 32;
1813
                break;
1814
            case USER_IMAGE_SIZE_MEDIUM:
1815
                $width = 64;
1816
                break;
1817
            case USER_IMAGE_SIZE_BIG:
1818
                $width = 128;
1819
                break;
1820
            case USER_IMAGE_SIZE_ORIGINAL:
1821
            default:
1822
                $width = 0;
1823
                break;
1824
        }
1825
1826
        $url = $illustrationRepo->getIllustrationUrl($user);
1827
        $params = [];
1828
        if (!empty($width)) {
1829
            $params['w'] = $width;
1830
        }
1831
1832
        if ($addRandomId) {
1833
            $params['rand'] = uniqid('u_', true);
1834
        }
1835
1836
        $paramsToString = '';
1837
        if (!empty($params)) {
1838
            $paramsToString = '?'.http_build_query($params);
1839
        }
1840
1841
        return $url.$paramsToString;
1842
1843
        /*
1844
        // Make sure userInfo is defined. Otherwise, define it!
1845
        if (empty($userInfo) || !is_array($userInfo) || 0 == count($userInfo)) {
1846
            if (empty($user_id)) {
1847
                return '';
1848
            } else {
1849
                $userInfo = api_get_user_info($user_id);
1850
            }
1851
        }
1852
1853
        $imageWebPath = self::get_user_picture_path_by_id(
1854
            $user_id,
1855
            'web',
1856
            $userInfo
1857
        );
1858
        $pictureWebFile = $imageWebPath['file'];
1859
        $pictureWebDir = $imageWebPath['dir'];
1860
1861
        $pictureAnonymousSize = '128';
1862
        $gravatarSize = 22;
1863
        $realSizeName = 'small_';
1864
1865
        switch ($size) {
1866
            case USER_IMAGE_SIZE_SMALL:
1867
                $pictureAnonymousSize = '32';
1868
                $realSizeName = 'small_';
1869
                $gravatarSize = 32;
1870
                break;
1871
            case USER_IMAGE_SIZE_MEDIUM:
1872
                $pictureAnonymousSize = '64';
1873
                $realSizeName = 'medium_';
1874
                $gravatarSize = 64;
1875
                break;
1876
            case USER_IMAGE_SIZE_ORIGINAL:
1877
                $pictureAnonymousSize = '128';
1878
                $realSizeName = '';
1879
                $gravatarSize = 128;
1880
                break;
1881
            case USER_IMAGE_SIZE_BIG:
1882
                $pictureAnonymousSize = '128';
1883
                $realSizeName = 'big_';
1884
                $gravatarSize = 128;
1885
                break;
1886
        }
1887
1888
        $gravatarEnabled = api_get_setting('gravatar_enabled');
1889
        $anonymousPath = Display::returnIconPath('unknown.png', $pictureAnonymousSize);
1890
        if ('unknown.jpg' == $pictureWebFile || empty($pictureWebFile)) {
1891
            if ('true' === $gravatarEnabled) {
1892
                $file = self::getGravatar(
1893
                    $imageWebPath['email'],
1894
                    $gravatarSize,
1895
                    api_get_setting('gravatar_type')
1896
                );
1897
1898
                if ($addRandomId) {
1899
                    $file .= '&rand='.uniqid();
1900
                }
1901
1902
                return $file;
1903
            }
1904
1905
            return $anonymousPath;
1906
        }
1907
1908
        if ($addRandomId) {
1909
            $picture .= '?rand='.uniqid();
1910
        }
1911
1912
        return $picture;*/
1913
    }
1914
1915
    /**
1916
     * Creates new user photos in various sizes of a user, or deletes user photos.
1917
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
1918
     *
1919
     * @param int    $user_id the user internal identification number
1920
     * @param string $file    The common file name for the newly created photos.
1921
     *                        It will be checked and modified for compatibility with the file system.
1922
     *                        If full name is provided, path component is ignored.
1923
     *                        If an empty name is provided, then old user photos are deleted only,
1924
     *
1925
     * @see     UserManager::delete_user_picture() as the prefered way for deletion.
1926
     *
1927
     * @param string $source_file    the full system name of the image from which user photos will be created
1928
     * @param string $cropParameters Optional string that contents "x,y,width,height" of a cropped image format
1929
     *
1930
     * @return bool Returns the resulting common file name of created images which usually should be stored in database.
1931
     *              When deletion is requested returns empty string.
1932
     *              In case of internal error or negative validation returns FALSE.
1933
     */
1934
    public static function update_user_picture($userId, UploadedFile $file, string $crop = '')
1935
    {
1936
        if (empty($userId) || empty($file)) {
1937
            return false;
1938
        }
1939
1940
        $repo = Container::getUserRepository();
1941
        $user = $repo->find($userId);
1942
        if ($user) {
1943
            $repoIllustration = Container::getIllustrationRepository();
1944
            $repoIllustration->addIllustration($user, $user, $file, $crop);
1945
        }
1946
    }
1947
1948
    /**
1949
     * Deletes user photos.
1950
     *
1951
     * @param int $userId the user internal identification number
1952
     *
1953
     * @return mixed returns empty string on success, FALSE on error
1954
     */
1955
    public static function deleteUserPicture($userId)
1956
    {
1957
        $repo = Container::getUserRepository();
1958
        $user = $repo->find($userId);
1959
        if ($user) {
1960
            $illustrationRepo = Container::getIllustrationRepository();
1961
            $illustrationRepo->deleteIllustration($user);
1962
        }
1963
    }
1964
1965
    /**
1966
     * Returns an XHTML formatted list of productions for a user, or FALSE if he
1967
     * doesn't have any.
1968
     *
1969
     * If there has been a request to remove a production, the function will return
1970
     * without building the list unless forced to do so by the optional second
1971
     * parameter. This increases performance by avoiding to read through the
1972
     * productions on the filesystem before the removal request has been carried
1973
     * out because they'll have to be re-read afterwards anyway.
1974
     *
1975
     * @deprecated This method is being removed from chamilo 2.0
1976
     * @param int  $user_id    User id
1977
     * @param bool $force      Optional parameter to force building after a removal request
1978
     * @param bool $showDelete
1979
     *
1980
     * @return string A string containing the XHTML code to display the production list, or FALSE
1981
     */
1982
    public static function build_production_list($user_id, $force = false, $showDelete = false)
1983
    {
1984
        if (!$force && !empty($_POST['remove_production'])) {
1985
            return true; // postpone reading from the filesystem
1986
        }
1987
1988
        $productions = self::get_user_productions($user_id);
1989
1990
        if (empty($productions)) {
1991
            return false;
1992
        }
1993
1994
        return false;
1995
1996
        $production_dir = self::getUserPathById($user_id, 'web');
0 ignored issues
show
Unused Code introduced by
$production_dir = self::...thById($user_id, 'web') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1997
        $del_image = Display::returnIconPath('delete.png');
1998
        $add_image = Display::returnIconPath('archive.png');
1999
        $del_text = get_lang('Delete');
2000
        $production_list = '';
2001
        if (count($productions) > 0) {
2002
            $production_list = '<div class="files-production"><ul id="productions">';
2003
            foreach ($productions as $file) {
2004
                $production_list .= '<li>
2005
                    <img src="'.$add_image.'" />
2006
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2007
                        '.htmlentities($file).'
2008
                    </a>';
2009
                if ($showDelete) {
2010
                    $production_list .= '&nbsp;&nbsp;
2011
                        <input
2012
                            style="width:16px;"
2013
                            type="image"
2014
                            name="remove_production['.urlencode($file).']"
2015
                            src="'.$del_image.'"
2016
                            alt="'.$del_text.'"
2017
                            title="'.$del_text.' '.htmlentities($file).'"
2018
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2019
                }
2020
            }
2021
            $production_list .= '</ul></div>';
2022
        }
2023
2024
        return $production_list;
2025
    }
2026
2027
    /**
2028
     * Returns an array with the user's productions.
2029
     *
2030
     * @param int $user_id User id
2031
     *
2032
     * @return array An array containing the user's productions
2033
     */
2034
    public static function get_user_productions($user_id)
2035
    {
2036
        return [];
2037
2038
        $production_repository = self::getUserPathById($user_id, 'system');
0 ignored issues
show
Unused Code introduced by
$production_repository =...yId($user_id, 'system') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
2039
        $productions = [];
2040
2041
        if (is_dir($production_repository)) {
2042
            $handle = opendir($production_repository);
2043
            while ($file = readdir($handle)) {
2044
                if ('.' == $file ||
2045
                    '..' == $file ||
2046
                    '.htaccess' == $file ||
2047
                    is_dir($production_repository.$file)
2048
                ) {
2049
                    // skip current/parent directory and .htaccess
2050
                    continue;
2051
                }
2052
2053
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
2054
                    // User's photos should not be listed as productions.
2055
                    continue;
2056
                }
2057
                $productions[] = $file;
2058
            }
2059
        }
2060
2061
        return $productions;
2062
    }
2063
2064
    /**
2065
     * Remove a user production.
2066
     *
2067
     * @param int    $user_id    User id
2068
     * @param string $production The production to remove
2069
     *
2070
     * @return bool
2071
     */
2072
    public static function remove_user_production($user_id, $production)
2073
    {
2074
        throw new Exception('remove_user_production');
2075
        /*$production_path = self::get_user_picture_path_by_id($user_id, 'system');
2076
        $production_file = $production_path['dir'].$production;
2077
        if (is_file($production_file)) {
2078
            unlink($production_file);
2079
2080
            return true;
2081
        }
2082
2083
        return false;*/
2084
    }
2085
2086
    /**
2087
     * Update an extra field value for a given user.
2088
     *
2089
     * @param int    $userId   User ID
2090
     * @param string $variable Field variable name
2091
     * @param string $value    Field value
2092
     *
2093
     * @return bool true if field updated, false otherwise
2094
     */
2095
    public static function update_extra_field_value($userId, $variable, $value = '')
2096
    {
2097
        $extraFieldValue = new ExtraFieldValue('user');
2098
        $params = [
2099
            'item_id' => $userId,
2100
            'variable' => $variable,
2101
            'field_value' => $value,
2102
        ];
2103
2104
        return $extraFieldValue->save($params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraFieldValue->save($params) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2105
    }
2106
2107
    /**
2108
     * Get an array of extra fields with field details (type, default value and options).
2109
     *
2110
     * @param    int    Offset (from which row)
2111
     * @param    int    Number of items
2112
     * @param    int    Column on which sorting is made
2113
     * @param    string    Sorting direction
2114
     * @param    bool    Optional. Whether we get all the fields or just the visible ones
0 ignored issues
show
Documentation Bug introduced by
The doc comment Optional. at position 0 could not be parsed: Unknown type name 'Optional.' at position 0 in Optional..
Loading history...
2115
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
2116
     *
2117
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
2118
     */
2119
    public static function get_extra_fields(
2120
        $from = 0,
2121
        $number_of_items = 0,
2122
        $column = 5,
2123
        $direction = 'ASC',
2124
        $all_visibility = true,
2125
        $field_filter = null
2126
    ) {
2127
        $fields = [];
2128
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2129
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
2130
        $columns = [
2131
            'id',
2132
            'variable',
2133
            'value_type',
2134
            'display_text',
2135
            'default_value',
2136
            'field_order',
2137
            'filter',
2138
        ];
2139
        $column = (int) $column;
2140
        $sort_direction = '';
2141
        if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
2142
            $sort_direction = strtoupper($direction);
2143
        }
2144
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
2145
        $sqlf = "SELECT * FROM $t_uf WHERE item_type = $extraFieldType ";
2146
        if (!$all_visibility) {
2147
            $sqlf .= " AND visible_to_self = 1 ";
2148
        }
2149
        if (!is_null($field_filter)) {
2150
            $field_filter = (int) $field_filter;
2151
            $sqlf .= " AND filter = $field_filter ";
2152
        }
2153
        $sqlf .= " ORDER BY `".$columns[$column]."` $sort_direction ";
2154
        if (0 != $number_of_items) {
2155
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
2156
        }
2157
        $resf = Database::query($sqlf);
2158
        if (Database::num_rows($resf) > 0) {
2159
            while ($rowf = Database::fetch_array($resf)) {
2160
                $fields[$rowf['id']] = [
2161
                    0 => $rowf['id'],
2162
                    1 => $rowf['variable'],
2163
                    2 => $rowf['value_type'],
2164
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
2165
                    4 => $rowf['default_value'],
2166
                    5 => $rowf['field_order'],
2167
                    6 => $rowf['visible_to_self'],
2168
                    7 => $rowf['changeable'],
2169
                    8 => $rowf['filter'],
2170
                    9 => [],
2171
                    10 => '<a name="'.$rowf['id'].'"></a>',
2172
                ];
2173
2174
                $sqlo = "SELECT * FROM $t_ufo
2175
                         WHERE field_id = ".$rowf['id']."
2176
                         ORDER BY option_order ASC";
2177
                $reso = Database::query($sqlo);
2178
                if (Database::num_rows($reso) > 0) {
2179
                    while ($rowo = Database::fetch_array($reso)) {
2180
                        $fields[$rowf['id']][9][$rowo['id']] = [
2181
                            0 => $rowo['id'],
2182
                            1 => $rowo['option_value'],
2183
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
2184
                            3 => $rowo['option_order'],
2185
                        ];
2186
                    }
2187
                }
2188
            }
2189
        }
2190
2191
        return $fields;
2192
    }
2193
2194
    /**
2195
     * Creates a new extra field.
2196
     *
2197
     * @param string $variable    Field's internal variable name
2198
     * @param int    $fieldType   Field's type
2199
     * @param string $displayText Field's language var name
2200
     * @param string $default     Field's default value
2201
     *
2202
     * @return int
2203
     */
2204
    public static function create_extra_field(
2205
        $variable,
2206
        $valueType,
2207
        $displayText,
2208
        $default
2209
    ) {
2210
        $extraField = new ExtraField('user');
2211
        $params = [
2212
            'variable' => $variable,
2213
            'value_type' => $valueType,
2214
            'display_text' => $displayText,
2215
            'default_value' => $default,
2216
        ];
2217
2218
        return $extraField->save($params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->save($params) also could return the type boolean which is incompatible with the documented return type integer.
Loading history...
2219
    }
2220
2221
    /**
2222
     * Check if a field is available.
2223
     *
2224
     * @param string $variable
2225
     *
2226
     * @return bool
2227
     */
2228
    public static function is_extra_field_available($variable)
2229
    {
2230
        $extraField = new ExtraField('user');
2231
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
2232
2233
        return !empty($data) ? true : false;
2234
    }
2235
2236
    /**
2237
     * Gets user extra fields data.
2238
     *
2239
     * @param    int    User ID
2240
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
2241
     * @param    bool    Whether to return invisible fields as well
2242
     * @param    bool    Whether to split multiple-selection fields or not
2243
     *
2244
     * @return array Array of fields => value for the given user
2245
     */
2246
    public static function get_extra_user_data(
2247
        $user_id,
2248
        $prefix = false,
2249
        $allVisibility = true,
2250
        $splitMultiple = false,
2251
        $fieldFilter = null
2252
    ) {
2253
        $user_id = (int) $user_id;
2254
2255
        if (empty($user_id)) {
2256
            return [];
2257
        }
2258
2259
        $extra_data = [];
2260
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2261
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2262
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2263
                FROM $t_uf f
2264
                WHERE
2265
                    item_type = ".EntityExtraField::USER_FIELD_TYPE."
2266
                ";
2267
        $filter_cond = '';
2268
2269
        if (!$allVisibility) {
2270
            if (isset($fieldFilter)) {
2271
                $fieldFilter = (int) $fieldFilter;
2272
                $filter_cond .= " AND filter = $fieldFilter ";
2273
            }
2274
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
2275
        } else {
2276
            if (isset($fieldFilter)) {
2277
                $fieldFilter = (int) $fieldFilter;
2278
                $sql .= " AND filter = $fieldFilter ";
2279
            }
2280
        }
2281
2282
        $sql .= ' ORDER BY f.field_order';
2283
2284
        $res = Database::query($sql);
2285
        if (Database::num_rows($res) > 0) {
2286
            while ($row = Database::fetch_array($res)) {
2287
                if (self::USER_FIELD_TYPE_TAG == $row['type']) {
2288
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
2289
                    $extra_data['extra_'.$row['fvar']] = $tags;
2290
                } else {
2291
                    $sqlu = "SELECT field_value as fval
2292
                            FROM $t_ufv
2293
                            WHERE field_id=".$row['id']." AND item_id = ".$user_id;
2294
                    $resu = Database::query($sqlu);
2295
                    // get default value
2296
                    $sql_df = "SELECT default_value as fval_df FROM $t_uf
2297
                               WHERE id=".$row['id'];
2298
                    $res_df = Database::query($sql_df);
2299
2300
                    if (Database::num_rows($resu) > 0) {
2301
                        $rowu = Database::fetch_array($resu);
2302
                        $fval = $rowu['fval'];
2303
                        if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2304
                            $fval = explode(';', $rowu['fval']);
2305
                        }
2306
                    } else {
2307
                        $row_df = Database::fetch_array($res_df);
2308
                        $fval = $row_df['fval_df'];
2309
                    }
2310
                    // We get here (and fill the $extra_data array) even if there
2311
                    // is no user with data (we fill it with default values)
2312
                    if ($prefix) {
2313
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2314
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2315
                        } else {
2316
                            $extra_data['extra_'.$row['fvar']] = $fval;
2317
                        }
2318
                    } else {
2319
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2320
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2321
                        } else {
2322
                            $extra_data[$row['fvar']] = $fval;
2323
                        }
2324
                    }
2325
                }
2326
            }
2327
        }
2328
2329
        return $extra_data;
2330
    }
2331
2332
    /** Get extra user data by field.
2333
     * @param int    user ID
2334
     * @param string the internal variable name of the field
2335
     *
2336
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2337
     */
2338
    public static function get_extra_user_data_by_field(
2339
        $user_id,
2340
        $field_variable,
2341
        $prefix = false,
2342
        $all_visibility = true,
2343
        $splitmultiple = false
2344
    ) {
2345
        $user_id = (int) $user_id;
2346
2347
        if (empty($user_id)) {
2348
            return [];
2349
        }
2350
2351
        $extra_data = [];
2352
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2353
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2354
2355
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2356
                FROM $t_uf f
2357
                WHERE f.variable = '$field_variable' ";
2358
2359
        if (!$all_visibility) {
2360
            $sql .= " AND f.visible_to_self = 1 ";
2361
        }
2362
2363
        $sql .= " AND item_type = ".EntityExtraField::USER_FIELD_TYPE;
2364
        $sql .= " ORDER BY f.field_order ";
2365
2366
        $res = Database::query($sql);
2367
        if (Database::num_rows($res) > 0) {
2368
            while ($row = Database::fetch_array($res)) {
2369
                $sqlu = "SELECT field_value as fval FROM $t_ufv v
2370
                         INNER JOIN $t_uf f
2371
                         ON (v.field_id = f.id)
2372
                         WHERE
2373
                            item_type = ".EntityExtraField::USER_FIELD_TYPE." AND
2374
                            field_id = ".$row['id']." AND
2375
                            item_id = ".$user_id;
2376
                $resu = Database::query($sqlu);
2377
                $fval = '';
2378
                if (Database::num_rows($resu) > 0) {
2379
                    $rowu = Database::fetch_array($resu);
2380
                    $fval = $rowu['fval'];
2381
                    if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2382
                        $fval = explode(';', $rowu['fval']);
2383
                    }
2384
                }
2385
                if ($prefix) {
2386
                    $extra_data['extra_'.$row['fvar']] = $fval;
2387
                } else {
2388
                    $extra_data[$row['fvar']] = $fval;
2389
                }
2390
            }
2391
        }
2392
2393
        return $extra_data;
2394
    }
2395
2396
    /**
2397
     * Get the extra field information for a certain field (the options as well).
2398
     *
2399
     * @param int $variable The name of the field we want to know everything about
2400
     *
2401
     * @return array Array containing all the information about the extra profile field
2402
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2403
     *               as returned by the database)
2404
     *
2405
     * @author Julio Montoya
2406
     *
2407
     * @since v1.8.6
2408
     */
2409
    public static function get_extra_field_information_by_name($variable)
2410
    {
2411
        $extraField = new ExtraField('user');
2412
2413
        return $extraField->get_handler_field_info_by_field_variable($variable);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->get_...eld_variable($variable) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2414
    }
2415
2416
    /**
2417
     * Get the extra field information for user tag (the options as well).
2418
     *
2419
     * @param int $variable The name of the field we want to know everything about
2420
     *
2421
     * @return array Array containing all the information about the extra profile field
2422
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2423
     *               as returned by the database)
2424
     *
2425
     * @author José Loguercio
2426
     *
2427
     * @since v1.11.0
2428
     */
2429
    public static function get_extra_field_tags_information_by_name($variable)
2430
    {
2431
        $extraField = new ExtraField('user');
2432
2433
        return $extraField->get_handler_field_info_by_tags($variable);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->get_...info_by_tags($variable) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2434
    }
2435
2436
    /**
2437
     * Get all the extra field information of a certain field (also the options).
2438
     *
2439
     * @param int $fieldId the ID of the field we want to know everything of
2440
     *
2441
     * @return array $return containing all th information about the extra profile field
2442
     *
2443
     * @author Julio Montoya
2444
     *
2445
     * @deprecated
2446
     * @since v1.8.6
2447
     */
2448
    public static function get_extra_field_information($fieldId)
2449
    {
2450
        $extraField = new ExtraField('user');
2451
2452
        return $extraField->getFieldInfoByFieldId($fieldId);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->getF...InfoByFieldId($fieldId) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2453
    }
2454
2455
    /**
2456
     * Get extra user data by value.
2457
     *
2458
     * @param string $variable       the internal variable name of the field
2459
     * @param string $value          the internal value of the field
2460
     * @param bool   $all_visibility
2461
     *
2462
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2463
     */
2464
    public static function get_extra_user_data_by_value($variable, $value, $all_visibility = true)
2465
    {
2466
        $extraFieldValue = new ExtraFieldValue('user');
2467
        $extraField = new ExtraField('user');
2468
2469
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
2470
2471
        if (false === $info) {
2472
            return [];
2473
        }
2474
2475
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
2476
            $variable,
2477
            $value,
2478
            false,
2479
            false,
2480
            true
2481
        );
2482
2483
        $result = [];
2484
        if (!empty($data)) {
2485
            foreach ($data as $item) {
2486
                $result[] = $item['item_id'];
2487
            }
2488
        }
2489
2490
        return $result;
2491
    }
2492
2493
    /**
2494
     * Get extra user data by tags value.
2495
     *
2496
     * @param int    $fieldId the ID of the field we want to know everything of
2497
     * @param string $tag     the tag name for search
2498
     *
2499
     * @return array with extra data info of a user
2500
     *
2501
     * @author José Loguercio
2502
     *
2503
     * @since v1.11.0
2504
     */
2505
    public static function get_extra_user_data_by_tags($fieldId, $tag)
2506
    {
2507
        $extraField = new ExtraField('user');
2508
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
2509
        $array = [];
2510
        foreach ($result as $index => $user) {
2511
            $array[] = $user['user_id'];
2512
        }
2513
2514
        return $array;
2515
    }
2516
2517
    /**
2518
     * Get extra user data by field variable.
2519
     *
2520
     * @param string $variable field variable
2521
     *
2522
     * @return array data
2523
     */
2524
    public static function get_extra_user_data_by_field_variable($variable)
2525
    {
2526
        $extraInfo = self::get_extra_field_information_by_name($variable);
2527
        $field_id = (int) $extraInfo['id'];
2528
2529
        $extraField = new ExtraFieldValue('user');
2530
        $data = $extraField->getValuesByFieldId($field_id);
2531
2532
        if (!empty($data)) {
2533
            foreach ($data as $row) {
2534
                $user_id = $row['item_id'];
2535
                $data[$user_id] = $row;
2536
            }
2537
        }
2538
2539
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2540
    }
2541
2542
    /**
2543
     * Get extra user data tags by field variable.
2544
     *
2545
     * @param string $variable field variable
2546
     *
2547
     * @return array
2548
     */
2549
    public static function get_extra_user_data_for_tags($variable)
2550
    {
2551
        $data = self::get_extra_field_tags_information_by_name($variable);
2552
2553
        return $data;
2554
    }
2555
2556
    /**
2557
     * Gives a list of [session_category][session_id] for the current user.
2558
     *
2559
     * @param int  $user_id
2560
     * @param bool $is_time_over                 whether to fill the first element or not
2561
     *                                           (to give space for courses out of categories)
2562
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
2563
     * @param bool $ignoreTimeLimit              ignore time start/end
2564
     * @param bool $getCount
2565
     *
2566
     * @return array list of statuses [session_category][session_id]
2567
     *
2568
     * @todo ensure multiple access urls are managed correctly
2569
     */
2570
    public static function get_sessions_by_category(
2571
        $user_id,
2572
        $is_time_over = true,
2573
        $ignore_visibility_for_admins = false,
2574
        $ignoreTimeLimit = false,
2575
        $getCount = false
2576
    ) {
2577
        $user_id = (int) $user_id;
2578
2579
        if (empty($user_id)) {
2580
            return [];
2581
        }
2582
2583
        // Get the list of sessions per user
2584
        $now = new DateTime('now', new DateTimeZone('UTC'));
2585
2586
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
2587
        // join would not catch session-courses where the user is general
2588
        // session coach but which do not have students nor coaches registered
2589
        $dqlSelect = ' COUNT(DISTINCT s.id) ';
2590
2591
        if (!$getCount) {
2592
            $dqlSelect = " DISTINCT
2593
                s.id,
2594
                s.title,
2595
                s.accessStartDate AS access_start_date,
2596
                s.accessEndDate AS access_end_date,
2597
                s.duration,
2598
                sc.id AS session_category_id,
2599
                sc.title AS session_category_title,
2600
                sc.dateStart AS session_category_date_start,
2601
                sc.dateEnd AS session_category_date_end,
2602
                s.coachAccessStartDate AS coach_access_start_date,
2603
                s.coachAccessEndDate AS coach_access_end_date,
2604
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
2605
                , s.position AS position
2606
            ";
2607
        }
2608
2609
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
2610
        // is awfully inefficient for large sets of data (1m25s for 58K
2611
        // sessions, BT#14115) but executing a similar query twice and grouping
2612
        // the results afterwards in PHP takes about 1/1000th of the time
2613
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
2614
        $dqlStudent = "SELECT $dqlSelect
2615
            FROM ChamiloCoreBundle:Session AS s
2616
            LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
2617
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2618
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2619
            WHERE scu.user = :user AND url.url = :url ";
2620
        $dqlCoach = "SELECT $dqlSelect
2621
            FROM ChamiloCoreBundle:Session AS s
2622
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2623
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2624
            INNER JOIN ChamiloCoreBundle:SessionRelUser AS su WITH su.session = s
2625
            WHERE (su.user = :user AND su.relationType = ".SessionEntity::GENERAL_COACH.") AND url.url = :url ";
2626
2627
        // Default order
2628
        $order = 'ORDER BY sc.title, s.title';
2629
2630
        // Order by date if showing all sessions
2631
        $showAllSessions = ('true' === api_get_setting('course.show_all_sessions_on_my_course_page'));
2632
        if ($showAllSessions) {
2633
            $order = 'ORDER BY s.accessStartDate';
2634
        }
2635
2636
        // Order by position
2637
        if ('true' === api_get_setting('session.session_list_order')) {
2638
            $order = 'ORDER BY s.position';
2639
        }
2640
2641
        // Order by dates according to settings
2642
        $orderBySettings = api_get_setting('session.my_courses_session_order', true);
2643
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
2644
            $field = $orderBySettings['field'];
2645
            $orderSetting = $orderBySettings['order'];
2646
            switch ($field) {
2647
                case 'start_date':
2648
                    $order = " ORDER BY s.accessStartDate $orderSetting";
2649
                    break;
2650
                case 'end_date':
2651
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
2652
                    if ('asc' == $orderSetting) {
2653
                        // Put null values at the end
2654
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
2655
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
2656
                    }
2657
                    break;
2658
                case 'name':
2659
                case 'title':
2660
                    $order = " ORDER BY s.title $orderSetting ";
2661
                    break;
2662
            }
2663
        }
2664
2665
        $dqlStudent .= $order;
2666
        $dqlCoach .= $order;
2667
2668
        $accessUrlId = api_get_current_access_url_id();
2669
        $dqlStudent = Database::getManager()
2670
            ->createQuery($dqlStudent)
2671
            ->setParameters(
2672
                ['user' => $user_id, 'url' => $accessUrlId]
2673
            )
2674
        ;
2675
        $dqlCoach = Database::getManager()
2676
            ->createQuery($dqlCoach)
2677
            ->setParameters(
2678
                ['user' => $user_id, 'url' => $accessUrlId]
2679
            )
2680
        ;
2681
2682
        if ($getCount) {
2683
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
2684
        }
2685
2686
        $sessionDataStudent = $dqlStudent->getResult();
2687
        $sessionDataCoach = $dqlCoach->getResult();
2688
2689
        $sessionData = [];
2690
        // First fill $sessionData with student sessions
2691
        if (!empty($sessionDataStudent)) {
2692
            foreach ($sessionDataStudent as $row) {
2693
                $sessionData[$row['id']] = $row;
2694
            }
2695
        }
2696
        // Overwrite session data of the user as a student with session data
2697
        // of the user as a coach.
2698
        // There shouldn't be such duplicate rows, but just in case...
2699
        if (!empty($sessionDataCoach)) {
2700
            foreach ($sessionDataCoach as $row) {
2701
                $sessionData[$row['id']] = $row;
2702
            }
2703
        }
2704
2705
        $collapsable = ('true' === api_get_setting('session.allow_user_session_collapsable'));
2706
2707
2708
2709
        $extraField = new ExtraFieldValue('session');
2710
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
2711
2712
        if (empty($sessionData)) {
2713
            return [];
2714
        }
2715
        $categories = [];
2716
        foreach ($sessionData as $row) {
2717
            $session_id = $row['id'];
2718
            $coachList = SessionManager::getCoachesBySession($session_id);
2719
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
2720
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
2721
            $courseList = self::get_courses_list_by_session($user_id, $session_id);
2722
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
2723
2724
            // User portal filters:
2725
            if (false === $ignoreTimeLimit) {
2726
                if ($is_time_over) {
2727
                    // History
2728
                    if ($row['duration']) {
2729
                        if ($daysLeft >= 0) {
2730
                            continue;
2731
                        }
2732
                    } else {
2733
                        if (empty($row['access_end_date'])) {
2734
                            continue;
2735
                        } else {
2736
                            if ($row['access_end_date'] > $now) {
2737
                                continue;
2738
                            }
2739
                        }
2740
                    }
2741
                } else {
2742
                    // Current user portal
2743
                    $isGeneralCoach = api_get_session_entity($row['id'])->hasUserAsGeneralCoach(api_get_user_entity($user_id));
2744
                    $isCoachOfCourse = in_array($user_id, $coachList);
2745
2746
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
2747
                        // Teachers can access the session depending in the access_coach date
2748
                    } else {
2749
                        if ($row['duration']) {
2750
                            if ($daysLeft <= 0) {
2751
                                continue;
2752
                            }
2753
                        } else {
2754
                            if (isset($row['access_end_date']) &&
2755
                                !empty($row['access_end_date'])
2756
                            ) {
2757
                                if ($row['access_end_date'] <= $now) {
2758
                                    continue;
2759
                                }
2760
                            }
2761
                        }
2762
                    }
2763
                }
2764
            }
2765
2766
            $categories[$row['session_category_id']]['session_category'] = [
2767
                'id' => $row['session_category_id'],
2768
                'name' => $row['session_category_title'],
2769
                'date_start' => $categoryStart,
2770
                'date_end' => $categoryEnd,
2771
            ];
2772
2773
            $visibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2773
            $visibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2774
                $session_id,
2775
                null,
2776
                $ignore_visibility_for_admins
2777
            );
2778
2779
            if (SESSION_VISIBLE != $visibility) {
2780
                // Course Coach session visibility.
2781
                $blockedCourseCount = 0;
2782
                $closedVisibilityList = [
2783
                    COURSE_VISIBILITY_CLOSED,
2784
                    COURSE_VISIBILITY_HIDDEN,
2785
                ];
2786
2787
                foreach ($courseList as $course) {
2788
                    // Checking session visibility
2789
                    $sessionCourseVisibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2789
                    $sessionCourseVisibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2790
                        $session_id,
2791
                        $course['real_id'],
2792
                        $ignore_visibility_for_admins
2793
                    );
2794
2795
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
2796
                    if (false === $courseIsVisible || SESSION_INVISIBLE == $sessionCourseVisibility) {
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2796
                    if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
2797
                        $blockedCourseCount++;
2798
                    }
2799
                }
2800
2801
                // If all courses are blocked then no show in the list.
2802
                if ($blockedCourseCount === count($courseList)) {
2803
                    $visibility = SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2803
                    $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2804
                } else {
2805
                    $visibility = $sessionCourseVisibility;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionCourseVisibility does not seem to be defined for all execution paths leading up to this point.
Loading history...
2806
                }
2807
            }
2808
2809
            switch ($visibility) {
2810
                case SESSION_VISIBLE_READ_ONLY:
2811
                case SESSION_VISIBLE:
2812
                case SESSION_AVAILABLE:
2813
                    break;
2814
                case SESSION_INVISIBLE:
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2814
                case /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE:
Loading history...
2815
                    if (false === $ignore_visibility_for_admins) {
2816
                        continue 2;
2817
                    }
2818
            }
2819
2820
            $collapsed = '';
2821
            $collapsedAction = '';
2822
            if ($collapsable) {
2823
                $collapsableData = SessionManager::getCollapsableData(
2824
                    $user_id,
2825
                    $session_id,
2826
                    $extraField,
2827
                    $collapsableLink
2828
                );
2829
                $collapsed = $collapsableData['collapsed'];
2830
                $collapsedAction = $collapsableData['collapsable_link'];
2831
            }
2832
2833
            $categories[$row['session_category_id']]['sessions'][] = [
2834
                'session_name' => $row['title'],
2835
                'session_id' => $row['id'],
2836
                'access_start_date' => $row['access_start_date'] ? $row['access_start_date']->format('Y-m-d H:i:s') : null,
2837
                'access_end_date' => $row['access_end_date'] ? $row['access_end_date']->format('Y-m-d H:i:s') : null,
2838
                'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
2839
                'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
2840
                'courses' => $courseList,
2841
                'collapsed' => $collapsed,
2842
                'collapsable_link' => $collapsedAction,
2843
                'duration' => $row['duration'],
2844
            ];
2845
        }
2846
2847
        return $categories;
2848
    }
2849
2850
    /**
2851
     * Gives a list of [session_id-course_code] => [status] for the current user.
2852
     *
2853
     * @param  int  $user_id
2854
     * @param  int  $sessionLimit
2855
     *
2856
     * @return array list of statuses (session_id-course_code => status)
2857
     *
2858
     * @throws Exception
2859
     */
2860
    public static function get_personal_session_course_list($user_id, $sessionLimit = null)
2861
    {
2862
        // Database Table Definitions
2863
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
2864
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
2865
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
2866
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
2867
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
2868
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2869
2870
        $user_id = (int) $user_id;
2871
2872
        if (empty($user_id)) {
2873
            return [];
2874
        }
2875
2876
        $sessionRepo = Container::getSessionRepository();
2877
2878
        $user = api_get_user_entity($user_id);
2879
        $url = null;
2880
        $formattedUserName = Container::$container->get(NameConventionHelper::class)->getPersonName($user);
2881
2882
        // We filter the courses from the URL
2883
        $join_access_url = $where_access_url = '';
2884
        if (api_get_multiple_access_url()) {
2885
            $access_url_id = api_get_current_access_url_id();
2886
            if (-1 != $access_url_id) {
2887
                $url = api_get_url_entity($access_url_id);
2888
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
2889
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id";
2890
                $where_access_url = " AND access_url_id = $access_url_id ";
2891
            }
2892
        }
2893
2894
        // Courses in which we subscribed out of any session
2895
2896
        $sql = "SELECT
2897
                    course.code,
2898
                    course_rel_user.status course_rel_status,
2899
                    course_rel_user.sort sort,
2900
                    course_rel_user.user_course_cat user_course_cat
2901
                 FROM $tbl_course_user course_rel_user
2902
                 LEFT JOIN $tbl_course course
2903
                 ON course.id = course_rel_user.c_id
2904
                 $join_access_url
2905
                 WHERE
2906
                    course_rel_user.user_id = '".$user_id."' AND
2907
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
2908
                    $where_access_url
2909
                 ORDER BY course_rel_user.sort, course.title ASC";
2910
2911
        $course_list_sql_result = Database::query($sql);
2912
        $personal_course_list = [];
2913
        if (Database::num_rows($course_list_sql_result) > 0) {
2914
            while ($result_row = Database::fetch_assoc($course_list_sql_result)) {
2915
                $course_info = api_get_course_info($result_row['code']);
2916
                $result_row['course_info'] = $course_info;
2917
                $personal_course_list[] = $result_row;
2918
            }
2919
        }
2920
2921
        $coachCourseConditions = '';
2922
        // Getting sessions that are related to a coach in the session_rel_course_rel_user table
2923
        if (api_is_allowed_to_create_course()) {
2924
            $sessionListFromCourseCoach = [];
2925
            $sql = " SELECT DISTINCT session_id
2926
                    FROM $tbl_session_course_user
2927
                    WHERE user_id = $user_id AND status = ".SessionEntity::COURSE_COACH;
2928
2929
            $result = Database::query($sql);
2930
            if (Database::num_rows($result)) {
2931
                $result = Database::store_result($result);
2932
                foreach ($result as $session) {
2933
                    $sessionListFromCourseCoach[] = $session['session_id'];
2934
                }
2935
            }
2936
            if (!empty($sessionListFromCourseCoach)) {
2937
                $condition = implode("','", $sessionListFromCourseCoach);
2938
                $coachCourseConditions = " OR ( s.id IN ('$condition'))";
2939
            }
2940
        }
2941
2942
        // Get the list of sessions where the user is subscribed
2943
        // This is divided into two different queries
2944
        $sessions = [];
2945
        $sessionLimitRestriction = '';
2946
        if (!empty($sessionLimit)) {
2947
            $sessionLimit = (int) $sessionLimit;
2948
            $sessionLimitRestriction = "LIMIT $sessionLimit";
2949
        }
2950
2951
        $sql = "SELECT DISTINCT s.id, s.title, access_start_date, access_end_date
2952
                FROM $tbl_session_user su INNER JOIN $tbl_session s
2953
                ON (s.id = su.session_id)
2954
                WHERE (
2955
                    su.user_id = $user_id AND
2956
                    su.relation_type = ".SessionEntity::STUDENT."
2957
                )
2958
                $coachCourseConditions
2959
                ORDER BY access_start_date, access_end_date, s.title
2960
                $sessionLimitRestriction
2961
        ";
2962
2963
        $result = Database::query($sql);
2964
        if (Database::num_rows($result) > 0) {
2965
            while ($row = Database::fetch_assoc($result)) {
2966
                $sessions[$row['id']] = $row;
2967
            }
2968
        }
2969
2970
        $sql = "SELECT DISTINCT
2971
                s.id, s.title, s.access_start_date, s.access_end_date
2972
                FROM $tbl_session s
2973
                INNER JOIN $tbl_session_user sru ON sru.session_id = s.id
2974
                WHERE (
2975
                    sru.user_id = $user_id AND sru.relation_type = ".SessionEntity::GENERAL_COACH."
2976
                )
2977
                $coachCourseConditions
2978
                ORDER BY s.access_start_date, s.access_end_date, s.title";
2979
2980
        $result = Database::query($sql);
2981
        if (Database::num_rows($result) > 0) {
2982
            while ($row = Database::fetch_assoc($result)) {
2983
                if (empty($sessions[$row['id']])) {
2984
                    $sessions[$row['id']] = $row;
2985
                }
2986
            }
2987
        }
2988
2989
        if (api_is_allowed_to_create_course()) {
2990
            foreach ($sessions as $enreg) {
2991
                $session_id = $enreg['id'];
2992
                $session_visibility = api_get_session_visibility($session_id);
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2992
                $session_visibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility($session_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2993
                $session = api_get_session_entity($session_id);
2994
2995
                if (SESSION_INVISIBLE == $session_visibility) {
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2995
                if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
2996
                    continue;
2997
                }
2998
2999
                $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3000
                    $user,
3001
                    $session,
3002
                    SessionEntity::GENERAL_COACH,
3003
                    $url
3004
                );
3005
                $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3006
                    $user,
3007
                    $session,
3008
                    SessionEntity::COURSE_COACH,
3009
                    $url
3010
                );
3011
3012
                // This query is horribly slow when more than a few thousand
3013
                // users and just a few sessions to which they are subscribed
3014
                $coursesInSession = array_map(
3015
                    function (SessionRelCourse $courseInSession) {
3016
                        $course = $courseInSession->getCourse();
3017
3018
                        return [
3019
                            'code' => $course->getCode(),
3020
                            'i' => $course->getTitle(),
3021
                            'l' => $course->getCourseLanguage(),
3022
                            'sort' => 1,
3023
                        ];
3024
                    },
3025
                    array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3026
                );
3027
3028
                foreach ($coursesInSession as $result_row) {
3029
                    $result_row['t'] = $formattedUserName;
3030
                    $result_row['email'] = $user->getEmail();
3031
                    $result_row['access_start_date'] = $session->getAccessStartDate()?->format('Y-m-d H:i:s');
3032
                    $result_row['access_end_date'] = $session->getAccessEndDate()?->format('Y-m-d H:i:s');
3033
                    $result_row['session_id'] = $session->getId();
3034
                    $result_row['session_name'] = $session->getTitle();
3035
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
3036
                    $key = $result_row['session_id'].' - '.$result_row['code'];
3037
                    $personal_course_list[$key] = $result_row;
3038
                }
3039
            }
3040
        }
3041
3042
        foreach ($sessions as $enreg) {
3043
            $session_id = $enreg['id'];
3044
            $session_visibility = api_get_session_visibility($session_id);
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3044
            $session_visibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility($session_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3045
            if (SESSION_INVISIBLE == $session_visibility) {
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3045
            if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3046
                continue;
3047
            }
3048
3049
            /* This query is very similar to the above query,
3050
               but it will check the session_rel_course_user table if there are courses registered to our user or not */
3051
            $sql = "SELECT DISTINCT
3052
                course.code code,
3053
                course.title i, CONCAT(user.lastname,' ',user.firstname) t,
3054
                email,
3055
                course.course_language l,
3056
                1 sort,
3057
                access_start_date,
3058
                access_end_date,
3059
                session.id as session_id,
3060
                session.title as session_name,
3061
                IF((session_course_user.user_id = 3 AND session_course_user.status = ".SessionEntity::COURSE_COACH."),'2', '5')
3062
            FROM $tbl_session_course_user as session_course_user
3063
            INNER JOIN $tbl_course AS course
3064
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
3065
            INNER JOIN $tbl_session as session
3066
            ON session_course_user.session_id = session.id
3067
            LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
3068
            WHERE session_course_user.user_id = $user_id
3069
            ORDER BY i";
3070
3071
            $course_list_sql_result = Database::query($sql);
3072
            while ($result_row = Database::fetch_assoc($course_list_sql_result)) {
3073
                $result_row['course_info'] = api_get_course_info($result_row['code']);
3074
                $key = $result_row['session_id'].' - '.$result_row['code'];
3075
                if (!isset($personal_course_list[$key])) {
3076
                    $personal_course_list[$key] = $result_row;
3077
                }
3078
            }
3079
        }
3080
3081
        return $personal_course_list;
3082
    }
3083
3084
    /**
3085
     * Gives a list of courses for the given user in the given session.
3086
     *
3087
     * @param int $user_id
3088
     * @param int $session_id
3089
     *
3090
     * @return array list of statuses (session_id-course_code => status)
3091
     */
3092
    public static function get_courses_list_by_session($user_id, $session_id)
3093
    {
3094
        // Database Table Definitions
3095
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
3096
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3097
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3098
3099
        $user_id = (int) $user_id;
3100
        $session_id = (int) $session_id;
3101
3102
        $sessionRepo = Container::getSessionRepository();
3103
3104
        $user = api_get_user_entity($user_id);
3105
        $session = api_get_session_entity($session_id);
3106
        $url = null;
3107
3108
        // We filter the courses from the URL
3109
        $join_access_url = $where_access_url = '';
3110
        if (api_get_multiple_access_url()) {
3111
            $urlId = api_get_current_access_url_id();
3112
            if (-1 != $urlId) {
3113
                $url = api_get_url_entity($urlId);
3114
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3115
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
3116
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
3117
            }
3118
        }
3119
3120
        /* This query is very similar to the query below, but it will check the
3121
        session_rel_course_user table if there are courses registered
3122
        to our user or not */
3123
        $sql = "SELECT DISTINCT
3124
                    c.title,
3125
                    c.visibility,
3126
                    c.id as real_id,
3127
                    c.code as course_code,
3128
                    sc.position,
3129
                    c.unsubscribe
3130
                FROM $tbl_session_course_user as scu
3131
                INNER JOIN $tbl_session_course sc
3132
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
3133
                INNER JOIN $tableCourse as c
3134
                ON (scu.c_id = c.id)
3135
                $join_access_url
3136
                WHERE
3137
                    scu.user_id = $user_id AND
3138
                    scu.session_id = $session_id
3139
                    $where_access_url
3140
                ORDER BY sc.position ASC";
3141
3142
        $myCourseList = [];
3143
        $courses = [];
3144
        $result = Database::query($sql);
3145
        if (Database::num_rows($result) > 0) {
3146
            while ($result_row = Database::fetch_assoc($result)) {
3147
                $result_row['status'] = 5;
3148
                if (!in_array($result_row['real_id'], $courses)) {
3149
                    $position = $result_row['position'];
3150
                    if (!isset($myCourseList[$position])) {
3151
                        $myCourseList[$position] = $result_row;
3152
                    } else {
3153
                        $myCourseList[] = $result_row;
3154
                    }
3155
                    $courses[] = $result_row['real_id'];
3156
                }
3157
            }
3158
        }
3159
3160
        if (api_is_allowed_to_create_course()) {
3161
            $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3162
                $user,
3163
                $session,
3164
                SessionEntity::GENERAL_COACH,
3165
                $url
3166
            );
3167
            $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3168
                $user,
3169
                $session,
3170
                SessionEntity::COURSE_COACH,
3171
                $url
3172
            );
3173
3174
            $coursesInSession = array_map(
3175
                function (SessionRelCourse $courseInSession) {
3176
                    $course = $courseInSession->getCourse();
3177
3178
                    return [
3179
                        'title' => $course->getTitle(),
3180
                        'visibility' => $course->getVisibility(),
3181
                        'real_id' => $course->getId(),
3182
                        'course_code' => $course->getCode(),
3183
                        'position' => $courseInSession->getPosition(),
3184
                        'unsubscribe' => $course->getUnsubscribe(),
3185
                    ];
3186
                },
3187
                array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3188
            );
3189
3190
            foreach ($coursesInSession as $result_row) {
3191
                $result_row['status'] = 2;
3192
                if (!in_array($result_row['real_id'], $courses)) {
3193
                    $position = $result_row['position'];
3194
                    if (!isset($myCourseList[$position])) {
3195
                        $myCourseList[$position] = $result_row;
3196
                    } else {
3197
                        $myCourseList[] = $result_row;
3198
                    }
3199
                    $courses[] = $result_row['real_id'];
3200
                }
3201
            }
3202
        }
3203
3204
        if (api_is_drh()) {
3205
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
3206
            $sessionList = array_keys($sessionList);
3207
            if (in_array($session_id, $sessionList)) {
3208
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3209
                if (!empty($courseList)) {
3210
                    foreach ($courseList as $course) {
3211
                        if (!in_array($course['id'], $courses)) {
3212
                            $position = $course['position'];
3213
                            if (!isset($myCourseList[$position])) {
3214
                                $myCourseList[$position] = $course;
3215
                            } else {
3216
                                $myCourseList[] = $course;
3217
                            }
3218
                        }
3219
                    }
3220
                }
3221
            }
3222
        } else {
3223
            //check if user is general coach for this session
3224
            if ($session && $session->hasUserAsGeneralCoach($user)) {
3225
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3226
                if (!empty($courseList)) {
3227
                    foreach ($courseList as $course) {
3228
                        if (!in_array($course['id'], $courses)) {
3229
                            $position = $course['position'];
3230
                            if (!isset($myCourseList[$position])) {
3231
                                $myCourseList[$position] = $course;
3232
                            } else {
3233
                                $myCourseList[] = $course;
3234
                            }
3235
                        }
3236
                    }
3237
                }
3238
            }
3239
        }
3240
3241
        if (!empty($myCourseList)) {
3242
            ksort($myCourseList);
3243
            $checkPosition = array_filter(array_column($myCourseList, 'position'));
3244
            if (empty($checkPosition)) {
3245
                // The session course list doesn't have any position,
3246
                // then order the course list by course code
3247
                $list = array_column($myCourseList, 'course_code');
3248
                array_multisort($myCourseList, SORT_ASC, $list);
0 ignored issues
show
Bug introduced by
SORT_ASC cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3248
                array_multisort($myCourseList, /** @scrutinizer ignore-type */ SORT_ASC, $list);
Loading history...
3249
            }
3250
        }
3251
3252
        return $myCourseList;
3253
    }
3254
3255
    /**
3256
     * Get user id from a username.
3257
     *
3258
     * @param string $username
3259
     *
3260
     * @return int User ID (or false if not found)
3261
     */
3262
    public static function get_user_id_from_username($username)
3263
    {
3264
        if (empty($username)) {
3265
            return false;
3266
        }
3267
        $username = trim($username);
3268
        $username = Database::escape_string($username);
3269
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
3270
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
3271
        $res = Database::query($sql);
3272
3273
        if (false === $res) {
3274
            return false;
3275
        }
3276
        if (1 !== Database::num_rows($res)) {
3277
            return false;
3278
        }
3279
        $row = Database::fetch_array($res);
3280
3281
        return $row['id'];
3282
    }
3283
3284
    /**
3285
     * Get the users files upload from his share_folder.
3286
     *
3287
     * @param string $user_id      User ID
3288
     * @param string $course       course directory
3289
     * @param string $resourceType resource type: images, all
3290
     *
3291
     * @return string
3292
     */
3293
    /*public static function get_user_upload_files_by_course(
3294
        $user_id,
3295
        $course,
3296
        $resourceType = 'all'
3297
    ) {
3298
        $return = '';
3299
        $user_id = (int) $user_id;
3300
3301
        if (!empty($user_id) && !empty($course)) {
3302
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3303
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3304
            $file_list = [];
3305
3306
            if (is_dir($path)) {
3307
                $handle = opendir($path);
3308
                while ($file = readdir($handle)) {
3309
                    if ('.' == $file || '..' == $file || '.htaccess' == $file || is_dir($path.$file)) {
3310
                        continue; // skip current/parent directory and .htaccess
3311
                    }
3312
                    $file_list[] = $file;
3313
                }
3314
                if (count($file_list) > 0) {
3315
                    $return = "<h4>$course</h4>";
3316
                    $return .= '<ul class="thumbnails">';
3317
                }
3318
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
3319
                foreach ($file_list as $file) {
3320
                    if ('all' == $resourceType) {
3321
                        $return .= '<li>
3322
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
3323
                    } elseif ('images' == $resourceType) {
3324
                        //get extension
3325
                        $ext = explode('.', $file);
3326
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
3327
                            $return .= '<li class="span2">
3328
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
3329
                                                <img src="'.$web_path.urlencode($file).'" >
3330
                                            </a>
3331
                                        </li>';
3332
                        }
3333
                    }
3334
                }
3335
                if (count($file_list) > 0) {
3336
                    $return .= '</ul>';
3337
                }
3338
            }
3339
        }
3340
3341
        return $return;
3342
    }*/
3343
3344
    /**
3345
     * Gets the API key (or keys) and return them into an array.
3346
     *
3347
     * @param int     Optional user id (defaults to the result of api_get_user_id())
3348
     * @param string $api_service
3349
     *
3350
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
3351
     */
3352
    public static function get_api_keys($user_id = null, $api_service = 'default')
3353
    {
3354
        if ($user_id != strval(intval($user_id))) {
3355
            return false;
3356
        }
3357
        if (empty($user_id)) {
3358
            $user_id = api_get_user_id();
3359
        }
3360
        if (false === $user_id) {
3361
            return false;
3362
        }
3363
        $service_name = Database::escape_string($api_service);
3364
        if (false === is_string($service_name)) {
3365
            return false;
3366
        }
3367
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3368
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
3369
        $res = Database::query($sql);
3370
        if (false === $res) {
3371
            return false;
3372
        } //error during query
3373
        $num = Database::num_rows($res);
3374
        if (0 == $num) {
3375
            return false;
3376
        }
3377
        $list = [];
3378
        while ($row = Database::fetch_array($res)) {
3379
            $list[$row['id']] = $row['api_key'];
3380
        }
3381
3382
        return $list;
3383
    }
3384
3385
    /**
3386
     * Adds a new API key to the users' account.
3387
     *
3388
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
3389
     * @param string $api_service
3390
     *
3391
     * @return bool True on success, false on failure
3392
     */
3393
    public static function add_api_key($user_id = null, $api_service = 'default')
3394
    {
3395
        if ($user_id != strval(intval($user_id))) {
3396
            return false;
3397
        }
3398
        if (empty($user_id)) {
3399
            $user_id = api_get_user_id();
3400
        }
3401
        if (false === $user_id) {
3402
            return false;
3403
        }
3404
        $service_name = Database::escape_string($api_service);
3405
        if (false === is_string($service_name)) {
3406
            return false;
3407
        }
3408
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3409
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
3410
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
3411
        $res = Database::query($sql);
3412
        if (false === $res) {
3413
            return false;
3414
        } //error during query
3415
        $num = Database::insert_id();
3416
3417
        return 0 == $num ? false : $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 == $num ? false : $num also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
3418
    }
3419
3420
    /**
3421
     * Deletes an API key from the user's account.
3422
     *
3423
     * @param   int     API key's internal ID
3424
     *
3425
     * @return bool True on success, false on failure
3426
     */
3427
    public static function delete_api_key($key_id)
3428
    {
3429
        if ($key_id != strval(intval($key_id))) {
3430
            return false;
3431
        }
3432
        if (false === $key_id) {
3433
            return false;
3434
        }
3435
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3436
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
3437
        $res = Database::query($sql);
3438
        if (false === $res) {
3439
            return false;
3440
        } //error during query
3441
        $num = Database::num_rows($res);
3442
        if (1 !== $num) {
3443
            return false;
3444
        }
3445
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
3446
        $res = Database::query($sql);
3447
        if (false === $res) {
3448
            return false;
3449
        } //error during query
3450
3451
        return true;
3452
    }
3453
3454
    /**
3455
     * Regenerate an API key from the user's account.
3456
     *
3457
     * @param   int     user ID (defaults to the results of api_get_user_id())
3458
     * @param   string  API key's internal ID
3459
     *
3460
     * @return int num
3461
     */
3462
    public static function update_api_key($user_id, $api_service)
3463
    {
3464
        if ($user_id != strval(intval($user_id))) {
3465
            return false;
3466
        }
3467
        if (false === $user_id) {
3468
            return false;
3469
        }
3470
        $service_name = Database::escape_string($api_service);
3471
        if (false === is_string($service_name)) {
3472
            return false;
3473
        }
3474
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3475
        $sql = "SELECT id FROM $t_api
3476
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3477
        $res = Database::query($sql);
3478
        $num = Database::num_rows($res);
3479
        if (1 == $num) {
3480
            $id_key = Database::fetch_assoc($res);
3481
            self::delete_api_key($id_key['id']);
3482
            $num = self::add_api_key($user_id, $api_service);
3483
        } elseif (0 == $num) {
3484
            $num = self::add_api_key($user_id, $api_service);
3485
        }
3486
3487
        return $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $num also could return the type boolean which is incompatible with the documented return type integer.
Loading history...
3488
    }
3489
3490
    /**
3491
     * @param   int     user ID (defaults to the results of api_get_user_id())
3492
     * @param   string    API key's internal ID
3493
     *
3494
     * @return int row ID, or return false if not found
3495
     */
3496
    public static function get_api_key_id($user_id, $api_service)
3497
    {
3498
        if ($user_id != strval(intval($user_id))) {
3499
            return false;
3500
        }
3501
        if (false === $user_id) {
3502
            return false;
3503
        }
3504
        if (empty($api_service)) {
3505
            return false;
3506
        }
3507
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3508
        $api_service = Database::escape_string($api_service);
3509
        $sql = "SELECT id FROM $t_api
3510
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3511
        $res = Database::query($sql);
3512
        if (Database::num_rows($res) < 1) {
3513
            return false;
3514
        }
3515
        $row = Database::fetch_assoc($res);
3516
3517
        return $row['id'];
3518
    }
3519
3520
    /**
3521
     * Checks if a user_id is platform admin.
3522
     *
3523
     * @param   int user ID
3524
     *
3525
     * @return bool True if is admin, false otherwise
3526
     *
3527
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
3528
     */
3529
    public static function is_admin($user_id)
3530
    {
3531
        $user_id = (int) $user_id;
3532
        if (empty($user_id)) {
3533
            return false;
3534
        }
3535
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
3536
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
3537
        $res = Database::query($sql);
3538
3539
        return 1 === Database::num_rows($res);
3540
    }
3541
3542
    /**
3543
     * Get the total count of users.
3544
     *
3545
     * @param ?int $status Status of users to be counted
3546
     * @param ?int $access_url_id Access URL ID (optional)
3547
     * @param ?int $active
3548
     *
3549
     * @return mixed Number of users or false on error
3550
     * @throws \Doctrine\DBAL\Exception
3551
     */
3552
    public static function get_number_of_users(
3553
        ?int $status = 0,
3554
        ?int $access_url_id = 1,
3555
        ?int $active = null,
3556
        ?string $dateFrom = null,
3557
        ?string $dateUntil = null
3558
    ): mixed {
3559
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
3560
        $tableAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3561
3562
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3562
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3563
            $sql = "SELECT count(u.id)
3564
                    FROM $tableUser u
3565
                    INNER JOIN $tableAccessUrlRelUser url_user
3566
                    ON (u.id = url_user.user_id)
3567
                    WHERE url_user.access_url_id = $access_url_id
3568
            ";
3569
        } else {
3570
            $sql = "SELECT count(u.id)
3571
                    FROM $tableUser u
3572
                    WHERE 1 = 1 ";
3573
        }
3574
3575
        $status = (int) $status;
3576
        if (!empty($status) && $status > 0) {
3577
            $sql .= " AND u.status = $status ";
3578
        }
3579
3580
        if (isset($active)) {
3581
            $active = (int) $active;
3582
            $sql .= " AND u.active = $active ";
3583
        }
3584
3585
        if (!empty($dateFrom)) {
3586
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
3587
            $sql .= " AND u.created_at >= '$dateFrom' ";
3588
        }
3589
        if (!empty($dateUntil)) {
3590
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
3591
            $sql .= " AND u.created_at <= '$dateUntil' ";
3592
        }
3593
3594
        $res = Database::query($sql);
3595
        if (1 === Database::num_rows($res)) {
3596
            return (int) Database::result($res, 0, 0);
3597
        }
3598
3599
        return false;
3600
    }
3601
3602
    /**
3603
     * Gets the tags of a specific field_id
3604
     * USER TAGS.
3605
     *
3606
     * Instructions to create a new user tag by Julio Montoya <[email protected]>
3607
     *
3608
     * 1. Create a new extra field in main/admin/user_fields.php with the "TAG" field type make it available and visible.
3609
     *    Called it "books" for example.
3610
     * 2. Go to profile main/auth/profile.php There you will see a special input (facebook style) that will show suggestions of tags.
3611
     * 3. All the tags are registered in the user_tag table and the relationship between user and tags is in the user_rel_tag table
3612
     * 4. Tags are independent this means that tags can't be shared between tags + book + hobbies.
3613
     * 5. Test and enjoy.
3614
     *
3615
     * @param string $tag
3616
     * @param int    $field_id      field_id
3617
     * @param string $return_format how we are going to result value in array or in a string (json)
3618
     * @param $limit
3619
     *
3620
     * @return mixed
3621
     */
3622
    public static function get_tags($tag, $field_id, $return_format = 'json', $limit = 10)
3623
    {
3624
        // database table definition
3625
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3626
        $field_id = (int) $field_id;
3627
        $limit = (int) $limit;
3628
        $tag = trim(Database::escape_string($tag));
3629
3630
        // all the information of the field
3631
        $sql = "SELECT DISTINCT id, tag from $table_user_tag
3632
                WHERE field_id = $field_id AND tag LIKE '$tag%' ORDER BY tag LIMIT $limit";
3633
        $result = Database::query($sql);
3634
        $return = [];
3635
        if (Database::num_rows($result) > 0) {
3636
            while ($row = Database::fetch_assoc($result)) {
3637
                $return[] = ['id' => $row['tag'], 'text' => $row['tag']];
3638
            }
3639
        }
3640
        if ('json' === $return_format) {
3641
            $return = json_encode($return);
3642
        }
3643
3644
        return $return;
3645
    }
3646
3647
    /**
3648
     * @param int $field_id
3649
     * @param int $limit
3650
     *
3651
     * @return array
3652
     */
3653
    public static function get_top_tags($field_id, $limit = 100)
3654
    {
3655
        // database table definition
3656
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3657
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3658
        $field_id = (int) $field_id;
3659
        $limit = (int) $limit;
3660
        // all the information of the field
3661
        $sql = "SELECT count(*) count, tag FROM $table_user_tag_values  uv
3662
                INNER JOIN $table_user_tag ut
3663
                ON (ut.id = uv.tag_id)
3664
                WHERE field_id = $field_id
3665
                GROUP BY tag_id
3666
                ORDER BY count DESC
3667
                LIMIT $limit";
3668
        $result = Database::query($sql);
3669
        $return = [];
3670
        if (Database::num_rows($result) > 0) {
3671
            while ($row = Database::fetch_assoc($result)) {
3672
                $return[] = $row;
3673
            }
3674
        }
3675
3676
        return $return;
3677
    }
3678
3679
    /**
3680
     * Get user's tags.
3681
     *
3682
     * @param int $user_id
3683
     * @param int $field_id
3684
     *
3685
     * @return array
3686
     */
3687
    public static function get_user_tags($user_id, $field_id)
3688
    {
3689
        // database table definition
3690
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3691
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3692
        $field_id = (int) $field_id;
3693
        $user_id = (int) $user_id;
3694
3695
        // all the information of the field
3696
        $sql = "SELECT ut.id, tag, count
3697
                FROM $table_user_tag ut
3698
                INNER JOIN $table_user_tag_values uv
3699
                ON (uv.tag_id=ut.ID)
3700
                WHERE field_id = $field_id AND user_id = $user_id
3701
                ORDER BY tag";
3702
        $result = Database::query($sql);
3703
        $return = [];
3704
        if (Database::num_rows($result) > 0) {
3705
            while ($row = Database::fetch_assoc($result)) {
3706
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
3707
            }
3708
        }
3709
3710
        return $return;
3711
    }
3712
3713
    /**
3714
     * Get user's tags.
3715
     *
3716
     * @param int  $user_id
3717
     * @param int  $field_id
3718
     * @param bool $show_links show links or not
3719
     *
3720
     * @return string
3721
     */
3722
    public static function get_user_tags_to_string($user_id, $field_id, $show_links = true)
3723
    {
3724
        // database table definition
3725
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3726
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3727
        $field_id = (int) $field_id;
3728
        $user_id = (int) $user_id;
3729
3730
        // all the information of the field
3731
        $sql = "SELECT ut.id, tag,count FROM $table_user_tag ut
3732
                INNER JOIN $table_user_tag_values uv
3733
                ON (uv.tag_id = ut.id)
3734
                WHERE field_id = $field_id AND user_id = $user_id
3735
                ORDER BY tag";
3736
3737
        $result = Database::query($sql);
3738
        $return = [];
3739
        if (Database::num_rows($result) > 0) {
3740
            while ($row = Database::fetch_assoc($result)) {
3741
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
3742
            }
3743
        }
3744
        $user_tags = $return;
3745
        $tag_tmp = [];
3746
        foreach ($user_tags as $tag) {
3747
            if ($show_links) {
3748
                $tag_tmp[] = '<a href="'.api_get_path(WEB_PATH).'main/search/index.php?q='.$tag['tag'].'">'.
3749
                    $tag['tag'].
3750
                '</a>';
3751
            } else {
3752
                $tag_tmp[] = $tag['tag'];
3753
            }
3754
        }
3755
3756
        if (is_array($user_tags) && count($user_tags) > 0) {
3757
            return implode(', ', $tag_tmp);
3758
        } else {
3759
            return '';
3760
        }
3761
    }
3762
3763
    /**
3764
     * Get the tag id.
3765
     *
3766
     * @param int $tag
3767
     * @param int $field_id
3768
     *
3769
     * @return int returns 0 if fails otherwise the tag id
3770
     */
3771
    public static function get_tag_id($tag, $field_id)
3772
    {
3773
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3774
        $tag = Database::escape_string($tag);
3775
        $field_id = (int) $field_id;
3776
        //with COLLATE latin1_bin to select query in a case sensitive mode
3777
        $sql = "SELECT id FROM $table_user_tag
3778
                WHERE tag LIKE '$tag' AND field_id = $field_id";
3779
        $result = Database::query($sql);
3780
        if (Database::num_rows($result) > 0) {
3781
            $row = Database::fetch_assoc($result);
3782
3783
            return $row['id'];
3784
        } else {
3785
            return 0;
3786
        }
3787
    }
3788
3789
    /**
3790
     * Get the tag id.
3791
     *
3792
     * @param int $tag_id
3793
     * @param int $field_id
3794
     *
3795
     * @return int 0 if fails otherwise the tag id
3796
     */
3797
    public static function get_tag_id_from_id($tag_id, $field_id)
3798
    {
3799
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3800
        $tag_id = (int) $tag_id;
3801
        $field_id = (int) $field_id;
3802
        $sql = "SELECT id FROM $table_user_tag
3803
                WHERE id = '$tag_id' AND field_id = $field_id";
3804
        $result = Database::query($sql);
3805
        if (Database::num_rows($result) > 0) {
3806
            $row = Database::fetch_assoc($result);
3807
3808
            return $row['id'];
3809
        } else {
3810
            return false;
3811
        }
3812
    }
3813
3814
    /**
3815
     * Adds a user-tag value.
3816
     *
3817
     * @param mixed $tag
3818
     * @param int   $user_id
3819
     * @param int   $field_id field id of the tag
3820
     *
3821
     * @return bool True if the tag was inserted or updated. False otherwise.
3822
     *              The return value doesn't take into account *values* added to the tag.
3823
     *              Only the creation/update of the tag field itself.
3824
     */
3825
    public static function add_tag($tag, $user_id, $field_id)
3826
    {
3827
        // database table definition
3828
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3829
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3830
        $tag = trim(Database::escape_string($tag));
3831
        $user_id = (int) $user_id;
3832
        $field_id = (int) $field_id;
3833
3834
        $tag_id = self::get_tag_id($tag, $field_id);
3835
3836
        /* IMPORTANT
3837
         *  @todo we don't create tags with numbers
3838
         *
3839
         */
3840
3841
        //this is a new tag
3842
        if (0 == $tag_id) {
3843
            //the tag doesn't exist
3844
            $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
3845
            Database::query($sql);
3846
            $last_insert_id = Database::insert_id();
3847
        } else {
3848
            //the tag exists we update it
3849
            $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $tag_id";
3850
            Database::query($sql);
3851
            $last_insert_id = $tag_id;
3852
        }
3853
3854
        if (!empty($last_insert_id) && (0 != $last_insert_id)) {
3855
            //we insert the relationship user-tag
3856
            $sql = "SELECT tag_id FROM $table_user_tag_values
3857
                    WHERE user_id = $user_id AND tag_id = $last_insert_id ";
3858
            $result = Database::query($sql);
3859
            //if the relationship does not exist we create it
3860
            if (0 == Database::num_rows($result)) {
3861
                $sql = "INSERT INTO $table_user_tag_values SET user_id = $user_id, tag_id = $last_insert_id";
3862
                Database::query($sql);
3863
            }
3864
3865
            return true;
3866
        }
3867
3868
        return false;
3869
    }
3870
3871
    /**
3872
     * Deletes an user tag.
3873
     *
3874
     * @param int $user_id
3875
     * @param int $field_id
3876
     */
3877
    public static function delete_user_tags($user_id, $field_id)
3878
    {
3879
        // database table definition
3880
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3881
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3882
        $user_id = (int) $user_id;
3883
3884
        $tags = self::get_user_tags($user_id, $field_id);
3885
        if (is_array($tags) && count($tags) > 0) {
3886
            foreach ($tags as $key => $tag) {
3887
                if ($tag['count'] > '0') {
3888
                    $sql = "UPDATE $table_user_tag SET count = count - 1  WHERE id = $key ";
3889
                    Database::query($sql);
3890
                }
3891
                $sql = "DELETE FROM $table_user_tag_values
3892
                        WHERE user_id = $user_id AND tag_id = $key";
3893
                Database::query($sql);
3894
            }
3895
        }
3896
    }
3897
3898
    /**
3899
     * Process the tag list comes from the UserManager::update_extra_field_value() function.
3900
     *
3901
     * @param array $tags     the tag list that will be added
3902
     * @param int   $user_id
3903
     * @param int   $field_id
3904
     *
3905
     * @return bool
3906
     */
3907
    public static function process_tags($tags, $user_id, $field_id)
3908
    {
3909
        // We loop the tags and add it to the DB
3910
        if (is_array($tags)) {
3911
            foreach ($tags as $tag) {
3912
                self::add_tag($tag, $user_id, $field_id);
3913
            }
3914
        } else {
3915
            self::add_tag($tags, $user_id, $field_id);
3916
        }
3917
3918
        return true;
3919
    }
3920
3921
    /**
3922
     * Returns a list of all administrators.
3923
     *
3924
     * @return array
3925
     */
3926
    public static function get_all_administrators()
3927
    {
3928
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
3929
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
3930
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3931
        $access_url_id = api_get_current_access_url_id();
3932
        if (api_get_multiple_access_url()) {
3933
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active, locale
3934
                    FROM $tbl_url_rel_user as url
3935
                    INNER JOIN $table_admin as admin
3936
                    ON (admin.user_id=url.user_id)
3937
                    INNER JOIN $table_user u
3938
                    ON (u.id=admin.user_id)
3939
                    WHERE access_url_id ='".$access_url_id."'";
3940
        } else {
3941
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active, locale
3942
                    FROM $table_admin as admin
3943
                    INNER JOIN $table_user u
3944
                    ON (u.id=admin.user_id)";
3945
        }
3946
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
3947
        $result = Database::query($sql);
3948
        $return = [];
3949
        if (Database::num_rows($result) > 0) {
3950
            while ($row = Database::fetch_assoc($result)) {
3951
                $return[$row['user_id']] = $row;
3952
            }
3953
        }
3954
3955
        return $return;
3956
    }
3957
3958
    /**
3959
     * Search an user (tags, first name, last name and email ).
3960
     *
3961
     * @param string $tag
3962
     * @param int    $field_id        field id of the tag
3963
     * @param int    $from            where to start in the query
3964
     * @param int    $number_of_items
3965
     * @param bool   $getCount        get count or not
3966
     *
3967
     * @return array
3968
     */
3969
    public static function get_all_user_tags(
3970
        $tag,
3971
        $field_id = 0,
3972
        $from = 0,
3973
        $number_of_items = 10,
3974
        $getCount = false
3975
    ) {
3976
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
3977
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3978
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3979
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3980
3981
        $field_id = intval($field_id);
3982
        $from = intval($from);
3983
        $number_of_items = intval($number_of_items);
3984
3985
        $where_field = "";
3986
        $where_extra_fields = self::get_search_form_where_extra_fields();
3987
        if (0 != $field_id) {
3988
            $where_field = " field_id = $field_id AND ";
3989
        }
3990
3991
        // all the information of the field
3992
        if ($getCount) {
3993
            $select = "SELECT count(DISTINCT u.id) count";
3994
        } else {
3995
            $select = "SELECT DISTINCT u.id, u.username, firstname, lastname, email, tag, picture_uri";
3996
        }
3997
3998
        $sql = " $select
3999
                FROM $user_table u
4000
                INNER JOIN $access_url_rel_user_table url_rel_user
4001
                ON (u.id = url_rel_user.user_id)
4002
                LEFT JOIN $table_user_tag_values uv
4003
                ON (u.id AND uv.user_id AND uv.user_id = url_rel_user.user_id)
4004
                LEFT JOIN $table_user_tag ut ON (uv.tag_id = ut.id)
4005
                WHERE
4006
                    ($where_field tag LIKE '".Database::escape_string($tag."%")."') OR
4007
                    (
4008
                        u.firstname LIKE '".Database::escape_string("%".$tag."%")."' OR
4009
                        u.lastname LIKE '".Database::escape_string("%".$tag."%")."' OR
4010
                        u.username LIKE '".Database::escape_string("%".$tag."%")."' OR
4011
                        concat(u.firstname, ' ', u.lastname) LIKE '".Database::escape_string("%".$tag."%")."' OR
4012
                        concat(u.lastname, ' ', u.firstname) LIKE '".Database::escape_string("%".$tag."%")."'
4013
                     )
4014
                     ".(!empty($where_extra_fields) ? $where_extra_fields : '')."
4015
                     AND url_rel_user.access_url_id=".api_get_current_access_url_id();
4016
4017
        $keyword_active = true;
4018
        // only active users
4019
        if ($keyword_active) {
4020
            $sql .= " AND u.active='1'";
4021
        }
4022
        // avoid anonymous
4023
        $sql .= " AND u.status <> 6 ";
4024
        $sql .= " ORDER BY username";
4025
        $sql .= " LIMIT $from , $number_of_items";
4026
4027
        $result = Database::query($sql);
4028
        $return = [];
4029
4030
        if (Database::num_rows($result) > 0) {
4031
            if ($getCount) {
4032
                $row = Database::fetch_assoc($result);
4033
4034
                return $row['count'];
4035
            }
4036
            while ($row = Database::fetch_assoc($result)) {
4037
                $return[$row['id']] = $row;
4038
            }
4039
        }
4040
4041
        return $return;
4042
    }
4043
4044
    /**
4045
     * Get extra filterable user fields (only type select).
4046
     *
4047
     * @return array Array of extra fields as [int => ['name' => ..., 'variable' => ..., 'data' => ...]] (
4048
     *               or empty array if no extra field)
4049
     */
4050
    public static function getExtraFilterableFields()
4051
    {
4052
        $extraFieldList = self::get_extra_fields();
4053
        $fields = [];
4054
        if (is_array($extraFieldList)) {
4055
            foreach ($extraFieldList as $extraField) {
4056
                // If is enabled to filter and is a "<select>" field type
4057
                if (1 == $extraField[8] && 4 == $extraField[2]) {
4058
                    $fields[] = [
4059
                        'name' => $extraField[3],
4060
                        'variable' => $extraField[1],
4061
                        'data' => $extraField[9],
4062
                    ];
4063
                }
4064
            }
4065
        }
4066
4067
        return $fields;
4068
    }
4069
4070
    /**
4071
     * Get extra where clauses for finding users based on extra filterable user fields (type select).
4072
     *
4073
     * @return string With AND clauses based on user's ID which have the values to search in extra user fields
4074
     *                (or empty if no extra field exists)
4075
     */
4076
    public static function get_search_form_where_extra_fields()
4077
    {
4078
        $useExtraFields = false;
4079
        $extraFields = self::getExtraFilterableFields();
4080
        $extraFieldResult = [];
4081
        if (is_array($extraFields) && count($extraFields) > 0) {
4082
            foreach ($extraFields as $extraField) {
4083
                $varName = 'field_'.$extraField['variable'];
4084
                if (self::is_extra_field_available($extraField['variable'])) {
4085
                    if (isset($_GET[$varName]) && '0' != $_GET[$varName]) {
4086
                        $useExtraFields = true;
4087
                        $extraFieldResult[] = self::get_extra_user_data_by_value(
4088
                            $extraField['variable'],
4089
                            $_GET[$varName]
4090
                        );
4091
                    }
4092
                }
4093
            }
4094
        }
4095
4096
        if ($useExtraFields) {
4097
            $finalResult = [];
4098
            if (count($extraFieldResult) > 1) {
4099
                for ($i = 0; $i < count($extraFieldResult) - 1; $i++) {
4100
                    if (is_array($extraFieldResult[$i]) && is_array($extraFieldResult[$i + 1])) {
4101
                        $finalResult = array_intersect($extraFieldResult[$i], $extraFieldResult[$i + 1]);
4102
                    }
4103
                }
4104
            } else {
4105
                $finalResult = $extraFieldResult[0];
4106
            }
4107
4108
            if (is_array($finalResult) && count($finalResult) > 0) {
4109
                $whereFilter = " AND u.id IN  ('".implode("','", $finalResult)."') ";
4110
            } else {
4111
                //no results
4112
                $whereFilter = " AND u.id  = -1 ";
4113
            }
4114
4115
            return $whereFilter;
4116
        }
4117
4118
        return '';
4119
    }
4120
4121
    /**
4122
     * Show the search form.
4123
     *
4124
     * @param string $query the value of the search box
4125
     *
4126
     * @throws Exception
4127
     *
4128
     * @return string HTML form
4129
     */
4130
    public static function get_search_form($query, $defaultParams = [])
4131
    {
4132
        $searchType = isset($_GET['search_type']) ? $_GET['search_type'] : null;
4133
        $form = new FormValidator(
4134
            'search_user',
4135
            'get',
4136
            api_get_path(WEB_PATH).'main/social/search.php',
4137
            '',
4138
            [],
4139
            FormValidator::LAYOUT_HORIZONTAL
4140
        );
4141
4142
        $query = Security::remove_XSS($query);
4143
4144
        if (!empty($query)) {
4145
            $form->addHeader(get_lang('Results and feedback').' "'.$query.'"');
4146
        }
4147
4148
        $form->addText(
4149
            'q',
4150
            get_lang('Users, Groups'),
4151
            false,
4152
            [
4153
                'id' => 'q',
4154
            ]
4155
        );
4156
        $options = [
4157
            0 => get_lang('Select'),
4158
            1 => get_lang('User'),
4159
            2 => get_lang('Group'),
4160
        ];
4161
        $form->addSelect(
4162
            'search_type',
4163
            get_lang('Type'),
4164
            $options,
4165
            ['onchange' => 'javascript: extra_field_toogle();', 'id' => 'search_type']
4166
        );
4167
4168
        // Extra fields
4169
        $extraFields = self::getExtraFilterableFields();
4170
        $defaults = [];
4171
        if (is_array($extraFields) && count($extraFields) > 0) {
4172
            foreach ($extraFields as $extraField) {
4173
                $varName = 'field_'.$extraField['variable'];
4174
                $options = [
4175
                    0 => get_lang('Select'),
4176
                ];
4177
                foreach ($extraField['data'] as $option) {
4178
                    if (isset($_GET[$varName])) {
4179
                        if ($_GET[$varName] == $option[1]) {
4180
                            $defaults[$option[1]] = true;
4181
                        }
4182
                    }
4183
4184
                    $options[$option[1]] = $option[1];
4185
                }
4186
                $form->addSelect($varName, $extraField['name'], $options);
4187
            }
4188
        }
4189
4190
        $defaults['search_type'] = (int) $searchType;
4191
        $defaults['q'] = $query;
4192
4193
        if (!empty($defaultParams)) {
4194
            $defaults = array_merge($defaults, $defaultParams);
4195
        }
4196
        $form->setDefaults($defaults);
4197
        $form->addButtonSearch(get_lang('Search'));
4198
4199
        $js = '<script>
4200
        extra_field_toogle();
4201
        function extra_field_toogle() {
4202
            if (jQuery("select[name=search_type]").val() != "1") {
4203
                jQuery(".extra_field").hide();
4204
            } else {
4205
                jQuery(".extra_field").show();
4206
            }
4207
        }
4208
        </script>';
4209
4210
        return $js.$form->returnForm();
4211
    }
4212
4213
    /**
4214
     * @param int $userId
4215
     *
4216
     * @return array
4217
     */
4218
    public static function getDrhListFromUser($userId)
4219
    {
4220
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
4221
        $tblUserRelUser = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4222
        $tblUserRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4223
        $userId = (int) $userId;
4224
4225
        $orderBy = null;
4226
        if (api_is_western_name_order()) {
4227
            $orderBy .= ' ORDER BY firstname, lastname ';
4228
        } else {
4229
            $orderBy .= ' ORDER BY lastname, firstname ';
4230
        }
4231
4232
        $sql = "SELECT u.id, username, u.firstname, u.lastname
4233
                FROM $tblUser u
4234
                INNER JOIN $tblUserRelUser uru ON (uru.friend_user_id = u.id)
4235
                INNER JOIN $tblUserRelAccessUrl a ON (a.user_id = u.id)
4236
                WHERE
4237
                    access_url_id = ".api_get_current_access_url_id()." AND
4238
                    uru.user_id = '$userId' AND
4239
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4240
                    $orderBy
4241
                ";
4242
        $result = Database::query($sql);
4243
4244
        return Database::store_result($result);
4245
    }
4246
4247
    /**
4248
     * get users followed by human resource manager.
4249
     *
4250
     * @param int    $userId
4251
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
4252
     * @param bool   $getOnlyUserId
4253
     * @param bool   $getSql
4254
     * @param bool   $getCount
4255
     * @param int    $from
4256
     * @param int    $numberItems
4257
     * @param int    $column
4258
     * @param string $direction
4259
     * @param int    $active
4260
     * @param string $lastConnectionDate
4261
     *
4262
     * @return array users
4263
     */
4264
    public static function get_users_followed_by_drh(
4265
        $userId,
4266
        $userStatus = 0,
4267
        $getOnlyUserId = false,
4268
        $getSql = false,
4269
        $getCount = false,
4270
        $from = null,
4271
        $numberItems = null,
4272
        $column = null,
4273
        $direction = null,
4274
        $active = null,
4275
        $lastConnectionDate = null
4276
    ) {
4277
        return self::getUsersFollowedByUser(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getUsersFol...astConnectionDate, DRH) also could return the type string which is incompatible with the documented return type array.
Loading history...
4278
            $userId,
4279
            $userStatus,
4280
            $getOnlyUserId,
4281
            $getSql,
4282
            $getCount,
4283
            $from,
4284
            $numberItems,
4285
            $column,
4286
            $direction,
4287
            $active,
4288
            $lastConnectionDate,
4289
            DRH
4290
        );
4291
    }
4292
4293
    /**
4294
     * Get users followed by human resource manager.
4295
     *
4296
     * @param int    $userId
4297
     * @param int    $userStatus             Filter users by status (STUDENT, COURSEMANAGER, etc)
4298
     * @param bool   $getOnlyUserId
4299
     * @param bool   $getSql
4300
     * @param bool   $getCount
4301
     * @param int    $from
4302
     * @param int    $numberItems
4303
     * @param int    $column
4304
     * @param string $direction
4305
     * @param int    $active
4306
     * @param string $lastConnectionDate
4307
     * @param int    $status                 the function is called by who? COURSEMANAGER, DRH?
4308
     * @param string $keyword
4309
     * @param bool   $checkSessionVisibility
4310
     *
4311
     * @return mixed Users list (array) or the SQL query if $getSQL was set to true
4312
     */
4313
    public static function getUsersFollowedByUser(
4314
        $userId,
4315
        $userStatus = null,
4316
        $getOnlyUserId = false,
4317
        $getSql = false,
4318
        $getCount = false,
4319
        $from = null,
4320
        $numberItems = null,
4321
        $column = null,
4322
        $direction = null,
4323
        $active = null,
4324
        $lastConnectionDate = null,
4325
        $status = null,
4326
        $keyword = null,
4327
        $checkSessionVisibility = false
4328
    ) {
4329
        // Database Table Definitions
4330
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
4331
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4332
        $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4333
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4334
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4335
        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4336
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4337
        $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
4338
4339
        $userId = (int) $userId;
4340
        $limitCondition = '';
4341
4342
        if (isset($from) && isset($numberItems)) {
4343
            $from = (int) $from;
4344
            $numberItems = (int) $numberItems;
4345
            $limitCondition = "LIMIT $from, $numberItems";
4346
        }
4347
4348
        $column = Database::escape_string($column);
4349
        $direction = in_array(strtolower($direction), ['asc', 'desc']) ? $direction : null;
4350
4351
        $userConditions = '';
4352
        if (!empty($userStatus)) {
4353
            $userConditions .= ' AND u.status = '.intval($userStatus);
4354
        }
4355
4356
        $select = " SELECT DISTINCT u.id user_id, u.username, u.lastname, u.firstname, u.email ";
4357
        if ($getOnlyUserId) {
4358
            $select = " SELECT DISTINCT u.id user_id";
4359
        }
4360
4361
        $masterSelect = "SELECT DISTINCT * FROM ";
4362
4363
        if ($getCount) {
4364
            $masterSelect = "SELECT COUNT(DISTINCT(user_id)) as count FROM ";
4365
            $select = " SELECT DISTINCT(u.id) user_id";
4366
        }
4367
4368
        if (!is_null($active)) {
4369
            $active = intval($active);
4370
            $userConditions .= " AND u.active = $active ";
4371
        }
4372
4373
        if (!empty($keyword)) {
4374
            $keyword = trim(Database::escape_string($keyword));
4375
            $keywordParts = array_filter(explode(' ', $keyword));
4376
            $extraKeyword = '';
4377
            if (!empty($keywordParts)) {
4378
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
4379
                if (!empty($keywordPartsFixed)) {
4380
                    $extraKeyword .= " OR
4381
                        CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keywordPartsFixed%' OR
4382
                        CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keywordPartsFixed%' ";
4383
                }
4384
            }
4385
            $userConditions .= " AND (
4386
                u.username LIKE '%$keyword%' OR
4387
                u.firstname LIKE '%$keyword%' OR
4388
                u.lastname LIKE '%$keyword%' OR
4389
                u.official_code LIKE '%$keyword%' OR
4390
                u.email LIKE '%$keyword%' OR
4391
                CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keyword%' OR
4392
                CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keyword%'
4393
                $extraKeyword
4394
            )";
4395
        }
4396
4397
        if (!empty($lastConnectionDate)) {
4398
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
4399
            $userConditions .= " AND u.last_login <= '$lastConnectionDate' ";
4400
        }
4401
4402
        $sessionConditionsCoach = null;
4403
        $dateCondition = '';
4404
        $drhConditions = null;
4405
        $teacherSelect = null;
4406
        $urlId = api_get_current_access_url_id();
4407
4408
        switch ($status) {
4409
            case DRH:
4410
                $drhConditions .= " AND
4411
                    friend_user_id = '$userId' AND
4412
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4413
                ";
4414
                break;
4415
            case COURSEMANAGER:
4416
                $drhConditions .= " AND
4417
                    friend_user_id = '$userId' AND
4418
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4419
                ";
4420
4421
                $sessionConditionsTeacher = " AND
4422
                    (scu.status = ".SessionEntity::COURSE_COACH." AND scu.user_id = '$userId')
4423
                ";
4424
4425
                if ($checkSessionVisibility) {
4426
                    $today = api_strtotime('now', 'UTC');
4427
                    $today = date('Y-m-d', $today);
4428
                    $dateCondition = "
4429
                        AND
4430
                        (
4431
                            (s.access_start_date <= '$today' AND '$today' <= s.access_end_date) OR
4432
                            (s.access_start_date IS NULL AND s.access_end_date IS NULL) OR
4433
                            (s.access_start_date <= '$today' AND s.access_end_date IS NULL) OR
4434
                            ('$today' <= s.access_end_date AND s.access_start_date IS NULL)
4435
                        )
4436
					";
4437
                }
4438
4439
                // Use $tbl_session_rel_course_rel_user instead of $tbl_session_rel_user
4440
                /*
4441
                INNER JOIN $tbl_session_rel_user sru
4442
                ON (sru.user_id = u.id)
4443
                INNER JOIN $tbl_session_rel_course_rel_user scu
4444
                ON (scu.user_id = u.id AND scu.c_id IS NOT NULL AND visibility = 1)*/
4445
                $teacherSelect =
4446
                "UNION ALL (
4447
                        $select
4448
                        FROM $tbl_user u
4449
                        INNER JOIN $tbl_session_rel_user sru ON (sru.user_id = u.id)
4450
                        WHERE
4451
                            (
4452
                                sru.session_id IN (
4453
                                    SELECT DISTINCT(s.id) FROM $tbl_session s INNER JOIN
4454
                                    $tbl_session_rel_access_url session_rel_access_rel_user
4455
                                    ON session_rel_access_rel_user.session_id = s.id
4456
                                    INNER JOIN $tbl_session_rel_user sru ON s.id = sru.session_id
4457
                                    WHERE access_url_id = ".$urlId."
4458
                                        AND (sru.relation_type = ".SessionEntity::GENERAL_COACH."
4459
                                        AND sru.user_id = $userId)
4460
                                ) OR sru.session_id IN (
4461
                                    SELECT DISTINCT(s.id) FROM $tbl_session s
4462
                                    INNER JOIN $tbl_session_rel_access_url url
4463
                                    ON (url.session_id = s.id)
4464
                                    INNER JOIN $tbl_session_rel_course_rel_user scu
4465
                                    ON (scu.session_id = s.id)
4466
                                    WHERE access_url_id = ".$urlId."
4467
                                    $sessionConditionsTeacher
4468
                                    $dateCondition
4469
                                )
4470
                            )
4471
                            $userConditions
4472
                    )
4473
                    UNION ALL(
4474
                        $select
4475
                        FROM $tbl_user u
4476
                        INNER JOIN $tbl_course_user cu ON (cu.user_id = u.id)
4477
                        WHERE cu.c_id IN (
4478
                            SELECT DISTINCT(c_id) FROM $tbl_course_user
4479
                            WHERE user_id = $userId AND status = ".COURSEMANAGER."
4480
                        )
4481
                        $userConditions
4482
                    )"
4483
                ;
4484
                break;
4485
            case STUDENT_BOSS:
4486
                $drhConditions = " AND friend_user_id = $userId AND relation_type = ".UserRelUser::USER_RELATION_TYPE_BOSS;
4487
                break;
4488
            case HRM_REQUEST:
4489
                $drhConditions .= " AND
4490
                    friend_user_id = '$userId' AND
4491
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_HRM_REQUEST."'
4492
                ";
4493
                break;
4494
        }
4495
4496
        $join = null;
4497
        $sql = " $masterSelect
4498
                (
4499
                    (
4500
                        $select
4501
                        FROM $tbl_user u
4502
                        INNER JOIN $tbl_user_rel_user uru ON (uru.user_id = u.id)
4503
                        LEFT JOIN $tbl_user_rel_access_url a ON (a.user_id = u.id)
4504
                        $join
4505
                        WHERE
4506
                            access_url_id = ".$urlId."
4507
                            $drhConditions
4508
                            $userConditions
4509
                    )
4510
                    $teacherSelect
4511
4512
                ) as t1";
4513
4514
        if ($getSql) {
4515
            return $sql;
4516
        }
4517
        if ($getCount) {
4518
            $result = Database::query($sql);
4519
            $row = Database::fetch_array($result);
4520
4521
            return $row['count'];
4522
        }
4523
4524
        $orderBy = null;
4525
        if (false == $getOnlyUserId) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
4526
            if (api_is_western_name_order()) {
4527
                $orderBy .= " ORDER BY firstname, lastname ";
4528
            } else {
4529
                $orderBy .= " ORDER BY lastname, firstname ";
4530
            }
4531
4532
            if (!empty($column) && !empty($direction)) {
4533
                // Fixing order due the UNIONs
4534
                $column = str_replace('u.', '', $column);
4535
                $orderBy = " ORDER BY `$column` $direction ";
4536
            }
4537
        }
4538
4539
        $sql .= $orderBy;
4540
        $sql .= $limitCondition;
4541
4542
        $result = Database::query($sql);
4543
        $users = [];
4544
        if (Database::num_rows($result) > 0) {
4545
            while ($row = Database::fetch_array($result)) {
4546
                $users[$row['user_id']] = $row;
4547
            }
4548
        }
4549
4550
        return $users;
4551
    }
4552
4553
    /**
4554
     * Subscribes users to human resource manager (Dashboard feature).
4555
     *
4556
     * @param int   $hr_dept_id
4557
     * @param array $users_id
4558
     * @param bool  $deleteOtherAssignedUsers
4559
     */
4560
    public static function subscribeUsersToHRManager(
4561
        $hr_dept_id,
4562
        $users_id,
4563
        $deleteOtherAssignedUsers = true
4564
    ): void {
4565
        self::subscribeUsersToUser(
4566
            $hr_dept_id,
4567
            $users_id,
4568
            UserRelUser::USER_RELATION_TYPE_RRHH,
4569
            false,
4570
            $deleteOtherAssignedUsers
4571
        );
4572
    }
4573
4574
    /**
4575
     * Register request to assign users to HRM.
4576
     *
4577
     * @param int   $hrmId   The HRM ID
4578
     * @param array $usersId The users IDs
4579
     */
4580
    public static function requestUsersToHRManager($hrmId, $usersId): void
4581
    {
4582
        self::subscribeUsersToUser(
4583
            $hrmId,
4584
            $usersId,
4585
            UserRelUser::USER_RELATION_TYPE_HRM_REQUEST,
4586
            false,
4587
            false
4588
        );
4589
    }
4590
4591
    /**
4592
     * Remove the requests for assign a user to a HRM.
4593
     *
4594
     * @param array $usersId List of user IDs from whom to remove all relations requests with HRM
4595
     */
4596
    public static function clearHrmRequestsForUser(User $hrmId, $usersId)
4597
    {
4598
        $users = implode(', ', $usersId);
4599
        Database::getManager()
4600
            ->createQuery('
4601
                DELETE FROM ChamiloCoreBundle:UserRelUser uru
4602
                WHERE uru.friendUserId = :hrm_id AND uru.relationType = :relation_type AND uru.userId IN (:users_ids)
4603
            ')
4604
            ->execute(['hrm_id' => $hrmId, 'relation_type' => UserRelUser::USER_RELATION_TYPE_HRM_REQUEST, 'users_ids' => $users]);
4605
    }
4606
4607
    /**
4608
     * Add subscribed users to a user by relation type.
4609
     *
4610
     * @param int   $userId                   The user id
4611
     * @param array $subscribedUsersId        The id of subscribed users
4612
     * @param int   $relationType             The relation type
4613
     * @param bool  $deleteUsersBeforeInsert
4614
     * @param bool  $deleteOtherAssignedUsers
4615
     */
4616
    public static function subscribeUsersToUser(
4617
        $userId,
4618
        $subscribedUsersId,
4619
        $relationType,
4620
        $deleteUsersBeforeInsert = false,
4621
        $deleteOtherAssignedUsers = true
4622
    ): void {
4623
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4624
        $userRelAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4625
4626
        $userId = (int) $userId;
4627
        $relationType = (int) $relationType;
4628
4629
        if ($deleteOtherAssignedUsers) {
4630
            if (api_get_multiple_access_url()) {
4631
                // Deleting assigned users to hrm_id
4632
                $sql = "SELECT s.user_id
4633
                        FROM $userRelUserTable s
4634
                        INNER JOIN $userRelAccessUrlTable a
4635
                        ON (a.user_id = s.user_id)
4636
                        WHERE
4637
                            friend_user_id = $userId AND
4638
                            relation_type = $relationType AND
4639
                            access_url_id = ".api_get_current_access_url_id();
4640
            } else {
4641
                $sql = "SELECT user_id
4642
                        FROM $userRelUserTable
4643
                        WHERE
4644
                            friend_user_id = $userId AND
4645
                            relation_type = $relationType";
4646
            }
4647
            $result = Database::query($sql);
4648
4649
            if (Database::num_rows($result) > 0) {
4650
                while ($row = Database::fetch_array($result)) {
4651
                    $sql = "DELETE FROM $userRelUserTable
4652
                            WHERE
4653
                                user_id = {$row['user_id']} AND
4654
                                friend_user_id = $userId AND
4655
                                relation_type = $relationType";
4656
                    Database::query($sql);
4657
                }
4658
            }
4659
        }
4660
4661
        if ($deleteUsersBeforeInsert) {
4662
            $sql = "DELETE FROM $userRelUserTable
4663
                    WHERE
4664
                        user_id = $userId AND
4665
                        relation_type = $relationType";
4666
            Database::query($sql);
4667
        }
4668
4669
        // Inserting new user list.
4670
        if (is_array($subscribedUsersId)) {
4671
            foreach ($subscribedUsersId as $subscribedUserId) {
4672
                $subscribedUserId = (int) $subscribedUserId;
4673
                $sql = "SELECT id
4674
                        FROM $userRelUserTable
4675
                        WHERE
4676
                            user_id = $subscribedUserId AND
4677
                            friend_user_id = $userId AND
4678
                            relation_type = $relationType";
4679
4680
                $result = Database::query($sql);
4681
                $num = Database::num_rows($result);
4682
                if (0 === $num) {
4683
                    $userRelUser = (new UserRelUser())
4684
                        ->setUser(api_get_user_entity($subscribedUserId))
4685
                        ->setFriend(api_get_user_entity($userId))
4686
                        ->setRelationType($relationType)
4687
                    ;
4688
                    $em = Database::getManager();
4689
                    $em->persist($userRelUser);
4690
                    $em->flush();
4691
                }
4692
            }
4693
        }
4694
    }
4695
4696
    /**
4697
     * This function checks if a user is followed by provided human resources managers.
4698
     *
4699
     * @param int $user_id
4700
     * @param int $hr_dept_id Human resources manager
4701
     *
4702
     * @return bool
4703
     * @throws Exception
4704
     * @throws \Doctrine\DBAL\Exception
4705
     */
4706
    public static function is_user_followed_by_drh(int $user_id, int $hr_dept_id): bool
4707
    {
4708
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4709
        $result = false;
4710
4711
        $sql = "SELECT user_id FROM $tbl_user_rel_user
4712
                WHERE
4713
                    user_id = $user_id AND
4714
                    friend_user_id = $hr_dept_id AND
4715
                    relation_type = ".UserRelUser::USER_RELATION_TYPE_RRHH;
4716
        $rs = Database::query($sql);
4717
        if (Database::num_rows($rs) > 0) {
4718
            $result = true;
4719
        }
4720
4721
        return $result;
4722
    }
4723
4724
    /**
4725
     * Return the user id of teacher or session administrator.
4726
     *
4727
     * @param array $courseInfo
4728
     *
4729
     * @return int The user id, or 0 if the session ID was negative
4730
     * @throws Exception
4731
     * @throws \Doctrine\DBAL\Exception
4732
     */
4733
    public static function get_user_id_of_course_admin_or_session_admin(array $courseInfo): int
4734
    {
4735
        $session = api_get_session_id();
4736
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
4737
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4738
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4739
4740
        if (empty($courseInfo)) {
4741
            return 0;
4742
        }
4743
4744
        $courseId = $courseInfo['real_id'];
4745
4746
        if (0 == $session) {
4747
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
4748
                    INNER JOIN '.$table_course_user.' ru
4749
                    ON ru.user_id = u.id
4750
                    WHERE
4751
                        ru.status = 1 AND
4752
                        ru.c_id = "'.$courseId.'" ';
4753
            $rs = Database::query($sql);
4754
            $num_rows = Database::num_rows($rs);
4755
            if (1 == $num_rows) {
4756
                $row = Database::fetch_array($rs);
4757
4758
                return (int) $row['uid'];
4759
            } else {
4760
                $my_num_rows = $num_rows;
4761
4762
                return (int) Database::result($rs, $my_num_rows - 1, 'uid');
4763
            }
4764
        } elseif ($session > 0) {
4765
            $sql = 'SELECT u.id as uid FROM '.$table_user.' u
4766
                    INNER JOIN '.$table_session_course_user.' sru
4767
                    ON sru.user_id = u.id
4768
                    WHERE
4769
                        sru.c_id = '.$courseId.' AND
4770
                        sru.status = '.SessionEntity::COURSE_COACH;
4771
            $rs = Database::query($sql);
4772
            if (Database::num_rows($rs) > 0) {
4773
                $row = Database::fetch_assoc($rs);
4774
4775
                return (int) $row['uid'];
4776
            }
4777
        }
4778
4779
        return 0;
4780
    }
4781
4782
    /**
4783
     * Determines if a user is a gradebook certified.
4784
     *
4785
     * @param int $cat_id  The category id of gradebook
4786
     * @param int $user_id The user id
4787
     *
4788
     * @return bool
4789
     */
4790
    public static function is_user_certified($cat_id, $user_id)
4791
    {
4792
        $cat_id = (int) $cat_id;
4793
        $user_id = (int) $user_id;
4794
4795
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
4796
        $sql = 'SELECT path_certificate
4797
                FROM '.$table.'
4798
                WHERE
4799
                    cat_id = "'.$cat_id.'" AND
4800
                    user_id = "'.$user_id.'"';
4801
        $rs = Database::query($sql);
4802
        $row = Database::fetch_array($rs);
4803
4804
        if (!isset($row['path_certificate']) || '' == $row['path_certificate'] || is_null($row['path_certificate'])) {
4805
            return false;
4806
        }
4807
4808
        return true;
4809
    }
4810
4811
    /**
4812
     * Gets the info about a gradebook certificate for a user by course.
4813
     *
4814
     * @param array $course_info The course code
4815
     * @param int   $session_id
4816
     * @param int   $user_id     The user id
4817
     *
4818
     * @return array if there is not information return false
4819
     */
4820
    public static function get_info_gradebook_certificate($course_info, $session_id, $user_id)
4821
    {
4822
        $tbl_grade_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
4823
        $tbl_grade_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
4824
        $session_id = (int) $session_id;
4825
        $user_id = (int) $user_id;
4826
        $courseId = $course_info['real_id'];
4827
4828
        if (empty($session_id)) {
4829
            $session_condition = ' AND (session_id = "" OR session_id = 0 OR session_id IS NULL )';
4830
        } else {
4831
            $session_condition = " AND session_id = $session_id";
4832
        }
4833
4834
        $sql = 'SELECT * FROM '.$tbl_grade_certificate.'
4835
                WHERE cat_id = (
4836
                    SELECT id FROM '.$tbl_grade_category.'
4837
                    WHERE
4838
                        c_id = "'.$courseId.'" '.$session_condition.'
4839
                    LIMIT 1
4840
                ) AND user_id='.$user_id;
4841
4842
        $rs = Database::query($sql);
4843
        if (Database::num_rows($rs) > 0) {
4844
            $row = Database::fetch_assoc($rs);
4845
            $score = $row['score_certificate'];
4846
            $category_id = $row['cat_id'];
4847
            $cat = Category::load($category_id);
4848
            $displayscore = ScoreDisplay::instance();
4849
            if (isset($cat) && $displayscore->is_custom()) {
4850
                $grade = $displayscore->display_score(
4851
                    [$score, $cat[0]->get_weight()],
4852
                    SCORE_DIV_PERCENT_WITH_CUSTOM
4853
                );
4854
            } else {
4855
                $grade = $displayscore->display_score(
4856
                    [$score, $cat[0]->get_weight()]
4857
                );
4858
            }
4859
            $row['grade'] = $grade;
4860
4861
            return $row;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $row also could return the type boolean which is incompatible with the documented return type array.
Loading history...
4862
        }
4863
4864
        return false;
4865
    }
4866
4867
    /**
4868
     * This function check if the user is a coach inside session course.
4869
     *
4870
     * @param int $user_id    User id
4871
     * @param int $courseId
4872
     * @param int $session_id
4873
     *
4874
     * @return bool True if the user is a coach
4875
     */
4876
    public static function is_session_course_coach($user_id, $courseId, $session_id)
4877
    {
4878
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4879
        // Protect data
4880
        $user_id = intval($user_id);
4881
        $courseId = intval($courseId);
4882
        $session_id = intval($session_id);
4883
        $result = false;
4884
4885
        $sql = "SELECT session_id FROM $table
4886
                WHERE
4887
                  session_id = $session_id AND
4888
                  c_id = $courseId AND
4889
                  user_id = $user_id AND
4890
                  status = ".SessionEntity::COURSE_COACH;
4891
        $res = Database::query($sql);
4892
4893
        if (Database::num_rows($res) > 0) {
4894
            $result = true;
4895
        }
4896
4897
        return $result;
4898
    }
4899
4900
    /**
4901
     * This function returns an icon path that represents the favicon of the website of which the url given.
4902
     * Defaults to the current Chamilo favicon.
4903
     *
4904
     * @param string $url1 URL of website where to look for favicon.ico
4905
     * @param string $url2 Optional second URL of website where to look for favicon.ico
4906
     *
4907
     * @return string Path of icon to load
4908
     */
4909
    public static function get_favicon_from_url($url1, $url2 = null)
4910
    {
4911
        $icon_link = '';
4912
        $url = $url1;
4913
        if (empty($url1)) {
4914
            $url = $url2;
4915
            if (empty($url)) {
4916
                $url = api_get_access_url(api_get_current_access_url_id());
4917
                $url = $url[0];
4918
            }
4919
        }
4920
        if (!empty($url)) {
4921
            $pieces = parse_url($url);
4922
            $icon_link = $pieces['scheme'].'://'.$pieces['host'].'/favicon.ico';
4923
        }
4924
4925
        return $icon_link;
4926
    }
4927
4928
    public static function addUserAsAdmin(User $user)
4929
    {
4930
        $userId = $user->getId();
4931
4932
        if (!self::is_admin($userId)) {
4933
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
4934
            $sql = "INSERT INTO $table SET user_id = $userId";
4935
            Database::query($sql);
4936
        }
4937
4938
        $user->addRole('ROLE_ADMIN');
4939
        self::getRepository()->updateUser($user, true);
4940
    }
4941
4942
    public static function removeUserAdmin(User $user)
4943
    {
4944
        $userId = (int) $user->getId();
4945
        if (self::is_admin($userId)) {
4946
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
4947
            $sql = "DELETE FROM $table WHERE user_id = $userId";
4948
            Database::query($sql);
4949
            $user->removeRole('ROLE_ADMIN');
4950
            self::getRepository()->updateUser($user, true);
4951
        }
4952
    }
4953
4954
    /**
4955
     * @param string $from
4956
     * @param string $to
4957
     */
4958
    public static function update_all_user_languages($from, $to)
4959
    {
4960
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
4961
        $from = Database::escape_string($from);
4962
        $to = Database::escape_string($to);
4963
4964
        if (!empty($to) && !empty($from)) {
4965
            $sql = "UPDATE $table_user SET language = '$to'
4966
                    WHERE language = '$from'";
4967
            Database::query($sql);
4968
        }
4969
    }
4970
4971
    /**
4972
     * Subscribe boss to students.
4973
     *
4974
     * @param int   $bossId                   The boss id
4975
     * @param array $usersId                  The users array
4976
     * @param bool  $deleteOtherAssignedUsers
4977
     */
4978
    public static function subscribeBossToUsers($bossId, $usersId, $deleteOtherAssignedUsers = true): void
4979
    {
4980
        self::subscribeUsersToUser(
4981
            $bossId,
4982
            $usersId,
4983
            UserRelUser::USER_RELATION_TYPE_BOSS,
4984
            false,
4985
            $deleteOtherAssignedUsers
4986
        );
4987
    }
4988
4989
    /**
4990
     * @param int $userId
4991
     *
4992
     * @return bool
4993
     */
4994
    public static function removeAllBossFromStudent($userId)
4995
    {
4996
        $userId = (int) $userId;
4997
4998
        if (empty($userId)) {
4999
            return false;
5000
        }
5001
5002
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5003
        $sql = "DELETE FROM $userRelUserTable
5004
                WHERE user_id = $userId AND relation_type = ".UserRelUser::USER_RELATION_TYPE_BOSS;
5005
        Database::query($sql);
5006
5007
        return true;
5008
    }
5009
5010
    /**
5011
     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
5012
     *
5013
     * @param int   $studentId
5014
     * @param array $bossList
5015
     * @param bool  $sendNotification
5016
     *
5017
     * @return mixed Affected rows or false on failure
5018
     */
5019
    public static function subscribeUserToBossList(
5020
        $studentId,
5021
        $bossList,
5022
        $sendNotification = false
5023
    ) {
5024
        $inserted = 0;
5025
        if (!empty($bossList)) {
5026
            sort($bossList);
5027
            $studentId = (int) $studentId;
5028
            $studentInfo = api_get_user_info($studentId);
5029
5030
            if (empty($studentInfo)) {
5031
                return false;
5032
            }
5033
5034
            $previousBossList = self::getStudentBossList($studentId);
5035
            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
5036
            sort($previousBossList);
5037
5038
            // Boss list is the same, nothing changed.
5039
            if ($bossList == $previousBossList) {
5040
                return false;
5041
            }
5042
5043
            $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5044
            self::removeAllBossFromStudent($studentId);
5045
5046
            foreach ($bossList as $bossId) {
5047
                $bossId = (int) $bossId;
5048
                $bossInfo = api_get_user_info($bossId);
5049
5050
                if (empty($bossInfo)) {
5051
                    continue;
5052
                }
5053
5054
                $bossLanguage = $bossInfo['locale'];
5055
                $sql = "INSERT IGNORE INTO $userRelUserTable (user_id, friend_user_id, relation_type)
5056
                        VALUES ($studentId, $bossId, ".UserRelUser::USER_RELATION_TYPE_BOSS.")";
5057
                $insertId = Database::query($sql);
5058
5059
                if ($insertId) {
5060
                    if ($sendNotification) {
5061
                        $name = $studentInfo['complete_name'];
5062
                        $url = api_get_path(WEB_CODE_PATH).'my_space/myStudents.php?student='.$studentId;
5063
                        $url = Display::url($url, $url);
5064
                        $subject = sprintf(get_lang('You have been assigned the learner %s', $bossLanguage), $name);
5065
                        $message = sprintf(get_lang('You have been assigned the learner %s with url %s', $bossLanguage), $name, $url);
5066
                        MessageManager::send_message_simple(
5067
                            $bossId,
5068
                            $subject,
5069
                            $message
5070
                        );
5071
                    }
5072
                    $inserted++;
5073
                }
5074
            }
5075
        } else {
5076
            self::removeAllBossFromStudent($studentId);
5077
        }
5078
5079
        return $inserted;
5080
    }
5081
5082
    /**
5083
     * Get users followed by student boss.
5084
     *
5085
     * @param int    $userId
5086
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
5087
     * @param bool   $getOnlyUserId
5088
     * @param bool   $getSql
5089
     * @param bool   $getCount
5090
     * @param int    $from
5091
     * @param int    $numberItems
5092
     * @param int    $column
5093
     * @param string $direction
5094
     * @param int    $active
5095
     * @param string $lastConnectionDate
5096
     *
5097
     * @return array users
5098
     */
5099
    public static function getUsersFollowedByStudentBoss(
5100
        $userId,
5101
        $userStatus = 0,
5102
        $getOnlyUserId = false,
5103
        $getSql = false,
5104
        $getCount = false,
5105
        $from = null,
5106
        $numberItems = null,
5107
        $column = null,
5108
        $direction = null,
5109
        $active = null,
5110
        $lastConnectionDate = null
5111
    ) {
5112
        return self::getUsersFollowedByUser(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getUsersFol...tionDate, STUDENT_BOSS) also could return the type string which is incompatible with the documented return type array.
Loading history...
5113
            $userId,
5114
            $userStatus,
5115
            $getOnlyUserId,
5116
            $getSql,
5117
            $getCount,
5118
            $from,
5119
            $numberItems,
5120
            $column,
5121
            $direction,
5122
            $active,
5123
            $lastConnectionDate,
5124
            STUDENT_BOSS
5125
        );
5126
    }
5127
5128
    /**
5129
     * @return array
5130
     */
5131
    public static function getOfficialCodeGrouped()
5132
    {
5133
        $user = Database::get_main_table(TABLE_MAIN_USER);
5134
        $sql = "SELECT DISTINCT official_code
5135
                FROM $user
5136
                GROUP BY official_code";
5137
        $result = Database::query($sql);
5138
        $values = Database::store_result($result, 'ASSOC');
5139
        $result = [];
5140
        foreach ($values as $value) {
5141
            $result[$value['official_code']] = $value['official_code'];
5142
        }
5143
5144
        return $result;
5145
    }
5146
5147
    /**
5148
     * @param string $officialCode
5149
     *
5150
     * @return array
5151
     */
5152
    public static function getUsersByOfficialCode($officialCode)
5153
    {
5154
        $user = Database::get_main_table(TABLE_MAIN_USER);
5155
        $officialCode = Database::escape_string($officialCode);
5156
5157
        $sql = "SELECT DISTINCT id
5158
                FROM $user
5159
                WHERE official_code = '$officialCode'
5160
                ";
5161
        $result = Database::query($sql);
5162
5163
        $users = [];
5164
        while ($row = Database::fetch_array($result)) {
5165
            $users[] = $row['id'];
5166
        }
5167
5168
        return $users;
5169
    }
5170
5171
    /**
5172
     * Calc the expended time (in seconds) by a user in a course.
5173
     *
5174
     * @param int    $userId    The user id
5175
     * @param int    $courseId  The course id
5176
     * @param int    $sessionId Optional. The session id
5177
     * @param string $from      Optional. From date
5178
     * @param string $until     Optional. Until date
5179
     *
5180
     * @return int The time
5181
     */
5182
    public static function getTimeSpentInCourses(
5183
        $userId,
5184
        $courseId,
5185
        $sessionId = 0,
5186
        $from = '',
5187
        $until = ''
5188
    ) {
5189
        $userId = (int) $userId;
5190
        $sessionId = (int) $sessionId;
5191
5192
        $trackCourseAccessTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5193
        $whereConditions = [
5194
            'user_id = ? ' => $userId,
5195
            'AND c_id = ? ' => $courseId,
5196
            'AND session_id = ? ' => $sessionId,
5197
        ];
5198
5199
        if (!empty($from) && !empty($until)) {
5200
            $whereConditions["AND (login_course_date >= '?' "] = $from;
5201
            $whereConditions["AND logout_course_date <= DATE_ADD('?', INTERVAL 1 DAY)) "] = $until;
5202
        }
5203
5204
        $trackResult = Database::select(
5205
            'SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as total_time',
5206
            $trackCourseAccessTable,
5207
            [
5208
                'where' => $whereConditions,
5209
            ],
5210
            'first'
5211
        );
5212
5213
        if (false != $trackResult) {
5214
            return $trackResult['total_time'] ? $trackResult['total_time'] : 0;
5215
        }
5216
5217
        return 0;
5218
    }
5219
5220
    /**
5221
     * Get the boss user ID from a followed user id.
5222
     *
5223
     * @param $userId
5224
     *
5225
     * @return bool
5226
     */
5227
    public static function getFirstStudentBoss($userId)
5228
    {
5229
        $userId = (int) $userId;
5230
        if ($userId > 0) {
5231
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5232
            $row = Database::select(
5233
                'DISTINCT friend_user_id AS boss_id',
5234
                $userRelTable,
5235
                [
5236
                    'where' => [
5237
                        'user_id = ? AND relation_type = ? LIMIT 1' => [
5238
                            $userId,
5239
                            UserRelUser::USER_RELATION_TYPE_BOSS,
5240
                        ],
5241
                    ],
5242
                ]
5243
            );
5244
            if (!empty($row)) {
5245
                return $row[0]['boss_id'];
5246
            }
5247
        }
5248
5249
        return false;
5250
    }
5251
5252
    /**
5253
     * Get the boss user ID from a followed user id.
5254
     *
5255
     * @param int $userId student id
5256
     *
5257
     * @return array
5258
     */
5259
    public static function getStudentBossList($userId)
5260
    {
5261
        $userId = (int) $userId;
5262
5263
        if ($userId > 0) {
5264
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5265
5266
            return Database::select(
5267
                'DISTINCT friend_user_id AS boss_id',
5268
                $userRelTable,
5269
                [
5270
                    'where' => [
5271
                        'user_id = ? AND relation_type = ? ' => [
5272
                            $userId,
5273
                            UserRelUser::USER_RELATION_TYPE_BOSS,
5274
                        ],
5275
                    ],
5276
                ]
5277
            );
5278
        }
5279
5280
        return [];
5281
    }
5282
5283
    /**
5284
     * @param int $bossId
5285
     * @param int $studentId
5286
     *
5287
     * @return bool
5288
     */
5289
    public static function userIsBossOfStudent($bossId, $studentId)
5290
    {
5291
        $result = false;
5292
        $bossList = self::getStudentBossList($studentId);
5293
        if (!empty($bossList)) {
5294
            $bossList = array_column($bossList, 'boss_id');
5295
            if (in_array($bossId, $bossList)) {
5296
                $result = true;
5297
            }
5298
        }
5299
5300
        return $result;
5301
    }
5302
5303
    /**
5304
     * Displays the name of the user and makes the link to the user profile.
5305
     *
5306
     * @param array $userInfo
5307
     *
5308
     * @return string
5309
     */
5310
    public static function getUserProfileLink($userInfo)
5311
    {
5312
        if (isset($userInfo) && isset($userInfo['user_id'])) {
5313
            return Display::url(
5314
                $userInfo['complete_name_with_username'],
5315
                $userInfo['profile_url']
5316
            );
5317
        }
5318
5319
        return get_lang('Anonymous');
5320
    }
5321
5322
    /**
5323
     * Get users whose name matches $firstname and $lastname.
5324
     *
5325
     * @param string $firstname Firstname to search
5326
     * @param string $lastname  Lastname to search
5327
     *
5328
     * @return array The user list
5329
     */
5330
    public static function getUsersByName($firstname, $lastname)
5331
    {
5332
        $firstname = Database::escape_string($firstname);
5333
        $lastname = Database::escape_string($lastname);
5334
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
5335
5336
        $sql = <<<SQL
5337
            SELECT id, username, lastname, firstname
5338
            FROM $userTable
5339
            WHERE
5340
                firstname LIKE '$firstname%' AND
5341
                lastname LIKE '$lastname%'
5342
SQL;
5343
        $result = Database::query($sql);
5344
        $users = [];
5345
        while ($resultData = Database::fetch_object($result)) {
5346
            $users[] = $resultData;
5347
        }
5348
5349
        return $users;
5350
    }
5351
5352
    /**
5353
     * @param int $optionSelected
5354
     *
5355
     * @return string
5356
     */
5357
    public static function getUserSubscriptionTab($optionSelected = 1)
5358
    {
5359
        $allowAdmin = api_get_setting('allow_user_course_subscription_by_course_admin');
5360
        if (('true' === $allowAdmin && api_is_allowed_to_edit()) ||
5361
            api_is_platform_admin()
5362
        ) {
5363
            $userPath = api_get_path(WEB_CODE_PATH).'user/';
5364
5365
            $headers = [
5366
                [
5367
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.STUDENT,
5368
                    'content' => get_lang('Learners'),
5369
                ],
5370
                [
5371
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.COURSEMANAGER,
5372
                    'content' => get_lang('Trainers'),
5373
                ],
5374
                /*[
5375
                    'url' => $userPath.'subscribe_user.php?'.api_get_cidreq(),
5376
                    'content' => get_lang('Learners'),
5377
                ],
5378
                [
5379
                    'url' => $userPath.'subscribe_user.php?type=teacher&'.api_get_cidreq(),
5380
                    'content' => get_lang('Trainers'),
5381
                ],*/
5382
                [
5383
                    'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
5384
                    'content' => get_lang('Groups'),
5385
                ],
5386
                [
5387
                    'url' => $userPath.'class.php?'.api_get_cidreq(),
5388
                    'content' => get_lang('Classes'),
5389
                ],
5390
            ];
5391
5392
            return Display::tabsOnlyLink($headers, $optionSelected);
5393
        }
5394
5395
        return '';
5396
    }
5397
5398
    /**
5399
     * Make sure this function is protected because it does NOT check password!
5400
     *
5401
     * This function defines globals.
5402
     *
5403
     * @param int  $userId
5404
     * @param bool $checkIfUserCanLoginAs
5405
     *
5406
     * @return bool
5407
     *
5408
     * @author Evie Embrechts
5409
     * @author Yannick Warnier <[email protected]>
5410
     */
5411
    public static function loginAsUser($userId, $checkIfUserCanLoginAs = true)
5412
    {
5413
        $userId = (int) $userId;
5414
        $userInfo = api_get_user_info($userId);
5415
5416
        // Check if the user is allowed to 'login_as'
5417
        $canLoginAs = true;
5418
        if ($checkIfUserCanLoginAs) {
5419
            $canLoginAs = api_can_login_as($userId);
5420
        }
5421
5422
        if (!$canLoginAs || empty($userInfo)) {
5423
            return false;
5424
        }
5425
5426
        if ($userId) {
5427
            $logInfo = [
5428
                'tool' => 'logout',
5429
                'tool_id' => 0,
5430
                'tool_id_detail' => 0,
5431
                'action' => '',
5432
                'info' => 'Change user (login as)',
5433
            ];
5434
            Event::registerLog($logInfo);
0 ignored issues
show
Bug introduced by
The method registerLog() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

5434
            Event::/** @scrutinizer ignore-call */ 
5435
                   registerLog($logInfo);

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...
5435
5436
            // Logout the current user
5437
            self::loginDelete(api_get_user_id());
5438
5439
            return true;
5440
5441
            Session::erase('_user');
0 ignored issues
show
Unused Code introduced by
ChamiloSession::erase('_user') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
5442
            Session::erase('is_platformAdmin');
5443
            Session::erase('is_allowedCreateCourse');
5444
            Session::erase('_uid');
5445
5446
            // Cleaning session variables
5447
            $_user['firstName'] = $userInfo['firstname'];
5448
            $_user['lastName'] = $userInfo['lastname'];
5449
            $_user['mail'] = $userInfo['email'];
5450
            $_user['official_code'] = $userInfo['official_code'];
5451
            $_user['picture_uri'] = $userInfo['picture_uri'];
5452
            $_user['user_id'] = $userId;
5453
            $_user['id'] = $userId;
5454
            $_user['status'] = $userInfo['status'];
5455
5456
            // Filling session variables with new data
5457
            Session::write('_uid', $userId);
5458
            Session::write('_user', $userInfo);
5459
            Session::write('is_platformAdmin', (bool) self::is_admin($userId));
5460
            Session::write('is_allowedCreateCourse', 1 == $userInfo['status']);
5461
            // will be useful later to know if the user is actually an admin or not (example reporting)
5462
            Session::write('login_as', true);
5463
            $logInfo = [
5464
                'tool' => 'login',
5465
                'tool_id' => 0,
5466
                'tool_id_detail' => 0,
5467
                'info' => $userId,
5468
            ];
5469
            Event::registerLog($logInfo);
5470
5471
            return true;
5472
        }
5473
5474
        return false;
5475
    }
5476
5477
    /**
5478
     * Remove all login records from the track_e_online stats table,
5479
     * for the given user ID.
5480
     *
5481
     * @param int $userId User ID
5482
     */
5483
    public static function loginDelete($userId)
5484
    {
5485
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
5486
        $userId = (int) $userId;
5487
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
5488
        Database::query($query);
5489
    }
5490
5491
    /**
5492
     * Login as first admin user registered in the platform.
5493
     *
5494
     * @return array
5495
     */
5496
    public static function logInAsFirstAdmin()
5497
    {
5498
        $adminList = self::get_all_administrators();
5499
5500
        if (!empty($adminList)) {
5501
            $userInfo = current($adminList);
5502
            if (!empty($userInfo)) {
5503
                $result = self::loginAsUser($userInfo['user_id'], false);
5504
                if ($result && api_is_platform_admin()) {
5505
                    return api_get_user_info();
0 ignored issues
show
Bug Best Practice introduced by
The expression return api_get_user_info() could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
5506
                }
5507
            }
5508
        }
5509
5510
        return [];
5511
    }
5512
5513
    /**
5514
     * Check if user is teacher of a student based in their courses.
5515
     *
5516
     * @param $teacherId
5517
     * @param $studentId
5518
     *
5519
     * @return array
5520
     */
5521
    public static function getCommonCoursesBetweenTeacherAndStudent($teacherId, $studentId)
5522
    {
5523
        $courses = CourseManager::getCoursesFollowedByUser(
5524
            $teacherId,
5525
            COURSEMANAGER
5526
        );
5527
        if (empty($courses)) {
5528
            return false;
5529
        }
5530
5531
        $coursesFromUser = CourseManager::get_courses_list_by_user_id($studentId);
5532
        if (empty($coursesFromUser)) {
5533
            return false;
5534
        }
5535
5536
        $coursesCodeList = array_column($courses, 'code');
5537
        $coursesCodeFromUserList = array_column($coursesFromUser, 'code');
5538
        $commonCourses = array_intersect($coursesCodeList, $coursesCodeFromUserList);
5539
        $commonCourses = array_filter($commonCourses);
5540
5541
        if (!empty($commonCourses)) {
5542
            return $commonCourses;
5543
        }
5544
5545
        return [];
5546
    }
5547
5548
    /**
5549
     * @param int $teacherId
5550
     * @param int $studentId
5551
     *
5552
     * @return bool
5553
     */
5554
    public static function isTeacherOfStudent($teacherId, $studentId)
5555
    {
5556
        $courses = self::getCommonCoursesBetweenTeacherAndStudent(
5557
            $teacherId,
5558
            $studentId
5559
        );
5560
5561
        if (!empty($courses)) {
5562
            return true;
5563
        }
5564
5565
        return false;
5566
    }
5567
5568
    /**
5569
     * Send user confirmation mail.
5570
     *
5571
     * @throws Exception
5572
     */
5573
    public static function sendUserConfirmationMail(User $user)
5574
    {
5575
        $uniqueId = api_get_unique_id();
5576
        $user->setConfirmationToken($uniqueId);
5577
5578
        Database::getManager()->persist($user);
5579
        Database::getManager()->flush();
5580
5581
        $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
5582
5583
        // Check if the user was originally set for an automated subscription to a course or session
5584
        $courseCodeToRedirect = Session::read('course_redirect');
5585
        $sessionToRedirect = Session::read('session_redirect');
5586
        if (!empty($courseCodeToRedirect)) {
5587
            $url .= '&c='.$courseCodeToRedirect;
5588
        }
5589
        if (!empty($sessionToRedirect)) {
5590
            $url .= '&s='.$sessionToRedirect;
5591
        }
5592
        $mailSubject = get_lang('Registration confirmation');
5593
        $mailBody = get_lang('Registration confirmationEmailMessage')
5594
            .PHP_EOL
5595
            .Display::url($url, $url);
5596
5597
        api_mail_html(
5598
            self::formatUserFullName($user),
5599
            $user->getEmail(),
5600
            $mailSubject,
5601
            $mailBody
5602
        );
5603
        Display::addFlash(Display::return_message(get_lang('Check your e-mail and follow the instructions.')));
5604
    }
5605
5606
    /**
5607
     * Anonymize a user. Replace personal info by anonymous info.
5608
     *
5609
     * @param int  $userId   User id
5610
     * @param bool $deleteIP Whether to replace the IP address in logs tables by 127.0.0.1 or to leave as is
5611
     *
5612
     * @throws \Exception
5613
     *
5614
     * @return bool
5615
     * @assert (0) === false
5616
     */
5617
    public static function anonymize($userId, $deleteIP = true)
5618
    {
5619
        global $debug;
5620
5621
        $userId = (int) $userId;
5622
5623
        if (empty($userId)) {
5624
            return false;
5625
        }
5626
5627
        $em = Database::getManager();
5628
        $user = api_get_user_entity($userId);
5629
        $uniqueId = uniqid('anon', true);
5630
        $user
5631
            ->setFirstname($uniqueId)
5632
            ->setLastname($uniqueId)
5633
            ->setBiography('')
5634
            ->setAddress('')
5635
            //->setCurriculumItems(null)
5636
            ->setDateOfBirth(null)
5637
            ->setCompetences('')
5638
            ->setDiplomas('')
5639
            ->setOpenarea('')
5640
            ->setTeach('')
5641
            ->setProductions(null)
5642
            ->setOpenid('')
5643
            ->setEmailCanonical($uniqueId.'@example.com')
5644
            ->setEmail($uniqueId.'@example.com')
5645
            ->setUsername($uniqueId)
5646
            ->setUsernameCanonical($uniqueId)
5647
            ->setPhone('')
5648
            ->setOfficialCode('')
5649
        ;
5650
5651
        self::deleteUserPicture($userId);
5652
        self::cleanUserRequestsOfRemoval($userId);
5653
5654
        // The IP address is a border-case personal data, as it does
5655
        // not directly allow for personal identification (it is not
5656
        // a completely safe value in most countries - the IP could
5657
        // be used by neighbours and crackers)
5658
        if ($deleteIP) {
5659
            $substitute = '127.0.0.1';
5660
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
5661
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE access_user_id = $userId";
5662
            $res = Database::query($sql);
5663
            if (false === $res && $debug > 0) {
5664
                error_log("Could not anonymize IP address for user $userId ($sql)");
5665
            }
5666
5667
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5668
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5669
            $res = Database::query($sql);
5670
            if (false === $res && $debug > 0) {
5671
                error_log("Could not anonymize IP address for user $userId ($sql)");
5672
            }
5673
5674
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5675
            $sql = "UPDATE $table SET user_ip = '$substitute' WHERE exe_user_id = $userId";
5676
            $res = Database::query($sql);
5677
            if (false === $res && $debug > 0) {
5678
                error_log("Could not anonymize IP address for user $userId ($sql)");
5679
            }
5680
5681
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
5682
            $sql = "UPDATE $table SET user_ip = '$substitute' WHERE login_user_id = $userId";
5683
            $res = Database::query($sql);
5684
            if (false === $res && $debug > 0) {
5685
                error_log("Could not anonymize IP address for user $userId ($sql)");
5686
            }
5687
5688
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
5689
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
5690
            $res = Database::query($sql);
5691
            if (false === $res && $debug > 0) {
5692
                error_log("Could not anonymize IP address for user $userId ($sql)");
5693
            }
5694
5695
            $table = Database::get_course_table(TABLE_WIKI);
5696
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5697
            $res = Database::query($sql);
5698
            if (false === $res && $debug > 0) {
5699
                error_log("Could not anonymize IP address for user $userId ($sql)");
5700
            }
5701
5702
            $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
5703
            $sql = "UPDATE $table set ip_address = '$substitute' WHERE sys_insert_user_id = $userId";
5704
            $res = Database::query($sql);
5705
            if (false === $res && $debug > 0) {
5706
                error_log("Could not anonymize IP address for user $userId ($sql)");
5707
            }
5708
5709
            $table = Database::get_course_table(TABLE_WIKI);
5710
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5711
            $res = Database::query($sql);
5712
            if (false === $res && $debug > 0) {
5713
                error_log("Could not anonymize IP address for user $userId ($sql)");
5714
            }
5715
        }
5716
5717
        $extraFieldRepository = $em->getRepository(EntityExtraField::class);
5718
        $autoRemoveFields = $extraFieldRepository->findBy([
5719
            'autoRemove' => 1,
5720
            'itemType' => EntityExtraField::USER_FIELD_TYPE
5721
        ]);
5722
5723
        foreach ($autoRemoveFields as $field) {
5724
            $extraFieldValueRepository = $em->getRepository(EntityExtraFieldValues::class);
5725
            $extraFieldValue = $extraFieldValueRepository->findOneBy([
5726
                'field' => $field,
5727
                'itemId' => $userId
5728
            ]);
5729
5730
            if ($extraFieldValue) {
5731
                $em->remove($extraFieldValue);
5732
            }
5733
        }
5734
5735
        $em->persist($user);
5736
        $em->flush();
5737
        Event::addEvent(LOG_USER_ANONYMIZE, LOG_USER_ID, $userId);
5738
5739
        return true;
5740
    }
5741
5742
    /**
5743
     * @param int $userId
5744
     *
5745
     * @throws Exception
5746
     *
5747
     * @return string
5748
     */
5749
    public static function anonymizeUserWithVerification($userId)
5750
    {
5751
        $allowDelete = ('true' === api_get_setting('session.allow_delete_user_for_session_admin'));
5752
5753
        $message = '';
5754
        if (api_is_platform_admin() ||
5755
            ($allowDelete && api_is_session_admin())
5756
        ) {
5757
            $userToUpdateInfo = api_get_user_info($userId);
5758
            $currentUserId = api_get_user_id();
5759
5760
            if ($userToUpdateInfo &&
5761
                api_global_admin_can_edit_admin($userId, null, $allowDelete)
5762
            ) {
5763
                if ($userId != $currentUserId &&
5764
                    self::anonymize($userId)
5765
                ) {
5766
                    $message = Display::return_message(
5767
                        sprintf(get_lang('User %s information anonymized.'), $userToUpdateInfo['complete_name_with_username']),
5768
                        'confirmation'
5769
                    );
5770
                } else {
5771
                    $message = Display::return_message(
5772
                        sprintf(get_lang('We could not anonymize user %s information. Please try again or check the logs.'), $userToUpdateInfo['complete_name_with_username']),
5773
                        'error'
5774
                    );
5775
                }
5776
            } else {
5777
                $message = Display::return_message(
5778
                    sprintf(get_lang('You don\'t have permissions to anonymize user %s. You need the same permissions as to delete users.'), $userToUpdateInfo['complete_name_with_username']),
5779
                    'error'
5780
                );
5781
            }
5782
        }
5783
5784
        return $message;
5785
    }
5786
5787
    /**
5788
     * @param int $userId
5789
     *
5790
     * @throws Exception
5791
     *
5792
     * @return string
5793
     */
5794
    public static function deleteUserWithVerification($userId, bool $destroy = false)
5795
    {
5796
        $allowDelete = ('true' === api_get_setting('session.allow_delete_user_for_session_admin'));
5797
        $message = Display::return_message(get_lang('You cannot delete this user'), 'error');
5798
        $userToUpdateInfo = api_get_user_info($userId);
5799
5800
        // User must exist.
5801
        if (empty($userToUpdateInfo)) {
5802
            return $message;
5803
        }
5804
5805
        $currentUserId = api_get_user_id();
5806
5807
        // Cannot delete myself.
5808
        if ($userId == $currentUserId) {
5809
            return $message;
5810
        }
5811
5812
        if (api_is_platform_admin() ||
5813
            ($allowDelete && api_is_session_admin())
5814
        ) {
5815
            if (api_global_admin_can_edit_admin($userId, null, $allowDelete)) {
5816
                if (self::delete_user($userId, $destroy)) {
5817
                    $message = Display::return_message(
5818
                        get_lang('The user has been deleted').': '.$userToUpdateInfo['complete_name_with_username'],
5819
                        'confirmation'
5820
                    );
5821
                } else {
5822
                    $message = Display::return_message(get_lang('You cannot delete this userBecauseOwnsCourse'), 'error');
5823
                }
5824
            }
5825
        }
5826
5827
        return $message;
5828
    }
5829
5830
    /**
5831
     * @return array
5832
     */
5833
    public static function createDataPrivacyExtraFields()
5834
    {
5835
        self::create_extra_field(
5836
            'request_for_legal_agreement_consent_removal_justification',
5837
            1, //text
5838
            'Request for legal agreement consent removal justification	',
5839
            ''
5840
        );
5841
5842
        self::create_extra_field(
5843
            'request_for_delete_account_justification',
5844
            1, //text
5845
            'Request for delete account justification',
5846
            ''
5847
        );
5848
5849
        $extraFieldId = self::create_extra_field(
5850
            'request_for_legal_agreement_consent_removal',
5851
            1, //text
5852
            'Request for legal agreement consent removal',
5853
            ''
5854
        );
5855
5856
        $extraFieldIdDeleteAccount = self::create_extra_field(
5857
            'request_for_delete_account',
5858
            1, //text
5859
            'Request for delete user account',
5860
            ''
5861
        );
5862
5863
        return [
5864
            'delete_account_extra_field' => $extraFieldIdDeleteAccount,
5865
            'delete_legal' => $extraFieldId,
5866
        ];
5867
    }
5868
5869
    /**
5870
     * @param int $userId
5871
     */
5872
    public static function cleanUserRequestsOfRemoval($userId)
5873
    {
5874
        $userId = (int) $userId;
5875
5876
        $extraFieldValue = new ExtraFieldValue('user');
5877
        $extraFieldsToDelete = [
5878
            'legal_accept',
5879
            'request_for_legal_agreement_consent_removal',
5880
            'request_for_legal_agreement_consent_removal_justification',
5881
            'request_for_delete_account_justification', // just in case delete also this
5882
            'request_for_delete_account',
5883
        ];
5884
5885
        foreach ($extraFieldsToDelete as $variable) {
5886
            $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5887
                $userId,
5888
                $variable
5889
            );
5890
            if ($value && isset($value['id'])) {
5891
                $extraFieldValue->delete($value['id']);
5892
            }
5893
        }
5894
    }
5895
5896
    /**
5897
     * @param int $searchYear
5898
     *
5899
     * @throws Exception
5900
     *
5901
     * @return array
5902
     */
5903
    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
5904
    {
5905
        $timezone = new DateTimeZone(api_get_timezone());
5906
5907
        $sessions = [];
5908
        if (DRH == $userInfo['status']) {
5909
            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
5910
        } elseif (api_is_platform_admin(true)) {
5911
            $sessions = SessionManager::getSessionsForAdmin($userInfo['id']);
5912
        } else {
5913
            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
5914
            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
5915
5916
            foreach ($sessionsByCategory as $sessionsInCategory) {
5917
                $sessions = array_merge($sessions, $sessionsInCategory);
5918
            }
5919
        }
5920
5921
        $sessions = array_map(
5922
            function ($sessionInfo) {
5923
                if (!isset($sessionInfo['session_id'])) {
5924
                    $sessionInfo['session_id'] = $sessionInfo['id'];
5925
                }
5926
                if (!isset($sessionInfo['session_name'])) {
5927
                    $sessionInfo['session_name'] = $sessionInfo['name'];
5928
                }
5929
5930
                return $sessionInfo;
5931
            },
5932
            $sessions
5933
        );
5934
5935
        $calendarSessions = [];
5936
5937
        foreach ($sessions as $sessionInfo) {
5938
            if (!empty($sessionInfo['duration'])) {
5939
                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
5940
                    $sessionInfo['session_id'],
5941
                    $userInfo['id']
5942
                );
5943
5944
                if (empty($courseAccess)) {
5945
                    continue;
5946
                }
5947
5948
                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
5949
                $lastAccessDate = clone $firstAcessDate;
5950
                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
5951
5952
                $firstAccessYear = (int) $firstAcessDate->format('Y');
5953
                $lastAccessYear = (int) $lastAccessDate->format('Y');
5954
5955
                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
5956
                    $calendarSessions[$sessionInfo['session_id']] = [
5957
                        'name' => $sessionInfo['session_name'],
5958
                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
5959
                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
5960
                    ];
5961
                }
5962
5963
                continue;
5964
            }
5965
5966
            $accessStartDate = !empty($sessionInfo['access_start_date'])
5967
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
5968
                : null;
5969
            $accessEndDate = !empty($sessionInfo['access_end_date'])
5970
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
5971
                : null;
5972
            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
5973
            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
5974
5975
            $isValid = false;
5976
5977
            if ($accessStartYear && $accessEndYear) {
5978
                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
5979
                    $isValid = true;
5980
                }
5981
            }
5982
5983
            if ($accessStartYear && !$accessEndYear) {
5984
                if ($accessStartYear == $searchYear) {
5985
                    $isValid = true;
5986
                }
5987
            }
5988
5989
            if (!$accessStartYear && $accessEndYear) {
5990
                if ($accessEndYear == $searchYear) {
5991
                    $isValid = true;
5992
                }
5993
            }
5994
5995
            if ($isValid) {
5996
                $calendarSessions[$sessionInfo['session_id']] = [
5997
                    'name' => $sessionInfo['session_name'],
5998
                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
5999
                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
6000
                ];
6001
            }
6002
        }
6003
6004
        return $calendarSessions;
6005
    }
6006
6007
    /**
6008
     * Get sessions info for planification calendar.
6009
     *
6010
     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
6011
     * @param int   $searchYear
6012
     *
6013
     * @throws Exception
6014
     *
6015
     * @return array
6016
     */
6017
    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
6018
    {
6019
        $timezone = new DateTimeZone(api_get_timezone());
6020
        $calendar = [];
6021
6022
        foreach ($sessionsList as $sessionId => $sessionInfo) {
6023
            $startDate = $sessionInfo['access_start_date']
6024
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
6025
                : null;
6026
            $endDate = $sessionInfo['access_end_date']
6027
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
6028
                : null;
6029
6030
            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
6031
            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
6032
            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
6033
            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
6034
            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
6035
            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
6036
6037
            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
6038
            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
6039
6040
            $calendar[] = [
6041
                'id' => $sessionId,
6042
                'name' => $sessionInfo['name'],
6043
                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
6044
                'start_in_last_year' => $startYear < $searchYear,
6045
                'end_in_next_year' => $endYear > $searchYear,
6046
                'no_start' => !$startWeek,
6047
                'no_end' => !$endWeek,
6048
                'start' => $start,
6049
                'duration' => $duration > 0 ? $duration : 1,
6050
            ];
6051
        }
6052
6053
        usort(
6054
            $calendar,
6055
            function ($sA, $sB) {
6056
                if ($sA['start'] == $sB['start']) {
6057
                    return 0;
6058
                }
6059
6060
                if ($sA['start'] < $sB['start']) {
6061
                    return -1;
6062
                }
6063
6064
                return 1;
6065
            }
6066
        );
6067
6068
        return $calendar;
6069
    }
6070
6071
    /**
6072
     * Return the user's full name. Optionally with the username.
6073
     */
6074
    public static function formatUserFullName(User $user, bool $includeUsername = false): string
6075
    {
6076
        $fullName = api_get_person_name($user->getFirstname(), $user->getLastname());
6077
6078
        if ($includeUsername && 'false' === api_get_setting('profile.hide_username_with_complete_name')) {
6079
            $username = $user->getUsername();
6080
6081
            return "$fullName ($username)";
6082
        }
6083
6084
        return $fullName;
6085
    }
6086
6087
    /**
6088
     * @param int $userId
6089
     *
6090
     * @return array
6091
     */
6092
    public static function getUserCareers($userId)
6093
    {
6094
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6095
        $tableCareer = Database::get_main_table(TABLE_CAREER);
6096
        $userId = (int) $userId;
6097
6098
        $sql = "SELECT c.id, c.title
6099
                FROM $table uc
6100
                INNER JOIN $tableCareer c
6101
                ON uc.career_id = c.id
6102
                WHERE user_id = $userId
6103
                ORDER BY uc.created_at
6104
                ";
6105
        $result = Database::query($sql);
6106
6107
        return Database::store_result($result, 'ASSOC');
6108
    }
6109
6110
    /**
6111
     * @param int $userId
6112
     * @param int $careerId
6113
     */
6114
    public static function addUserCareer($userId, $careerId)
6115
    {
6116
        if ('true' !== api_get_setting('profile.allow_career_users')) {
6117
            return false;
6118
        }
6119
6120
        if (false === self::userHasCareer($userId, $careerId)) {
6121
            $params = ['user_id' => $userId, 'career_id' => $careerId, 'created_at' => api_get_utc_datetime(), 'updated_at' => api_get_utc_datetime()];
6122
            $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6123
            Database::insert($table, $params);
6124
        }
6125
6126
        return true;
6127
    }
6128
6129
    /**
6130
     * @param int   $userCareerId
6131
     * @param array $data
6132
     *
6133
     * @return bool
6134
     */
6135
    public static function updateUserCareer($userCareerId, $data)
6136
    {
6137
        if ('true' !== api_get_setting('profile.allow_career_users')) {
6138
            return false;
6139
        }
6140
6141
        $params = ['extra_data' => $data, 'updated_at' => api_get_utc_datetime()];
6142
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6143
        Database::update(
6144
            $table,
6145
            $params,
6146
            ['id = ?' => (int) $userCareerId]
6147
        );
6148
6149
        return true;
6150
    }
6151
6152
    /**
6153
     * @param int $userId
6154
     * @param int $careerId
6155
     *
6156
     * @return array
6157
     */
6158
    public static function getUserCareer($userId, $careerId)
6159
    {
6160
        $userId = (int) $userId;
6161
        $careerId = (int) $careerId;
6162
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6163
6164
        $sql = "SELECT * FROM $table WHERE user_id = $userId AND career_id = $careerId";
6165
        $result = Database::query($sql);
6166
6167
        return Database::fetch_assoc($result);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::fetch_assoc($result) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
6168
    }
6169
6170
    /**
6171
     * @param int $userId
6172
     * @param int $careerId
6173
     *
6174
     * @return bool
6175
     */
6176
    public static function userHasCareer($userId, $careerId)
6177
    {
6178
        $userId = (int) $userId;
6179
        $careerId = (int) $careerId;
6180
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6181
6182
        $sql = "SELECT id FROM $table WHERE user_id = $userId AND career_id = $careerId";
6183
        $result = Database::query($sql);
6184
6185
        return Database::num_rows($result) > 0;
6186
    }
6187
6188
    /**
6189
     * Disables or enables a user.
6190
     *
6191
     * @param int $user_id
6192
     * @param int $active  Enable or disable
6193
     *
6194
     * @return bool True on success, false on failure
6195
     * @assert (-1,0) === false
6196
     * @assert (1,1) === true
6197
     */
6198
    public static function change_active_state($user_id, $active)
6199
    {
6200
        $user_id = (int) $user_id;
6201
        $active = (int) $active;
6202
6203
        if (empty($user_id)) {
6204
            return false;
6205
        }
6206
6207
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6208
        $sql = "UPDATE $table_user SET active = '$active' WHERE id = $user_id";
6209
        $r = Database::query($sql);
6210
        $ev = LOG_USER_DISABLE;
6211
        if (1 == $active) {
6212
            $ev = LOG_USER_ENABLE;
6213
        }
6214
        if (false !== $r) {
6215
            Event::addEvent($ev, LOG_USER_ID, $user_id);
6216
        }
6217
6218
        return $r;
6219
    }
6220
6221
    /**
6222
     * Get either a Gravatar URL or complete image tag for a specified email address.
6223
     *
6224
     * @param string $email The email address
6225
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
6226
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
6227
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
6228
     * @param bool   $img   True to return a complete IMG tag False for just the URL
6229
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
6230
     *
6231
     * @return string containing either just a URL or a complete image tag
6232
     * @source http://gravatar.com/site/implement/images/php/
6233
     */
6234
    private static function getGravatar(
0 ignored issues
show
Unused Code introduced by
The method getGravatar() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
6235
        $email,
6236
        $s = 80,
6237
        $d = 'mm',
6238
        $r = 'g',
6239
        $img = false,
6240
        $atts = []
6241
    ) {
6242
        $url = 'http://www.gravatar.com/avatar/';
6243
        if (!empty($_SERVER['HTTPS'])) {
6244
            $url = 'https://secure.gravatar.com/avatar/';
6245
        }
6246
        $url .= md5(strtolower(trim($email)));
6247
        $url .= "?s=$s&d=$d&r=$r";
6248
        if ($img) {
6249
            $url = '<img src="'.$url.'"';
6250
            foreach ($atts as $key => $val) {
6251
                $url .= ' '.$key.'="'.$val.'"';
6252
            }
6253
            $url .= ' />';
6254
        }
6255
6256
        return $url;
6257
    }
6258
6259
    /**
6260
     * Count users in courses and if they have certificate.
6261
     * This function is resource intensive.
6262
     *
6263
     * @return array
6264
     * @throws Exception
6265
     * @throws \Doctrine\DBAL\Exception
6266
     */
6267
    public static function countUsersWhoFinishedCourses()
6268
    {
6269
        $courses = [];
6270
        $currentAccessUrlId = api_get_current_access_url_id();
6271
        $sql = "SELECT course.code, course.id as cid, cru.user_id
6272
                FROM course_rel_user cru
6273
                    JOIN course ON cru.c_id = course.id
6274
                    JOIN access_url_rel_user auru on cru.user_id = auru.user_id
6275
                    JOIN access_url_rel_course ON course.id = access_url_rel_course.c_id
6276
                WHERE access_url_rel_course.access_url_id = $currentAccessUrlId
6277
                ORDER BY course.code
6278
        ";
6279
        $res = Database::query($sql);
6280
        if (Database::num_rows($res) > 0) {
6281
            while ($row = Database::fetch_array($res)) {
6282
                if (!isset($courses[$row['code']])) {
6283
                    $courses[$row['code']] = [
6284
                        'subscribed' => 0,
6285
                        'finished' => 0,
6286
                    ];
6287
                }
6288
                $courses[$row['code']]['subscribed']++;
6289
                $entityManager = Database::getManager();
6290
                $repository = $entityManager->getRepository(GradebookCategory::class);
6291
                //todo check when have more than 1 gradebook
6292
                /** @var GradebookCategory $gradebook */
6293
                $gradebook = $repository->findOneBy(['course' => $row['cid']]);
6294
                if (!empty($gradebook)) {
6295
                    $finished = 0;
6296
                    Database::getManager()->persist($gradebook);
6297
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6298
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6299
                    if (!empty($finished)) {
6300
                        $courses[$row['code']]['finished']++;
6301
                    }
6302
                }
6303
            }
6304
        }
6305
        return $courses;
6306
    }
6307
6308
    /**
6309
     * Count users in sessions and if they have certificate.
6310
     * This function is resource intensive.
6311
     *
6312
     * @return array
6313
     * @throws Exception
6314
     * @throws \Doctrine\DBAL\Exception
6315
     */
6316
    public static function countUsersWhoFinishedCoursesInSessions()
6317
    {
6318
        $coursesInSessions = [];
6319
        $currentAccessUrlId = api_get_current_access_url_id();
6320
        $sql = "SELECT course.code, srcru.session_id, srcru.user_id, session.title
6321
                FROM session_rel_course_rel_user srcru
6322
                    JOIN course ON srcru.c_id = course.id
6323
                    JOIN access_url_rel_session aurs on srcru.session_id = aurs.session_id
6324
                    JOIN session ON srcru.session_id = session.id
6325
                WHERE aurs.access_url_id = $currentAccessUrlId
6326
                ORDER BY course.code, session.title
6327
        ";
6328
        $res = Database::query($sql);
6329
        if (Database::num_rows($res) > 0) {
6330
            while ($row = Database::fetch_array($res)) {
6331
                $index = $row['code'].' ('.$row['title'].')';
6332
                if (!isset($coursesInSessions[$index])) {
6333
                    $coursesInSessions[$index] = [
6334
                        'subscribed' => 0,
6335
                        'finished' => 0,
6336
                    ];
6337
                }
6338
                $coursesInSessions[$index]['subscribed']++;
6339
                $entityManager = Database::getManager();
6340
                $repository = $entityManager->getRepository(GradebookCategory::class);
6341
                /** @var GradebookCategory $gradebook */
6342
                $gradebook = $repository->findOneBy(
6343
                    [
6344
                        'course' => $row['cid'],
6345
                        'sessionId' => $row['session_id'],
6346
                    ]
6347
                );
6348
                if (!empty($gradebook)) {
6349
                    $finished = 0;
6350
                    Database::getManager()->persist($gradebook);
6351
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6352
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6353
                    if (!empty($finished)) {
6354
                        $coursesInSessions[$index]['finished']++;
6355
                    }
6356
                }
6357
            }
6358
        }
6359
        return $coursesInSessions;
6360
    }
6361
6362
    public static function redirectToResetPassword($userId): void
6363
    {
6364
        if ('true' !== api_get_setting('platform.force_renew_password_at_first_login')) {
6365
            return;
6366
        }
6367
        $askPassword = self::get_extra_user_data_by_field(
6368
            $userId,
6369
            'ask_new_password'
6370
        );
6371
        if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
6372
            1 === (int) $askPassword['ask_new_password']
6373
        ) {
6374
            $uniqueId = api_get_unique_id();
6375
            $userObj = api_get_user_entity($userId);
6376
            $userObj->setConfirmationToken($uniqueId);
6377
            $userObj->setPasswordRequestedAt(new \DateTime());
6378
            Database::getManager()->persist($userObj);
6379
            Database::getManager()->flush();
6380
            $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
6381
            api_location($url);
6382
        }
6383
    }
6384
6385
}
6386