UserManager::is_user_certified()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 19
rs 9.8666
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 required'))
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('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
        $fallbackUser = $repository->getFallbackUser();
817
        $fallbackId = (int) $fallbackUser->getId();
818
        $affectedIds = [];
819
        if ($destroy && $fallbackId && $fallbackId !== $user_id) {
820
            $em = Database::getManager();
821
            $rows = $em->createQuery(
822
                'SELECT u.id
823
                     FROM ' . User::class . ' u
824
                     WHERE u.creatorId = :oldCreator AND u.id <> :fallbackId'
825
            )
826
                ->setParameter('oldCreator', $user_id)
827
                ->setParameter('fallbackId', $fallbackId)
828
                ->getScalarResult();
829
830
            $affectedIds = array_map(
831
                static fn(array $r): int => (int) $r['id'],
832
                $rows
833
            );
834
835
            $em->createQuery(
836
                'UPDATE ' . User::class . ' u
837
                 SET u.creatorId = :newCreator
838
                 WHERE u.creatorId = :oldCreator AND u.id <> :fallbackId'
839
            )
840
                ->setParameter('newCreator', $fallbackId)
841
                ->setParameter('oldCreator', $user_id)
842
                ->setParameter('fallbackId', $fallbackId)
843
                ->execute();
844
        }
845
846
        $repository->deleteUser($user, $destroy);
847
848
        if ($destroy) {
849
            Event::addEvent(
850
                LOG_USER_DELETE,
851
                LOG_USER_OBJECT,
852
                api_get_user_info($user_id),
853
                api_get_utc_datetime(),
854
                api_get_user_id()
855
            );
856
857
            // Log one event per affected user AFTER the deletion
858
            if (!empty($affectedIds) && $fallbackId && $fallbackId !== $user_id) {
859
                $nowUtc = api_get_utc_datetime();
860
                $actor  = api_get_user_id();
861
                foreach ($affectedIds as $affectedId) {
862
                    Event::addEvent(
863
                        LOG_USER_CREATOR_DELETED,
864
                        LOG_USER_ID,
865
                        [
866
                            'user_id'        => $affectedId,
867
                            'old_creator_id' => $user_id,
868
                            'new_creator_id' => $fallbackId,
869
                        ],
870
                        $nowUtc,
871
                        $actor
872
                    );
873
                }
874
            }
875
        }
876
877
        return true;
878
    }
879
880
    /**
881
     * Deletes users completely. Can be called either as:
882
     * - UserManager::delete_users(1, 2, 3); or
883
     * - UserManager::delete_users(array(1, 2, 3));.
884
     *
885
     * @param array|int $ids
886
     *
887
     * @return bool True if at least one user was successfuly deleted. False otherwise.
888
     *
889
     * @author Laurent Opprecht
890
     *
891
     * @uses \UserManager::delete_user() to actually delete each user
892
     * @assert (null) === false
893
     * @assert (-1) === false
894
     * @assert (array(-1)) === false
895
     */
896
    public static function delete_users($ids = [])
897
    {
898
        $result = false;
899
        $ids = is_array($ids) ? $ids : func_get_args();
900
        if (!is_array($ids) || 0 == count($ids)) {
901
            return false;
902
        }
903
        $ids = array_map('intval', $ids);
904
        foreach ($ids as $id) {
905
            if (empty($id) || $id < 1) {
906
                continue;
907
            }
908
            $deleted = self::delete_user($id);
909
            $result = $deleted || $result;
910
        }
911
912
        return $result;
913
    }
914
915
    /**
916
     * Disable users. Can be called either as:
917
     * - UserManager::deactivate_users(1, 2, 3);
918
     * - UserManager::deactivate_users(array(1, 2, 3));.
919
     *
920
     * @param array|int $ids
921
     *
922
     * @return bool
923
     *
924
     * @author Laurent Opprecht
925
     * @assert (null) === false
926
     * @assert (array(-1)) === false
927
     */
928
    public static function deactivate_users($ids = [])
929
    {
930
        if (empty($ids)) {
931
            return false;
932
        }
933
934
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
935
936
        $ids = is_array($ids) ? $ids : func_get_args();
937
        $ids = array_map('intval', $ids);
938
        $ids = implode(',', $ids);
939
940
        $sql = "UPDATE $table_user SET active = 0 WHERE id IN ($ids)";
941
        $r = Database::query($sql);
942
        if (false !== $r) {
943
            Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $ids);
944
945
            return true;
946
        }
947
948
        return false;
949
    }
950
951
    /**
952
     * Enable users. Can be called either as:
953
     * - UserManager::activate_users(1, 2, 3);
954
     * - UserManager::activate_users(array(1, 2, 3));.
955
     *
956
     * @param array|int IDs of the users to enable
957
     *
958
     * @return bool
959
     *
960
     * @author Laurent Opprecht
961
     * @assert (null) === false
962
     * @assert (array(-1)) === false
963
     */
964
    public static function activate_users($ids = [])
965
    {
966
        if (empty($ids)) {
967
            return false;
968
        }
969
970
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
971
972
        $ids = is_array($ids) ? $ids : func_get_args();
973
        $ids = array_map('intval', $ids);
974
        $ids = implode(',', $ids);
975
976
        $sql = "UPDATE $table_user SET active = 1 WHERE id IN ($ids)";
977
        $r = Database::query($sql);
978
        if (false !== $r) {
979
            Event::addEvent(LOG_USER_ENABLE, LOG_USER_ID, $ids);
980
981
            return true;
982
        }
983
984
        return false;
985
    }
986
987
    /**
988
     * Update user information with all the parameters passed to this function.
989
     *
990
     * @param int    $user_id         The ID of the user to be updated
991
     * @param string $firstname       The user's firstname
992
     * @param string $lastname        The user's lastname
993
     * @param string $username        The user's username (login)
994
     * @param string $password        The user's password
995
     * @param string $auth_sources     The authentication source (default: "platform")
996
     * @param string $email           The user's e-mail address
997
     * @param int    $status          The user's status
998
     * @param string $official_code   The user's official code (usually just an internal institutional code)
999
     * @param string $phone           The user's phone number
1000
     * @param string $picture_uri     The user's picture URL (internal to the Chamilo directory)
1001
     * @param string $expiration_date The date at which this user will be automatically disabled
1002
     * @param int    $active          Whether this account needs to be enabled (1) or disabled (0)
1003
     * @param int    $creator_id      The user ID of the person who registered this user (optional, defaults to null)
1004
     * @param int    $hr_dept_id      The department of HR in which the user is registered (optional, defaults to 0)
1005
     * @param array  $extra           Additional fields to add to this user as extra fields (defaults to null)
1006
     * @param string $language        The language to which the user account will be set
1007
     * @param string $encrypt_method  The cipher method. This parameter is deprecated. It will use the system's default
1008
     * @param bool   $send_email      Whether to send an e-mail to the user after the update is complete
1009
     * @param int    $reset_password  Method used to reset password (0, 1, 2 or 3 - see usage examples for details)
1010
     * @param string $address
1011
     * @param array  $emailTemplate
1012
     *
1013
     * @return bool|int False on error, or the user ID if the user information was updated
1014
     * @assert (false, false, false, false, false, false, false, false, false, false, false, false, false) === false
1015
     */
1016
    public static function update_user(
1017
        $user_id,
1018
        $firstname,
1019
        $lastname,
1020
        $username,
1021
        $password,
1022
        array $auth_sources,
1023
        $email,
1024
        $status,
1025
        $official_code,
1026
        $phone,
1027
        $picture_uri,
1028
        $expiration_date,
1029
        $active,
1030
        $creator_id = null,
1031
        $hr_dept_id = 0,
1032
        $extra = null,
1033
        $language = 'en_US',
1034
        $encrypt_method = '',
1035
        $send_email = false,
1036
        $reset_password = 0,
1037
        $address = null,
1038
        $emailTemplate = []
1039
    ) {
1040
        $eventDispatcher = Container::getEventDispatcher();
1041
1042
        $eventDispatcher->dispatch(
1043
            new UserUpdatedEvent([], AbstractEvent::TYPE_PRE),
1044
            Events::USER_UPDATED
1045
        );
1046
1047
        $original_password = $password;
1048
        $user_id = (int) $user_id;
1049
        $creator_id = (int) $creator_id;
1050
1051
        if (empty($user_id)) {
1052
            return false;
1053
        }
1054
1055
        $userManager = self::getRepository();
1056
        $user = api_get_user_entity($user_id);
1057
1058
        if (null === $user) {
1059
            return false;
1060
        }
1061
1062
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
1063
1064
        if (0 == $reset_password) {
1065
            $password = null;
1066
            $auth_sources = $user->getAuthSourcesAuthentications($accessUrl);
1067
        } elseif (1 == $reset_password) {
1068
            $original_password = $password = api_generate_password();
1069
            $auth_sources = [UserAuthSource::PLATFORM];
1070
        } elseif (2 == $reset_password) {
1071
            $auth_sources = [UserAuthSource::PLATFORM];
1072
        }
1073
1074
        // Checking the user language
1075
        $languages = array_keys(api_get_languages());
1076
        if (!in_array($language, $languages)) {
1077
            $language = api_get_setting('platformLanguage');
1078
        }
1079
1080
        $change_active = 0;
1081
        $isUserActive = $user->isActive();
1082
        if ($active != USER_SOFT_DELETED) {
1083
            if ($isUserActive != $active) {
1084
                $change_active = 1;
1085
            }
1086
        }
1087
1088
        $originalUsername = $user->getUsername();
1089
1090
        // If username is different from original then check if it exists.
1091
        if ($originalUsername !== $username) {
1092
            $available = self::is_username_available($username);
1093
            if (false === $available) {
1094
                return false;
1095
            }
1096
        }
1097
1098
        if (!empty($expiration_date)) {
1099
            $expiration_date = api_get_utc_datetime($expiration_date);
1100
            $expiration_date = new \DateTime($expiration_date, new DateTimeZone('UTC'));
1101
        }
1102
1103
        $previousStatus = $user->getStatus();
1104
        $previousRole = $user->getRoleFromStatus($previousStatus);
1105
1106
        $user
1107
            ->removeRole($previousRole)
1108
            ->setLastname($lastname)
1109
            ->setFirstname($firstname)
1110
            ->setUsername($username)
1111
            ->setStatus($status)
1112
            ->setLocale($language)
1113
            ->setEmail($email)
1114
            ->setOfficialCode($official_code)
1115
            ->setPhone($phone)
1116
            ->setAddress($address)
1117
            ->setExpirationDate($expiration_date)
1118
            ->setActive($active)
1119
            ->setHrDeptId((int) $hr_dept_id)
1120
            ->removeAuthSources()
1121
        ;
1122
1123
        foreach ($auth_sources as $authSource) {
1124
            $user->addAuthSourceByAuthentication($authSource, $accessUrl);
1125
        }
1126
1127
        if (!is_null($password)) {
1128
            $user->setPlainPassword($password);
1129
        }
1130
1131
        $user->setRoleFromStatus($status);
1132
        $userManager->updateUser($user, true);
1133
        Event::addEvent(LOG_USER_UPDATE, LOG_USER_ID, $user_id);
1134
1135
        if (1 == $change_active) {
1136
            $event_title = LOG_USER_DISABLE;
1137
            if (1 == $active) {
1138
                $event_title = LOG_USER_ENABLE;
1139
            }
1140
            Event::addEvent($event_title, LOG_USER_ID, $user_id);
1141
        }
1142
1143
        if (is_array($extra) && count($extra) > 0) {
1144
            $res = true;
1145
            foreach ($extra as $fname => $fvalue) {
1146
                $res = $res && self::update_extra_field_value(
1147
                    $user_id,
1148
                    $fname,
1149
                    $fvalue
1150
                );
1151
            }
1152
        }
1153
1154
        if (!empty($email) && $send_email) {
1155
            $recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1156
            $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

1156
            $emailsubject = '['./** @scrutinizer ignore-type */ api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
Loading history...
1157
            $sender_name = api_get_person_name(
1158
                api_get_setting('administratorName'),
1159
                api_get_setting('administratorSurname'),
1160
                null,
1161
                PERSON_NAME_EMAIL_ADDRESS
1162
            );
1163
            $email_admin = api_get_setting('emailAdministrator');
1164
            $url = api_get_path(WEB_PATH);
1165
            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

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

1584
        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...
1585
            if ($idCampus) {
1586
                $urlId = $idCampus;
1587
            } else {
1588
                $urlId = api_get_current_access_url_id();
1589
            }
1590
            $sql .= " INNER JOIN $userUrlTable url_user
1591
                      ON (user.id = url_user.user_id)
1592
                      WHERE url_user.access_url_id = $urlId";
1593
        } else {
1594
            $sql .= " WHERE 1=1 ";
1595
        }
1596
1597
        if (count($conditions) > 0) {
1598
            foreach ($conditions as $field => $value) {
1599
                $field = Database::escape_string($field);
1600
                $value = Database::escape_string($value);
1601
                $sql .= " AND $field = '$value'";
1602
            }
1603
        }
1604
1605
        if (count($order_by) > 0) {
1606
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1607
        }
1608
1609
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1610
            $limit_from = (int) $limit_from;
1611
            $limit_to = (int) $limit_to;
1612
            $sql .= " LIMIT $limit_from, $limit_to";
1613
        }
1614
        $sql_result = Database::query($sql);
1615
        while ($result = Database::fetch_array($sql_result)) {
1616
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1617
            $return_array[] = $result;
1618
        }
1619
1620
        return $return_array;
1621
    }
1622
1623
    public static function getUserListExtraConditions(
1624
        $conditions = [],
1625
        $order_by = [],
1626
        $limit_from = false,
1627
        $limit_to = false,
1628
        $idCampus = null,
1629
        $extraConditions = '',
1630
        $getCount = false
1631
    ) {
1632
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1633
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1634
        $return_array = [];
1635
        $sql = "SELECT user.*, user.id as user_id FROM $user_table user ";
1636
1637
        if ($getCount) {
1638
            $sql = "SELECT count(user.id) count FROM $user_table user ";
1639
        }
1640
1641
        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

1641
        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...
1642
            if ($idCampus) {
1643
                $urlId = $idCampus;
1644
            } else {
1645
                $urlId = api_get_current_access_url_id();
1646
            }
1647
            $sql .= " INNER JOIN $userUrlTable url_user
1648
                      ON (user.id = url_user.user_id)
1649
                      WHERE url_user.access_url_id = $urlId";
1650
        } else {
1651
            $sql .= " WHERE 1=1 ";
1652
        }
1653
1654
        $sql .= " AND status <> ".ANONYMOUS." ";
1655
1656
        if (count($conditions) > 0) {
1657
            foreach ($conditions as $field => $value) {
1658
                $field = Database::escape_string($field);
1659
                $value = Database::escape_string($value);
1660
                $sql .= " AND $field = '$value'";
1661
            }
1662
        }
1663
1664
        $sql .= str_replace("\'", "'", Database::escape_string($extraConditions));
1665
1666
        if (!empty($order_by) && count($order_by) > 0) {
1667
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1668
        }
1669
1670
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1671
            $limit_from = (int) $limit_from;
1672
            $limit_to = (int) $limit_to;
1673
            $sql .= " LIMIT $limit_from, $limit_to";
1674
        }
1675
1676
        $sql_result = Database::query($sql);
1677
1678
        if ($getCount) {
1679
            $result = Database::fetch_array($sql_result);
1680
1681
            return $result['count'];
1682
        }
1683
1684
        while ($result = Database::fetch_array($sql_result)) {
1685
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1686
            $return_array[] = $result;
1687
        }
1688
1689
        return $return_array;
1690
    }
1691
1692
    /**
1693
     * Get a list of users of which the given conditions match with a LIKE '%cond%'.
1694
     *
1695
     * @param array  $conditions       a list of condition (exemple : status=>STUDENT)
1696
     * @param array  $order_by         a list of fields on which sort
1697
     * @param bool   $simple_like      Whether we want a simple LIKE 'abc' or a LIKE '%abc%'
1698
     * @param string $condition        Whether we want the filters to be combined by AND or OR
1699
     * @param array  $onlyThisUserList
1700
     *
1701
     * @return array an array with all users of the platform
1702
     *
1703
     * @todo optional course code parameter, optional sorting parameters...
1704
     * @todo security filter order_by
1705
     */
1706
    public static function getUserListLike(
1707
        $conditions = [],
1708
        $order_by = [],
1709
        $simple_like = false,
1710
        $condition = 'AND',
1711
        $onlyThisUserList = [],
1712
        int $limit = 0,
1713
        int $offset = 0
1714
    ) {
1715
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1716
        $tblAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1717
        $return_array = [];
1718
        $sql_query = "SELECT user.id, user.username, user.firstname, user.lastname, user.official_code, user.status
1719
                  FROM $user_table user ";
1720
1721
        if (api_get_multiple_access_url()) {
1722
            $sql_query .= " INNER JOIN $tblAccessUrlRelUser auru ON auru.user_id = user.id ";
1723
        }
1724
1725
        $sql_query .= ' WHERE 1 = 1 ';
1726
        if (count($conditions) > 0) {
1727
            $temp_conditions = [];
1728
            foreach ($conditions as $field => $value) {
1729
                $field = Database::escape_string($field);
1730
                $value = Database::escape_string($value);
1731
                if ($simple_like) {
1732
                    $temp_conditions[] = "$field LIKE '$value%'";
1733
                } else {
1734
                    if (in_array($field, ['user.id', 'user.status'])) {
1735
                        $temp_conditions[] = "$field = '$value'";
1736
                    } else {
1737
                        $temp_conditions[] = "$field LIKE '%$value%'";
1738
                    }
1739
                }
1740
            }
1741
            if (!empty($temp_conditions)) {
1742
                $sql_query .= ' AND '.implode(" $condition ", $temp_conditions);
1743
            }
1744
1745
            if (api_get_multiple_access_url()) {
1746
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1747
            }
1748
        } else {
1749
            if (api_get_multiple_access_url()) {
1750
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1751
            }
1752
        }
1753
1754
        if (!empty($onlyThisUserList)) {
1755
            $onlyThisUserListToString = implode("','", array_map('intval', $onlyThisUserList));
1756
            $sql_query .= " AND user.id IN ('$onlyThisUserListToString') ";
1757
        }
1758
1759
        if (!empty($order_by)) {
1760
            $sql_query .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1761
        }
1762
1763
        if ($limit > 0) {
1764
            $sql_query .= ' LIMIT '.intval($limit);
1765
            if ($offset > 0) {
1766
                $sql_query .= ' OFFSET '.intval($offset);
1767
            }
1768
        }
1769
1770
        $sql_result = Database::query($sql_query);
1771
        while ($result = Database::fetch_array($sql_result)) {
1772
            $return_array[] = $result;
1773
        }
1774
1775
        return $return_array;
1776
    }
1777
1778
    /**
1779
     * Get user path from user ID (returns an array).
1780
     * The return format is a complete path to a folder ending with "/"
1781
     * In case the first level of subdirectory of users/ does not exist, the
1782
     * function will attempt to create it. Probably not the right place to do it
1783
     * but at least it avoids headaches in many other places.
1784
     *
1785
     * @param int    $id   User ID
1786
     * @param string $type Type of path to return (can be 'system', 'web', 'last')
1787
     *
1788
     * @return string User folder path (i.e. /var/www/chamilo/app/upload/users/1/1/)
1789
     */
1790
    public static function getUserPathById($id, $type)
1791
    {
1792
        $id = (int) $id;
1793
        if (!$id) {
1794
            return null;
1795
        }
1796
1797
        $userPath = "users/$id/";
1798
        if (api_get_setting('split_users_upload_directory') === 'true') {
1799
            $userPath = 'users/'.substr((string) $id, 0, 1).'/'.$id.'/';
1800
            // In exceptional cases, on some portals, the intermediate base user
1801
            // directory might not have been created. Make sure it is before
1802
            // going further.
1803
1804
            $rootPath = api_get_path(SYS_PATH).'../app/upload/users/'.substr((string) $id, 0, 1);
1805
            if (!is_dir($rootPath)) {
1806
                $perm = api_get_permissions_for_new_directories();
1807
                try {
1808
                    mkdir($rootPath, $perm);
1809
                } catch (Exception $e) {
1810
                    error_log($e->getMessage());
1811
                }
1812
            }
1813
        }
1814
        switch ($type) {
1815
            case 'system': // Base: absolute system path.
1816
                $userPath = api_get_path(SYS_PATH).'../app/upload/'.$userPath;
1817
                break;
1818
            case 'web': // Base: absolute web path.
1819
                $userPath = api_get_path(WEB_PATH).'../app/upload/'.$userPath;
1820
                break;
1821
            case 'last': // Only the last part starting with users/
1822
                break;
1823
        }
1824
1825
        return $userPath;
1826
    }
1827
1828
    /**
1829
     * Gets the current user image.
1830
     *
1831
     * @param string $userId
1832
     * @param int    $size        it can be USER_IMAGE_SIZE_SMALL,
1833
     *                            USER_IMAGE_SIZE_MEDIUM, USER_IMAGE_SIZE_BIG or  USER_IMAGE_SIZE_ORIGINAL
1834
     * @param bool   $addRandomId
1835
     * @param array  $userInfo    to avoid query the DB
1836
     *
1837
     * @todo add gravatar support
1838
     * @todo replace $userId with User entity
1839
     *
1840
     * @return string
1841
     */
1842
    public static function getUserPicture(
1843
        $userId,
1844
        int $size = USER_IMAGE_SIZE_MEDIUM,
1845
        $addRandomId = true,
1846
        $userInfo = []
1847
    ) {
1848
        $user = api_get_user_entity($userId);
1849
        $illustrationRepo = Container::getIllustrationRepository();
1850
1851
        switch ($size) {
1852
            case USER_IMAGE_SIZE_SMALL:
1853
                $width = 32;
1854
                break;
1855
            case USER_IMAGE_SIZE_MEDIUM:
1856
                $width = 64;
1857
                break;
1858
            case USER_IMAGE_SIZE_BIG:
1859
                $width = 128;
1860
                break;
1861
            case USER_IMAGE_SIZE_ORIGINAL:
1862
            default:
1863
                $width = 0;
1864
                break;
1865
        }
1866
1867
        $url = $illustrationRepo->getIllustrationUrl($user);
1868
        $params = [];
1869
        if (!empty($width)) {
1870
            $params['w'] = $width;
1871
        }
1872
1873
        if ($addRandomId) {
1874
            $params['rand'] = uniqid('u_', true);
1875
        }
1876
1877
        $paramsToString = '';
1878
        if (!empty($params)) {
1879
            $paramsToString = '?'.http_build_query($params);
1880
        }
1881
1882
        return $url.$paramsToString;
1883
1884
        /*
1885
        // Make sure userInfo is defined. Otherwise, define it!
1886
        if (empty($userInfo) || !is_array($userInfo) || 0 == count($userInfo)) {
1887
            if (empty($user_id)) {
1888
                return '';
1889
            } else {
1890
                $userInfo = api_get_user_info($user_id);
1891
            }
1892
        }
1893
1894
        $imageWebPath = self::get_user_picture_path_by_id(
1895
            $user_id,
1896
            'web',
1897
            $userInfo
1898
        );
1899
        $pictureWebFile = $imageWebPath['file'];
1900
        $pictureWebDir = $imageWebPath['dir'];
1901
1902
        $pictureAnonymousSize = '128';
1903
        $gravatarSize = 22;
1904
        $realSizeName = 'small_';
1905
1906
        switch ($size) {
1907
            case USER_IMAGE_SIZE_SMALL:
1908
                $pictureAnonymousSize = '32';
1909
                $realSizeName = 'small_';
1910
                $gravatarSize = 32;
1911
                break;
1912
            case USER_IMAGE_SIZE_MEDIUM:
1913
                $pictureAnonymousSize = '64';
1914
                $realSizeName = 'medium_';
1915
                $gravatarSize = 64;
1916
                break;
1917
            case USER_IMAGE_SIZE_ORIGINAL:
1918
                $pictureAnonymousSize = '128';
1919
                $realSizeName = '';
1920
                $gravatarSize = 128;
1921
                break;
1922
            case USER_IMAGE_SIZE_BIG:
1923
                $pictureAnonymousSize = '128';
1924
                $realSizeName = 'big_';
1925
                $gravatarSize = 128;
1926
                break;
1927
        }
1928
1929
        $gravatarEnabled = api_get_setting('gravatar_enabled');
1930
        $anonymousPath = Display::returnIconPath('unknown.png', $pictureAnonymousSize);
1931
        if ('unknown.jpg' == $pictureWebFile || empty($pictureWebFile)) {
1932
            if ('true' === $gravatarEnabled) {
1933
                $file = self::getGravatar(
1934
                    $imageWebPath['email'],
1935
                    $gravatarSize,
1936
                    api_get_setting('gravatar_type')
1937
                );
1938
1939
                if ($addRandomId) {
1940
                    $file .= '&rand='.uniqid();
1941
                }
1942
1943
                return $file;
1944
            }
1945
1946
            return $anonymousPath;
1947
        }
1948
1949
        if ($addRandomId) {
1950
            $picture .= '?rand='.uniqid();
1951
        }
1952
1953
        return $picture;*/
1954
    }
1955
1956
    /**
1957
     * Creates new user photos in various sizes of a user, or deletes user photos.
1958
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
1959
     *
1960
     * @param int    $user_id the user internal identification number
1961
     * @param string $file    The common file name for the newly created photos.
1962
     *                        It will be checked and modified for compatibility with the file system.
1963
     *                        If full name is provided, path component is ignored.
1964
     *                        If an empty name is provided, then old user photos are deleted only,
1965
     *
1966
     * @see     UserManager::delete_user_picture() as the prefered way for deletion.
1967
     *
1968
     * @param string $source_file    the full system name of the image from which user photos will be created
1969
     * @param string $cropParameters Optional string that contents "x,y,width,height" of a cropped image format
1970
     *
1971
     * @return bool Returns the resulting common file name of created images which usually should be stored in database.
1972
     *              When deletion is requested returns empty string.
1973
     *              In case of internal error or negative validation returns FALSE.
1974
     */
1975
    public static function update_user_picture($userId, UploadedFile $file, string $crop = '')
1976
    {
1977
        if (empty($userId) || empty($file)) {
1978
            return false;
1979
        }
1980
1981
        $repo = Container::getUserRepository();
1982
        $user = $repo->find($userId);
1983
        if ($user) {
1984
            $repoIllustration = Container::getIllustrationRepository();
1985
            $repoIllustration->addIllustration($user, $user, $file, $crop);
1986
        }
1987
    }
1988
1989
    /**
1990
     * Deletes user photos.
1991
     *
1992
     * @param int $userId the user internal identification number
1993
     *
1994
     * @return mixed returns empty string on success, FALSE on error
1995
     */
1996
    public static function deleteUserPicture($userId)
1997
    {
1998
        $repo = Container::getUserRepository();
1999
        $user = $repo->find($userId);
2000
        if ($user) {
2001
            $illustrationRepo = Container::getIllustrationRepository();
2002
            $illustrationRepo->deleteIllustration($user);
2003
        }
2004
    }
2005
2006
    /**
2007
     * Returns an XHTML formatted list of productions for a user, or FALSE if he
2008
     * doesn't have any.
2009
     *
2010
     * If there has been a request to remove a production, the function will return
2011
     * without building the list unless forced to do so by the optional second
2012
     * parameter. This increases performance by avoiding to read through the
2013
     * productions on the filesystem before the removal request has been carried
2014
     * out because they'll have to be re-read afterwards anyway.
2015
     *
2016
     * @deprecated This method is being removed from chamilo 2.0
2017
     * @param int  $user_id    User id
2018
     * @param bool $force      Optional parameter to force building after a removal request
2019
     * @param bool $showDelete
2020
     *
2021
     * @return string A string containing the XHTML code to display the production list, or FALSE
2022
     */
2023
    public static function build_production_list($user_id, $force = false, $showDelete = false)
2024
    {
2025
        if (!$force && !empty($_POST['remove_production'])) {
2026
            return true; // postpone reading from the filesystem
2027
        }
2028
2029
        $productions = self::get_user_productions($user_id);
2030
2031
        if (empty($productions)) {
2032
            return false;
2033
        }
2034
2035
        return false;
2036
2037
        $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...
2038
        $del_image = Display::returnIconPath('delete.png');
2039
        $add_image = Display::returnIconPath('archive.png');
2040
        $del_text = get_lang('Delete');
2041
        $production_list = '';
2042
        if (count($productions) > 0) {
2043
            $production_list = '<div class="files-production"><ul id="productions">';
2044
            foreach ($productions as $file) {
2045
                $production_list .= '<li>
2046
                    <img src="'.$add_image.'" />
2047
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2048
                        '.htmlentities($file).'
2049
                    </a>';
2050
                if ($showDelete) {
2051
                    $production_list .= '&nbsp;&nbsp;
2052
                        <input
2053
                            style="width:16px;"
2054
                            type="image"
2055
                            name="remove_production['.urlencode($file).']"
2056
                            src="'.$del_image.'"
2057
                            alt="'.$del_text.'"
2058
                            title="'.$del_text.' '.htmlentities($file).'"
2059
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2060
                }
2061
            }
2062
            $production_list .= '</ul></div>';
2063
        }
2064
2065
        return $production_list;
2066
    }
2067
2068
    /**
2069
     * Returns an array with the user's productions.
2070
     *
2071
     * @param int $user_id User id
2072
     *
2073
     * @return array An array containing the user's productions
2074
     */
2075
    public static function get_user_productions($user_id)
2076
    {
2077
        return [];
2078
2079
        $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...
2080
        $productions = [];
2081
2082
        if (is_dir($production_repository)) {
2083
            $handle = opendir($production_repository);
2084
            while ($file = readdir($handle)) {
2085
                if ('.' == $file ||
2086
                    '..' == $file ||
2087
                    '.htaccess' == $file ||
2088
                    is_dir($production_repository.$file)
2089
                ) {
2090
                    // skip current/parent directory and .htaccess
2091
                    continue;
2092
                }
2093
2094
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
2095
                    // User's photos should not be listed as productions.
2096
                    continue;
2097
                }
2098
                $productions[] = $file;
2099
            }
2100
        }
2101
2102
        return $productions;
2103
    }
2104
2105
    /**
2106
     * Remove a user production.
2107
     *
2108
     * @param int    $user_id    User id
2109
     * @param string $production The production to remove
2110
     *
2111
     * @return bool
2112
     */
2113
    public static function remove_user_production($user_id, $production)
2114
    {
2115
        throw new Exception('remove_user_production');
2116
        /*$production_path = self::get_user_picture_path_by_id($user_id, 'system');
2117
        $production_file = $production_path['dir'].$production;
2118
        if (is_file($production_file)) {
2119
            unlink($production_file);
2120
2121
            return true;
2122
        }
2123
2124
        return false;*/
2125
    }
2126
2127
    /**
2128
     * Update an extra field value for a given user.
2129
     *
2130
     * @param int    $userId   User ID
2131
     * @param string $variable Field variable name
2132
     * @param string $value    Field value
2133
     *
2134
     * @return bool true if field updated, false otherwise
2135
     */
2136
    public static function update_extra_field_value($userId, $variable, $value = '')
2137
    {
2138
        $extraFieldValue = new ExtraFieldValue('user');
2139
        $params = [
2140
            'item_id' => $userId,
2141
            'variable' => $variable,
2142
            'field_value' => $value,
2143
        ];
2144
2145
        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...
2146
    }
2147
2148
    /**
2149
     * Get an array of extra fields with field details (type, default value and options).
2150
     *
2151
     * @param    int    Offset (from which row)
2152
     * @param    int    Number of items
2153
     * @param    int    Column on which sorting is made
2154
     * @param    string    Sorting direction
2155
     * @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...
2156
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
2157
     *
2158
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
2159
     */
2160
    public static function get_extra_fields(
2161
        $from = 0,
2162
        $number_of_items = 0,
2163
        $column = 5,
2164
        $direction = 'ASC',
2165
        $all_visibility = true,
2166
        $field_filter = null
2167
    ) {
2168
        $fields = [];
2169
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2170
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
2171
        $columns = [
2172
            'id',
2173
            'variable',
2174
            'value_type',
2175
            'display_text',
2176
            'default_value',
2177
            'field_order',
2178
            'filter',
2179
        ];
2180
        $column = (int) $column;
2181
        $sort_direction = '';
2182
        if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
2183
            $sort_direction = strtoupper($direction);
2184
        }
2185
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
2186
        $sqlf = "SELECT * FROM $t_uf WHERE item_type = $extraFieldType ";
2187
        if (!$all_visibility) {
2188
            $sqlf .= " AND visible_to_self = 1 ";
2189
        }
2190
        if (!is_null($field_filter)) {
2191
            $field_filter = (int) $field_filter;
2192
            $sqlf .= " AND filter = $field_filter ";
2193
        }
2194
        $sqlf .= " ORDER BY `".$columns[$column]."` $sort_direction ";
2195
        if (0 != $number_of_items) {
2196
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
2197
        }
2198
        $resf = Database::query($sqlf);
2199
        if (Database::num_rows($resf) > 0) {
2200
            while ($rowf = Database::fetch_array($resf)) {
2201
                $fields[$rowf['id']] = [
2202
                    0 => $rowf['id'],
2203
                    1 => $rowf['variable'],
2204
                    2 => $rowf['value_type'],
2205
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
2206
                    4 => $rowf['default_value'],
2207
                    5 => $rowf['field_order'],
2208
                    6 => $rowf['visible_to_self'],
2209
                    7 => $rowf['changeable'],
2210
                    8 => $rowf['filter'],
2211
                    9 => [],
2212
                    10 => '<a name="'.$rowf['id'].'"></a>',
2213
                ];
2214
2215
                $sqlo = "SELECT * FROM $t_ufo
2216
                         WHERE field_id = ".$rowf['id']."
2217
                         ORDER BY option_order ASC";
2218
                $reso = Database::query($sqlo);
2219
                if (Database::num_rows($reso) > 0) {
2220
                    while ($rowo = Database::fetch_array($reso)) {
2221
                        $fields[$rowf['id']][9][$rowo['id']] = [
2222
                            0 => $rowo['id'],
2223
                            1 => $rowo['option_value'],
2224
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
2225
                            3 => $rowo['option_order'],
2226
                        ];
2227
                    }
2228
                }
2229
            }
2230
        }
2231
2232
        return $fields;
2233
    }
2234
2235
    /**
2236
     * Creates a new extra field.
2237
     *
2238
     * @param string $variable    Field's internal variable name
2239
     * @param int    $fieldType   Field's type
2240
     * @param string $displayText Field's language var name
2241
     * @param string $default     Field's default value
2242
     *
2243
     * @return int
2244
     */
2245
    public static function create_extra_field(
2246
        $variable,
2247
        $valueType,
2248
        $displayText,
2249
        $default
2250
    ) {
2251
        $extraField = new ExtraField('user');
2252
        $params = [
2253
            'variable' => $variable,
2254
            'value_type' => $valueType,
2255
            'display_text' => $displayText,
2256
            'default_value' => $default,
2257
        ];
2258
2259
        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...
2260
    }
2261
2262
    /**
2263
     * Check if a field is available.
2264
     *
2265
     * @param string $variable
2266
     *
2267
     * @return bool
2268
     */
2269
    public static function is_extra_field_available($variable)
2270
    {
2271
        $extraField = new ExtraField('user');
2272
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
2273
2274
        return !empty($data) ? true : false;
2275
    }
2276
2277
    /**
2278
     * Gets user extra fields data.
2279
     *
2280
     * @param    int    User ID
2281
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
2282
     * @param    bool    Whether to return invisible fields as well
2283
     * @param    bool    Whether to split multiple-selection fields or not
2284
     *
2285
     * @return array Array of fields => value for the given user
2286
     */
2287
    public static function get_extra_user_data(
2288
        $user_id,
2289
        $prefix = false,
2290
        $allVisibility = true,
2291
        $splitMultiple = false,
2292
        $fieldFilter = null
2293
    ) {
2294
        $user_id = (int) $user_id;
2295
2296
        if (empty($user_id)) {
2297
            return [];
2298
        }
2299
2300
        $extra_data = [];
2301
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2302
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2303
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2304
                FROM $t_uf f
2305
                WHERE
2306
                    item_type = ".EntityExtraField::USER_FIELD_TYPE."
2307
                ";
2308
        $filter_cond = '';
2309
2310
        if (!$allVisibility) {
2311
            if (isset($fieldFilter)) {
2312
                $fieldFilter = (int) $fieldFilter;
2313
                $filter_cond .= " AND filter = $fieldFilter ";
2314
            }
2315
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
2316
        } else {
2317
            if (isset($fieldFilter)) {
2318
                $fieldFilter = (int) $fieldFilter;
2319
                $sql .= " AND filter = $fieldFilter ";
2320
            }
2321
        }
2322
2323
        $sql .= ' ORDER BY f.field_order';
2324
2325
        $res = Database::query($sql);
2326
        if (Database::num_rows($res) > 0) {
2327
            while ($row = Database::fetch_array($res)) {
2328
                if (self::USER_FIELD_TYPE_TAG == $row['type']) {
2329
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
2330
                    $extra_data['extra_'.$row['fvar']] = $tags;
2331
                } else {
2332
                    $sqlu = "SELECT field_value as fval
2333
                            FROM $t_ufv
2334
                            WHERE field_id=".$row['id']." AND item_id = ".$user_id;
2335
                    $resu = Database::query($sqlu);
2336
                    // get default value
2337
                    $sql_df = "SELECT default_value as fval_df FROM $t_uf
2338
                               WHERE id=".$row['id'];
2339
                    $res_df = Database::query($sql_df);
2340
2341
                    if (Database::num_rows($resu) > 0) {
2342
                        $rowu = Database::fetch_array($resu);
2343
                        $fval = $rowu['fval'];
2344
                        if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2345
                            $fval = explode(';', $rowu['fval']);
2346
                        }
2347
                    } else {
2348
                        $row_df = Database::fetch_array($res_df);
2349
                        $fval = $row_df['fval_df'];
2350
                    }
2351
                    // We get here (and fill the $extra_data array) even if there
2352
                    // is no user with data (we fill it with default values)
2353
                    if ($prefix) {
2354
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2355
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2356
                        } else {
2357
                            $extra_data['extra_'.$row['fvar']] = $fval;
2358
                        }
2359
                    } else {
2360
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2361
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2362
                        } else {
2363
                            $extra_data[$row['fvar']] = $fval;
2364
                        }
2365
                    }
2366
                }
2367
            }
2368
        }
2369
2370
        return $extra_data;
2371
    }
2372
2373
    /** Get extra user data by field.
2374
     * @param int    user ID
2375
     * @param string the internal variable name of the field
2376
     *
2377
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2378
     */
2379
    public static function get_extra_user_data_by_field(
2380
        $user_id,
2381
        $field_variable,
2382
        $prefix = false,
2383
        $all_visibility = true,
2384
        $splitmultiple = false
2385
    ) {
2386
        $user_id = (int) $user_id;
2387
2388
        if (empty($user_id)) {
2389
            return [];
2390
        }
2391
2392
        $extra_data = [];
2393
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2394
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2395
2396
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2397
                FROM $t_uf f
2398
                WHERE f.variable = '$field_variable' ";
2399
2400
        if (!$all_visibility) {
2401
            $sql .= " AND f.visible_to_self = 1 ";
2402
        }
2403
2404
        $sql .= " AND item_type = ".EntityExtraField::USER_FIELD_TYPE;
2405
        $sql .= " ORDER BY f.field_order ";
2406
2407
        $res = Database::query($sql);
2408
        if (Database::num_rows($res) > 0) {
2409
            while ($row = Database::fetch_array($res)) {
2410
                $sqlu = "SELECT field_value as fval FROM $t_ufv v
2411
                         INNER JOIN $t_uf f
2412
                         ON (v.field_id = f.id)
2413
                         WHERE
2414
                            item_type = ".EntityExtraField::USER_FIELD_TYPE." AND
2415
                            field_id = ".$row['id']." AND
2416
                            item_id = ".$user_id;
2417
                $resu = Database::query($sqlu);
2418
                $fval = '';
2419
                if (Database::num_rows($resu) > 0) {
2420
                    $rowu = Database::fetch_array($resu);
2421
                    $fval = $rowu['fval'];
2422
                    if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2423
                        $fval = explode(';', $rowu['fval']);
2424
                    }
2425
                }
2426
                if ($prefix) {
2427
                    $extra_data['extra_'.$row['fvar']] = $fval;
2428
                } else {
2429
                    $extra_data[$row['fvar']] = $fval;
2430
                }
2431
            }
2432
        }
2433
2434
        return $extra_data;
2435
    }
2436
2437
    /**
2438
     * Get the extra field information for a certain field (the options as well).
2439
     *
2440
     * @param int $variable The name of the field we want to know everything about
2441
     *
2442
     * @return array Array containing all the information about the extra profile field
2443
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2444
     *               as returned by the database)
2445
     *
2446
     * @author Julio Montoya
2447
     *
2448
     * @since v1.8.6
2449
     */
2450
    public static function get_extra_field_information_by_name($variable)
2451
    {
2452
        $extraField = new ExtraField('user');
2453
2454
        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...
2455
    }
2456
2457
    /**
2458
     * Get the extra field information for user tag (the options as well).
2459
     *
2460
     * @param int $variable The name of the field we want to know everything about
2461
     *
2462
     * @return array Array containing all the information about the extra profile field
2463
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2464
     *               as returned by the database)
2465
     *
2466
     * @author José Loguercio
2467
     *
2468
     * @since v1.11.0
2469
     */
2470
    public static function get_extra_field_tags_information_by_name($variable)
2471
    {
2472
        $extraField = new ExtraField('user');
2473
2474
        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...
2475
    }
2476
2477
    /**
2478
     * Get all the extra field information of a certain field (also the options).
2479
     *
2480
     * @param int $fieldId the ID of the field we want to know everything of
2481
     *
2482
     * @return array $return containing all th information about the extra profile field
2483
     *
2484
     * @author Julio Montoya
2485
     *
2486
     * @deprecated
2487
     * @since v1.8.6
2488
     */
2489
    public static function get_extra_field_information($fieldId)
2490
    {
2491
        $extraField = new ExtraField('user');
2492
2493
        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...
2494
    }
2495
2496
    /**
2497
     * Get extra user data by value.
2498
     *
2499
     * @param string $variable       the internal variable name of the field
2500
     * @param string $value          the internal value of the field
2501
     * @param bool   $all_visibility
2502
     *
2503
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2504
     */
2505
    public static function get_extra_user_data_by_value($variable, $value, $all_visibility = true)
2506
    {
2507
        $extraFieldValue = new ExtraFieldValue('user');
2508
        $extraField = new ExtraField('user');
2509
2510
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
2511
2512
        if (false === $info) {
2513
            return [];
2514
        }
2515
2516
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
2517
            $variable,
2518
            $value,
2519
            false,
2520
            false,
2521
            true
2522
        );
2523
2524
        $result = [];
2525
        if (!empty($data)) {
2526
            foreach ($data as $item) {
2527
                $result[] = $item['item_id'];
2528
            }
2529
        }
2530
2531
        return $result;
2532
    }
2533
2534
    /**
2535
     * Get extra user data by tags value.
2536
     *
2537
     * @param int    $fieldId the ID of the field we want to know everything of
2538
     * @param string $tag     the tag name for search
2539
     *
2540
     * @return array with extra data info of a user
2541
     *
2542
     * @author José Loguercio
2543
     *
2544
     * @since v1.11.0
2545
     */
2546
    public static function get_extra_user_data_by_tags($fieldId, $tag)
2547
    {
2548
        $extraField = new ExtraField('user');
2549
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
2550
        $array = [];
2551
        foreach ($result as $index => $user) {
2552
            $array[] = $user['user_id'];
2553
        }
2554
2555
        return $array;
2556
    }
2557
2558
    /**
2559
     * Get extra user data by field variable.
2560
     *
2561
     * @param string $variable field variable
2562
     *
2563
     * @return array data
2564
     */
2565
    public static function get_extra_user_data_by_field_variable($variable)
2566
    {
2567
        $extraInfo = self::get_extra_field_information_by_name($variable);
2568
        $field_id = (int) $extraInfo['id'];
2569
2570
        $extraField = new ExtraFieldValue('user');
2571
        $data = $extraField->getValuesByFieldId($field_id);
2572
2573
        if (!empty($data)) {
2574
            foreach ($data as $row) {
2575
                $user_id = $row['item_id'];
2576
                $data[$user_id] = $row;
2577
            }
2578
        }
2579
2580
        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...
2581
    }
2582
2583
    /**
2584
     * Get extra user data tags by field variable.
2585
     *
2586
     * @param string $variable field variable
2587
     *
2588
     * @return array
2589
     */
2590
    public static function get_extra_user_data_for_tags($variable)
2591
    {
2592
        $data = self::get_extra_field_tags_information_by_name($variable);
2593
2594
        return $data;
2595
    }
2596
2597
    /**
2598
     * Gives a list of [session_category][session_id] for the current user.
2599
     *
2600
     * @param int  $user_id
2601
     * @param bool $is_time_over                 whether to fill the first element or not
2602
     *                                           (to give space for courses out of categories)
2603
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
2604
     * @param bool $ignoreTimeLimit              ignore time start/end
2605
     * @param bool $getCount
2606
     *
2607
     * @return array list of statuses [session_category][session_id]
2608
     *
2609
     * @todo ensure multiple access urls are managed correctly
2610
     */
2611
    public static function get_sessions_by_category(
2612
        $user_id,
2613
        $is_time_over = true,
2614
        $ignore_visibility_for_admins = false,
2615
        $ignoreTimeLimit = false,
2616
        $getCount = false
2617
    ) {
2618
        $user_id = (int) $user_id;
2619
2620
        if (empty($user_id)) {
2621
            return [];
2622
        }
2623
2624
        // Get the list of sessions per user
2625
        $now = new DateTime('now', new DateTimeZone('UTC'));
2626
2627
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
2628
        // join would not catch session-courses where the user is general
2629
        // session coach but which do not have students nor coaches registered
2630
        $dqlSelect = ' COUNT(DISTINCT s.id) ';
2631
2632
        if (!$getCount) {
2633
            $dqlSelect = " DISTINCT
2634
                s.id,
2635
                s.title,
2636
                s.accessStartDate AS access_start_date,
2637
                s.accessEndDate AS access_end_date,
2638
                s.duration,
2639
                sc.id AS session_category_id,
2640
                sc.title AS session_category_title,
2641
                sc.dateStart AS session_category_date_start,
2642
                sc.dateEnd AS session_category_date_end,
2643
                s.coachAccessStartDate AS coach_access_start_date,
2644
                s.coachAccessEndDate AS coach_access_end_date,
2645
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
2646
                , s.position AS position
2647
            ";
2648
        }
2649
2650
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
2651
        // is awfully inefficient for large sets of data (1m25s for 58K
2652
        // sessions, BT#14115) but executing a similar query twice and grouping
2653
        // the results afterwards in PHP takes about 1/1000th of the time
2654
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
2655
        $dqlStudent = "SELECT $dqlSelect
2656
            FROM ChamiloCoreBundle:Session AS s
2657
            LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
2658
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2659
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2660
            WHERE scu.user = :user AND url.url = :url ";
2661
        $dqlCoach = "SELECT $dqlSelect
2662
            FROM ChamiloCoreBundle:Session AS s
2663
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2664
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2665
            INNER JOIN ChamiloCoreBundle:SessionRelUser AS su WITH su.session = s
2666
            WHERE (su.user = :user AND su.relationType = ".SessionEntity::GENERAL_COACH.") AND url.url = :url ";
2667
2668
        // Default order
2669
        $order = 'ORDER BY sc.title, s.title';
2670
2671
        // Order by date if showing all sessions
2672
        $showAllSessions = ('true' === api_get_setting('course.show_all_sessions_on_my_course_page'));
2673
        if ($showAllSessions) {
2674
            $order = 'ORDER BY s.accessStartDate';
2675
        }
2676
2677
        // Order by position
2678
        if ('true' === api_get_setting('session.session_list_order')) {
2679
            $order = 'ORDER BY s.position';
2680
        }
2681
2682
        // Order by dates according to settings
2683
        $orderBySettings = api_get_setting('session.my_courses_session_order', true);
2684
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
2685
            $field = $orderBySettings['field'];
2686
            $orderSetting = $orderBySettings['order'];
2687
            switch ($field) {
2688
                case 'start_date':
2689
                    $order = " ORDER BY s.accessStartDate $orderSetting";
2690
                    break;
2691
                case 'end_date':
2692
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
2693
                    if ('asc' == $orderSetting) {
2694
                        // Put null values at the end
2695
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
2696
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
2697
                    }
2698
                    break;
2699
                case 'name':
2700
                case 'title':
2701
                    $order = " ORDER BY s.title $orderSetting ";
2702
                    break;
2703
            }
2704
        }
2705
2706
        $dqlStudent .= $order;
2707
        $dqlCoach .= $order;
2708
2709
        $accessUrlId = api_get_current_access_url_id();
2710
        $dqlStudent = Database::getManager()
2711
            ->createQuery($dqlStudent)
2712
            ->setParameters(
2713
                ['user' => $user_id, 'url' => $accessUrlId]
2714
            )
2715
        ;
2716
        $dqlCoach = Database::getManager()
2717
            ->createQuery($dqlCoach)
2718
            ->setParameters(
2719
                ['user' => $user_id, 'url' => $accessUrlId]
2720
            )
2721
        ;
2722
2723
        if ($getCount) {
2724
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
2725
        }
2726
2727
        $sessionDataStudent = $dqlStudent->getResult();
2728
        $sessionDataCoach = $dqlCoach->getResult();
2729
2730
        $sessionData = [];
2731
        // First fill $sessionData with student sessions
2732
        if (!empty($sessionDataStudent)) {
2733
            foreach ($sessionDataStudent as $row) {
2734
                $sessionData[$row['id']] = $row;
2735
            }
2736
        }
2737
        // Overwrite session data of the user as a student with session data
2738
        // of the user as a coach.
2739
        // There shouldn't be such duplicate rows, but just in case...
2740
        if (!empty($sessionDataCoach)) {
2741
            foreach ($sessionDataCoach as $row) {
2742
                $sessionData[$row['id']] = $row;
2743
            }
2744
        }
2745
2746
        $collapsable = ('true' === api_get_setting('session.allow_user_session_collapsable'));
2747
2748
2749
2750
        $extraField = new ExtraFieldValue('session');
2751
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
2752
2753
        if (empty($sessionData)) {
2754
            return [];
2755
        }
2756
        $categories = [];
2757
        foreach ($sessionData as $row) {
2758
            $session_id = $row['id'];
2759
            $coachList = SessionManager::getCoachesBySession($session_id);
2760
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
2761
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
2762
            $courseList = self::get_courses_list_by_session($user_id, $session_id);
2763
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
2764
2765
            // User portal filters:
2766
            if (false === $ignoreTimeLimit) {
2767
                if ($is_time_over) {
2768
                    // History
2769
                    if ($row['duration']) {
2770
                        if ($daysLeft >= 0) {
2771
                            continue;
2772
                        }
2773
                    } else {
2774
                        if (empty($row['access_end_date'])) {
2775
                            continue;
2776
                        } else {
2777
                            if ($row['access_end_date'] > $now) {
2778
                                continue;
2779
                            }
2780
                        }
2781
                    }
2782
                } else {
2783
                    // Current user portal
2784
                    $isGeneralCoach = api_get_session_entity($row['id'])->hasUserAsGeneralCoach(api_get_user_entity($user_id));
2785
                    $isCoachOfCourse = in_array($user_id, $coachList);
2786
2787
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
2788
                        // Teachers can access the session depending in the access_coach date
2789
                    } else {
2790
                        if ($row['duration']) {
2791
                            if ($daysLeft <= 0) {
2792
                                continue;
2793
                            }
2794
                        } else {
2795
                            if (isset($row['access_end_date']) &&
2796
                                !empty($row['access_end_date'])
2797
                            ) {
2798
                                if ($row['access_end_date'] <= $now) {
2799
                                    continue;
2800
                                }
2801
                            }
2802
                        }
2803
                    }
2804
                }
2805
            }
2806
2807
            $categories[$row['session_category_id']]['session_category'] = [
2808
                'id' => $row['session_category_id'],
2809
                'name' => $row['session_category_title'],
2810
                'date_start' => $categoryStart,
2811
                'date_end' => $categoryEnd,
2812
            ];
2813
2814
            $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

2814
            $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...
2815
                $session_id,
2816
                null,
2817
                $ignore_visibility_for_admins
2818
            );
2819
2820
            if (SESSION_VISIBLE != $visibility) {
2821
                // Course Coach session visibility.
2822
                $blockedCourseCount = 0;
2823
                $closedVisibilityList = [
2824
                    COURSE_VISIBILITY_CLOSED,
2825
                    COURSE_VISIBILITY_HIDDEN,
2826
                ];
2827
2828
                foreach ($courseList as $course) {
2829
                    // Checking session visibility
2830
                    $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

2830
                    $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...
2831
                        $session_id,
2832
                        $course['real_id'],
2833
                        $ignore_visibility_for_admins
2834
                    );
2835
2836
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
2837
                    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

2837
                    if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
2838
                        $blockedCourseCount++;
2839
                    }
2840
                }
2841
2842
                // If all courses are blocked then no show in the list.
2843
                if ($blockedCourseCount === count($courseList)) {
2844
                    $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

2844
                    $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2845
                } else {
2846
                    $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...
2847
                }
2848
            }
2849
2850
            switch ($visibility) {
2851
                case SESSION_VISIBLE_READ_ONLY:
2852
                case SESSION_VISIBLE:
2853
                case SESSION_AVAILABLE:
2854
                    break;
2855
                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

2855
                case /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE:
Loading history...
2856
                    if (false === $ignore_visibility_for_admins) {
2857
                        continue 2;
2858
                    }
2859
            }
2860
2861
            $collapsed = '';
2862
            $collapsedAction = '';
2863
            if ($collapsable) {
2864
                $collapsableData = SessionManager::getCollapsableData(
2865
                    $user_id,
2866
                    $session_id,
2867
                    $extraField,
2868
                    $collapsableLink
2869
                );
2870
                $collapsed = $collapsableData['collapsed'];
2871
                $collapsedAction = $collapsableData['collapsable_link'];
2872
            }
2873
2874
            $categories[$row['session_category_id']]['sessions'][] = [
2875
                'session_name' => $row['title'],
2876
                'session_id' => $row['id'],
2877
                'access_start_date' => $row['access_start_date'] ? $row['access_start_date']->format('Y-m-d H:i:s') : null,
2878
                'access_end_date' => $row['access_end_date'] ? $row['access_end_date']->format('Y-m-d H:i:s') : null,
2879
                'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
2880
                'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
2881
                'courses' => $courseList,
2882
                'collapsed' => $collapsed,
2883
                'collapsable_link' => $collapsedAction,
2884
                'duration' => $row['duration'],
2885
            ];
2886
        }
2887
2888
        return $categories;
2889
    }
2890
2891
    /**
2892
     * Gives a list of [session_id-course_code] => [status] for the current user.
2893
     *
2894
     * @param  int  $user_id
2895
     * @param  int  $sessionLimit
2896
     *
2897
     * @return array list of statuses (session_id-course_code => status)
2898
     *
2899
     * @throws Exception
2900
     */
2901
    public static function get_personal_session_course_list($user_id, $sessionLimit = null)
2902
    {
2903
        // Database Table Definitions
2904
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
2905
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
2906
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
2907
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
2908
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
2909
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2910
2911
        $user_id = (int) $user_id;
2912
2913
        if (empty($user_id)) {
2914
            return [];
2915
        }
2916
2917
        $sessionRepo = Container::getSessionRepository();
2918
2919
        $user = api_get_user_entity($user_id);
2920
        $url = null;
2921
        $formattedUserName = Container::$container->get(NameConventionHelper::class)->getPersonName($user);
2922
2923
        // We filter the courses from the URL
2924
        $join_access_url = $where_access_url = '';
2925
        if (api_get_multiple_access_url()) {
2926
            $access_url_id = api_get_current_access_url_id();
2927
            if (-1 != $access_url_id) {
2928
                $url = api_get_url_entity($access_url_id);
2929
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
2930
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id";
2931
                $where_access_url = " AND access_url_id = $access_url_id ";
2932
            }
2933
        }
2934
2935
        // Courses in which we subscribed out of any session
2936
2937
        $sql = "SELECT
2938
                    course.code,
2939
                    course_rel_user.status course_rel_status,
2940
                    course_rel_user.sort sort,
2941
                    course_rel_user.user_course_cat user_course_cat
2942
                 FROM $tbl_course_user course_rel_user
2943
                 LEFT JOIN $tbl_course course
2944
                 ON course.id = course_rel_user.c_id
2945
                 $join_access_url
2946
                 WHERE
2947
                    course_rel_user.user_id = '".$user_id."' AND
2948
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
2949
                    $where_access_url
2950
                 ORDER BY course_rel_user.sort, course.title ASC";
2951
2952
        $course_list_sql_result = Database::query($sql);
2953
        $personal_course_list = [];
2954
        if (Database::num_rows($course_list_sql_result) > 0) {
2955
            while ($result_row = Database::fetch_assoc($course_list_sql_result)) {
2956
                $course_info = api_get_course_info($result_row['code']);
2957
                $result_row['course_info'] = $course_info;
2958
                $personal_course_list[] = $result_row;
2959
            }
2960
        }
2961
2962
        $coachCourseConditions = '';
2963
        // Getting sessions that are related to a coach in the session_rel_course_rel_user table
2964
        if (api_is_allowed_to_create_course()) {
2965
            $sessionListFromCourseCoach = [];
2966
            $sql = " SELECT DISTINCT session_id
2967
                    FROM $tbl_session_course_user
2968
                    WHERE user_id = $user_id AND status = ".SessionEntity::COURSE_COACH;
2969
2970
            $result = Database::query($sql);
2971
            if (Database::num_rows($result)) {
2972
                $result = Database::store_result($result);
2973
                foreach ($result as $session) {
2974
                    $sessionListFromCourseCoach[] = $session['session_id'];
2975
                }
2976
            }
2977
            if (!empty($sessionListFromCourseCoach)) {
2978
                $condition = implode("','", $sessionListFromCourseCoach);
2979
                $coachCourseConditions = " OR ( s.id IN ('$condition'))";
2980
            }
2981
        }
2982
2983
        // Get the list of sessions where the user is subscribed
2984
        // This is divided into two different queries
2985
        $sessions = [];
2986
        $sessionLimitRestriction = '';
2987
        if (!empty($sessionLimit)) {
2988
            $sessionLimit = (int) $sessionLimit;
2989
            $sessionLimitRestriction = "LIMIT $sessionLimit";
2990
        }
2991
2992
        $sql = "SELECT DISTINCT s.id, s.title, access_start_date, access_end_date
2993
                FROM $tbl_session_user su INNER JOIN $tbl_session s
2994
                ON (s.id = su.session_id)
2995
                WHERE (
2996
                    su.user_id = $user_id AND
2997
                    su.relation_type = ".SessionEntity::STUDENT."
2998
                )
2999
                $coachCourseConditions
3000
                ORDER BY access_start_date, access_end_date, s.title
3001
                $sessionLimitRestriction
3002
        ";
3003
3004
        $result = Database::query($sql);
3005
        if (Database::num_rows($result) > 0) {
3006
            while ($row = Database::fetch_assoc($result)) {
3007
                $sessions[$row['id']] = $row;
3008
            }
3009
        }
3010
3011
        $sql = "SELECT DISTINCT
3012
                s.id, s.title, s.access_start_date, s.access_end_date
3013
                FROM $tbl_session s
3014
                INNER JOIN $tbl_session_user sru ON sru.session_id = s.id
3015
                WHERE (
3016
                    sru.user_id = $user_id AND sru.relation_type = ".SessionEntity::GENERAL_COACH."
3017
                )
3018
                $coachCourseConditions
3019
                ORDER BY s.access_start_date, s.access_end_date, s.title";
3020
3021
        $result = Database::query($sql);
3022
        if (Database::num_rows($result) > 0) {
3023
            while ($row = Database::fetch_assoc($result)) {
3024
                if (empty($sessions[$row['id']])) {
3025
                    $sessions[$row['id']] = $row;
3026
                }
3027
            }
3028
        }
3029
3030
        if (api_is_allowed_to_create_course()) {
3031
            foreach ($sessions as $enreg) {
3032
                $session_id = $enreg['id'];
3033
                $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

3033
                $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...
3034
                $session = api_get_session_entity($session_id);
3035
3036
                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

3036
                if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3037
                    continue;
3038
                }
3039
3040
                $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3041
                    $user,
3042
                    $session,
3043
                    SessionEntity::GENERAL_COACH,
3044
                    $url
3045
                );
3046
                $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3047
                    $user,
3048
                    $session,
3049
                    SessionEntity::COURSE_COACH,
3050
                    $url
3051
                );
3052
3053
                // This query is horribly slow when more than a few thousand
3054
                // users and just a few sessions to which they are subscribed
3055
                $coursesInSession = array_map(
3056
                    function (SessionRelCourse $courseInSession) {
3057
                        $course = $courseInSession->getCourse();
3058
3059
                        return [
3060
                            'code' => $course->getCode(),
3061
                            'i' => $course->getTitle(),
3062
                            'l' => $course->getCourseLanguage(),
3063
                            'sort' => 1,
3064
                        ];
3065
                    },
3066
                    array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3067
                );
3068
3069
                foreach ($coursesInSession as $result_row) {
3070
                    $result_row['t'] = $formattedUserName;
3071
                    $result_row['email'] = $user->getEmail();
3072
                    $result_row['access_start_date'] = $session->getAccessStartDate()?->format('Y-m-d H:i:s');
3073
                    $result_row['access_end_date'] = $session->getAccessEndDate()?->format('Y-m-d H:i:s');
3074
                    $result_row['session_id'] = $session->getId();
3075
                    $result_row['session_name'] = $session->getTitle();
3076
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
3077
                    $key = $result_row['session_id'].' - '.$result_row['code'];
3078
                    $personal_course_list[$key] = $result_row;
3079
                }
3080
            }
3081
        }
3082
3083
        foreach ($sessions as $enreg) {
3084
            $session_id = $enreg['id'];
3085
            $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

3085
            $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...
3086
            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

3086
            if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3087
                continue;
3088
            }
3089
3090
            /* This query is very similar to the above query,
3091
               but it will check the session_rel_course_user table if there are courses registered to our user or not */
3092
            $sql = "SELECT DISTINCT
3093
                course.code code,
3094
                course.title i, CONCAT(user.lastname,' ',user.firstname) t,
3095
                email,
3096
                course.course_language l,
3097
                1 sort,
3098
                access_start_date,
3099
                access_end_date,
3100
                session.id as session_id,
3101
                session.title as session_name,
3102
                IF((session_course_user.user_id = 3 AND session_course_user.status = ".SessionEntity::COURSE_COACH."),'2', '5')
3103
            FROM $tbl_session_course_user as session_course_user
3104
            INNER JOIN $tbl_course AS course
3105
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
3106
            INNER JOIN $tbl_session as session
3107
            ON session_course_user.session_id = session.id
3108
            LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
3109
            WHERE session_course_user.user_id = $user_id
3110
            ORDER BY i";
3111
3112
            $course_list_sql_result = Database::query($sql);
3113
            while ($result_row = Database::fetch_assoc($course_list_sql_result)) {
3114
                $result_row['course_info'] = api_get_course_info($result_row['code']);
3115
                $key = $result_row['session_id'].' - '.$result_row['code'];
3116
                if (!isset($personal_course_list[$key])) {
3117
                    $personal_course_list[$key] = $result_row;
3118
                }
3119
            }
3120
        }
3121
3122
        return $personal_course_list;
3123
    }
3124
3125
    /**
3126
     * Gives a list of courses for the given user in the given session.
3127
     *
3128
     * @param int $user_id
3129
     * @param int $session_id
3130
     *
3131
     * @return array list of statuses (session_id-course_code => status)
3132
     */
3133
    public static function get_courses_list_by_session($user_id, $session_id)
3134
    {
3135
        // Database Table Definitions
3136
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
3137
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3138
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3139
3140
        $user_id = (int) $user_id;
3141
        $session_id = (int) $session_id;
3142
3143
        $sessionRepo = Container::getSessionRepository();
3144
3145
        $user = api_get_user_entity($user_id);
3146
        $session = api_get_session_entity($session_id);
3147
        $url = null;
3148
3149
        // We filter the courses from the URL
3150
        $join_access_url = $where_access_url = '';
3151
        if (api_get_multiple_access_url()) {
3152
            $urlId = api_get_current_access_url_id();
3153
            if (-1 != $urlId) {
3154
                $url = api_get_url_entity($urlId);
3155
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3156
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
3157
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
3158
            }
3159
        }
3160
3161
        /* This query is very similar to the query below, but it will check the
3162
        session_rel_course_user table if there are courses registered
3163
        to our user or not */
3164
        $sql = "SELECT DISTINCT
3165
                    c.title,
3166
                    c.visibility,
3167
                    c.id as real_id,
3168
                    c.code as course_code,
3169
                    sc.position,
3170
                    c.unsubscribe
3171
                FROM $tbl_session_course_user as scu
3172
                INNER JOIN $tbl_session_course sc
3173
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
3174
                INNER JOIN $tableCourse as c
3175
                ON (scu.c_id = c.id)
3176
                $join_access_url
3177
                WHERE
3178
                    scu.user_id = $user_id AND
3179
                    scu.session_id = $session_id
3180
                    $where_access_url
3181
                ORDER BY sc.position ASC";
3182
3183
        $myCourseList = [];
3184
        $courses = [];
3185
        $result = Database::query($sql);
3186
        if (Database::num_rows($result) > 0) {
3187
            while ($result_row = Database::fetch_assoc($result)) {
3188
                $result_row['status'] = 5;
3189
                if (!in_array($result_row['real_id'], $courses)) {
3190
                    $position = $result_row['position'];
3191
                    if (!isset($myCourseList[$position])) {
3192
                        $myCourseList[$position] = $result_row;
3193
                    } else {
3194
                        $myCourseList[] = $result_row;
3195
                    }
3196
                    $courses[] = $result_row['real_id'];
3197
                }
3198
            }
3199
        }
3200
3201
        if (api_is_allowed_to_create_course()) {
3202
            $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3203
                $user,
3204
                $session,
3205
                SessionEntity::GENERAL_COACH,
3206
                $url
3207
            );
3208
            $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3209
                $user,
3210
                $session,
3211
                SessionEntity::COURSE_COACH,
3212
                $url
3213
            );
3214
3215
            $coursesInSession = array_map(
3216
                function (SessionRelCourse $courseInSession) {
3217
                    $course = $courseInSession->getCourse();
3218
3219
                    return [
3220
                        'title' => $course->getTitle(),
3221
                        'visibility' => $course->getVisibility(),
3222
                        'real_id' => $course->getId(),
3223
                        'course_code' => $course->getCode(),
3224
                        'position' => $courseInSession->getPosition(),
3225
                        'unsubscribe' => $course->getUnsubscribe(),
3226
                    ];
3227
                },
3228
                array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3229
            );
3230
3231
            foreach ($coursesInSession as $result_row) {
3232
                $result_row['status'] = 2;
3233
                if (!in_array($result_row['real_id'], $courses)) {
3234
                    $position = $result_row['position'];
3235
                    if (!isset($myCourseList[$position])) {
3236
                        $myCourseList[$position] = $result_row;
3237
                    } else {
3238
                        $myCourseList[] = $result_row;
3239
                    }
3240
                    $courses[] = $result_row['real_id'];
3241
                }
3242
            }
3243
        }
3244
3245
        if (api_is_drh()) {
3246
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
3247
            $sessionList = array_keys($sessionList);
3248
            if (in_array($session_id, $sessionList)) {
3249
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3250
                if (!empty($courseList)) {
3251
                    foreach ($courseList as $course) {
3252
                        if (!in_array($course['id'], $courses)) {
3253
                            $position = $course['position'];
3254
                            if (!isset($myCourseList[$position])) {
3255
                                $myCourseList[$position] = $course;
3256
                            } else {
3257
                                $myCourseList[] = $course;
3258
                            }
3259
                        }
3260
                    }
3261
                }
3262
            }
3263
        } else {
3264
            //check if user is general coach for this session
3265
            if ($session && $session->hasUserAsGeneralCoach($user)) {
3266
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3267
                if (!empty($courseList)) {
3268
                    foreach ($courseList as $course) {
3269
                        if (!in_array($course['id'], $courses)) {
3270
                            $position = $course['position'];
3271
                            if (!isset($myCourseList[$position])) {
3272
                                $myCourseList[$position] = $course;
3273
                            } else {
3274
                                $myCourseList[] = $course;
3275
                            }
3276
                        }
3277
                    }
3278
                }
3279
            }
3280
        }
3281
3282
        if (!empty($myCourseList)) {
3283
            ksort($myCourseList);
3284
            $checkPosition = array_filter(array_column($myCourseList, 'position'));
3285
            if (empty($checkPosition)) {
3286
                // The session course list doesn't have any position,
3287
                // then order the course list by course code
3288
                $list = array_column($myCourseList, 'course_code');
3289
                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

3289
                array_multisort($myCourseList, /** @scrutinizer ignore-type */ SORT_ASC, $list);
Loading history...
3290
            }
3291
        }
3292
3293
        return $myCourseList;
3294
    }
3295
3296
    /**
3297
     * Get user id from a username.
3298
     *
3299
     * @param string $username
3300
     *
3301
     * @return int User ID (or false if not found)
3302
     */
3303
    public static function get_user_id_from_username($username)
3304
    {
3305
        if (empty($username)) {
3306
            return false;
3307
        }
3308
        $username = trim($username);
3309
        $username = Database::escape_string($username);
3310
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
3311
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
3312
        $res = Database::query($sql);
3313
3314
        if (false === $res) {
3315
            return false;
3316
        }
3317
        if (1 !== Database::num_rows($res)) {
3318
            return false;
3319
        }
3320
        $row = Database::fetch_array($res);
3321
3322
        return $row['id'];
3323
    }
3324
3325
    /**
3326
     * Get the users files upload from his share_folder.
3327
     *
3328
     * @param string $user_id      User ID
3329
     * @param string $course       course directory
3330
     * @param string $resourceType resource type: images, all
3331
     *
3332
     * @return string
3333
     */
3334
    /*public static function get_user_upload_files_by_course(
3335
        $user_id,
3336
        $course,
3337
        $resourceType = 'all'
3338
    ) {
3339
        $return = '';
3340
        $user_id = (int) $user_id;
3341
3342
        if (!empty($user_id) && !empty($course)) {
3343
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3344
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3345
            $file_list = [];
3346
3347
            if (is_dir($path)) {
3348
                $handle = opendir($path);
3349
                while ($file = readdir($handle)) {
3350
                    if ('.' == $file || '..' == $file || '.htaccess' == $file || is_dir($path.$file)) {
3351
                        continue; // skip current/parent directory and .htaccess
3352
                    }
3353
                    $file_list[] = $file;
3354
                }
3355
                if (count($file_list) > 0) {
3356
                    $return = "<h4>$course</h4>";
3357
                    $return .= '<ul class="thumbnails">';
3358
                }
3359
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
3360
                foreach ($file_list as $file) {
3361
                    if ('all' == $resourceType) {
3362
                        $return .= '<li>
3363
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
3364
                    } elseif ('images' == $resourceType) {
3365
                        //get extension
3366
                        $ext = explode('.', $file);
3367
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
3368
                            $return .= '<li class="span2">
3369
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
3370
                                                <img src="'.$web_path.urlencode($file).'" >
3371
                                            </a>
3372
                                        </li>';
3373
                        }
3374
                    }
3375
                }
3376
                if (count($file_list) > 0) {
3377
                    $return .= '</ul>';
3378
                }
3379
            }
3380
        }
3381
3382
        return $return;
3383
    }*/
3384
3385
    /**
3386
     * Gets the API key (or keys) and return them into an array.
3387
     *
3388
     * @param int     Optional user id (defaults to the result of api_get_user_id())
3389
     * @param string $api_service
3390
     *
3391
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
3392
     */
3393
    public static function get_api_keys($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
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
3410
        $res = Database::query($sql);
3411
        if (false === $res) {
3412
            return false;
3413
        } //error during query
3414
        $num = Database::num_rows($res);
3415
        if (0 == $num) {
3416
            return false;
3417
        }
3418
        $list = [];
3419
        while ($row = Database::fetch_array($res)) {
3420
            $list[$row['id']] = $row['api_key'];
3421
        }
3422
3423
        return $list;
3424
    }
3425
3426
    /**
3427
     * Adds a new API key to the users' account.
3428
     *
3429
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
3430
     * @param string $api_service
3431
     *
3432
     * @return bool True on success, false on failure
3433
     */
3434
    public static function add_api_key($user_id = null, $api_service = 'default')
3435
    {
3436
        if ($user_id != strval(intval($user_id))) {
3437
            return false;
3438
        }
3439
        if (empty($user_id)) {
3440
            $user_id = api_get_user_id();
3441
        }
3442
        if (false === $user_id) {
3443
            return false;
3444
        }
3445
        $service_name = Database::escape_string($api_service);
3446
        if (false === is_string($service_name)) {
3447
            return false;
3448
        }
3449
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3450
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
3451
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
3452
        $res = Database::query($sql);
3453
        if (false === $res) {
3454
            return false;
3455
        } //error during query
3456
        $num = Database::insert_id();
3457
3458
        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...
3459
    }
3460
3461
    /**
3462
     * Deletes an API key from the user's account.
3463
     *
3464
     * @param   int     API key's internal ID
3465
     *
3466
     * @return bool True on success, false on failure
3467
     */
3468
    public static function delete_api_key($key_id)
3469
    {
3470
        if ($key_id != strval(intval($key_id))) {
3471
            return false;
3472
        }
3473
        if (false === $key_id) {
3474
            return false;
3475
        }
3476
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3477
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
3478
        $res = Database::query($sql);
3479
        if (false === $res) {
3480
            return false;
3481
        } //error during query
3482
        $num = Database::num_rows($res);
3483
        if (1 !== $num) {
3484
            return false;
3485
        }
3486
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
3487
        $res = Database::query($sql);
3488
        if (false === $res) {
3489
            return false;
3490
        } //error during query
3491
3492
        return true;
3493
    }
3494
3495
    /**
3496
     * Regenerate an API key from the user's account.
3497
     *
3498
     * @param   int     user ID (defaults to the results of api_get_user_id())
3499
     * @param   string  API key's internal ID
3500
     *
3501
     * @return int num
3502
     */
3503
    public static function update_api_key($user_id, $api_service)
3504
    {
3505
        if ($user_id != strval(intval($user_id))) {
3506
            return false;
3507
        }
3508
        if (false === $user_id) {
3509
            return false;
3510
        }
3511
        $service_name = Database::escape_string($api_service);
3512
        if (false === is_string($service_name)) {
3513
            return false;
3514
        }
3515
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3516
        $sql = "SELECT id FROM $t_api
3517
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3518
        $res = Database::query($sql);
3519
        $num = Database::num_rows($res);
3520
        if (1 == $num) {
3521
            $id_key = Database::fetch_assoc($res);
3522
            self::delete_api_key($id_key['id']);
3523
            $num = self::add_api_key($user_id, $api_service);
3524
        } elseif (0 == $num) {
3525
            $num = self::add_api_key($user_id, $api_service);
3526
        }
3527
3528
        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...
3529
    }
3530
3531
    /**
3532
     * @param   int     user ID (defaults to the results of api_get_user_id())
3533
     * @param   string    API key's internal ID
3534
     *
3535
     * @return int row ID, or return false if not found
3536
     */
3537
    public static function get_api_key_id($user_id, $api_service)
3538
    {
3539
        if ($user_id != strval(intval($user_id))) {
3540
            return false;
3541
        }
3542
        if (false === $user_id) {
3543
            return false;
3544
        }
3545
        if (empty($api_service)) {
3546
            return false;
3547
        }
3548
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3549
        $api_service = Database::escape_string($api_service);
3550
        $sql = "SELECT id FROM $t_api
3551
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3552
        $res = Database::query($sql);
3553
        if (Database::num_rows($res) < 1) {
3554
            return false;
3555
        }
3556
        $row = Database::fetch_assoc($res);
3557
3558
        return $row['id'];
3559
    }
3560
3561
    /**
3562
     * Checks if a user_id is platform admin.
3563
     *
3564
     * @param   int user ID
3565
     *
3566
     * @return bool True if is admin, false otherwise
3567
     *
3568
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
3569
     */
3570
    public static function is_admin($user_id)
3571
    {
3572
        $user_id = (int) $user_id;
3573
        if (empty($user_id)) {
3574
            return false;
3575
        }
3576
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
3577
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
3578
        $res = Database::query($sql);
3579
3580
        return 1 === Database::num_rows($res);
3581
    }
3582
3583
    /**
3584
     * Get the total count of users.
3585
     *
3586
     * @param ?int $status Status of users to be counted
3587
     * @param ?int $access_url_id Access URL ID (optional)
3588
     * @param ?int $active
3589
     *
3590
     * @return mixed Number of users or false on error
3591
     * @throws \Doctrine\DBAL\Exception
3592
     */
3593
    public static function get_number_of_users(
3594
        ?int $status = 0,
3595
        ?int $access_url_id = 1,
3596
        ?int $active = null,
3597
        ?string $dateFrom = null,
3598
        ?string $dateUntil = null
3599
    ): mixed {
3600
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
3601
        $tableAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3602
3603
        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

3603
        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...
3604
            $sql = "SELECT count(u.id)
3605
                    FROM $tableUser u
3606
                    INNER JOIN $tableAccessUrlRelUser url_user
3607
                    ON (u.id = url_user.user_id)
3608
                    WHERE url_user.access_url_id = $access_url_id
3609
            ";
3610
        } else {
3611
            $sql = "SELECT count(u.id)
3612
                    FROM $tableUser u
3613
                    WHERE 1 = 1 ";
3614
        }
3615
3616
        $status = (int) $status;
3617
        if (!empty($status) && $status > 0) {
3618
            $sql .= " AND u.status = $status ";
3619
        }
3620
3621
        if (isset($active)) {
3622
            $active = (int) $active;
3623
            $sql .= " AND u.active = $active ";
3624
        }
3625
3626
        if (!empty($dateFrom)) {
3627
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
3628
            $sql .= " AND u.created_at >= '$dateFrom' ";
3629
        }
3630
        if (!empty($dateUntil)) {
3631
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
3632
            $sql .= " AND u.created_at <= '$dateUntil' ";
3633
        }
3634
3635
        $res = Database::query($sql);
3636
        if (1 === Database::num_rows($res)) {
3637
            return (int) Database::result($res, 0, 0);
3638
        }
3639
3640
        return false;
3641
    }
3642
3643
    /**
3644
     * Gets the tags of a specific field_id
3645
     * USER TAGS.
3646
     *
3647
     * Instructions to create a new user tag by Julio Montoya <[email protected]>
3648
     *
3649
     * 1. Create a new extra field in main/admin/user_fields.php with the "TAG" field type make it available and visible.
3650
     *    Called it "books" for example.
3651
     * 2. Go to profile main/auth/profile.php There you will see a special input (facebook style) that will show suggestions of tags.
3652
     * 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
3653
     * 4. Tags are independent this means that tags can't be shared between tags + book + hobbies.
3654
     * 5. Test and enjoy.
3655
     *
3656
     * @param string $tag
3657
     * @param int    $field_id      field_id
3658
     * @param string $return_format how we are going to result value in array or in a string (json)
3659
     * @param $limit
3660
     *
3661
     * @return mixed
3662
     */
3663
    public static function get_tags($tag, $field_id, $return_format = 'json', $limit = 10)
3664
    {
3665
        // database table definition
3666
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3667
        $field_id = (int) $field_id;
3668
        $limit = (int) $limit;
3669
        $tag = trim(Database::escape_string($tag));
3670
3671
        // all the information of the field
3672
        $sql = "SELECT DISTINCT id, tag from $table_user_tag
3673
                WHERE field_id = $field_id AND tag LIKE '$tag%' ORDER BY tag LIMIT $limit";
3674
        $result = Database::query($sql);
3675
        $return = [];
3676
        if (Database::num_rows($result) > 0) {
3677
            while ($row = Database::fetch_assoc($result)) {
3678
                $return[] = ['id' => $row['tag'], 'text' => $row['tag']];
3679
            }
3680
        }
3681
        if ('json' === $return_format) {
3682
            $return = json_encode($return);
3683
        }
3684
3685
        return $return;
3686
    }
3687
3688
    /**
3689
     * @param int $field_id
3690
     * @param int $limit
3691
     *
3692
     * @return array
3693
     */
3694
    public static function get_top_tags($field_id, $limit = 100)
3695
    {
3696
        // database table definition
3697
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3698
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3699
        $field_id = (int) $field_id;
3700
        $limit = (int) $limit;
3701
        // all the information of the field
3702
        $sql = "SELECT count(*) count, tag FROM $table_user_tag_values  uv
3703
                INNER JOIN $table_user_tag ut
3704
                ON (ut.id = uv.tag_id)
3705
                WHERE field_id = $field_id
3706
                GROUP BY tag_id
3707
                ORDER BY count DESC
3708
                LIMIT $limit";
3709
        $result = Database::query($sql);
3710
        $return = [];
3711
        if (Database::num_rows($result) > 0) {
3712
            while ($row = Database::fetch_assoc($result)) {
3713
                $return[] = $row;
3714
            }
3715
        }
3716
3717
        return $return;
3718
    }
3719
3720
    /**
3721
     * Get user's tags.
3722
     *
3723
     * @param int $user_id
3724
     * @param int $field_id
3725
     *
3726
     * @return array
3727
     */
3728
    public static function get_user_tags($user_id, $field_id)
3729
    {
3730
        // database table definition
3731
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3732
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3733
        $field_id = (int) $field_id;
3734
        $user_id = (int) $user_id;
3735
3736
        // all the information of the field
3737
        $sql = "SELECT ut.id, tag, count
3738
                FROM $table_user_tag ut
3739
                INNER JOIN $table_user_tag_values uv
3740
                ON (uv.tag_id=ut.ID)
3741
                WHERE field_id = $field_id AND user_id = $user_id
3742
                ORDER BY tag";
3743
        $result = Database::query($sql);
3744
        $return = [];
3745
        if (Database::num_rows($result) > 0) {
3746
            while ($row = Database::fetch_assoc($result)) {
3747
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
3748
            }
3749
        }
3750
3751
        return $return;
3752
    }
3753
3754
    /**
3755
     * Get user's tags.
3756
     *
3757
     * @param int  $user_id
3758
     * @param int  $field_id
3759
     * @param bool $show_links show links or not
3760
     *
3761
     * @return string
3762
     */
3763
    public static function get_user_tags_to_string($user_id, $field_id, $show_links = true)
3764
    {
3765
        // database table definition
3766
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3767
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3768
        $field_id = (int) $field_id;
3769
        $user_id = (int) $user_id;
3770
3771
        // all the information of the field
3772
        $sql = "SELECT ut.id, tag,count FROM $table_user_tag ut
3773
                INNER JOIN $table_user_tag_values uv
3774
                ON (uv.tag_id = ut.id)
3775
                WHERE field_id = $field_id AND user_id = $user_id
3776
                ORDER BY tag";
3777
3778
        $result = Database::query($sql);
3779
        $return = [];
3780
        if (Database::num_rows($result) > 0) {
3781
            while ($row = Database::fetch_assoc($result)) {
3782
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
3783
            }
3784
        }
3785
        $user_tags = $return;
3786
        $tag_tmp = [];
3787
        foreach ($user_tags as $tag) {
3788
            if ($show_links) {
3789
                $tag_tmp[] = '<a href="'.api_get_path(WEB_PATH).'main/search/index.php?q='.$tag['tag'].'">'.
3790
                    $tag['tag'].
3791
                '</a>';
3792
            } else {
3793
                $tag_tmp[] = $tag['tag'];
3794
            }
3795
        }
3796
3797
        if (is_array($user_tags) && count($user_tags) > 0) {
3798
            return implode(', ', $tag_tmp);
3799
        } else {
3800
            return '';
3801
        }
3802
    }
3803
3804
    /**
3805
     * Get the tag id.
3806
     *
3807
     * @param int $tag
3808
     * @param int $field_id
3809
     *
3810
     * @return int returns 0 if fails otherwise the tag id
3811
     */
3812
    public static function get_tag_id($tag, $field_id)
3813
    {
3814
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3815
        $tag = Database::escape_string($tag);
3816
        $field_id = (int) $field_id;
3817
        //with COLLATE latin1_bin to select query in a case sensitive mode
3818
        $sql = "SELECT id FROM $table_user_tag
3819
                WHERE tag LIKE '$tag' AND field_id = $field_id";
3820
        $result = Database::query($sql);
3821
        if (Database::num_rows($result) > 0) {
3822
            $row = Database::fetch_assoc($result);
3823
3824
            return $row['id'];
3825
        } else {
3826
            return 0;
3827
        }
3828
    }
3829
3830
    /**
3831
     * Get the tag id.
3832
     *
3833
     * @param int $tag_id
3834
     * @param int $field_id
3835
     *
3836
     * @return int 0 if fails otherwise the tag id
3837
     */
3838
    public static function get_tag_id_from_id($tag_id, $field_id)
3839
    {
3840
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3841
        $tag_id = (int) $tag_id;
3842
        $field_id = (int) $field_id;
3843
        $sql = "SELECT id FROM $table_user_tag
3844
                WHERE id = '$tag_id' AND field_id = $field_id";
3845
        $result = Database::query($sql);
3846
        if (Database::num_rows($result) > 0) {
3847
            $row = Database::fetch_assoc($result);
3848
3849
            return $row['id'];
3850
        } else {
3851
            return false;
3852
        }
3853
    }
3854
3855
    /**
3856
     * Adds a user-tag value.
3857
     *
3858
     * @param mixed $tag
3859
     * @param int   $user_id
3860
     * @param int   $field_id field id of the tag
3861
     *
3862
     * @return bool True if the tag was inserted or updated. False otherwise.
3863
     *              The return value doesn't take into account *values* added to the tag.
3864
     *              Only the creation/update of the tag field itself.
3865
     */
3866
    public static function add_tag($tag, $user_id, $field_id)
3867
    {
3868
        // database table definition
3869
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3870
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3871
        $tag = trim(Database::escape_string($tag));
3872
        $user_id = (int) $user_id;
3873
        $field_id = (int) $field_id;
3874
3875
        $tag_id = self::get_tag_id($tag, $field_id);
3876
3877
        /* IMPORTANT
3878
         *  @todo we don't create tags with numbers
3879
         *
3880
         */
3881
3882
        //this is a new tag
3883
        if (0 == $tag_id) {
3884
            //the tag doesn't exist
3885
            $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
3886
            Database::query($sql);
3887
            $last_insert_id = Database::insert_id();
3888
        } else {
3889
            //the tag exists we update it
3890
            $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $tag_id";
3891
            Database::query($sql);
3892
            $last_insert_id = $tag_id;
3893
        }
3894
3895
        if (!empty($last_insert_id) && (0 != $last_insert_id)) {
3896
            //we insert the relationship user-tag
3897
            $sql = "SELECT tag_id FROM $table_user_tag_values
3898
                    WHERE user_id = $user_id AND tag_id = $last_insert_id ";
3899
            $result = Database::query($sql);
3900
            //if the relationship does not exist we create it
3901
            if (0 == Database::num_rows($result)) {
3902
                $sql = "INSERT INTO $table_user_tag_values SET user_id = $user_id, tag_id = $last_insert_id";
3903
                Database::query($sql);
3904
            }
3905
3906
            return true;
3907
        }
3908
3909
        return false;
3910
    }
3911
3912
    /**
3913
     * Deletes an user tag.
3914
     *
3915
     * @param int $user_id
3916
     * @param int $field_id
3917
     */
3918
    public static function delete_user_tags($user_id, $field_id)
3919
    {
3920
        // database table definition
3921
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3922
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3923
        $user_id = (int) $user_id;
3924
3925
        $tags = self::get_user_tags($user_id, $field_id);
3926
        if (is_array($tags) && count($tags) > 0) {
3927
            foreach ($tags as $key => $tag) {
3928
                if ($tag['count'] > '0') {
3929
                    $sql = "UPDATE $table_user_tag SET count = count - 1  WHERE id = $key ";
3930
                    Database::query($sql);
3931
                }
3932
                $sql = "DELETE FROM $table_user_tag_values
3933
                        WHERE user_id = $user_id AND tag_id = $key";
3934
                Database::query($sql);
3935
            }
3936
        }
3937
    }
3938
3939
    /**
3940
     * Process the tag list comes from the UserManager::update_extra_field_value() function.
3941
     *
3942
     * @param array $tags     the tag list that will be added
3943
     * @param int   $user_id
3944
     * @param int   $field_id
3945
     *
3946
     * @return bool
3947
     */
3948
    public static function process_tags($tags, $user_id, $field_id)
3949
    {
3950
        // We loop the tags and add it to the DB
3951
        if (is_array($tags)) {
3952
            foreach ($tags as $tag) {
3953
                self::add_tag($tag, $user_id, $field_id);
3954
            }
3955
        } else {
3956
            self::add_tag($tags, $user_id, $field_id);
3957
        }
3958
3959
        return true;
3960
    }
3961
3962
    /**
3963
     * Returns a list of all administrators.
3964
     *
3965
     * @return array
3966
     */
3967
    public static function get_all_administrators()
3968
    {
3969
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
3970
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
3971
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3972
        $access_url_id = api_get_current_access_url_id();
3973
        if (api_get_multiple_access_url()) {
3974
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active, locale
3975
                    FROM $tbl_url_rel_user as url
3976
                    INNER JOIN $table_admin as admin
3977
                    ON (admin.user_id=url.user_id)
3978
                    INNER JOIN $table_user u
3979
                    ON (u.id=admin.user_id)
3980
                    WHERE access_url_id ='".$access_url_id."'";
3981
        } else {
3982
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active, locale
3983
                    FROM $table_admin as admin
3984
                    INNER JOIN $table_user u
3985
                    ON (u.id=admin.user_id)";
3986
        }
3987
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
3988
        $result = Database::query($sql);
3989
        $return = [];
3990
        if (Database::num_rows($result) > 0) {
3991
            while ($row = Database::fetch_assoc($result)) {
3992
                $return[$row['user_id']] = $row;
3993
            }
3994
        }
3995
3996
        return $return;
3997
    }
3998
3999
    /**
4000
     * Search an user (tags, first name, last name and email ).
4001
     *
4002
     * @param string $tag
4003
     * @param int    $field_id        field id of the tag
4004
     * @param int    $from            where to start in the query
4005
     * @param int    $number_of_items
4006
     * @param bool   $getCount        get count or not
4007
     *
4008
     * @return array
4009
     */
4010
    public static function get_all_user_tags(
4011
        $tag,
4012
        $field_id = 0,
4013
        $from = 0,
4014
        $number_of_items = 10,
4015
        $getCount = false
4016
    ) {
4017
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
4018
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4019
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4020
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4021
4022
        $field_id = intval($field_id);
4023
        $from = intval($from);
4024
        $number_of_items = intval($number_of_items);
4025
4026
        $where_field = "";
4027
        $where_extra_fields = self::get_search_form_where_extra_fields();
4028
        if (0 != $field_id) {
4029
            $where_field = " field_id = $field_id AND ";
4030
        }
4031
4032
        // all the information of the field
4033
        if ($getCount) {
4034
            $select = "SELECT count(DISTINCT u.id) count";
4035
        } else {
4036
            $select = "SELECT DISTINCT u.id, u.username, firstname, lastname, email, tag, picture_uri";
4037
        }
4038
4039
        $sql = " $select
4040
                FROM $user_table u
4041
                INNER JOIN $access_url_rel_user_table url_rel_user
4042
                ON (u.id = url_rel_user.user_id)
4043
                LEFT JOIN $table_user_tag_values uv
4044
                ON (u.id AND uv.user_id AND uv.user_id = url_rel_user.user_id)
4045
                LEFT JOIN $table_user_tag ut ON (uv.tag_id = ut.id)
4046
                WHERE
4047
                    ($where_field tag LIKE '".Database::escape_string($tag."%")."') OR
4048
                    (
4049
                        u.firstname LIKE '".Database::escape_string("%".$tag."%")."' OR
4050
                        u.lastname LIKE '".Database::escape_string("%".$tag."%")."' OR
4051
                        u.username LIKE '".Database::escape_string("%".$tag."%")."' OR
4052
                        concat(u.firstname, ' ', u.lastname) LIKE '".Database::escape_string("%".$tag."%")."' OR
4053
                        concat(u.lastname, ' ', u.firstname) LIKE '".Database::escape_string("%".$tag."%")."'
4054
                     )
4055
                     ".(!empty($where_extra_fields) ? $where_extra_fields : '')."
4056
                     AND url_rel_user.access_url_id=".api_get_current_access_url_id();
4057
4058
        $keyword_active = true;
4059
        // only active users
4060
        if ($keyword_active) {
4061
            $sql .= " AND u.active='1'";
4062
        }
4063
        // avoid anonymous
4064
        $sql .= " AND u.status <> 6 ";
4065
        $sql .= " ORDER BY username";
4066
        $sql .= " LIMIT $from , $number_of_items";
4067
4068
        $result = Database::query($sql);
4069
        $return = [];
4070
4071
        if (Database::num_rows($result) > 0) {
4072
            if ($getCount) {
4073
                $row = Database::fetch_assoc($result);
4074
4075
                return $row['count'];
4076
            }
4077
            while ($row = Database::fetch_assoc($result)) {
4078
                $return[$row['id']] = $row;
4079
            }
4080
        }
4081
4082
        return $return;
4083
    }
4084
4085
    /**
4086
     * Get extra filterable user fields (only type select).
4087
     *
4088
     * @return array Array of extra fields as [int => ['name' => ..., 'variable' => ..., 'data' => ...]] (
4089
     *               or empty array if no extra field)
4090
     */
4091
    public static function getExtraFilterableFields()
4092
    {
4093
        $extraFieldList = self::get_extra_fields();
4094
        $fields = [];
4095
        if (is_array($extraFieldList)) {
4096
            foreach ($extraFieldList as $extraField) {
4097
                // If is enabled to filter and is a "<select>" field type
4098
                if (1 == $extraField[8] && 4 == $extraField[2]) {
4099
                    $fields[] = [
4100
                        'name' => $extraField[3],
4101
                        'variable' => $extraField[1],
4102
                        'data' => $extraField[9],
4103
                    ];
4104
                }
4105
            }
4106
        }
4107
4108
        return $fields;
4109
    }
4110
4111
    /**
4112
     * Get extra where clauses for finding users based on extra filterable user fields (type select).
4113
     *
4114
     * @return string With AND clauses based on user's ID which have the values to search in extra user fields
4115
     *                (or empty if no extra field exists)
4116
     */
4117
    public static function get_search_form_where_extra_fields()
4118
    {
4119
        $useExtraFields = false;
4120
        $extraFields = self::getExtraFilterableFields();
4121
        $extraFieldResult = [];
4122
        if (is_array($extraFields) && count($extraFields) > 0) {
4123
            foreach ($extraFields as $extraField) {
4124
                $varName = 'field_'.$extraField['variable'];
4125
                if (self::is_extra_field_available($extraField['variable'])) {
4126
                    if (isset($_GET[$varName]) && '0' != $_GET[$varName]) {
4127
                        $useExtraFields = true;
4128
                        $extraFieldResult[] = self::get_extra_user_data_by_value(
4129
                            $extraField['variable'],
4130
                            $_GET[$varName]
4131
                        );
4132
                    }
4133
                }
4134
            }
4135
        }
4136
4137
        if ($useExtraFields) {
4138
            $finalResult = [];
4139
            if (count($extraFieldResult) > 1) {
4140
                for ($i = 0; $i < count($extraFieldResult) - 1; $i++) {
4141
                    if (is_array($extraFieldResult[$i]) && is_array($extraFieldResult[$i + 1])) {
4142
                        $finalResult = array_intersect($extraFieldResult[$i], $extraFieldResult[$i + 1]);
4143
                    }
4144
                }
4145
            } else {
4146
                $finalResult = $extraFieldResult[0];
4147
            }
4148
4149
            if (is_array($finalResult) && count($finalResult) > 0) {
4150
                $whereFilter = " AND u.id IN  ('".implode("','", $finalResult)."') ";
4151
            } else {
4152
                //no results
4153
                $whereFilter = " AND u.id  = -1 ";
4154
            }
4155
4156
            return $whereFilter;
4157
        }
4158
4159
        return '';
4160
    }
4161
4162
    /**
4163
     * Show the search form.
4164
     *
4165
     * @param string $query the value of the search box
4166
     *
4167
     * @throws Exception
4168
     *
4169
     * @return string HTML form
4170
     */
4171
    public static function get_search_form($query, $defaultParams = [])
4172
    {
4173
        $searchType = isset($_GET['search_type']) ? $_GET['search_type'] : null;
4174
        $form = new FormValidator(
4175
            'search_user',
4176
            'get',
4177
            api_get_path(WEB_PATH).'main/social/search.php',
4178
            '',
4179
            [],
4180
            FormValidator::LAYOUT_HORIZONTAL
4181
        );
4182
4183
        $query = Security::remove_XSS($query);
4184
4185
        if (!empty($query)) {
4186
            $form->addHeader(get_lang('Results and feedback').' "'.$query.'"');
4187
        }
4188
4189
        $form->addText(
4190
            'q',
4191
            get_lang('Users, Groups'),
4192
            false,
4193
            [
4194
                'id' => 'q',
4195
            ]
4196
        );
4197
        $options = [
4198
            0 => get_lang('Select'),
4199
            1 => get_lang('User'),
4200
            2 => get_lang('Group'),
4201
        ];
4202
        $form->addSelect(
4203
            'search_type',
4204
            get_lang('Type'),
4205
            $options,
4206
            ['onchange' => 'javascript: extra_field_toogle();', 'id' => 'search_type']
4207
        );
4208
4209
        // Extra fields
4210
        $extraFields = self::getExtraFilterableFields();
4211
        $defaults = [];
4212
        if (is_array($extraFields) && count($extraFields) > 0) {
4213
            foreach ($extraFields as $extraField) {
4214
                $varName = 'field_'.$extraField['variable'];
4215
                $options = [
4216
                    0 => get_lang('Select'),
4217
                ];
4218
                foreach ($extraField['data'] as $option) {
4219
                    if (isset($_GET[$varName])) {
4220
                        if ($_GET[$varName] == $option[1]) {
4221
                            $defaults[$option[1]] = true;
4222
                        }
4223
                    }
4224
4225
                    $options[$option[1]] = $option[1];
4226
                }
4227
                $form->addSelect($varName, $extraField['name'], $options);
4228
            }
4229
        }
4230
4231
        $defaults['search_type'] = (int) $searchType;
4232
        $defaults['q'] = $query;
4233
4234
        if (!empty($defaultParams)) {
4235
            $defaults = array_merge($defaults, $defaultParams);
4236
        }
4237
        $form->setDefaults($defaults);
4238
        $form->addButtonSearch(get_lang('Search'));
4239
4240
        $js = '<script>
4241
        extra_field_toogle();
4242
        function extra_field_toogle() {
4243
            if (jQuery("select[name=search_type]").val() != "1") {
4244
                jQuery(".extra_field").hide();
4245
            } else {
4246
                jQuery(".extra_field").show();
4247
            }
4248
        }
4249
        </script>';
4250
4251
        return $js.$form->returnForm();
4252
    }
4253
4254
    /**
4255
     * @param int $userId
4256
     *
4257
     * @return array
4258
     */
4259
    public static function getDrhListFromUser($userId)
4260
    {
4261
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
4262
        $tblUserRelUser = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4263
        $tblUserRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4264
        $userId = (int) $userId;
4265
4266
        $orderBy = null;
4267
        if (api_is_western_name_order()) {
4268
            $orderBy .= ' ORDER BY firstname, lastname ';
4269
        } else {
4270
            $orderBy .= ' ORDER BY lastname, firstname ';
4271
        }
4272
4273
        $sql = "SELECT u.id, username, u.firstname, u.lastname
4274
                FROM $tblUser u
4275
                INNER JOIN $tblUserRelUser uru ON (uru.friend_user_id = u.id)
4276
                INNER JOIN $tblUserRelAccessUrl a ON (a.user_id = u.id)
4277
                WHERE
4278
                    access_url_id = ".api_get_current_access_url_id()." AND
4279
                    uru.user_id = '$userId' AND
4280
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4281
                    $orderBy
4282
                ";
4283
        $result = Database::query($sql);
4284
4285
        return Database::store_result($result);
4286
    }
4287
4288
    /**
4289
     * get users followed by human resource manager.
4290
     *
4291
     * @param int    $userId
4292
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
4293
     * @param bool   $getOnlyUserId
4294
     * @param bool   $getSql
4295
     * @param bool   $getCount
4296
     * @param int    $from
4297
     * @param int    $numberItems
4298
     * @param int    $column
4299
     * @param string $direction
4300
     * @param int    $active
4301
     * @param string $lastConnectionDate
4302
     *
4303
     * @return array users
4304
     */
4305
    public static function get_users_followed_by_drh(
4306
        $userId,
4307
        $userStatus = 0,
4308
        $getOnlyUserId = false,
4309
        $getSql = false,
4310
        $getCount = false,
4311
        $from = null,
4312
        $numberItems = null,
4313
        $column = null,
4314
        $direction = null,
4315
        $active = null,
4316
        $lastConnectionDate = null
4317
    ) {
4318
        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...
4319
            $userId,
4320
            $userStatus,
4321
            $getOnlyUserId,
4322
            $getSql,
4323
            $getCount,
4324
            $from,
4325
            $numberItems,
4326
            $column,
4327
            $direction,
4328
            $active,
4329
            $lastConnectionDate,
4330
            DRH
4331
        );
4332
    }
4333
4334
    /**
4335
     * Get users followed by human resource manager.
4336
     *
4337
     * @param int    $userId
4338
     * @param int    $userStatus             Filter users by status (STUDENT, COURSEMANAGER, etc)
4339
     * @param bool   $getOnlyUserId
4340
     * @param bool   $getSql
4341
     * @param bool   $getCount
4342
     * @param int    $from
4343
     * @param int    $numberItems
4344
     * @param int    $column
4345
     * @param string $direction
4346
     * @param int    $active
4347
     * @param string $lastConnectionDate
4348
     * @param int    $status                 the function is called by who? COURSEMANAGER, DRH?
4349
     * @param string $keyword
4350
     * @param bool   $checkSessionVisibility
4351
     *
4352
     * @return mixed Users list (array) or the SQL query if $getSQL was set to true
4353
     */
4354
    public static function getUsersFollowedByUser(
4355
        $userId,
4356
        $userStatus = null,
4357
        $getOnlyUserId = false,
4358
        $getSql = false,
4359
        $getCount = false,
4360
        $from = null,
4361
        $numberItems = null,
4362
        $column = null,
4363
        $direction = null,
4364
        $active = null,
4365
        $lastConnectionDate = null,
4366
        $status = null,
4367
        $keyword = null,
4368
        $checkSessionVisibility = false
4369
    ) {
4370
        // Database Table Definitions
4371
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
4372
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4373
        $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4374
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4375
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4376
        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4377
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4378
        $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
4379
4380
        $userId = (int) $userId;
4381
        $limitCondition = '';
4382
4383
        if (isset($from) && isset($numberItems)) {
4384
            $from = (int) $from;
4385
            $numberItems = (int) $numberItems;
4386
            $limitCondition = "LIMIT $from, $numberItems";
4387
        }
4388
4389
        $column = Database::escape_string($column);
4390
        $direction = in_array(strtolower($direction), ['asc', 'desc']) ? $direction : null;
4391
4392
        $userConditions = '';
4393
        if (!empty($userStatus)) {
4394
            $userConditions .= ' AND u.status = '.intval($userStatus);
4395
        }
4396
4397
        $select = " SELECT DISTINCT u.id user_id, u.username, u.lastname, u.firstname, u.email ";
4398
        if ($getOnlyUserId) {
4399
            $select = " SELECT DISTINCT u.id user_id";
4400
        }
4401
4402
        $masterSelect = "SELECT DISTINCT * FROM ";
4403
4404
        if ($getCount) {
4405
            $masterSelect = "SELECT COUNT(DISTINCT(user_id)) as count FROM ";
4406
            $select = " SELECT DISTINCT(u.id) user_id";
4407
        }
4408
4409
        if (!is_null($active)) {
4410
            $active = intval($active);
4411
            $userConditions .= " AND u.active = $active ";
4412
        }
4413
4414
        if (!empty($keyword)) {
4415
            $keyword = trim(Database::escape_string($keyword));
4416
            $keywordParts = array_filter(explode(' ', $keyword));
4417
            $extraKeyword = '';
4418
            if (!empty($keywordParts)) {
4419
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
4420
                if (!empty($keywordPartsFixed)) {
4421
                    $extraKeyword .= " OR
4422
                        CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keywordPartsFixed%' OR
4423
                        CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keywordPartsFixed%' ";
4424
                }
4425
            }
4426
            $userConditions .= " AND (
4427
                u.username LIKE '%$keyword%' OR
4428
                u.firstname LIKE '%$keyword%' OR
4429
                u.lastname LIKE '%$keyword%' OR
4430
                u.official_code LIKE '%$keyword%' OR
4431
                u.email LIKE '%$keyword%' OR
4432
                CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keyword%' OR
4433
                CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keyword%'
4434
                $extraKeyword
4435
            )";
4436
        }
4437
4438
        if (!empty($lastConnectionDate)) {
4439
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
4440
            $userConditions .= " AND u.last_login <= '$lastConnectionDate' ";
4441
        }
4442
4443
        $sessionConditionsCoach = null;
4444
        $dateCondition = '';
4445
        $drhConditions = null;
4446
        $teacherSelect = null;
4447
        $urlId = api_get_current_access_url_id();
4448
4449
        switch ($status) {
4450
            case DRH:
4451
                $drhConditions .= " AND
4452
                    friend_user_id = '$userId' AND
4453
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4454
                ";
4455
                break;
4456
            case COURSEMANAGER:
4457
                $drhConditions .= " AND
4458
                    friend_user_id = '$userId' AND
4459
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_RRHH."'
4460
                ";
4461
4462
                $sessionConditionsTeacher = " AND
4463
                    (scu.status = ".SessionEntity::COURSE_COACH." AND scu.user_id = '$userId')
4464
                ";
4465
4466
                if ($checkSessionVisibility) {
4467
                    $today = api_strtotime('now', 'UTC');
4468
                    $today = date('Y-m-d', $today);
4469
                    $dateCondition = "
4470
                        AND
4471
                        (
4472
                            (s.access_start_date <= '$today' AND '$today' <= s.access_end_date) OR
4473
                            (s.access_start_date IS NULL AND s.access_end_date IS NULL) OR
4474
                            (s.access_start_date <= '$today' AND s.access_end_date IS NULL) OR
4475
                            ('$today' <= s.access_end_date AND s.access_start_date IS NULL)
4476
                        )
4477
					";
4478
                }
4479
4480
                // Use $tbl_session_rel_course_rel_user instead of $tbl_session_rel_user
4481
                /*
4482
                INNER JOIN $tbl_session_rel_user sru
4483
                ON (sru.user_id = u.id)
4484
                INNER JOIN $tbl_session_rel_course_rel_user scu
4485
                ON (scu.user_id = u.id AND scu.c_id IS NOT NULL AND visibility = 1)*/
4486
                $teacherSelect =
4487
                "UNION ALL (
4488
                        $select
4489
                        FROM $tbl_user u
4490
                        INNER JOIN $tbl_session_rel_user sru ON (sru.user_id = u.id)
4491
                        WHERE
4492
                            (
4493
                                sru.session_id IN (
4494
                                    SELECT DISTINCT(s.id) FROM $tbl_session s INNER JOIN
4495
                                    $tbl_session_rel_access_url session_rel_access_rel_user
4496
                                    ON session_rel_access_rel_user.session_id = s.id
4497
                                    INNER JOIN $tbl_session_rel_user sru ON s.id = sru.session_id
4498
                                    WHERE access_url_id = ".$urlId."
4499
                                        AND (sru.relation_type = ".SessionEntity::GENERAL_COACH."
4500
                                        AND sru.user_id = $userId)
4501
                                ) OR sru.session_id IN (
4502
                                    SELECT DISTINCT(s.id) FROM $tbl_session s
4503
                                    INNER JOIN $tbl_session_rel_access_url url
4504
                                    ON (url.session_id = s.id)
4505
                                    INNER JOIN $tbl_session_rel_course_rel_user scu
4506
                                    ON (scu.session_id = s.id)
4507
                                    WHERE access_url_id = ".$urlId."
4508
                                    $sessionConditionsTeacher
4509
                                    $dateCondition
4510
                                )
4511
                            )
4512
                            $userConditions
4513
                    )
4514
                    UNION ALL(
4515
                        $select
4516
                        FROM $tbl_user u
4517
                        INNER JOIN $tbl_course_user cu ON (cu.user_id = u.id)
4518
                        WHERE cu.c_id IN (
4519
                            SELECT DISTINCT(c_id) FROM $tbl_course_user
4520
                            WHERE user_id = $userId AND status = ".COURSEMANAGER."
4521
                        )
4522
                        $userConditions
4523
                    )"
4524
                ;
4525
                break;
4526
            case STUDENT_BOSS:
4527
                $drhConditions = " AND friend_user_id = $userId AND relation_type = ".UserRelUser::USER_RELATION_TYPE_BOSS;
4528
                break;
4529
            case HRM_REQUEST:
4530
                $drhConditions .= " AND
4531
                    friend_user_id = '$userId' AND
4532
                    relation_type = '".UserRelUser::USER_RELATION_TYPE_HRM_REQUEST."'
4533
                ";
4534
                break;
4535
        }
4536
4537
        $join = null;
4538
        $sql = " $masterSelect
4539
                (
4540
                    (
4541
                        $select
4542
                        FROM $tbl_user u
4543
                        INNER JOIN $tbl_user_rel_user uru ON (uru.user_id = u.id)
4544
                        LEFT JOIN $tbl_user_rel_access_url a ON (a.user_id = u.id)
4545
                        $join
4546
                        WHERE
4547
                            access_url_id = ".$urlId."
4548
                            $drhConditions
4549
                            $userConditions
4550
                    )
4551
                    $teacherSelect
4552
4553
                ) as t1";
4554
4555
        if ($getSql) {
4556
            return $sql;
4557
        }
4558
        if ($getCount) {
4559
            $result = Database::query($sql);
4560
            $row = Database::fetch_array($result);
4561
4562
            return $row['count'];
4563
        }
4564
4565
        $orderBy = null;
4566
        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...
4567
            if (api_is_western_name_order()) {
4568
                $orderBy .= " ORDER BY firstname, lastname ";
4569
            } else {
4570
                $orderBy .= " ORDER BY lastname, firstname ";
4571
            }
4572
4573
            if (!empty($column) && !empty($direction)) {
4574
                // Fixing order due the UNIONs
4575
                $column = str_replace('u.', '', $column);
4576
                $orderBy = " ORDER BY `$column` $direction ";
4577
            }
4578
        }
4579
4580
        $sql .= $orderBy;
4581
        $sql .= $limitCondition;
4582
4583
        $result = Database::query($sql);
4584
        $users = [];
4585
        if (Database::num_rows($result) > 0) {
4586
            while ($row = Database::fetch_array($result)) {
4587
                $users[$row['user_id']] = $row;
4588
            }
4589
        }
4590
4591
        return $users;
4592
    }
4593
4594
    /**
4595
     * Subscribes users to human resource manager (Dashboard feature).
4596
     *
4597
     * @param int   $hr_dept_id
4598
     * @param array $users_id
4599
     * @param bool  $deleteOtherAssignedUsers
4600
     */
4601
    public static function subscribeUsersToHRManager(
4602
        $hr_dept_id,
4603
        $users_id,
4604
        $deleteOtherAssignedUsers = true
4605
    ): void {
4606
        self::subscribeUsersToUser(
4607
            $hr_dept_id,
4608
            $users_id,
4609
            UserRelUser::USER_RELATION_TYPE_RRHH,
4610
            false,
4611
            $deleteOtherAssignedUsers
4612
        );
4613
    }
4614
4615
    /**
4616
     * Register request to assign users to HRM.
4617
     *
4618
     * @param int   $hrmId   The HRM ID
4619
     * @param array $usersId The users IDs
4620
     */
4621
    public static function requestUsersToHRManager($hrmId, $usersId): void
4622
    {
4623
        self::subscribeUsersToUser(
4624
            $hrmId,
4625
            $usersId,
4626
            UserRelUser::USER_RELATION_TYPE_HRM_REQUEST,
4627
            false,
4628
            false
4629
        );
4630
    }
4631
4632
    /**
4633
     * Remove the requests for assign a user to a HRM.
4634
     *
4635
     * @param array $usersId List of user IDs from whom to remove all relations requests with HRM
4636
     */
4637
    public static function clearHrmRequestsForUser(User $hrmId, $usersId)
4638
    {
4639
        $users = implode(', ', $usersId);
4640
        Database::getManager()
4641
            ->createQuery('
4642
                DELETE FROM ChamiloCoreBundle:UserRelUser uru
4643
                WHERE uru.friendUserId = :hrm_id AND uru.relationType = :relation_type AND uru.userId IN (:users_ids)
4644
            ')
4645
            ->execute(['hrm_id' => $hrmId, 'relation_type' => UserRelUser::USER_RELATION_TYPE_HRM_REQUEST, 'users_ids' => $users]);
4646
    }
4647
4648
    /**
4649
     * Add subscribed users to a user by relation type.
4650
     *
4651
     * @param int   $userId                   The user id
4652
     * @param array $subscribedUsersId        The id of subscribed users
4653
     * @param int   $relationType             The relation type
4654
     * @param bool  $deleteUsersBeforeInsert
4655
     * @param bool  $deleteOtherAssignedUsers
4656
     */
4657
    public static function subscribeUsersToUser(
4658
        $userId,
4659
        $subscribedUsersId,
4660
        $relationType,
4661
        $deleteUsersBeforeInsert = false,
4662
        $deleteOtherAssignedUsers = true
4663
    ): void {
4664
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4665
        $userRelAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4666
4667
        $userId = (int) $userId;
4668
        $relationType = (int) $relationType;
4669
4670
        if ($deleteOtherAssignedUsers) {
4671
            if (api_get_multiple_access_url()) {
4672
                // Deleting assigned users to hrm_id
4673
                $sql = "SELECT s.user_id
4674
                        FROM $userRelUserTable s
4675
                        INNER JOIN $userRelAccessUrlTable a
4676
                        ON (a.user_id = s.user_id)
4677
                        WHERE
4678
                            friend_user_id = $userId AND
4679
                            relation_type = $relationType AND
4680
                            access_url_id = ".api_get_current_access_url_id();
4681
            } else {
4682
                $sql = "SELECT user_id
4683
                        FROM $userRelUserTable
4684
                        WHERE
4685
                            friend_user_id = $userId AND
4686
                            relation_type = $relationType";
4687
            }
4688
            $result = Database::query($sql);
4689
4690
            if (Database::num_rows($result) > 0) {
4691
                while ($row = Database::fetch_array($result)) {
4692
                    $sql = "DELETE FROM $userRelUserTable
4693
                            WHERE
4694
                                user_id = {$row['user_id']} AND
4695
                                friend_user_id = $userId AND
4696
                                relation_type = $relationType";
4697
                    Database::query($sql);
4698
                }
4699
            }
4700
        }
4701
4702
        if ($deleteUsersBeforeInsert) {
4703
            $sql = "DELETE FROM $userRelUserTable
4704
                    WHERE
4705
                        user_id = $userId AND
4706
                        relation_type = $relationType";
4707
            Database::query($sql);
4708
        }
4709
4710
        // Inserting new user list.
4711
        if (is_array($subscribedUsersId)) {
4712
            foreach ($subscribedUsersId as $subscribedUserId) {
4713
                $subscribedUserId = (int) $subscribedUserId;
4714
                $sql = "SELECT id
4715
                        FROM $userRelUserTable
4716
                        WHERE
4717
                            user_id = $subscribedUserId AND
4718
                            friend_user_id = $userId AND
4719
                            relation_type = $relationType";
4720
4721
                $result = Database::query($sql);
4722
                $num = Database::num_rows($result);
4723
                if (0 === $num) {
4724
                    $userRelUser = (new UserRelUser())
4725
                        ->setUser(api_get_user_entity($subscribedUserId))
4726
                        ->setFriend(api_get_user_entity($userId))
4727
                        ->setRelationType($relationType)
4728
                    ;
4729
                    $em = Database::getManager();
4730
                    $em->persist($userRelUser);
4731
                    $em->flush();
4732
                }
4733
            }
4734
        }
4735
    }
4736
4737
    /**
4738
     * This function checks if a user is followed by provided human resources managers.
4739
     *
4740
     * @param int $user_id
4741
     * @param int $hr_dept_id Human resources manager
4742
     *
4743
     * @return bool
4744
     * @throws Exception
4745
     * @throws \Doctrine\DBAL\Exception
4746
     */
4747
    public static function is_user_followed_by_drh(int $user_id, int $hr_dept_id): bool
4748
    {
4749
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4750
        $result = false;
4751
4752
        $sql = "SELECT user_id FROM $tbl_user_rel_user
4753
                WHERE
4754
                    user_id = $user_id AND
4755
                    friend_user_id = $hr_dept_id AND
4756
                    relation_type = ".UserRelUser::USER_RELATION_TYPE_RRHH;
4757
        $rs = Database::query($sql);
4758
        if (Database::num_rows($rs) > 0) {
4759
            $result = true;
4760
        }
4761
4762
        return $result;
4763
    }
4764
4765
    /**
4766
     * Return the user id of teacher or session administrator.
4767
     *
4768
     * @param array $courseInfo
4769
     *
4770
     * @return int The user id, or 0 if the session ID was negative
4771
     * @throws Exception
4772
     * @throws \Doctrine\DBAL\Exception
4773
     */
4774
    public static function get_user_id_of_course_admin_or_session_admin(array $courseInfo): int
4775
    {
4776
        $session = api_get_session_id();
4777
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
4778
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4779
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4780
4781
        if (empty($courseInfo)) {
4782
            return 0;
4783
        }
4784
4785
        $courseId = $courseInfo['real_id'];
4786
4787
        if (0 == $session) {
4788
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
4789
                    INNER JOIN '.$table_course_user.' ru
4790
                    ON ru.user_id = u.id
4791
                    WHERE
4792
                        ru.status = 1 AND
4793
                        ru.c_id = "'.$courseId.'" ';
4794
            $rs = Database::query($sql);
4795
            $num_rows = Database::num_rows($rs);
4796
            if (1 == $num_rows) {
4797
                $row = Database::fetch_array($rs);
4798
4799
                return (int) $row['uid'];
4800
            } else {
4801
                $my_num_rows = $num_rows;
4802
4803
                return (int) Database::result($rs, $my_num_rows - 1, 'uid');
4804
            }
4805
        } elseif ($session > 0) {
4806
            $sql = 'SELECT u.id as uid FROM '.$table_user.' u
4807
                    INNER JOIN '.$table_session_course_user.' sru
4808
                    ON sru.user_id = u.id
4809
                    WHERE
4810
                        sru.c_id = '.$courseId.' AND
4811
                        sru.status = '.SessionEntity::COURSE_COACH;
4812
            $rs = Database::query($sql);
4813
            if (Database::num_rows($rs) > 0) {
4814
                $row = Database::fetch_assoc($rs);
4815
4816
                return (int) $row['uid'];
4817
            }
4818
        }
4819
4820
        return 0;
4821
    }
4822
4823
    /**
4824
     * Determines if a user is a gradebook certified.
4825
     *
4826
     * @param int $cat_id  The category id of gradebook
4827
     * @param int $user_id The user id
4828
     *
4829
     * @return bool
4830
     */
4831
    public static function is_user_certified($cat_id, $user_id)
4832
    {
4833
        $cat_id = (int) $cat_id;
4834
        $user_id = (int) $user_id;
4835
4836
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
4837
        $sql = 'SELECT path_certificate
4838
                FROM '.$table.'
4839
                WHERE
4840
                    cat_id = "'.$cat_id.'" AND
4841
                    user_id = "'.$user_id.'"';
4842
        $rs = Database::query($sql);
4843
        $row = Database::fetch_array($rs);
4844
4845
        if (!isset($row['path_certificate']) || '' == $row['path_certificate'] || is_null($row['path_certificate'])) {
4846
            return false;
4847
        }
4848
4849
        return true;
4850
    }
4851
4852
    /**
4853
     * Gets the info about a gradebook certificate for a user by course.
4854
     *
4855
     * @param array $course_info The course code
4856
     * @param int   $session_id
4857
     * @param int   $user_id     The user id
4858
     *
4859
     * @return array if there is not information return false
4860
     */
4861
    public static function get_info_gradebook_certificate($course_info, $session_id, $user_id)
4862
    {
4863
        $tbl_grade_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
4864
        $tbl_grade_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
4865
        $session_id = (int) $session_id;
4866
        $user_id = (int) $user_id;
4867
        $courseId = $course_info['real_id'];
4868
4869
        if (empty($session_id)) {
4870
            $session_condition = ' AND (session_id = "" OR session_id = 0 OR session_id IS NULL )';
4871
        } else {
4872
            $session_condition = " AND session_id = $session_id";
4873
        }
4874
4875
        $sql = 'SELECT * FROM '.$tbl_grade_certificate.'
4876
                WHERE cat_id = (
4877
                    SELECT id FROM '.$tbl_grade_category.'
4878
                    WHERE
4879
                        c_id = "'.$courseId.'" '.$session_condition.'
4880
                    LIMIT 1
4881
                ) AND user_id='.$user_id;
4882
4883
        $rs = Database::query($sql);
4884
        if (Database::num_rows($rs) > 0) {
4885
            $row = Database::fetch_assoc($rs);
4886
            $score = $row['score_certificate'];
4887
            $category_id = $row['cat_id'];
4888
            $cat = Category::load($category_id);
4889
            $displayscore = ScoreDisplay::instance();
4890
            if (isset($cat) && $displayscore->is_custom()) {
4891
                $grade = $displayscore->display_score(
4892
                    [$score, $cat[0]->get_weight()],
4893
                    SCORE_DIV_PERCENT_WITH_CUSTOM
4894
                );
4895
            } else {
4896
                $grade = $displayscore->display_score(
4897
                    [$score, $cat[0]->get_weight()]
4898
                );
4899
            }
4900
            $row['grade'] = $grade;
4901
4902
            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...
4903
        }
4904
4905
        return false;
4906
    }
4907
4908
    /**
4909
     * This function check if the user is a coach inside session course.
4910
     *
4911
     * @param int $user_id    User id
4912
     * @param int $courseId
4913
     * @param int $session_id
4914
     *
4915
     * @return bool True if the user is a coach
4916
     */
4917
    public static function is_session_course_coach($user_id, $courseId, $session_id)
4918
    {
4919
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4920
        // Protect data
4921
        $user_id = intval($user_id);
4922
        $courseId = intval($courseId);
4923
        $session_id = intval($session_id);
4924
        $result = false;
4925
4926
        $sql = "SELECT session_id FROM $table
4927
                WHERE
4928
                  session_id = $session_id AND
4929
                  c_id = $courseId AND
4930
                  user_id = $user_id AND
4931
                  status = ".SessionEntity::COURSE_COACH;
4932
        $res = Database::query($sql);
4933
4934
        if (Database::num_rows($res) > 0) {
4935
            $result = true;
4936
        }
4937
4938
        return $result;
4939
    }
4940
4941
    /**
4942
     * This function returns an icon path that represents the favicon of the website of which the url given.
4943
     * Defaults to the current Chamilo favicon.
4944
     *
4945
     * @param string $url1 URL of website where to look for favicon.ico
4946
     * @param string $url2 Optional second URL of website where to look for favicon.ico
4947
     *
4948
     * @return string Path of icon to load
4949
     */
4950
    public static function get_favicon_from_url($url1, $url2 = null)
4951
    {
4952
        $icon_link = '';
4953
        $url = $url1;
4954
        if (empty($url1)) {
4955
            $url = $url2;
4956
            if (empty($url)) {
4957
                $url = api_get_access_url(api_get_current_access_url_id());
4958
                $url = $url[0];
4959
            }
4960
        }
4961
        if (!empty($url)) {
4962
            $pieces = parse_url($url);
4963
            $icon_link = $pieces['scheme'].'://'.$pieces['host'].'/favicon.ico';
4964
        }
4965
4966
        return $icon_link;
4967
    }
4968
4969
    public static function addUserAsAdmin(User $user)
4970
    {
4971
        $userId = $user->getId();
4972
4973
        if (!self::is_admin($userId)) {
4974
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
4975
            $sql = "INSERT INTO $table SET user_id = $userId";
4976
            Database::query($sql);
4977
        }
4978
4979
        $user->addRole('ROLE_ADMIN');
4980
        self::getRepository()->updateUser($user, true);
4981
    }
4982
4983
    public static function removeUserAdmin(User $user)
4984
    {
4985
        $userId = (int) $user->getId();
4986
        if (self::is_admin($userId)) {
4987
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
4988
            $sql = "DELETE FROM $table WHERE user_id = $userId";
4989
            Database::query($sql);
4990
            $user->removeRole('ROLE_ADMIN');
4991
            self::getRepository()->updateUser($user, true);
4992
        }
4993
    }
4994
4995
    /**
4996
     * @param string $from
4997
     * @param string $to
4998
     */
4999
    public static function update_all_user_languages($from, $to)
5000
    {
5001
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
5002
        $from = Database::escape_string($from);
5003
        $to = Database::escape_string($to);
5004
5005
        if (!empty($to) && !empty($from)) {
5006
            $sql = "UPDATE $table_user SET language = '$to'
5007
                    WHERE language = '$from'";
5008
            Database::query($sql);
5009
        }
5010
    }
5011
5012
    /**
5013
     * Subscribe boss to students.
5014
     *
5015
     * @param int   $bossId                   The boss id
5016
     * @param array $usersId                  The users array
5017
     * @param bool  $deleteOtherAssignedUsers
5018
     */
5019
    public static function subscribeBossToUsers($bossId, $usersId, $deleteOtherAssignedUsers = true): void
5020
    {
5021
        self::subscribeUsersToUser(
5022
            $bossId,
5023
            $usersId,
5024
            UserRelUser::USER_RELATION_TYPE_BOSS,
5025
            false,
5026
            $deleteOtherAssignedUsers
5027
        );
5028
    }
5029
5030
    /**
5031
     * @param int $userId
5032
     *
5033
     * @return bool
5034
     */
5035
    public static function removeAllBossFromStudent($userId)
5036
    {
5037
        $userId = (int) $userId;
5038
5039
        if (empty($userId)) {
5040
            return false;
5041
        }
5042
5043
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5044
        $sql = "DELETE FROM $userRelUserTable
5045
                WHERE user_id = $userId AND relation_type = ".UserRelUser::USER_RELATION_TYPE_BOSS;
5046
        Database::query($sql);
5047
5048
        return true;
5049
    }
5050
5051
    /**
5052
     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
5053
     *
5054
     * @param int   $studentId
5055
     * @param array $bossList
5056
     * @param bool  $sendNotification
5057
     *
5058
     * @return mixed Affected rows or false on failure
5059
     */
5060
    public static function subscribeUserToBossList(
5061
        $studentId,
5062
        $bossList,
5063
        $sendNotification = false
5064
    ) {
5065
        $inserted = 0;
5066
        if (!empty($bossList)) {
5067
            sort($bossList);
5068
            $studentId = (int) $studentId;
5069
            $studentInfo = api_get_user_info($studentId);
5070
5071
            if (empty($studentInfo)) {
5072
                return false;
5073
            }
5074
5075
            $previousBossList = self::getStudentBossList($studentId);
5076
            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
5077
            sort($previousBossList);
5078
5079
            // Boss list is the same, nothing changed.
5080
            if ($bossList == $previousBossList) {
5081
                return false;
5082
            }
5083
5084
            $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5085
            self::removeAllBossFromStudent($studentId);
5086
5087
            foreach ($bossList as $bossId) {
5088
                $bossId = (int) $bossId;
5089
                $bossInfo = api_get_user_info($bossId);
5090
5091
                if (empty($bossInfo)) {
5092
                    continue;
5093
                }
5094
5095
                $bossLanguage = $bossInfo['locale'];
5096
                $sql = "INSERT IGNORE INTO $userRelUserTable (user_id, friend_user_id, relation_type)
5097
                        VALUES ($studentId, $bossId, ".UserRelUser::USER_RELATION_TYPE_BOSS.")";
5098
                $insertId = Database::query($sql);
5099
5100
                if ($insertId) {
5101
                    if ($sendNotification) {
5102
                        $name = $studentInfo['complete_name'];
5103
                        $url = api_get_path(WEB_CODE_PATH).'my_space/myStudents.php?student='.$studentId;
5104
                        $url = Display::url($url, $url);
5105
                        $subject = sprintf(get_lang('You have been assigned the learner %s', $bossLanguage), $name);
5106
                        $message = sprintf(get_lang('You have been assigned the learner %s with url %s', $bossLanguage), $name, $url);
5107
                        MessageManager::send_message_simple(
5108
                            $bossId,
5109
                            $subject,
5110
                            $message
5111
                        );
5112
                    }
5113
                    $inserted++;
5114
                }
5115
            }
5116
        } else {
5117
            self::removeAllBossFromStudent($studentId);
5118
        }
5119
5120
        return $inserted;
5121
    }
5122
5123
    /**
5124
     * Get users followed by student boss.
5125
     *
5126
     * @param int    $userId
5127
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
5128
     * @param bool   $getOnlyUserId
5129
     * @param bool   $getSql
5130
     * @param bool   $getCount
5131
     * @param int    $from
5132
     * @param int    $numberItems
5133
     * @param int    $column
5134
     * @param string $direction
5135
     * @param int    $active
5136
     * @param string $lastConnectionDate
5137
     *
5138
     * @return array users
5139
     */
5140
    public static function getUsersFollowedByStudentBoss(
5141
        $userId,
5142
        $userStatus = 0,
5143
        $getOnlyUserId = false,
5144
        $getSql = false,
5145
        $getCount = false,
5146
        $from = null,
5147
        $numberItems = null,
5148
        $column = null,
5149
        $direction = null,
5150
        $active = null,
5151
        $lastConnectionDate = null
5152
    ) {
5153
        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...
5154
            $userId,
5155
            $userStatus,
5156
            $getOnlyUserId,
5157
            $getSql,
5158
            $getCount,
5159
            $from,
5160
            $numberItems,
5161
            $column,
5162
            $direction,
5163
            $active,
5164
            $lastConnectionDate,
5165
            STUDENT_BOSS
5166
        );
5167
    }
5168
5169
    /**
5170
     * @return array
5171
     */
5172
    public static function getOfficialCodeGrouped()
5173
    {
5174
        $user = Database::get_main_table(TABLE_MAIN_USER);
5175
        $sql = "SELECT DISTINCT official_code
5176
                FROM $user
5177
                GROUP BY official_code";
5178
        $result = Database::query($sql);
5179
        $values = Database::store_result($result, 'ASSOC');
5180
        $result = [];
5181
        foreach ($values as $value) {
5182
            $result[$value['official_code']] = $value['official_code'];
5183
        }
5184
5185
        return $result;
5186
    }
5187
5188
    /**
5189
     * @param string $officialCode
5190
     *
5191
     * @return array
5192
     */
5193
    public static function getUsersByOfficialCode($officialCode)
5194
    {
5195
        $user = Database::get_main_table(TABLE_MAIN_USER);
5196
        $officialCode = Database::escape_string($officialCode);
5197
5198
        $sql = "SELECT DISTINCT id
5199
                FROM $user
5200
                WHERE official_code = '$officialCode'
5201
                ";
5202
        $result = Database::query($sql);
5203
5204
        $users = [];
5205
        while ($row = Database::fetch_array($result)) {
5206
            $users[] = $row['id'];
5207
        }
5208
5209
        return $users;
5210
    }
5211
5212
    /**
5213
     * Calc the expended time (in seconds) by a user in a course.
5214
     *
5215
     * @param int    $userId    The user id
5216
     * @param int    $courseId  The course id
5217
     * @param int    $sessionId Optional. The session id
5218
     * @param string $from      Optional. From date
5219
     * @param string $until     Optional. Until date
5220
     *
5221
     * @return int The time
5222
     */
5223
    public static function getTimeSpentInCourses(
5224
        $userId,
5225
        $courseId,
5226
        $sessionId = 0,
5227
        $from = '',
5228
        $until = ''
5229
    ) {
5230
        $userId = (int) $userId;
5231
        $sessionId = (int) $sessionId;
5232
5233
        $trackCourseAccessTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5234
        $whereConditions = [
5235
            'user_id = ? ' => $userId,
5236
            'AND c_id = ? ' => $courseId,
5237
            'AND session_id = ? ' => $sessionId,
5238
        ];
5239
5240
        if (!empty($from) && !empty($until)) {
5241
            $whereConditions["AND (login_course_date >= '?' "] = $from;
5242
            $whereConditions["AND logout_course_date <= DATE_ADD('?', INTERVAL 1 DAY)) "] = $until;
5243
        }
5244
5245
        $trackResult = Database::select(
5246
            'SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as total_time',
5247
            $trackCourseAccessTable,
5248
            [
5249
                'where' => $whereConditions,
5250
            ],
5251
            'first'
5252
        );
5253
5254
        if (false != $trackResult) {
5255
            return $trackResult['total_time'] ? $trackResult['total_time'] : 0;
5256
        }
5257
5258
        return 0;
5259
    }
5260
5261
    /**
5262
     * Get the boss user ID from a followed user id.
5263
     *
5264
     * @param $userId
5265
     *
5266
     * @return bool
5267
     */
5268
    public static function getFirstStudentBoss($userId)
5269
    {
5270
        $userId = (int) $userId;
5271
        if ($userId > 0) {
5272
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5273
            $row = Database::select(
5274
                'DISTINCT friend_user_id AS boss_id',
5275
                $userRelTable,
5276
                [
5277
                    'where' => [
5278
                        'user_id = ? AND relation_type = ? LIMIT 1' => [
5279
                            $userId,
5280
                            UserRelUser::USER_RELATION_TYPE_BOSS,
5281
                        ],
5282
                    ],
5283
                ]
5284
            );
5285
            if (!empty($row)) {
5286
                return $row[0]['boss_id'];
5287
            }
5288
        }
5289
5290
        return false;
5291
    }
5292
5293
    /**
5294
     * Get the boss user ID from a followed user id.
5295
     *
5296
     * @param int $userId student id
5297
     *
5298
     * @return array
5299
     */
5300
    public static function getStudentBossList($userId)
5301
    {
5302
        $userId = (int) $userId;
5303
5304
        if ($userId > 0) {
5305
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5306
5307
            return Database::select(
5308
                'DISTINCT friend_user_id AS boss_id',
5309
                $userRelTable,
5310
                [
5311
                    'where' => [
5312
                        'user_id = ? AND relation_type = ? ' => [
5313
                            $userId,
5314
                            UserRelUser::USER_RELATION_TYPE_BOSS,
5315
                        ],
5316
                    ],
5317
                ]
5318
            );
5319
        }
5320
5321
        return [];
5322
    }
5323
5324
    /**
5325
     * @param int $bossId
5326
     * @param int $studentId
5327
     *
5328
     * @return bool
5329
     */
5330
    public static function userIsBossOfStudent($bossId, $studentId)
5331
    {
5332
        $result = false;
5333
        $bossList = self::getStudentBossList($studentId);
5334
        if (!empty($bossList)) {
5335
            $bossList = array_column($bossList, 'boss_id');
5336
            if (in_array($bossId, $bossList)) {
5337
                $result = true;
5338
            }
5339
        }
5340
5341
        return $result;
5342
    }
5343
5344
    /**
5345
     * Displays the name of the user and makes the link to the user profile.
5346
     *
5347
     * @param array $userInfo
5348
     *
5349
     * @return string
5350
     */
5351
    public static function getUserProfileLink($userInfo)
5352
    {
5353
        if (isset($userInfo) && isset($userInfo['user_id'])) {
5354
            return Display::url(
5355
                $userInfo['complete_name_with_username'],
5356
                $userInfo['profile_url']
5357
            );
5358
        }
5359
5360
        return get_lang('Anonymous');
5361
    }
5362
5363
    /**
5364
     * Get users whose name matches $firstname and $lastname.
5365
     *
5366
     * @param string $firstname Firstname to search
5367
     * @param string $lastname  Lastname to search
5368
     *
5369
     * @return array The user list
5370
     */
5371
    public static function getUsersByName($firstname, $lastname)
5372
    {
5373
        $firstname = Database::escape_string($firstname);
5374
        $lastname = Database::escape_string($lastname);
5375
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
5376
5377
        $sql = <<<SQL
5378
            SELECT id, username, lastname, firstname
5379
            FROM $userTable
5380
            WHERE
5381
                firstname LIKE '$firstname%' AND
5382
                lastname LIKE '$lastname%'
5383
SQL;
5384
        $result = Database::query($sql);
5385
        $users = [];
5386
        while ($resultData = Database::fetch_object($result)) {
5387
            $users[] = $resultData;
5388
        }
5389
5390
        return $users;
5391
    }
5392
5393
    /**
5394
     * @param int $optionSelected
5395
     *
5396
     * @return string
5397
     */
5398
    public static function getUserSubscriptionTab($optionSelected = 1)
5399
    {
5400
        $allowAdmin = api_get_setting('allow_user_course_subscription_by_course_admin');
5401
        if (('true' === $allowAdmin && api_is_allowed_to_edit()) ||
5402
            api_is_platform_admin()
5403
        ) {
5404
            $userPath = api_get_path(WEB_CODE_PATH).'user/';
5405
5406
            $headers = [
5407
                [
5408
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.STUDENT,
5409
                    'content' => get_lang('Learners'),
5410
                ],
5411
                [
5412
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.COURSEMANAGER,
5413
                    'content' => get_lang('Trainers'),
5414
                ],
5415
                /*[
5416
                    'url' => $userPath.'subscribe_user.php?'.api_get_cidreq(),
5417
                    'content' => get_lang('Learners'),
5418
                ],
5419
                [
5420
                    'url' => $userPath.'subscribe_user.php?type=teacher&'.api_get_cidreq(),
5421
                    'content' => get_lang('Trainers'),
5422
                ],*/
5423
                [
5424
                    'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
5425
                    'content' => get_lang('Groups'),
5426
                ],
5427
                [
5428
                    'url' => $userPath.'class.php?'.api_get_cidreq(),
5429
                    'content' => get_lang('Classes'),
5430
                ],
5431
            ];
5432
5433
            return Display::tabsOnlyLink($headers, $optionSelected);
5434
        }
5435
5436
        return '';
5437
    }
5438
5439
    /**
5440
     * Make sure this function is protected because it does NOT check password!
5441
     *
5442
     * This function defines globals.
5443
     *
5444
     * @param int  $userId
5445
     * @param bool $checkIfUserCanLoginAs
5446
     *
5447
     * @return bool
5448
     *
5449
     * @author Evie Embrechts
5450
     * @author Yannick Warnier <[email protected]>
5451
     */
5452
    public static function loginAsUser($userId, $checkIfUserCanLoginAs = true)
5453
    {
5454
        $userId = (int) $userId;
5455
        $userInfo = api_get_user_info($userId);
5456
5457
        // Check if the user is allowed to 'login_as'
5458
        $canLoginAs = true;
5459
        if ($checkIfUserCanLoginAs) {
5460
            $canLoginAs = api_can_login_as($userId);
5461
        }
5462
5463
        if (!$canLoginAs || empty($userInfo)) {
5464
            return false;
5465
        }
5466
5467
        if ($userId) {
5468
            $logInfo = [
5469
                'tool' => 'logout',
5470
                'tool_id' => 0,
5471
                'tool_id_detail' => 0,
5472
                'action' => '',
5473
                'info' => 'Change user (login as)',
5474
            ];
5475
            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

5475
            Event::/** @scrutinizer ignore-call */ 
5476
                   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...
5476
5477
            // Logout the current user
5478
            self::loginDelete(api_get_user_id());
5479
5480
            return true;
5481
5482
            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...
5483
            Session::erase('is_platformAdmin');
5484
            Session::erase('is_allowedCreateCourse');
5485
            Session::erase('_uid');
5486
5487
            // Cleaning session variables
5488
            $_user['firstName'] = $userInfo['firstname'];
5489
            $_user['lastName'] = $userInfo['lastname'];
5490
            $_user['mail'] = $userInfo['email'];
5491
            $_user['official_code'] = $userInfo['official_code'];
5492
            $_user['picture_uri'] = $userInfo['picture_uri'];
5493
            $_user['user_id'] = $userId;
5494
            $_user['id'] = $userId;
5495
            $_user['status'] = $userInfo['status'];
5496
5497
            // Filling session variables with new data
5498
            Session::write('_uid', $userId);
5499
            Session::write('_user', $userInfo);
5500
            Session::write('is_platformAdmin', (bool) self::is_admin($userId));
5501
            Session::write('is_allowedCreateCourse', 1 == $userInfo['status']);
5502
            // will be useful later to know if the user is actually an admin or not (example reporting)
5503
            Session::write('login_as', true);
5504
            $logInfo = [
5505
                'tool' => 'login',
5506
                'tool_id' => 0,
5507
                'tool_id_detail' => 0,
5508
                'info' => $userId,
5509
            ];
5510
            Event::registerLog($logInfo);
5511
5512
            return true;
5513
        }
5514
5515
        return false;
5516
    }
5517
5518
    /**
5519
     * Remove all login records from the track_e_online stats table,
5520
     * for the given user ID.
5521
     *
5522
     * @param int $userId User ID
5523
     */
5524
    public static function loginDelete($userId)
5525
    {
5526
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
5527
        $userId = (int) $userId;
5528
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
5529
        Database::query($query);
5530
    }
5531
5532
    /**
5533
     * Login as first admin user registered in the platform.
5534
     *
5535
     * @return array
5536
     */
5537
    public static function logInAsFirstAdmin()
5538
    {
5539
        $adminList = self::get_all_administrators();
5540
5541
        if (!empty($adminList)) {
5542
            $userInfo = current($adminList);
5543
            if (!empty($userInfo)) {
5544
                $result = self::loginAsUser($userInfo['user_id'], false);
5545
                if ($result && api_is_platform_admin()) {
5546
                    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...
5547
                }
5548
            }
5549
        }
5550
5551
        return [];
5552
    }
5553
5554
    /**
5555
     * Check if user is teacher of a student based in their courses.
5556
     *
5557
     * @param $teacherId
5558
     * @param $studentId
5559
     *
5560
     * @return array
5561
     */
5562
    public static function getCommonCoursesBetweenTeacherAndStudent($teacherId, $studentId)
5563
    {
5564
        $courses = CourseManager::getCoursesFollowedByUser(
5565
            $teacherId,
5566
            COURSEMANAGER
5567
        );
5568
        if (empty($courses)) {
5569
            return false;
5570
        }
5571
5572
        $coursesFromUser = CourseManager::get_courses_list_by_user_id($studentId);
5573
        if (empty($coursesFromUser)) {
5574
            return false;
5575
        }
5576
5577
        $coursesCodeList = array_column($courses, 'code');
5578
        $coursesCodeFromUserList = array_column($coursesFromUser, 'code');
5579
        $commonCourses = array_intersect($coursesCodeList, $coursesCodeFromUserList);
5580
        $commonCourses = array_filter($commonCourses);
5581
5582
        if (!empty($commonCourses)) {
5583
            return $commonCourses;
5584
        }
5585
5586
        return [];
5587
    }
5588
5589
    /**
5590
     * @param int $teacherId
5591
     * @param int $studentId
5592
     *
5593
     * @return bool
5594
     */
5595
    public static function isTeacherOfStudent($teacherId, $studentId)
5596
    {
5597
        $courses = self::getCommonCoursesBetweenTeacherAndStudent(
5598
            $teacherId,
5599
            $studentId
5600
        );
5601
5602
        if (!empty($courses)) {
5603
            return true;
5604
        }
5605
5606
        return false;
5607
    }
5608
5609
    /**
5610
     * Send user confirmation mail.
5611
     *
5612
     * @throws Exception
5613
     */
5614
    public static function sendUserConfirmationMail(User $user)
5615
    {
5616
        $uniqueId = api_get_unique_id();
5617
        $user->setConfirmationToken($uniqueId);
5618
5619
        Database::getManager()->persist($user);
5620
        Database::getManager()->flush();
5621
5622
        $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
5623
5624
        // Check if the user was originally set for an automated subscription to a course or session
5625
        $courseCodeToRedirect = Session::read('course_redirect');
5626
        $sessionToRedirect = Session::read('session_redirect');
5627
        if (!empty($courseCodeToRedirect)) {
5628
            $url .= '&c='.$courseCodeToRedirect;
5629
        }
5630
        if (!empty($sessionToRedirect)) {
5631
            $url .= '&s='.$sessionToRedirect;
5632
        }
5633
        $mailSubject = get_lang('Registration confirmation');
5634
        $mailBody = get_lang('To complete your platform registration you need confirm your account by clicking the following link')
5635
            .PHP_EOL
5636
            .Display::url($url, $url);
5637
5638
        api_mail_html(
5639
            self::formatUserFullName($user),
5640
            $user->getEmail(),
5641
            $mailSubject,
5642
            $mailBody
5643
        );
5644
        Display::addFlash(Display::return_message(get_lang('Check your e-mail and follow the instructions.')));
5645
    }
5646
5647
    /**
5648
     * Anonymize a user. Replace personal info by anonymous info.
5649
     *
5650
     * @param int  $userId   User id
5651
     * @param bool $deleteIP Whether to replace the IP address in logs tables by 127.0.0.1 or to leave as is
5652
     *
5653
     * @throws \Exception
5654
     *
5655
     * @return bool
5656
     * @assert (0) === false
5657
     */
5658
    public static function anonymize($userId, $deleteIP = true)
5659
    {
5660
        global $debug;
5661
5662
        $userId = (int) $userId;
5663
5664
        if (empty($userId)) {
5665
            return false;
5666
        }
5667
5668
        $em = Database::getManager();
5669
        $user = api_get_user_entity($userId);
5670
        $uniqueId = uniqid('anon', true);
5671
        $user
5672
            ->setFirstname($uniqueId)
5673
            ->setLastname($uniqueId)
5674
            ->setBiography('')
5675
            ->setAddress('')
5676
            //->setCurriculumItems(null)
5677
            ->setDateOfBirth(null)
5678
            ->setCompetences('')
5679
            ->setDiplomas('')
5680
            ->setOpenarea('')
5681
            ->setTeach('')
5682
            ->setProductions(null)
5683
            ->setOpenid('')
5684
            ->setEmailCanonical($uniqueId.'@example.com')
5685
            ->setEmail($uniqueId.'@example.com')
5686
            ->setUsername($uniqueId)
5687
            ->setUsernameCanonical($uniqueId)
5688
            ->setPhone('')
5689
            ->setOfficialCode('')
5690
        ;
5691
5692
        self::deleteUserPicture($userId);
5693
        self::cleanUserRequestsOfRemoval($userId);
5694
5695
        // The IP address is a border-case personal data, as it does
5696
        // not directly allow for personal identification (it is not
5697
        // a completely safe value in most countries - the IP could
5698
        // be used by neighbours and crackers)
5699
        if ($deleteIP) {
5700
            $substitute = '127.0.0.1';
5701
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
5702
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE access_user_id = $userId";
5703
            $res = Database::query($sql);
5704
            if (false === $res && $debug > 0) {
5705
                error_log("Could not anonymize IP address for user $userId ($sql)");
5706
            }
5707
5708
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5709
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5710
            $res = Database::query($sql);
5711
            if (false === $res && $debug > 0) {
5712
                error_log("Could not anonymize IP address for user $userId ($sql)");
5713
            }
5714
5715
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
5716
            $sql = "UPDATE $table SET user_ip = '$substitute' WHERE exe_user_id = $userId";
5717
            $res = Database::query($sql);
5718
            if (false === $res && $debug > 0) {
5719
                error_log("Could not anonymize IP address for user $userId ($sql)");
5720
            }
5721
5722
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
5723
            $sql = "UPDATE $table SET user_ip = '$substitute' WHERE login_user_id = $userId";
5724
            $res = Database::query($sql);
5725
            if (false === $res && $debug > 0) {
5726
                error_log("Could not anonymize IP address for user $userId ($sql)");
5727
            }
5728
5729
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
5730
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
5731
            $res = Database::query($sql);
5732
            if (false === $res && $debug > 0) {
5733
                error_log("Could not anonymize IP address for user $userId ($sql)");
5734
            }
5735
5736
            $table = Database::get_course_table(TABLE_WIKI);
5737
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5738
            $res = Database::query($sql);
5739
            if (false === $res && $debug > 0) {
5740
                error_log("Could not anonymize IP address for user $userId ($sql)");
5741
            }
5742
5743
            $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
5744
            $sql = "UPDATE $table set ip_address = '$substitute' WHERE sys_insert_user_id = $userId";
5745
            $res = Database::query($sql);
5746
            if (false === $res && $debug > 0) {
5747
                error_log("Could not anonymize IP address for user $userId ($sql)");
5748
            }
5749
5750
            $table = Database::get_course_table(TABLE_WIKI);
5751
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
5752
            $res = Database::query($sql);
5753
            if (false === $res && $debug > 0) {
5754
                error_log("Could not anonymize IP address for user $userId ($sql)");
5755
            }
5756
        }
5757
5758
        $extraFieldRepository = $em->getRepository(EntityExtraField::class);
5759
        $autoRemoveFields = $extraFieldRepository->findBy([
5760
            'autoRemove' => 1,
5761
            'itemType' => EntityExtraField::USER_FIELD_TYPE
5762
        ]);
5763
5764
        foreach ($autoRemoveFields as $field) {
5765
            $extraFieldValueRepository = $em->getRepository(EntityExtraFieldValues::class);
5766
            $extraFieldValue = $extraFieldValueRepository->findOneBy([
5767
                'field' => $field,
5768
                'itemId' => $userId
5769
            ]);
5770
5771
            if ($extraFieldValue) {
5772
                $em->remove($extraFieldValue);
5773
            }
5774
        }
5775
5776
        $em->persist($user);
5777
        $em->flush();
5778
        Event::addEvent(LOG_USER_ANONYMIZE, LOG_USER_ID, $userId);
5779
5780
        return true;
5781
    }
5782
5783
    /**
5784
     * @param int $userId
5785
     *
5786
     * @throws Exception
5787
     *
5788
     * @return string
5789
     */
5790
    public static function anonymizeUserWithVerification($userId)
5791
    {
5792
        $allowDelete = ('true' === api_get_setting('session.allow_delete_user_for_session_admin'));
5793
5794
        $message = '';
5795
        if (api_is_platform_admin() ||
5796
            ($allowDelete && api_is_session_admin())
5797
        ) {
5798
            $userToUpdateInfo = api_get_user_info($userId);
5799
            $currentUserId = api_get_user_id();
5800
5801
            if ($userToUpdateInfo &&
5802
                api_global_admin_can_edit_admin($userId, null, $allowDelete)
5803
            ) {
5804
                if ($userId != $currentUserId &&
5805
                    self::anonymize($userId)
5806
                ) {
5807
                    $message = Display::return_message(
5808
                        sprintf(get_lang("User %s's information anonymized."), $userToUpdateInfo['complete_name_with_username']),
5809
                        'confirmation'
5810
                    );
5811
                } else {
5812
                    $message = Display::return_message(
5813
                        sprintf(get_lang("We could not anonymize user %s's information. Please try again or check the logs."), $userToUpdateInfo['complete_name_with_username']),
5814
                        'error'
5815
                    );
5816
                }
5817
            } else {
5818
                $message = Display::return_message(
5819
                    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']),
5820
                    'error'
5821
                );
5822
            }
5823
        }
5824
5825
        return $message;
5826
    }
5827
5828
    /**
5829
     * @param int $userId
5830
     *
5831
     * @throws Exception
5832
     *
5833
     * @return string
5834
     */
5835
    public static function deleteUserWithVerification($userId, bool $destroy = false)
5836
    {
5837
        $allowDelete = ('true' === api_get_setting('session.allow_delete_user_for_session_admin'));
5838
        $message = Display::return_message(get_lang('You cannot delete this user'), 'error');
5839
        $userToUpdateInfo = api_get_user_info($userId);
5840
5841
        // User must exist.
5842
        if (empty($userToUpdateInfo)) {
5843
            return $message;
5844
        }
5845
5846
        $currentUserId = api_get_user_id();
5847
5848
        // Cannot delete myself.
5849
        if ($userId == $currentUserId) {
5850
            return $message;
5851
        }
5852
5853
        if (api_is_platform_admin() ||
5854
            ($allowDelete && api_is_session_admin())
5855
        ) {
5856
            if (api_global_admin_can_edit_admin($userId, null, $allowDelete)) {
5857
                if (self::delete_user($userId, $destroy)) {
5858
                    $message = Display::return_message(
5859
                        get_lang('The user has been deleted').': '.$userToUpdateInfo['complete_name_with_username'],
5860
                        'confirmation'
5861
                    );
5862
                } else {
5863
                    $message = Display::return_message(get_lang('This user cannot be deleted because he is still teacher in a course. You can either remove his teacher status from these courses and then delete his account, or disable his account instead of deleting it.'), 'error');
5864
                }
5865
            }
5866
        }
5867
5868
        return $message;
5869
    }
5870
5871
    /**
5872
     * @return array
5873
     */
5874
    public static function createDataPrivacyExtraFields()
5875
    {
5876
        self::create_extra_field(
5877
            'request_for_legal_agreement_consent_removal_justification',
5878
            1, //text
5879
            'Request for legal agreement consent removal justification	',
5880
            ''
5881
        );
5882
5883
        self::create_extra_field(
5884
            'request_for_delete_account_justification',
5885
            1, //text
5886
            'Request for delete account justification',
5887
            ''
5888
        );
5889
5890
        $extraFieldId = self::create_extra_field(
5891
            'request_for_legal_agreement_consent_removal',
5892
            1, //text
5893
            'Request for legal agreement consent removal',
5894
            ''
5895
        );
5896
5897
        $extraFieldIdDeleteAccount = self::create_extra_field(
5898
            'request_for_delete_account',
5899
            1, //text
5900
            'Request for delete user account',
5901
            ''
5902
        );
5903
5904
        return [
5905
            'delete_account_extra_field' => $extraFieldIdDeleteAccount,
5906
            'delete_legal' => $extraFieldId,
5907
        ];
5908
    }
5909
5910
    /**
5911
     * @param int $userId
5912
     */
5913
    public static function cleanUserRequestsOfRemoval($userId)
5914
    {
5915
        $userId = (int) $userId;
5916
5917
        $extraFieldValue = new ExtraFieldValue('user');
5918
        $extraFieldsToDelete = [
5919
            'legal_accept',
5920
            'request_for_legal_agreement_consent_removal',
5921
            'request_for_legal_agreement_consent_removal_justification',
5922
            'request_for_delete_account_justification', // just in case delete also this
5923
            'request_for_delete_account',
5924
        ];
5925
5926
        foreach ($extraFieldsToDelete as $variable) {
5927
            $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5928
                $userId,
5929
                $variable
5930
            );
5931
            if ($value && isset($value['id'])) {
5932
                $extraFieldValue->delete($value['id']);
5933
            }
5934
        }
5935
    }
5936
5937
    /**
5938
     * @param int $searchYear
5939
     *
5940
     * @throws Exception
5941
     *
5942
     * @return array
5943
     */
5944
    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
5945
    {
5946
        $timezone = new DateTimeZone(api_get_timezone());
5947
5948
        $sessions = [];
5949
        if (DRH == $userInfo['status']) {
5950
            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
5951
        } elseif (api_is_platform_admin(true)) {
5952
            $sessions = SessionManager::getSessionsForAdmin($userInfo['id']);
5953
        } else {
5954
            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
5955
            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
5956
5957
            foreach ($sessionsByCategory as $sessionsInCategory) {
5958
                $sessions = array_merge($sessions, $sessionsInCategory);
5959
            }
5960
        }
5961
5962
        $sessions = array_map(
5963
            function ($sessionInfo) {
5964
                if (!isset($sessionInfo['session_id'])) {
5965
                    $sessionInfo['session_id'] = $sessionInfo['id'];
5966
                }
5967
                if (!isset($sessionInfo['session_name'])) {
5968
                    $sessionInfo['session_name'] = $sessionInfo['name'];
5969
                }
5970
5971
                return $sessionInfo;
5972
            },
5973
            $sessions
5974
        );
5975
5976
        $calendarSessions = [];
5977
5978
        foreach ($sessions as $sessionInfo) {
5979
            if (!empty($sessionInfo['duration'])) {
5980
                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
5981
                    $sessionInfo['session_id'],
5982
                    $userInfo['id']
5983
                );
5984
5985
                if (empty($courseAccess)) {
5986
                    continue;
5987
                }
5988
5989
                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
5990
                $lastAccessDate = clone $firstAcessDate;
5991
                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
5992
5993
                $firstAccessYear = (int) $firstAcessDate->format('Y');
5994
                $lastAccessYear = (int) $lastAccessDate->format('Y');
5995
5996
                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
5997
                    $calendarSessions[$sessionInfo['session_id']] = [
5998
                        'name' => $sessionInfo['session_name'],
5999
                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
6000
                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
6001
                    ];
6002
                }
6003
6004
                continue;
6005
            }
6006
6007
            $accessStartDate = !empty($sessionInfo['access_start_date'])
6008
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
6009
                : null;
6010
            $accessEndDate = !empty($sessionInfo['access_end_date'])
6011
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
6012
                : null;
6013
            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
6014
            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
6015
6016
            $isValid = false;
6017
6018
            if ($accessStartYear && $accessEndYear) {
6019
                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
6020
                    $isValid = true;
6021
                }
6022
            }
6023
6024
            if ($accessStartYear && !$accessEndYear) {
6025
                if ($accessStartYear == $searchYear) {
6026
                    $isValid = true;
6027
                }
6028
            }
6029
6030
            if (!$accessStartYear && $accessEndYear) {
6031
                if ($accessEndYear == $searchYear) {
6032
                    $isValid = true;
6033
                }
6034
            }
6035
6036
            if ($isValid) {
6037
                $calendarSessions[$sessionInfo['session_id']] = [
6038
                    'name' => $sessionInfo['session_name'],
6039
                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
6040
                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
6041
                ];
6042
            }
6043
        }
6044
6045
        return $calendarSessions;
6046
    }
6047
6048
    /**
6049
     * Get sessions info for planification calendar.
6050
     *
6051
     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
6052
     * @param int   $searchYear
6053
     *
6054
     * @throws Exception
6055
     *
6056
     * @return array
6057
     */
6058
    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
6059
    {
6060
        $timezone = new DateTimeZone(api_get_timezone());
6061
        $calendar = [];
6062
6063
        foreach ($sessionsList as $sessionId => $sessionInfo) {
6064
            $startDate = $sessionInfo['access_start_date']
6065
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
6066
                : null;
6067
            $endDate = $sessionInfo['access_end_date']
6068
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
6069
                : null;
6070
6071
            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
6072
            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
6073
            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
6074
            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
6075
            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
6076
            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
6077
6078
            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
6079
            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
6080
6081
            $calendar[] = [
6082
                'id' => $sessionId,
6083
                'name' => $sessionInfo['name'],
6084
                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
6085
                'start_in_last_year' => $startYear < $searchYear,
6086
                'end_in_next_year' => $endYear > $searchYear,
6087
                'no_start' => !$startWeek,
6088
                'no_end' => !$endWeek,
6089
                'start' => $start,
6090
                'duration' => $duration > 0 ? $duration : 1,
6091
            ];
6092
        }
6093
6094
        usort(
6095
            $calendar,
6096
            function ($sA, $sB) {
6097
                if ($sA['start'] == $sB['start']) {
6098
                    return 0;
6099
                }
6100
6101
                if ($sA['start'] < $sB['start']) {
6102
                    return -1;
6103
                }
6104
6105
                return 1;
6106
            }
6107
        );
6108
6109
        return $calendar;
6110
    }
6111
6112
    /**
6113
     * Return the user's full name. Optionally with the username.
6114
     */
6115
    public static function formatUserFullName(User $user, bool $includeUsername = false): string
6116
    {
6117
        $fullName = api_get_person_name($user->getFirstname(), $user->getLastname());
6118
6119
        if ($includeUsername && 'false' === api_get_setting('profile.hide_username_with_complete_name')) {
6120
            $username = $user->getUsername();
6121
6122
            return "$fullName ($username)";
6123
        }
6124
6125
        return $fullName;
6126
    }
6127
6128
    /**
6129
     * @param int $userId
6130
     *
6131
     * @return array
6132
     */
6133
    public static function getUserCareers($userId)
6134
    {
6135
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6136
        $tableCareer = Database::get_main_table(TABLE_CAREER);
6137
        $userId = (int) $userId;
6138
6139
        $sql = "SELECT c.id, c.title
6140
                FROM $table uc
6141
                INNER JOIN $tableCareer c
6142
                ON uc.career_id = c.id
6143
                WHERE user_id = $userId
6144
                ORDER BY uc.created_at
6145
                ";
6146
        $result = Database::query($sql);
6147
6148
        return Database::store_result($result, 'ASSOC');
6149
    }
6150
6151
    /**
6152
     * @param int $userId
6153
     * @param int $careerId
6154
     */
6155
    public static function addUserCareer($userId, $careerId)
6156
    {
6157
        if ('true' !== api_get_setting('profile.allow_career_users')) {
6158
            return false;
6159
        }
6160
6161
        if (false === self::userHasCareer($userId, $careerId)) {
6162
            $params = ['user_id' => $userId, 'career_id' => $careerId, 'created_at' => api_get_utc_datetime(), 'updated_at' => api_get_utc_datetime()];
6163
            $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6164
            Database::insert($table, $params);
6165
        }
6166
6167
        return true;
6168
    }
6169
6170
    /**
6171
     * @param int   $userCareerId
6172
     * @param array $data
6173
     *
6174
     * @return bool
6175
     */
6176
    public static function updateUserCareer($userCareerId, $data)
6177
    {
6178
        if ('true' !== api_get_setting('profile.allow_career_users')) {
6179
            return false;
6180
        }
6181
6182
        $params = ['extra_data' => $data, 'updated_at' => api_get_utc_datetime()];
6183
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6184
        Database::update(
6185
            $table,
6186
            $params,
6187
            ['id = ?' => (int) $userCareerId]
6188
        );
6189
6190
        return true;
6191
    }
6192
6193
    /**
6194
     * @param int $userId
6195
     * @param int $careerId
6196
     *
6197
     * @return array
6198
     */
6199
    public static function getUserCareer($userId, $careerId)
6200
    {
6201
        $userId = (int) $userId;
6202
        $careerId = (int) $careerId;
6203
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6204
6205
        $sql = "SELECT * FROM $table WHERE user_id = $userId AND career_id = $careerId";
6206
        $result = Database::query($sql);
6207
6208
        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...
6209
    }
6210
6211
    /**
6212
     * @param int $userId
6213
     * @param int $careerId
6214
     *
6215
     * @return bool
6216
     */
6217
    public static function userHasCareer($userId, $careerId)
6218
    {
6219
        $userId = (int) $userId;
6220
        $careerId = (int) $careerId;
6221
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
6222
6223
        $sql = "SELECT id FROM $table WHERE user_id = $userId AND career_id = $careerId";
6224
        $result = Database::query($sql);
6225
6226
        return Database::num_rows($result) > 0;
6227
    }
6228
6229
    /**
6230
     * Disables or enables a user.
6231
     *
6232
     * @param int $user_id
6233
     * @param int $active  Enable or disable
6234
     *
6235
     * @return bool True on success, false on failure
6236
     * @assert (-1,0) === false
6237
     * @assert (1,1) === true
6238
     */
6239
    public static function change_active_state($user_id, $active)
6240
    {
6241
        $user_id = (int) $user_id;
6242
        $active = (int) $active;
6243
6244
        if (empty($user_id)) {
6245
            return false;
6246
        }
6247
6248
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6249
        $sql = "UPDATE $table_user SET active = '$active' WHERE id = $user_id";
6250
        $r = Database::query($sql);
6251
        $ev = LOG_USER_DISABLE;
6252
        if (1 == $active) {
6253
            $ev = LOG_USER_ENABLE;
6254
        }
6255
        if (false !== $r) {
6256
            Event::addEvent($ev, LOG_USER_ID, $user_id);
6257
        }
6258
6259
        return $r;
6260
    }
6261
6262
    /**
6263
     * Get either a Gravatar URL or complete image tag for a specified email address.
6264
     *
6265
     * @param string $email The email address
6266
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
6267
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
6268
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
6269
     * @param bool   $img   True to return a complete IMG tag False for just the URL
6270
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
6271
     *
6272
     * @return string containing either just a URL or a complete image tag
6273
     * @source http://gravatar.com/site/implement/images/php/
6274
     */
6275
    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...
6276
        $email,
6277
        $s = 80,
6278
        $d = 'mm',
6279
        $r = 'g',
6280
        $img = false,
6281
        $atts = []
6282
    ) {
6283
        $url = 'http://www.gravatar.com/avatar/';
6284
        if (!empty($_SERVER['HTTPS'])) {
6285
            $url = 'https://secure.gravatar.com/avatar/';
6286
        }
6287
        $url .= md5(strtolower(trim($email)));
6288
        $url .= "?s=$s&d=$d&r=$r";
6289
        if ($img) {
6290
            $url = '<img src="'.$url.'"';
6291
            foreach ($atts as $key => $val) {
6292
                $url .= ' '.$key.'="'.$val.'"';
6293
            }
6294
            $url .= ' />';
6295
        }
6296
6297
        return $url;
6298
    }
6299
6300
    /**
6301
     * Count users in courses and if they have certificate.
6302
     * This function is resource intensive.
6303
     *
6304
     * @return array
6305
     * @throws Exception
6306
     * @throws \Doctrine\DBAL\Exception
6307
     */
6308
    public static function countUsersWhoFinishedCourses()
6309
    {
6310
        $courses = [];
6311
        $currentAccessUrlId = api_get_current_access_url_id();
6312
        $sql = "SELECT course.code, course.id as cid, cru.user_id
6313
                FROM course_rel_user cru
6314
                    JOIN course ON cru.c_id = course.id
6315
                    JOIN access_url_rel_user auru on cru.user_id = auru.user_id
6316
                    JOIN access_url_rel_course ON course.id = access_url_rel_course.c_id
6317
                WHERE access_url_rel_course.access_url_id = $currentAccessUrlId
6318
                ORDER BY course.code
6319
        ";
6320
        $res = Database::query($sql);
6321
        if (Database::num_rows($res) > 0) {
6322
            while ($row = Database::fetch_array($res)) {
6323
                if (!isset($courses[$row['code']])) {
6324
                    $courses[$row['code']] = [
6325
                        'subscribed' => 0,
6326
                        'finished' => 0,
6327
                    ];
6328
                }
6329
                $courses[$row['code']]['subscribed']++;
6330
                $entityManager = Database::getManager();
6331
                $repository = $entityManager->getRepository(GradebookCategory::class);
6332
                //todo check when have more than 1 gradebook
6333
                /** @var GradebookCategory $gradebook */
6334
                $gradebook = $repository->findOneBy(['course' => $row['cid']]);
6335
                if (!empty($gradebook)) {
6336
                    $finished = 0;
6337
                    Database::getManager()->persist($gradebook);
6338
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6339
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6340
                    if (!empty($finished)) {
6341
                        $courses[$row['code']]['finished']++;
6342
                    }
6343
                }
6344
            }
6345
        }
6346
        return $courses;
6347
    }
6348
6349
    /**
6350
     * Count users in sessions and if they have certificate.
6351
     * This function is resource intensive.
6352
     *
6353
     * @return array
6354
     * @throws Exception
6355
     * @throws \Doctrine\DBAL\Exception
6356
     */
6357
    public static function countUsersWhoFinishedCoursesInSessions()
6358
    {
6359
        $coursesInSessions = [];
6360
        $currentAccessUrlId = api_get_current_access_url_id();
6361
        $sql = "SELECT course.code, srcru.session_id, srcru.user_id, session.title
6362
                FROM session_rel_course_rel_user srcru
6363
                    JOIN course ON srcru.c_id = course.id
6364
                    JOIN access_url_rel_session aurs on srcru.session_id = aurs.session_id
6365
                    JOIN session ON srcru.session_id = session.id
6366
                WHERE aurs.access_url_id = $currentAccessUrlId
6367
                ORDER BY course.code, session.title
6368
        ";
6369
        $res = Database::query($sql);
6370
        if (Database::num_rows($res) > 0) {
6371
            while ($row = Database::fetch_array($res)) {
6372
                $index = $row['code'].' ('.$row['title'].')';
6373
                if (!isset($coursesInSessions[$index])) {
6374
                    $coursesInSessions[$index] = [
6375
                        'subscribed' => 0,
6376
                        'finished' => 0,
6377
                    ];
6378
                }
6379
                $coursesInSessions[$index]['subscribed']++;
6380
                $entityManager = Database::getManager();
6381
                $repository = $entityManager->getRepository(GradebookCategory::class);
6382
                /** @var GradebookCategory $gradebook */
6383
                $gradebook = $repository->findOneBy(
6384
                    [
6385
                        'course' => $row['cid'],
6386
                        'sessionId' => $row['session_id'],
6387
                    ]
6388
                );
6389
                if (!empty($gradebook)) {
6390
                    $finished = 0;
6391
                    Database::getManager()->persist($gradebook);
6392
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6393
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6394
                    if (!empty($finished)) {
6395
                        $coursesInSessions[$index]['finished']++;
6396
                    }
6397
                }
6398
            }
6399
        }
6400
        return $coursesInSessions;
6401
    }
6402
6403
    public static function redirectToResetPassword($userId): void
6404
    {
6405
        if ('true' !== api_get_setting('platform.force_renew_password_at_first_login')) {
6406
            return;
6407
        }
6408
        $askPassword = self::get_extra_user_data_by_field(
6409
            $userId,
6410
            'ask_new_password'
6411
        );
6412
        if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
6413
            1 === (int) $askPassword['ask_new_password']
6414
        ) {
6415
            $uniqueId = api_get_unique_id();
6416
            $userObj = api_get_user_entity($userId);
6417
            $userObj->setConfirmationToken($uniqueId);
6418
            $userObj->setPasswordRequestedAt(new \DateTime());
6419
            Database::getManager()->persist($userObj);
6420
            Database::getManager()->flush();
6421
            $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
6422
            api_location($url);
6423
        }
6424
    }
6425
6426
}
6427