Issues (2037)

main/inc/lib/usermanager.lib.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField;
6
use Chamilo\CoreBundle\Entity\Repository\AccessUrlRepository;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Entity\SkillRelUser;
9
use Chamilo\CoreBundle\Entity\SkillRelUserComment;
10
use Chamilo\CoreBundle\Entity\TrackELoginAttempt;
11
use Chamilo\UserBundle\Entity\User;
12
use Chamilo\UserBundle\Repository\UserRepository;
13
use ChamiloSession as Session;
14
use Symfony\Component\Filesystem\Filesystem;
15
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
16
17
/**
18
 * Class UserManager.
19
 *
20
 * This library provides functions for user management.
21
 * Include/require it in your code to use its functionality.
22
 *
23
 * @author Julio Montoya <[email protected]> Social network groups added 2009/12
24
 */
25
class UserManager
26
{
27
    // This constants are deprecated use the constants located in ExtraField
28
    public const USER_FIELD_TYPE_TEXT = 1;
29
    public const USER_FIELD_TYPE_TEXTAREA = 2;
30
    public const USER_FIELD_TYPE_RADIO = 3;
31
    public const USER_FIELD_TYPE_SELECT = 4;
32
    public const USER_FIELD_TYPE_SELECT_MULTIPLE = 5;
33
    public const USER_FIELD_TYPE_DATE = 6;
34
    public const USER_FIELD_TYPE_DATETIME = 7;
35
    public const USER_FIELD_TYPE_DOUBLE_SELECT = 8;
36
    public const USER_FIELD_TYPE_DIVIDER = 9;
37
    public const USER_FIELD_TYPE_TAG = 10;
38
    public const USER_FIELD_TYPE_TIMEZONE = 11;
39
    public const USER_FIELD_TYPE_SOCIAL_PROFILE = 12;
40
    public const USER_FIELD_TYPE_FILE = 13;
41
    public const USER_FIELD_TYPE_MOBILE_PHONE_NUMBER = 14;
42
43
    private static $encryptionMethod;
44
45
    /**
46
     * Constructor.
47
     *
48
     * @assert () === null
49
     */
50
    public function __construct()
51
    {
52
    }
53
54
    /**
55
     * Repository is use to query the DB, selects, etc.
56
     *
57
     * @return UserRepository
58
     */
59
    public static function getRepository()
60
    {
61
        /** @var UserRepository $userRepository */
62
63
        return Database::getManager()->getRepository('ChamiloUserBundle:User');
64
    }
65
66
    /**
67
     * Create/update/delete methods are available in the UserManager
68
     * (based in the Sonata\UserBundle\Entity\UserManager).
69
     *
70
     * @return Chamilo\UserBundle\Entity\Manager\UserManager
71
     */
72
    public static function getManager()
73
    {
74
        static $userManager;
75
76
        if (!isset($userManager)) {
77
            $encoderFactory = self::getEncoderFactory();
78
            $passwordUpdater = new \FOS\UserBundle\Util\PasswordUpdater($encoderFactory);
79
            $canonicalFieldUpdater = new \FOS\UserBundle\Util\CanonicalFieldsUpdater(
80
                new \FOS\UserBundle\Util\Canonicalizer(), new \FOS\UserBundle\Util\Canonicalizer()
81
            );
82
            $userManager = new Chamilo\UserBundle\Entity\Manager\UserManager(
83
                $passwordUpdater,
84
                $canonicalFieldUpdater,
85
                Database::getManager(),
86
                'Chamilo\\UserBundle\\Entity\\User'
87
            );
88
        }
89
90
        return $userManager;
91
    }
92
93
    /**
94
     * @param string $encryptionMethod
95
     */
96
    public static function setPasswordEncryption($encryptionMethod)
97
    {
98
        self::$encryptionMethod = $encryptionMethod;
99
    }
100
101
    /**
102
     * @return bool|mixed
103
     */
104
    public static function getPasswordEncryption()
105
    {
106
        $encryptionMethod = self::$encryptionMethod;
107
        if (empty($encryptionMethod)) {
108
            $encryptionMethod = api_get_configuration_value('password_encryption');
109
        }
110
111
        return $encryptionMethod;
112
    }
113
114
    /**
115
     * Validates the password.
116
     *
117
     * @param $encoded
118
     * @param $raw
119
     * @param $salt
120
     *
121
     * @return bool
122
     */
123
    public static function isPasswordValid($encoded, $raw, $salt)
124
    {
125
        $encoder = new \Chamilo\UserBundle\Security\Encoder(self::getPasswordEncryption());
126
127
        return $encoder->isPasswordValid($encoded, $raw, $salt);
128
    }
129
130
    /**
131
     * Detects and returns the type of encryption of the given encrypted
132
     * password.
133
     *
134
     * @param string $encoded The encrypted password
135
     * @param string $salt    The user salt, if any
136
     */
137
    public static function detectPasswordEncryption(string $encoded, string $salt): string
138
    {
139
        $encryption = false;
140
141
        $length = strlen($encoded);
142
143
        $pattern = '/^\$2y\$04\$[A-Za-z0-9\.\/]{53}$/';
144
145
        if ($length == 60 && preg_match($pattern, $encoded)) {
146
            $encryption = 'bcrypt';
147
        } elseif ($length == 32 && ctype_xdigit($encoded)) {
148
            $encryption = 'md5';
149
        } elseif ($length == 40 && ctype_xdigit($encoded)) {
150
            $encryption = 'sha1';
151
        } else {
152
            $start = strpos($encoded, '{');
153
            if ($start !== false && substr($encoded, -1, 1) == '}') {
154
                if (substr($encoded, $start + 1, -1) == $salt) {
155
                    $encryption = 'none';
156
                }
157
            }
158
        }
159
160
        return $encryption;
161
    }
162
163
    /**
164
     * Checks if the password is correct for this user.
165
     * If the password_conversion setting is true, also update the password
166
     * in the database to a new encryption method.
167
     *
168
     * @param string $encoded Encrypted password
169
     * @param string $raw     Clear password given through login form
170
     * @param string $salt    User salt, if any
171
     * @param int    $userId  The user's internal ID
172
     */
173
    public static function checkPassword(string $encoded, string $raw, string $salt, int $userId): bool
174
    {
175
        $result = false;
176
177
        if (true === api_get_configuration_value('password_conversion')) {
178
            $detectedEncryption = self::detectPasswordEncryption($encoded, $salt);
179
            if (self::getPasswordEncryption() != $detectedEncryption) {
180
                $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption);
181
                $result = $encoder->isPasswordValid($encoded, $raw, $salt);
182
                if ($result) {
183
                    $raw = $encoder->encodePassword($encoded, $salt);
184
                    self::updatePassword($userId, $raw);
185
                }
186
            } else {
187
                $result = self::isPasswordValid($encoded, $raw, $salt);
188
            }
189
        } else {
190
            $result = self::isPasswordValid($encoded, $raw, $salt);
191
        }
192
193
        return $result;
194
    }
195
196
    /**
197
     * Encrypt the password using the current encoder.
198
     *
199
     * @param string $raw The clear password
200
     */
201
    public static function encryptPassword(string $raw, User $user): string
202
    {
203
        $encoder = self::getEncoder($user);
204
205
        return $encoder->encodePassword(
206
            $raw,
207
            $user->getSalt()
208
        );
209
    }
210
211
    /**
212
     * Update the password of the given user to the given (in-clear) password.
213
     *
214
     * @param int    $userId   Internal user ID
215
     * @param string $password Password in clear
216
     */
217
    public static function updatePassword(int $userId, string $password): void
218
    {
219
        $repository = self::getRepository();
220
        /** @var User $user */
221
        $user = $repository->find($userId);
222
        $userManager = self::getManager();
223
        $user->setPlainPassword($password);
224
        $userManager->updateUser($user, true);
225
        Event::addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $userId);
226
    }
227
228
    /**
229
     * Updates user expiration date.
230
     *
231
     * @param int    $userId
232
     * @param string $expirationDate
233
     */
234
    public static function updateExpirationDate($userId, $expirationDate)
235
    {
236
        $repository = self::getRepository();
237
        /** @var User $user */
238
        $user = $repository->find($userId);
239
        $userManager = self::getManager();
240
        $expirationDate = api_get_utc_datetime($expirationDate, false, true);
241
        $user->setExpirationDate($expirationDate);
242
        $userManager->updateUser($user, true);
243
    }
244
245
    /**
246
     * Creates a new user for the platform.
247
     *
248
     * @author Hugues Peeters <[email protected]>,
249
     * @author Roan Embrechts <[email protected]>
250
     *
251
     * @param string        $firstName
252
     * @param string        $lastName
253
     * @param int           $status                  (1 for course tutor, 5 for student, 6 for anonymous)
254
     * @param string        $email
255
     * @param string        $loginName
256
     * @param string        $password
257
     * @param string        $official_code           Any official code (optional)
258
     * @param string        $language                User language    (optional)
259
     * @param string        $phone                   Phone number    (optional)
260
     * @param string        $picture_uri             Picture URI        (optional)
261
     * @param string        $authSource              Authentication source (defaults to 'platform', dependind on constant)
262
     * @param string        $expirationDate          Account expiration date (optional, defaults to null)
263
     * @param int           $active                  Whether the account is enabled or disabled by default
264
     * @param int           $hr_dept_id              The department of HR in which the user is registered (defaults to 0)
265
     * @param array         $extra                   Extra fields (prefix labels with "extra_")
266
     * @param string        $encrypt_method          Used if password is given encrypted. Set to an empty string by default
267
     * @param bool          $send_mail
268
     * @param bool          $isAdmin
269
     * @param string        $address
270
     * @param bool          $sendEmailToAllAdmins
271
     * @param FormValidator $form
272
     * @param int           $creatorId
273
     * @param array         $emailTemplate
274
     * @param string        $redirectToURLAfterLogin
275
     *
276
     * @return mixed new user id - if the new user creation succeeds, false otherwise
277
     * @desc The function tries to retrieve user id from the session.
278
     * If it exists, the current user id is the creator id. If a problem arises,
279
     * @assert ('Sam','Gamegie',5,'[email protected]','jo','jo') > 1
280
     * @assert ('Pippin','Took',null,null,'jo','jo') === false
281
     */
282
    public static function create_user(
283
        $firstName,
284
        $lastName,
285
        $status,
286
        $email,
287
        $loginName,
288
        $password,
289
        $official_code = '',
290
        $language = '',
291
        $phone = '',
292
        $picture_uri = '',
293
        $authSource = PLATFORM_AUTH_SOURCE,
294
        $expirationDate = null,
295
        $active = 1,
296
        $hr_dept_id = 0,
297
        $extra = [],
298
        $encrypt_method = '',
299
        $send_mail = false,
300
        $isAdmin = false,
301
        $address = '',
302
        $sendEmailToAllAdmins = false,
303
        $form = null,
304
        $creatorId = 0,
305
        $emailTemplate = [],
306
        $redirectToURLAfterLogin = ''
307
    ) {
308
        $creatorId = empty($creatorId) ? api_get_user_id() : 0;
309
        $creatorInfo = api_get_user_info($creatorId);
310
        $creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
311
312
        $hook = HookCreateUser::create();
313
        if (!empty($hook)) {
314
            $hook->notifyCreateUser(HOOK_EVENT_TYPE_PRE);
315
        }
316
317
        if ('true' === api_get_setting('registration', 'email')) {
318
            // Force email validation.
319
            if (false === api_valid_email($email)) {
320
                Display::addFlash(
321
                   Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$email, 'warning')
322
               );
323
324
                return false;
325
            }
326
        } else {
327
            // Allow empty email. If email is set, check if is valid.
328
            if (!empty($email) && false === api_valid_email($email)) {
329
                Display::addFlash(
330
                    Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$email, 'warning')
331
                );
332
333
                return false;
334
            }
335
        }
336
337
        if ('true' === api_get_setting('login_is_email')) {
338
            if (false === api_valid_email($loginName)) {
339
                Display::addFlash(
340
                    Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$loginName, 'warning')
341
                );
342
343
                return false;
344
            }
345
        } else {
346
            if (false === self::is_username_valid($loginName)) {
347
                Display::addFlash(
348
                    Display::return_message(get_lang('UsernameWrong').' - '.$loginName, 'warning')
349
                );
350
351
                return false;
352
            }
353
        }
354
355
        // First check wether the login already exists
356
        if (!self::is_username_available($loginName)) {
357
            Display::addFlash(
358
                Display::return_message(get_lang('LoginAlreadyTaken').' - '.$loginName, 'warning')
359
            );
360
361
            return false;
362
        }
363
364
        global $_configuration;
365
        $original_password = $password;
366
367
        $access_url_id = 1;
368
        if (api_get_multiple_access_url()) {
369
            $access_url_id = api_get_current_access_url_id();
370
        } else {
371
            // In some cases, the first access_url ID might be different from 1
372
            // for example when using a DB cluster or hacking the DB manually.
373
            // In this case, we want the first row, not necessarily "1".
374
            $dbm = Database::getManager();
375
            /** @var AccessUrlRepository $accessUrlRepository */
376
            $accessUrlRepository = $dbm->getRepository('ChamiloCoreBundle:AccessUrl');
377
            $accessUrl = $accessUrlRepository->getFirstId();
378
            if (!empty($accessUrl[0]) && !empty($accessUrl[0][1])) {
379
                $access_url_id = $accessUrl[0][1];
380
            }
381
        }
382
383
        if (isset($_configuration[$access_url_id]) &&
384
            is_array($_configuration[$access_url_id]) &&
385
            isset($_configuration[$access_url_id]['hosting_limit_users']) &&
386
            $_configuration[$access_url_id]['hosting_limit_users'] > 0) {
387
            $num = self::get_number_of_users(null, $access_url_id);
388
            if ($num >= $_configuration[$access_url_id]['hosting_limit_users']) {
389
                api_warn_hosting_contact('hosting_limit_users');
390
                Display::addFlash(
391
                    Display::return_message(
392
                        get_lang('PortalUsersLimitReached'),
393
                        'warning'
394
                    )
395
                );
396
397
                return false;
398
            }
399
        }
400
401
        if ($status === 1 &&
402
            isset($_configuration[$access_url_id]) &&
403
            is_array($_configuration[$access_url_id]) &&
404
            isset($_configuration[$access_url_id]['hosting_limit_teachers']) &&
405
            $_configuration[$access_url_id]['hosting_limit_teachers'] > 0
406
        ) {
407
            $num = self::get_number_of_users(1, $access_url_id);
408
            if ($num >= $_configuration[$access_url_id]['hosting_limit_teachers']) {
409
                Display::addFlash(
410
                    Display::return_message(
411
                        get_lang('PortalTeachersLimitReached'),
412
                        'warning'
413
                    )
414
                );
415
                api_warn_hosting_contact('hosting_limit_teachers');
416
417
                return false;
418
            }
419
        }
420
421
        if (empty($password)) {
422
            if ($authSource === PLATFORM_AUTH_SOURCE) {
423
                Display::addFlash(
424
                    Display::return_message(
425
                        get_lang('ThisFieldIsRequired').': '.get_lang(
426
                            'Password'
427
                        ),
428
                        'warning'
429
                    )
430
                );
431
432
                return false;
433
            }
434
435
            // We use the authSource as password.
436
            // The real validation will be by processed by the auth
437
            // source not Chamilo
438
            $password = $authSource;
439
        }
440
441
        // database table definition
442
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
443
444
        // Checking the user language
445
        $languages = api_get_languages();
446
        $language = strtolower($language);
447
448
        if (isset($languages['folder'])) {
449
            if (!in_array($language, $languages['folder'])) {
450
                $language = api_get_setting('platformLanguage');
451
            }
452
        }
453
454
        $currentDate = api_get_utc_datetime();
455
        $now = new DateTime();
456
457
        if (empty($expirationDate) || $expirationDate == '0000-00-00 00:00:00') {
458
            // Default expiration date
459
            // if there is a default duration of a valid account then
460
            // we have to change the expiration_date accordingly
461
            // Accept 0000-00-00 00:00:00 as a null value to avoid issues with
462
            // third party code using this method with the previous (pre-1.10)
463
            // value of 0000...
464
            if (api_get_setting('account_valid_duration') != '') {
465
                $expirationDate = new DateTime($currentDate);
466
                $days = (int) api_get_setting('account_valid_duration');
467
                $expirationDate->modify('+'.$days.' day');
468
            }
469
        } else {
470
            $expirationDate = api_get_utc_datetime($expirationDate);
471
            $expirationDate = new \DateTime($expirationDate, new DateTimeZone('UTC'));
472
        }
473
474
        $userManager = self::getManager();
475
476
        /** @var User $user */
477
        $user = $userManager->createUser();
478
479
        $user
480
            ->setLastname($lastName)
481
            ->setFirstname($firstName)
482
            ->setUsername($loginName)
483
            ->setStatus($status)
484
            ->setPlainPassword($password)
485
            ->setEmail($email)
486
            ->setOfficialCode($official_code)
487
            ->setPictureUri($picture_uri)
488
            ->setCreatorId($creatorId)
489
            ->setAuthSource($authSource)
490
            ->setPhone($phone)
491
            ->setAddress($address)
492
            ->setLanguage($language)
493
            ->setRegistrationDate($now)
494
            ->setHrDeptId($hr_dept_id)
495
            ->setActive($active)
496
            ->setEnabled($active)
497
        ;
498
499
        if (!empty($expirationDate)) {
500
            $user->setExpirationDate($expirationDate);
501
        }
502
503
        $userManager->updateUser($user);
504
        $userId = $user->getId();
505
506
        if (!empty($userId)) {
507
            $return = $userId;
508
            $sql = "UPDATE $table_user SET user_id = $return WHERE id = $return";
509
            Database::query($sql);
510
511
            if ($isAdmin) {
512
                self::addUserAsAdmin($user);
513
            }
514
515
            if (api_get_multiple_access_url()) {
516
                UrlManager::add_user_to_url($userId, api_get_current_access_url_id());
517
            } else {
518
                //we are adding by default the access_url_user table with access_url_id = 1
519
                UrlManager::add_user_to_url($userId, 1);
520
            }
521
522
            $extra['item_id'] = $userId;
523
524
            if (is_array($extra) && count($extra) > 0) {
525
                $userFieldValue = new ExtraFieldValue('user');
526
                // Force saving of extra fields (otherwise, if the current
527
                // user is not admin, fields not visible to the user - most
528
                // of them - are just ignored)
529
                $userFieldValue->saveFieldValues(
530
                    $extra,
531
                    true,
532
                    null,
533
                    null,
534
                    null,
535
                    true
536
                );
537
            } else {
538
                // Create notify settings by default
539
                self::update_extra_field_value(
540
                    $userId,
541
                    'mail_notify_invitation',
542
                    '1'
543
                );
544
                self::update_extra_field_value(
545
                    $userId,
546
                    'mail_notify_message',
547
                    '1'
548
                );
549
                self::update_extra_field_value(
550
                    $userId,
551
                    'mail_notify_group_message',
552
                    '1'
553
                );
554
            }
555
556
            self::update_extra_field_value(
557
                $userId,
558
                'already_logged_in',
559
                'false'
560
            );
561
562
            if (!empty($redirectToURLAfterLogin) && api_get_configuration_value('plugin_redirection_enabled')) {
563
                RedirectionPlugin::insert($userId, $redirectToURLAfterLogin);
564
            }
565
566
            if (!empty($email) && $send_mail) {
567
                $recipient_name = api_get_person_name(
568
                    $firstName,
569
                    $lastName,
570
                    null,
571
                    PERSON_NAME_EMAIL_ADDRESS
572
                );
573
                $tplSubject = new Template(
574
                    null,
575
                    false,
576
                    false,
577
                    false,
578
                    false,
579
                    false
580
		);
581
		// the complete_name is not used in the default Chamilo template but used in a specific template -refs BT#21334
582
		$tplSubject->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
583
                $layoutSubject = $tplSubject->get_template('mail/subject_registration_platform.tpl');
584
                $emailSubject = $tplSubject->fetch($layoutSubject);
585
                $sender_name = api_get_person_name(
586
                    api_get_setting('administratorName'),
587
                    api_get_setting('administratorSurname'),
588
                    null,
589
                    PERSON_NAME_EMAIL_ADDRESS
590
                );
591
                $email_admin = api_get_setting('emailAdministrator');
592
593
                $url = api_get_path(WEB_PATH);
594
                if (api_is_multiple_url_enabled()) {
595
                    $access_url_id = api_get_current_access_url_id();
596
                    if ($access_url_id != -1) {
597
                        $urlInfo = api_get_access_url($access_url_id);
598
                        if ($urlInfo) {
599
                            $url = $urlInfo['url'];
600
                        }
601
                    }
602
                }
603
604
                $tplContent = new Template(
605
                    null,
606
                    false,
607
                    false,
608
                    false,
609
                    false,
610
                    false
611
                );
612
                // variables for the default template
613
                $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
614
                $tplContent->assign('login_name', $loginName);
615
                $tplContent->assign('original_password', stripslashes($original_password));
616
                $tplContent->assign('mailWebPath', $url);
617
                $tplContent->assign('new_user', $user);
618
                // Adding this variable but not used in default template, used for task BT19518 with a customized template
619
                $tplContent->assign('status_type', $status);
620
621
                $layoutContent = $tplContent->get_template('mail/content_registration_platform.tpl');
622
                $emailBody = $tplContent->fetch($layoutContent);
623
624
                $userInfo = api_get_user_info($userId);
625
                $mailTemplateManager = new MailTemplateManager();
626
627
                /* MANAGE EVENT WITH MAIL */
628
                if (EventsMail::check_if_using_class('user_registration')) {
629
                    $values["about_user"] = $return;
630
                    $values["password"] = $original_password;
631
                    $values["send_to"] = [$return];
632
                    $values["prior_lang"] = null;
633
                    EventsDispatcher::events('user_registration', $values);
634
                } else {
635
                    $phoneNumber = isset($extra['mobile_phone_number']) ? $extra['mobile_phone_number'] : null;
636
                    $additionalParameters = [
637
                        'smsType' => SmsPlugin::WELCOME_LOGIN_PASSWORD,
638
                        'userId' => $return,
639
                        'mobilePhoneNumber' => $phoneNumber,
640
                        'password' => $original_password,
641
                    ];
642
643
                    $emailBodyTemplate = '';
644
                    if (!empty($emailTemplate)) {
645
                        if (isset($emailTemplate['content_registration_platform.tpl']) &&
646
                            !empty($emailTemplate['content_registration_platform.tpl'])
647
                        ) {
648
                            $emailBodyTemplate = $mailTemplateManager->parseTemplate(
649
                                $emailTemplate['content_registration_platform.tpl'],
650
                                $userInfo
651
                            );
652
                        }
653
                    }
654
655
                    $twoEmail = api_get_configuration_value('send_two_inscription_confirmation_mail');
656
                    if ($twoEmail === true) {
657
                        $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
658
                        $emailBody = $tplContent->fetch($layoutContent);
659
660
                        if (!empty($emailTemplate) &&
661
                            isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
662
                            !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
663
                        ) {
664
                            $emailBody = $mailTemplateManager->parseTemplate(
665
                                $emailTemplate['new_user_first_email_confirmation.tpl'],
666
                                $userInfo
667
                            );
668
                        }
669
670
                        api_mail_html(
671
                            $recipient_name,
672
                            $email,
673
                            $emailSubject,
674
                            $emailBody,
675
                            $sender_name,
676
                            $email_admin,
677
                            null,
678
                            null,
679
                            null,
680
                            $additionalParameters,
681
                            $creatorEmail
682
                        );
683
684
                        $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
685
                        $emailBody = $tplContent->fetch($layoutContent);
686
687
                        if (!empty($emailTemplate) &&
688
                            isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
689
                            !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
690
                        ) {
691
                            $emailBody = $mailTemplateManager->parseTemplate(
692
                                $emailTemplate['new_user_second_email_confirmation.tpl'],
693
                                $userInfo
694
                            );
695
                        }
696
697
                        api_mail_html(
698
                            $recipient_name,
699
                            $email,
700
                            $emailSubject,
701
                            $emailBody,
702
                            $sender_name,
703
                            $email_admin,
704
                            null,
705
                            null,
706
                            null,
707
                            $additionalParameters,
708
                            $creatorEmail
709
                        );
710
                    } else {
711
                        if (!empty($emailBodyTemplate)) {
712
                            $emailBody = $emailBodyTemplate;
713
                        }
714
                        $sendToInbox = api_get_configuration_value('send_inscription_msg_to_inbox');
715
                        if ($sendToInbox) {
716
                            $adminList = self::get_all_administrators();
717
                            $senderId = 1;
718
                            if (!empty($adminList)) {
719
                                $adminInfo = current($adminList);
720
                                $senderId = $adminInfo['user_id'];
721
                            }
722
723
                            MessageManager::send_message_simple(
724
                                $userId,
725
                                $emailSubject,
726
                                $emailBody,
727
                                $senderId
728
                            );
729
                        } else {
730
                            api_mail_html(
731
                                $recipient_name,
732
                                $email,
733
                                $emailSubject,
734
                                $emailBody,
735
                                $sender_name,
736
                                $email_admin,
737
                                null,
738
                                null,
739
                                null,
740
                                $additionalParameters,
741
                                $creatorEmail
742
                            );
743
                        }
744
                    }
745
746
                    $notification = api_get_configuration_value('send_notification_when_user_added');
747
                    if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {
748
                        foreach ($notification['admins'] as $adminId) {
749
                            $emailSubjectToAdmin = get_lang('UserAdded').': '.api_get_person_name($firstName, $lastName);
750
                            MessageManager::send_message_simple($adminId, $emailSubjectToAdmin, $emailBody, $userId);
751
                        }
752
                    }
753
                }
754
755
                if ($sendEmailToAllAdmins) {
756
                    $adminList = self::get_all_administrators();
757
758
                    $tplContent = new Template(
759
                        null,
760
                        false,
761
                        false,
762
                        false,
763
                        false,
764
                        false
765
                    );
766
                    // variables for the default template
767
                    $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
768
                    $tplContent->assign('user_added', $user);
769
                    $renderer = FormValidator::getDefaultRenderer();
770
                    // Form template
771
                    $elementTemplate = ' {label}: {element} <br />';
772
                    $renderer->setElementTemplate($elementTemplate);
773
                    /** @var FormValidator $form */
774
                    $form->freeze(null, $elementTemplate);
775
                    $form->removeElement('submit');
776
                    $formData = $form->returnForm();
777
                    $url = api_get_path(WEB_CODE_PATH).'admin/user_information.php?user_id='.$user->getId();
778
                    $tplContent->assign('link', Display::url($url, $url));
779
                    $tplContent->assign('form', $formData);
780
781
                    $layoutContent = $tplContent->get_template('mail/content_registration_platform_to_admin.tpl');
782
                    $emailBody = $tplContent->fetch($layoutContent);
783
784
                    if (!empty($emailTemplate) &&
785
                        isset($emailTemplate['content_registration_platform_to_admin.tpl']) &&
786
                        !empty($emailTemplate['content_registration_platform_to_admin.tpl'])
787
                    ) {
788
                        $emailBody = $mailTemplateManager->parseTemplate(
789
                            $emailTemplate['content_registration_platform_to_admin.tpl'],
790
                            $userInfo
791
                        );
792
                    }
793
794
                    $subject = get_lang('UserAdded');
795
                    foreach ($adminList as $adminId => $data) {
796
                        MessageManager::send_message_simple(
797
                            $adminId,
798
                            $subject,
799
                            $emailBody,
800
                            $userId
801
                        );
802
                    }
803
                }
804
                /* ENDS MANAGE EVENT WITH MAIL */
805
            }
806
807
            if (!empty($hook)) {
808
                $hook->setEventData([
809
                    'return' => $userId,
810
                    'originalPassword' => $original_password,
811
                ]);
812
                $hook->notifyCreateUser(HOOK_EVENT_TYPE_POST);
813
            }
814
            Event::addEvent(LOG_USER_CREATE, LOG_USER_ID, $userId);
815
        } else {
816
            Display::addFlash(
817
                Display::return_message(get_lang('ErrorContactPlatformAdmin'))
818
            );
819
820
            return false;
821
        }
822
823
        return $return;
824
    }
825
826
    /**
827
     * Ensure the CAS-authenticated user exists in the database.
828
     *
829
     * @param $casUser string the CAS user identifier
830
     *
831
     * @throws Exception if more than one user share the same CAS user identifier
832
     *
833
     * @return string|bool the recognised user login name or false if not found
834
     */
835
    public static function casUserLoginName($casUser)
836
    {
837
        $loginName = false;
838
839
        // look inside the casUser extra field
840
        if (UserManager::is_extra_field_available('cas_user')) {
841
            $valueModel = new ExtraFieldValue('user');
842
            $itemList = $valueModel->get_item_id_from_field_variable_and_field_value(
843
                'cas_user',
844
                $casUser,
845
                false,
846
                false,
847
                true
848
            );
849
            if (false !== $itemList) {
850
                // at least one user has $casUser in the 'cas_user' extra field
851
                // we attempt to load each candidate user because there might be deleted ones
852
                // (extra field values of a deleted user might remain)
853
                foreach ($itemList as $item) {
854
                    $userId = intval($item['item_id']);
855
                    $user = UserManager::getRepository()->find($userId);
856
                    if (!is_null($user)) {
857
                        if (false === $loginName) {
858
                            $loginName = $user->getUsername();
859
                        } else {
860
                            throw new Exception(get_lang('MoreThanOneUserMatched'));
861
                        }
862
                    }
863
                }
864
            }
865
        }
866
867
        if (false === $loginName) {
868
            // no matching 'cas_user' extra field value, or no such extra field
869
            // falling back to the old behaviour: $casUser must be the login name
870
            $userId = UserManager::get_user_id_from_username($casUser);
871
            if (false !== $userId) {
872
                $loginName = $casUser;
873
            }
874
        }
875
876
        return $loginName;
877
    }
878
879
    /**
880
     * Checks the availability of extra field 'cas_user'
881
     * and creates it if missing.
882
     *
883
     * @throws Exception on failure
884
     */
885
    public static function ensureCASUserExtraFieldExists()
886
    {
887
        if (!self::is_extra_field_available('cas_user')) {
888
            $extraField = new ExtraField('user');
889
            if (false === $extraField->save(
890
                    [
891
                        'variable' => 'cas_user',
892
                        'field_type' => ExtraField::FIELD_TYPE_TEXT,
893
                        'display_text' => get_lang('CAS User Identifier'),
894
                        'visible_to_self' => true,
895
                        'filter' => true,
896
                    ]
897
                )) {
898
                throw new Exception(get_lang('FailedToCreateExtraFieldCasUser'));
899
            }
900
901
            $rules = api_get_configuration_value('cas_user_map');
902
            if (!empty($rules) && isset($rules['extra'])) {
903
                foreach ($rules['extra'] as $extra) {
904
                    $extraField->save(
905
                        [
906
                            'variable' => $extra,
907
                            'field_type' => ExtraField::FIELD_TYPE_TEXT,
908
                            'display_text' => $extra,
909
                            'visible_to_self' => false,
910
                            'filter' => false,
911
                        ]
912
                    );
913
                }
914
            }
915
        }
916
    }
917
918
    /**
919
     * Create a CAS-authenticated user from scratch, from its CAS user identifier, with temporary default values.
920
     *
921
     * @param string $casUser the CAS user identifier
922
     *
923
     * @throws Exception on error
924
     *
925
     * @return string the login name of the new user
926
     */
927
    public static function createCASAuthenticatedUserFromScratch($casUser)
928
    {
929
        self::ensureCASUserExtraFieldExists();
930
931
        $loginName = 'cas_user_'.$casUser;
932
        $defaultValue = get_lang('EditInProfile');
933
        $defaultEmailValue = get_lang('EditInProfile');
934
        require_once __DIR__.'/../../auth/external_login/functions.inc.php';
935
        if ('true' === api_get_setting('login_is_email')) {
936
            $defaultEmailValue = $casUser;
937
        }
938
        $userId = external_add_user(
939
            [
940
                'username' => $loginName,
941
                'auth_source' => CAS_AUTH_SOURCE,
942
                'firstname' => $defaultValue,
943
                'lastname' => $defaultValue,
944
                'email' => $defaultEmailValue,
945
            ]
946
        );
947
        if (false === $userId) {
948
            throw new Exception(get_lang('FailedUserCreation'));
949
        }
950
        // Not checking function update_extra_field_value return value because not reliable
951
        self::update_extra_field_value($userId, 'cas_user', $casUser);
952
953
        return $loginName;
954
    }
955
956
    public static function updateCasUser($_user)
957
    {
958
        $rules = api_get_configuration_value('cas_user_map');
959
960
        if (empty($_user)) {
961
            return false;
962
        }
963
964
        if (!empty($rules)) {
965
            $userEntity = api_get_user_entity($_user['id']);
966
            $attributes = phpCAS::getAttributes();
967
            if (isset($rules['fields'])) {
968
                $isAdmin = false;
969
                foreach ($rules['fields'] as $field => $attributeName) {
970
                    if (!isset($attributes[$attributeName])) {
971
                        continue;
972
                    }
973
                    $value = $attributes[$attributeName];
974
                    // Check replace.
975
                    if (isset($rules['replace'][$attributeName])) {
976
                        $value = $rules['replace'][$attributeName][$value];
977
                    }
978
979
                    switch ($field) {
980
                        case 'email':
981
                            $userEntity->setEmail($value);
982
                            break;
983
                        case 'firstname':
984
                            $userEntity->setFirstname($value);
985
                            break;
986
                        case 'lastname':
987
                            $userEntity->setLastname($value);
988
                            break;
989
                        case 'active':
990
                            $userEntity->setActive('false' === $value);
991
                            break;
992
                        case 'status':
993
                            if (PLATFORM_ADMIN === (int) $value) {
994
                                $value = COURSEMANAGER;
995
                                $isAdmin = true;
996
                            }
997
                            $userEntity->setStatus($value);
998
                            break;
999
                    }
1000
1001
                    Database::getManager()->persist($userEntity);
1002
                    Database::getManager()->flush();
1003
1004
                    if ($isAdmin) {
1005
                        self::addUserAsAdmin($userEntity);
1006
                    }
1007
                }
1008
            }
1009
1010
            if (isset($rules['extra'])) {
1011
                foreach ($rules['extra'] as $variable) {
1012
                    if (isset($attributes[$variable])) {
1013
                        self::update_extra_field_value(
1014
                            $_user['id'],
1015
                            $variable,
1016
                            $attributes[$variable]
1017
                        );
1018
                    }
1019
                }
1020
            }
1021
        }
1022
    }
1023
1024
    /**
1025
     * Create a CAS-authenticated user from LDAP, from its CAS user identifier.
1026
     *
1027
     * @param $casUser
1028
     *
1029
     * @throws Exception
1030
     *
1031
     * @return string login name of the new user
1032
     */
1033
    public static function createCASAuthenticatedUserFromLDAP($casUser)
1034
    {
1035
        self::ensureCASUserExtraFieldExists();
1036
1037
        require_once __DIR__.'/../../auth/external_login/ldap.inc.php';
1038
        $login = extldapCasUserLogin($casUser);
1039
        if (false !== $login) {
1040
            $ldapUser = extldap_authenticate($login, 'nopass', true);
1041
            if (false !== $ldapUser) {
1042
                require_once __DIR__.'/../../auth/external_login/functions.inc.php';
1043
                $user = extldap_get_chamilo_user($ldapUser);
1044
                $user['username'] = $login;
1045
                $user['auth_source'] = CAS_AUTH_SOURCE;
1046
                $userId = external_add_user($user);
1047
                if (false !== $userId) {
1048
                    // Not checking function update_extra_field_value return value because not reliable
1049
                    self::update_extra_field_value($userId, 'cas_user', $casUser);
1050
1051
                    return $login;
1052
                } else {
1053
                    throw new Exception('Could not create the new user '.$login);
1054
                }
1055
            } else {
1056
                throw new Exception('Could not load the new user from LDAP using its login '.$login);
0 ignored issues
show
Are you sure $login of type string|true 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

1056
                throw new Exception('Could not load the new user from LDAP using its login './** @scrutinizer ignore-type */ $login);
Loading history...
1057
            }
1058
        } else {
1059
            throw new Exception('Could not find the new user from LDAP using its cas user identifier '.$casUser);
1060
        }
1061
    }
1062
1063
    /**
1064
     * updates user record in database from its LDAP record
1065
     * copies relevant LDAP attribute values : firstname, lastname and email.
1066
     *
1067
     * @param $login string the user login name
1068
     *
1069
     * @throws Exception when the user login name is not found in the LDAP or in the database
1070
     */
1071
    public static function updateUserFromLDAP($login)
1072
    {
1073
        require_once __DIR__.'/../../auth/external_login/ldap.inc.php';
1074
1075
        $ldapUser = extldap_authenticate($login, 'nopass', true);
1076
        if (false === $ldapUser) {
1077
            throw new Exception(get_lang('NoSuchUserInLDAP'));
1078
        }
1079
1080
        $user = extldap_get_chamilo_user($ldapUser);
1081
        $userInfo = api_get_user_info_from_username($login);
1082
        if (false === $userInfo) {
1083
            throw new Exception(get_lang('NoSuchUserInInternalDatabase'));
1084
        }
1085
1086
        $userId = UserManager::update_user(
1087
            $userInfo['user_id'],
1088
            $user["firstname"],
1089
            $user["lastname"],
1090
            $login,
1091
            null,
1092
            $userInfo['auth_source'],
1093
            $user["email"],
1094
            $userInfo['status'],
1095
            $userInfo['official_code'],
1096
            $userInfo['phone'],
1097
            $userInfo['picture_uri'],
1098
            $userInfo['expiration_date'],
1099
            $userInfo['active'],
1100
            $userInfo['creator_id'],
1101
            $userInfo['hr_dept_id'],
1102
            null,
1103
            $userInfo['language']
1104
        );
1105
        if (false === $userId) {
1106
            throw new Exception(get_lang('CouldNotUpdateUser'));
1107
        }
1108
    }
1109
1110
    /**
1111
     * Can user be deleted? This function checks whether there's a course
1112
     * in which the given user is the
1113
     * only course administrator. If that is the case, the user can't be
1114
     * deleted because the course would remain without a course admin.
1115
     *
1116
     * @param int $user_id The user id
1117
     *
1118
     * @return bool true if user can be deleted
1119
     *
1120
     * @assert (null) === false
1121
     * @assert (-1) === false
1122
     * @assert ('abc') === false
1123
     */
1124
    public static function canDeleteUser($user_id)
1125
    {
1126
        $deny = api_get_configuration_value('deny_delete_users');
1127
1128
        if ($deny) {
1129
            return false;
1130
        }
1131
1132
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1133
        $user_id = (int) $user_id;
1134
1135
        if (empty($user_id)) {
1136
            return false;
1137
        }
1138
1139
        $res = Database::query(
1140
            "SELECT c_id FROM $table_course_user WHERE status = 1 AND user_id = $user_id"
1141
        );
1142
        while ($course = Database::fetch_assoc($res)) {
1143
            $sql = Database::query(
1144
                "SELECT COUNT(id) number FROM $table_course_user WHERE status = 1 AND c_id = {$course['c_id']}"
1145
            );
1146
            $res2 = Database::fetch_assoc($sql);
1147
1148
            if ($res2['number'] == 1) {
1149
                return false;
1150
            }
1151
        }
1152
1153
        return true;
1154
    }
1155
1156
    /**
1157
     * Delete a user from the platform, and all its belongings. This is a
1158
     * very dangerous function that should only be accessible by
1159
     * super-admins. Other roles should only be able to disable a user,
1160
     * which removes access to the platform but doesn't delete anything.
1161
     *
1162
     * @param int The ID of th user to be deleted
1163
     *
1164
     * @throws Exception
1165
     *
1166
     * @return bool true if user is successfully deleted, false otherwise
1167
     * @assert (null) === false
1168
     * @assert ('abc') === false
1169
     */
1170
    public static function delete_user($user_id)
1171
    {
1172
        $user_id = (int) $user_id;
1173
1174
        if (empty($user_id)) {
1175
            return false;
1176
        }
1177
1178
        if (!self::canDeleteUser($user_id)) {
1179
            return false;
1180
        }
1181
1182
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1183
        $usergroup_rel_user = Database::get_main_table(TABLE_USERGROUP_REL_USER);
1184
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1185
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
1186
        $table_session = Database::get_main_table(TABLE_MAIN_SESSION);
1187
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
1188
        $table_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
1189
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1190
        $table_group = Database::get_course_table(TABLE_GROUP_USER);
1191
        $table_work = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
1192
1193
        // Unsubscribe the user from all groups in all his courses
1194
        $sql = "SELECT c.id
1195
                FROM $table_course c
1196
                INNER JOIN $table_course_user cu
1197
                ON (c.id = cu.c_id)
1198
                WHERE
1199
                    cu.user_id = '".$user_id."' AND
1200
                    relation_type<>".COURSE_RELATION_TYPE_RRHH."
1201
                ";
1202
1203
        $res = Database::query($sql);
1204
        while ($course = Database::fetch_object($res)) {
1205
            $sql = "DELETE FROM $table_group
1206
                    WHERE c_id = {$course->id} AND user_id = $user_id";
1207
            Database::query($sql);
1208
        }
1209
1210
        // Unsubscribe user from usergroup_rel_user
1211
        $sql = "DELETE FROM $usergroup_rel_user WHERE user_id = '".$user_id."'";
1212
        Database::query($sql);
1213
1214
        // Unsubscribe user from all courses
1215
        $sql = "DELETE FROM $table_course_user WHERE user_id = '".$user_id."'";
1216
        Database::query($sql);
1217
1218
        // Unsubscribe user from all courses in sessions
1219
        $sql = "DELETE FROM $table_session_course_user WHERE user_id = '".$user_id."'";
1220
        Database::query($sql);
1221
1222
        // If the user was added as a id_coach then set the current admin as coach see BT#
1223
        $currentUserId = api_get_user_id();
1224
        $sql = "UPDATE $table_session SET id_coach = $currentUserId
1225
                WHERE id_coach = '".$user_id."'";
1226
        Database::query($sql);
1227
1228
        $sql = "UPDATE $table_session SET session_admin_id = $currentUserId
1229
                WHERE session_admin_id = '".$user_id."'";
1230
        Database::query($sql);
1231
1232
        // Unsubscribe user from all sessions
1233
        $sql = "DELETE FROM $table_session_user
1234
                WHERE user_id = '".$user_id."'";
1235
        Database::query($sql);
1236
1237
        if (api_get_configuration_value('plugin_redirection_enabled')) {
1238
            RedirectionPlugin::deleteUserRedirection($user_id);
1239
        }
1240
1241
        $user_info = api_get_user_info($user_id);
1242
1243
        try {
1244
            self::deleteUserFiles($user_id);
1245
        } catch (Exception $exception) {
1246
            error_log('Delete user exception: '.$exception->getMessage());
1247
        }
1248
1249
        // Delete the personal course categories
1250
        $course_cat_table = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
1251
        $sql = "DELETE FROM $course_cat_table WHERE user_id = '".$user_id."'";
1252
        Database::query($sql);
1253
1254
        // Delete user from the admin table
1255
        $sql = "DELETE FROM $table_admin WHERE user_id = '".$user_id."'";
1256
        Database::query($sql);
1257
1258
        // Delete the personal agenda-items from this user
1259
        $agenda_table = Database::get_main_table(TABLE_PERSONAL_AGENDA);
1260
        $sql = "DELETE FROM $agenda_table WHERE user = '".$user_id."'";
1261
        Database::query($sql);
1262
1263
        $gradebook_results_table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
1264
        $sql = 'DELETE FROM '.$gradebook_results_table.' WHERE user_id = '.$user_id;
1265
        Database::query($sql);
1266
1267
        $extraFieldValue = new ExtraFieldValue('user');
1268
        $extraFieldValue->deleteValuesByItem($user_id);
1269
1270
        UrlManager::deleteUserFromAllUrls($user_id);
1271
1272
        if (api_get_setting('allow_social_tool') === 'true') {
1273
            $userGroup = new UserGroup();
1274
            //Delete user from portal groups
1275
            $group_list = $userGroup->get_groups_by_user($user_id);
1276
            if (!empty($group_list)) {
1277
                foreach ($group_list as $group_id => $data) {
1278
                    $userGroup->delete_user_rel_group($user_id, $group_id);
1279
                }
1280
            }
1281
1282
            // Delete user from friend lists
1283
            SocialManager::remove_user_rel_user($user_id, true);
1284
        }
1285
1286
        // Removing survey invitation
1287
        SurveyManager::delete_all_survey_invitations_by_user($user_id);
1288
1289
        // Delete students works
1290
        $sql = "DELETE FROM $table_work WHERE user_id = $user_id AND c_id <> 0";
1291
        Database::query($sql);
1292
1293
        $sql = "UPDATE c_item_property SET to_user_id = NULL
1294
                WHERE to_user_id = '".$user_id."'";
1295
        Database::query($sql);
1296
1297
        $sql = "UPDATE c_item_property SET insert_user_id = NULL
1298
                WHERE insert_user_id = '".$user_id."'";
1299
        Database::query($sql);
1300
1301
        $sql = "UPDATE c_item_property SET lastedit_user_id = NULL
1302
                WHERE lastedit_user_id = '".$user_id."'";
1303
        Database::query($sql);
1304
1305
        // Skills
1306
        $em = Database::getManager();
1307
1308
        $criteria = ['user' => $user_id];
1309
        $skills = $em->getRepository('ChamiloCoreBundle:SkillRelUser')->findBy($criteria);
1310
        if ($skills) {
1311
            /** @var SkillRelUser $skill */
1312
            foreach ($skills as $skill) {
1313
                $comments = $skill->getComments();
1314
                if ($comments) {
1315
                    /** @var SkillRelUserComment $comment */
1316
                    foreach ($comments as $comment) {
1317
                        $em->remove($comment);
1318
                    }
1319
                }
1320
                $em->remove($skill);
1321
            }
1322
            $em->flush();
1323
        }
1324
1325
        // ExtraFieldSavedSearch
1326
        $criteria = ['user' => $user_id];
1327
        $searchList = $em->getRepository('ChamiloCoreBundle:ExtraFieldSavedSearch')->findBy($criteria);
1328
        if ($searchList) {
1329
            foreach ($searchList as $search) {
1330
                $em->remove($search);
1331
            }
1332
            $em->flush();
1333
        }
1334
1335
        $connection = Database::getManager()->getConnection();
1336
        $tableExists = $connection->getSchemaManager()->tablesExist(['plugin_bbb_room']);
1337
        if ($tableExists) {
1338
            // Delete user from database
1339
            $sql = "DELETE FROM plugin_bbb_room WHERE participant_id = $user_id";
1340
            Database::query($sql);
1341
        }
1342
1343
        // Delete user/ticket relationships :(
1344
        $tableExists = $connection->getSchemaManager()->tablesExist(['ticket_ticket']);
1345
        if ($tableExists) {
1346
            TicketManager::deleteUserFromTicketSystem($user_id);
1347
        }
1348
1349
        $tableExists = $connection->getSchemaManager()->tablesExist(['c_lp_category_user']);
1350
        if ($tableExists) {
1351
            $sql = "DELETE FROM c_lp_category_user WHERE user_id = $user_id";
1352
            Database::query($sql);
1353
        }
1354
1355
        $app_plugin = new AppPlugin();
1356
        $app_plugin->performActionsWhenDeletingItem('user', $user_id);
1357
1358
        // Delete user from database
1359
        $sql = "DELETE FROM $table_user WHERE id = '".$user_id."'";
1360
        Database::query($sql);
1361
1362
        // Add event to system log
1363
        $user_id_manager = api_get_user_id();
1364
1365
        Event::addEvent(
1366
            LOG_USER_DELETE,
1367
            LOG_USER_ID,
1368
            $user_id,
1369
            api_get_utc_datetime(),
1370
            $user_id_manager
1371
        );
1372
1373
        Event::addEvent(
1374
            LOG_USER_DELETE,
1375
            LOG_USER_OBJECT,
1376
            $user_info,
1377
            api_get_utc_datetime(),
1378
            $user_id_manager
1379
        );
1380
        $cacheAvailable = api_get_configuration_value('apc');
1381
        if ($cacheAvailable === true) {
1382
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
1383
            if (apcu_exists($apcVar)) {
1384
                apcu_delete($apcVar);
1385
            }
1386
        }
1387
1388
        return true;
1389
    }
1390
1391
    /**
1392
     * Deletes users completely. Can be called either as:
1393
     * - UserManager::delete_users(1, 2, 3); or
1394
     * - UserManager::delete_users(array(1, 2, 3));.
1395
     *
1396
     * @param array|int $ids
1397
     *
1398
     * @return bool True if at least one user was successfuly deleted. False otherwise.
1399
     *
1400
     * @author Laurent Opprecht
1401
     *
1402
     * @uses \UserManager::delete_user() to actually delete each user
1403
     * @assert (null) === false
1404
     * @assert (-1) === false
1405
     * @assert (array(-1)) === false
1406
     */
1407
    public static function delete_users($ids = [])
1408
    {
1409
        $result = false;
1410
        $ids = is_array($ids) ? $ids : func_get_args();
1411
        if (!is_array($ids) || count($ids) == 0) {
1412
            return false;
1413
        }
1414
        $ids = array_map('intval', $ids);
1415
        foreach ($ids as $id) {
1416
            if (empty($id) || $id < 1) {
1417
                continue;
1418
            }
1419
            $deleted = self::delete_user($id);
1420
            $result = $deleted || $result;
1421
        }
1422
1423
        return $result;
1424
    }
1425
1426
    /**
1427
     * Disable users. Can be called either as:
1428
     * - UserManager::deactivate_users(1, 2, 3);
1429
     * - UserManager::deactivate_users(array(1, 2, 3));.
1430
     *
1431
     * @param array|int $ids
1432
     *
1433
     * @return bool
1434
     *
1435
     * @author Laurent Opprecht
1436
     * @assert (null) === false
1437
     * @assert (array(-1)) === false
1438
     */
1439
    public static function deactivate_users($ids = [])
1440
    {
1441
        if (empty($ids)) {
1442
            return false;
1443
        }
1444
1445
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1446
1447
        $ids = is_array($ids) ? $ids : func_get_args();
1448
        $ids = array_map('intval', $ids);
1449
        $ids = implode(',', $ids);
1450
1451
        $sql = "UPDATE $table_user SET active = 0 WHERE id IN ($ids)";
1452
        $r = Database::query($sql);
1453
        if ($r !== false) {
1454
            Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $ids);
1455
1456
            return true;
1457
        }
1458
1459
        return false;
1460
    }
1461
1462
    /**
1463
     * Enable users. Can be called either as:
1464
     * - UserManager::activate_users(1, 2, 3);
1465
     * - UserManager::activate_users(array(1, 2, 3));.
1466
     *
1467
     * @param array|int IDs of the users to enable
1468
     *
1469
     * @return bool
1470
     *
1471
     * @author Laurent Opprecht
1472
     * @assert (null) === false
1473
     * @assert (array(-1)) === false
1474
     */
1475
    public static function activate_users($ids = [])
1476
    {
1477
        if (empty($ids)) {
1478
            return false;
1479
        }
1480
1481
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1482
1483
        $ids = is_array($ids) ? $ids : func_get_args();
1484
        $ids = array_map('intval', $ids);
1485
        $ids = implode(',', $ids);
1486
1487
        $sql = "UPDATE $table_user SET active = 1 WHERE id IN ($ids)";
1488
        $r = Database::query($sql);
1489
        if ($r !== false) {
1490
            Event::addEvent(LOG_USER_ENABLE, LOG_USER_ID, $ids);
1491
1492
            return true;
1493
        }
1494
1495
        return false;
1496
    }
1497
1498
    /**
1499
     * Update user information with new openid.
1500
     *
1501
     * @param int    $user_id
1502
     * @param string $openid
1503
     *
1504
     * @return bool true if the user information was updated
1505
     * @assert (false,'') === false
1506
     * @assert (-1,'') === false
1507
     */
1508
    public static function update_openid($user_id, $openid)
1509
    {
1510
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1511
        if ($user_id != strval(intval($user_id))) {
1512
            return false;
1513
        }
1514
        if ($user_id === false) {
1515
            return false;
1516
        }
1517
        $sql = "UPDATE $table_user SET
1518
                openid='".Database::escape_string($openid)."'";
1519
        $sql .= " WHERE id= $user_id";
1520
1521
        if (Database::query($sql) !== false) {
1522
            return true;
1523
        }
1524
1525
        return false;
1526
    }
1527
1528
    /**
1529
     * Update user information with all the parameters passed to this function.
1530
     *
1531
     * @param int    $user_id         The ID of the user to be updated
1532
     * @param string $firstname       The user's firstname
1533
     * @param string $lastname        The user's lastname
1534
     * @param string $username        The user's username (login)
1535
     * @param string $password        The user's password
1536
     * @param string $auth_source     The authentication source (default: "platform")
1537
     * @param string $email           The user's e-mail address
1538
     * @param int    $status          The user's status
1539
     * @param string $official_code   The user's official code (usually just an internal institutional code)
1540
     * @param string $phone           The user's phone number
1541
     * @param string $picture_uri     The user's picture URL (internal to the Chamilo directory)
1542
     * @param string $expiration_date The date at which this user will be automatically disabled
1543
     * @param int    $active          Whether this account needs to be enabled (1) or disabled (0)
1544
     * @param int    $creator_id      The user ID of the person who registered this user (optional, defaults to null)
1545
     * @param int    $hr_dept_id      The department of HR in which the user is registered (optional, defaults to 0)
1546
     * @param array  $extra           Additional fields to add to this user as extra fields (defaults to null)
1547
     * @param string $language        The language to which the user account will be set
1548
     * @param string $encrypt_method  The cipher method. This parameter is deprecated. It will use the system's default
1549
     * @param bool   $send_email      Whether to send an e-mail to the user after the update is complete
1550
     * @param int    $reset_password  Method used to reset password (0, 1, 2 or 3 - see usage examples for details)
1551
     * @param string $address
1552
     * @param array  $emailTemplate
1553
     *
1554
     * @return bool|int False on error, or the user ID if the user information was updated
1555
     * @assert (false, false, false, false, false, false, false, false, false, false, false, false, false) === false
1556
     */
1557
    public static function update_user(
1558
        $user_id,
1559
        $firstname,
1560
        $lastname,
1561
        $username,
1562
        $password,
1563
        $auth_source,
1564
        $email,
1565
        $status,
1566
        $official_code,
1567
        $phone,
1568
        $picture_uri,
1569
        $expiration_date,
1570
        $active,
1571
        $creator_id = null,
1572
        $hr_dept_id = 0,
1573
        $extra = null,
1574
        $language = 'english',
1575
        $encrypt_method = '',
1576
        $send_email = false,
1577
        $reset_password = 0,
1578
        $address = null,
1579
        $emailTemplate = []
1580
    ) {
1581
        $hook = HookUpdateUser::create();
1582
        if (!empty($hook)) {
1583
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_PRE);
1584
        }
1585
        $original_password = $password;
1586
        $user_id = (int) $user_id;
1587
        $creator_id = (int) $creator_id;
1588
1589
        if (empty($user_id)) {
1590
            return false;
1591
        }
1592
1593
        $userManager = self::getManager();
1594
        /** @var User $user */
1595
        $user = self::getRepository()->find($user_id);
1596
1597
        if (empty($user)) {
1598
            return false;
1599
        }
1600
1601
        if ($reset_password == 0) {
1602
            $password = null;
1603
            $auth_source = $user->getAuthSource();
1604
        } elseif ($reset_password == 1) {
1605
            $original_password = $password = api_generate_password();
1606
            $auth_source = PLATFORM_AUTH_SOURCE;
1607
        } elseif ($reset_password == 2) {
1608
            //$password = $password;
1609
            $auth_source = PLATFORM_AUTH_SOURCE;
1610
        } elseif ($reset_password == 3) {
1611
            //$password = $password;
1612
            //$auth_source = $auth_source;
1613
        }
1614
1615
        // Checking the user language
1616
        $languages = api_get_languages();
1617
        if (!in_array($language, $languages['folder'])) {
1618
            $language = api_get_setting('platformLanguage');
1619
        }
1620
1621
        $change_active = 0;
1622
        $isUserActive = $user->getActive();
1623
        if ($isUserActive != $active) {
1624
            $change_active = 1;
1625
        }
1626
1627
        $originalUsername = $user->getUsername();
1628
1629
        // If username is different from original then check if it exists.
1630
        if ($originalUsername !== $username) {
1631
            $available = self::is_username_available($username);
1632
            if ($available === false) {
1633
                return false;
1634
            }
1635
        }
1636
1637
        if (!empty($expiration_date)) {
1638
            $expiration_date = api_get_utc_datetime($expiration_date);
1639
            $expiration_date = new \DateTime(
1640
                $expiration_date,
1641
                new DateTimeZone('UTC')
1642
            );
1643
        }
1644
1645
        $user
1646
            ->setLastname($lastname)
1647
            ->setFirstname($firstname)
1648
            ->setUsername($username)
1649
            ->setStatus($status)
1650
            ->setAuthSource($auth_source)
1651
            ->setLanguage($language)
1652
            ->setEmail($email)
1653
            ->setOfficialCode($official_code)
1654
            ->setPhone($phone)
1655
            ->setAddress($address)
1656
            ->setPictureUri($picture_uri)
1657
            ->setExpirationDate($expiration_date)
1658
            ->setActive($active)
1659
            ->setEnabled($active)
1660
            ->setHrDeptId($hr_dept_id)
1661
        ;
1662
1663
        if (!is_null($password)) {
1664
            $user->setPlainPassword($password);
1665
            Event::addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $user_id);
1666
            $date = api_get_local_time(
1667
                null,
1668
                null,
1669
                null,
1670
                null,
1671
                null,
1672
                null,
1673
                'Y-m-d'
1674
            );
1675
            $extraFieldValue = new ExtraFieldValue('user');
1676
            $extraFieldValue->save(
1677
                [
1678
                    'item_id' => $user->getId(),
1679
                    'variable' => 'password_updated_at',
1680
                    'value' => $date,
1681
                ]
1682
            );
1683
        }
1684
1685
        $userManager->updateUser($user, true);
1686
        Event::addEvent(LOG_USER_UPDATE, LOG_USER_ID, $user_id);
1687
1688
        if ($change_active == 1) {
1689
            if ($active == 1) {
1690
                $event_title = LOG_USER_ENABLE;
1691
            } else {
1692
                $event_title = LOG_USER_DISABLE;
1693
            }
1694
            Event::addEvent($event_title, LOG_USER_ID, $user_id);
1695
        }
1696
1697
        if (is_array($extra) && count($extra) > 0) {
1698
            $res = true;
1699
            foreach ($extra as $fname => $fvalue) {
1700
                $res = $res && self::update_extra_field_value(
1701
                    $user_id,
1702
                    $fname,
1703
                    $fvalue
1704
                );
1705
            }
1706
        }
1707
1708
        if (!empty($email) && $send_email) {
1709
            $recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1710
            $sender_name = api_get_person_name(
1711
                api_get_setting('administratorName'),
1712
                api_get_setting('administratorSurname'),
1713
                null,
1714
                PERSON_NAME_EMAIL_ADDRESS
1715
            );
1716
            $email_admin = api_get_setting('emailAdministrator');
1717
            $url = api_get_path(WEB_PATH);
1718
            if (api_is_multiple_url_enabled()) {
1719
                $access_url_id = api_get_current_access_url_id();
1720
                if ($access_url_id != -1) {
1721
                    $url = api_get_access_url($access_url_id);
1722
                    $url = $url['url'];
1723
                }
1724
            }
1725
1726
            $tplContent = new Template(
1727
                null,
1728
                false,
1729
                false,
1730
                false,
1731
                false,
1732
                false
1733
            );
1734
            $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstname, $lastname)));
1735
            $tplContent->assign('login_name', $username);
1736
            $originalPassword = '';
1737
            if ($reset_password > 0) {
1738
                $originalPassword = stripslashes($original_password);
1739
            }
1740
            $tplContent->assign('original_password', $originalPassword);
1741
            // variables for the default template
1742
            $tplContent->assign('portal_url', $url);
1743
            // Adding this variable but not used in default template, used for task BT19518 with a customized template
1744
            $tplContent->assign('status_type', $status);
1745
            $creatorInfo = api_get_user_info($creator_id);
1746
            $creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
1747
            $tplSubject = new Template(
1748
                null,
1749
                false,
1750
                false,
1751
                false,
1752
                false,
1753
                false
1754
            );
1755
            // the complete_name is not used in the default Chamilo template but used in a specific template -refs BT#21334
1756
            $tplSubject->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
1757
            $layoutSubject = $tplSubject->get_template('mail/subject_user_edit.tpl');
1758
            $emailSubject = $tplSubject->fetch($layoutSubject);
1759
1760
            if (!is_null($password) && api_get_configuration_value('send_two_inscription_confirmation_mail')) {
1761
                // The user has a new password *and* we need to tell him so,
1762
                // but the configuration is set to send 2 separate e-mails
1763
                // (one for username, one for password) when sending pass
1764
                $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
1765
                $emailBody = $tplContent->fetch($layoutContent);
1766
                $mailTemplateManager = new MailTemplateManager();
1767
                if (!empty($emailTemplate) &&
1768
                    isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
1769
                    !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
1770
                ) {
1771
                    $userInfo = api_get_user_info($user_id);
1772
                    $emailBody = $mailTemplateManager->parseTemplate(
1773
                        $emailTemplate['new_user_first_email_confirmation.tpl'],
1774
                        $userInfo
1775
                    );
1776
                }
1777
1778
                api_mail_html(
1779
                    $recipient_name,
1780
                    $email,
1781
                    $emailSubject,
1782
                    $emailBody,
1783
                    $sender_name,
1784
                    $email_admin,
1785
                    null,
1786
                    null,
1787
                    null,
1788
                    null,
1789
                    $creatorEmail
1790
                );
1791
1792
                $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
1793
                $emailBody = $tplContent->fetch($layoutContent);
1794
                $mailTemplateManager = new MailTemplateManager();
1795
                if (!empty($emailTemplate) &&
1796
                    isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
1797
                    !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
1798
                ) {
1799
                    $userInfo = api_get_user_info($user_id);
1800
                    $emailBody = $mailTemplateManager->parseTemplate(
1801
                        $emailTemplate['new_user_second_email_confirmation.tpl'],
1802
                        $userInfo
1803
                    );
1804
                }
1805
1806
                api_mail_html(
1807
                    $recipient_name,
1808
                    $email,
1809
                    $emailSubject,
1810
                    $emailBody,
1811
                    $sender_name,
1812
                    $email_admin,
1813
                    null,
1814
                    null,
1815
                    null,
1816
                    null,
1817
                    $creatorEmail
1818
                );
1819
            } else {
1820
                $layoutContent = $tplContent->get_template('mail/user_edit_content.tpl');
1821
                $emailBody = $tplContent->fetch($layoutContent);
1822
                $mailTemplateManager = new MailTemplateManager();
1823
                if (!empty($emailTemplate) &&
1824
                    isset($emailTemplate['user_edit_content.tpl']) &&
1825
                    !empty($emailTemplate['user_edit_content.tpl'])
1826
                ) {
1827
                    $userInfo = api_get_user_info($user_id);
1828
                    $emailBody = $mailTemplateManager->parseTemplate(
1829
                        $emailTemplate['user_edit_content.tpl'],
1830
                        $userInfo
1831
                    );
1832
                }
1833
1834
                api_mail_html(
1835
                    $recipient_name,
1836
                    $email,
1837
                    $emailSubject,
1838
                    $emailBody,
1839
                    $sender_name,
1840
                    $email_admin,
1841
                    null,
1842
                    null,
1843
                    null,
1844
                    null,
1845
                    $creatorEmail
1846
                );
1847
            }
1848
        }
1849
1850
        if (!empty($hook)) {
1851
            $hook->setEventData(['user' => $user]);
1852
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_POST);
1853
        }
1854
1855
        $cacheAvailable = api_get_configuration_value('apc');
1856
        if ($cacheAvailable === true) {
1857
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
1858
            if (apcu_exists($apcVar)) {
1859
                apcu_delete($apcVar);
1860
            }
1861
        }
1862
1863
        return $user->getId();
1864
    }
1865
1866
    /**
1867
     * Disables a user.
1868
     *
1869
     * @param int User id
1870
     *
1871
     * @return bool
1872
     *
1873
     * @uses \UserManager::change_active_state() to actually disable the user
1874
     * @assert (0) === false
1875
     */
1876
    public static function disable($user_id)
1877
    {
1878
        if (empty($user_id)) {
1879
            return false;
1880
        }
1881
        self::change_active_state($user_id, 0);
1882
1883
        return true;
1884
    }
1885
1886
    /**
1887
     * Enable a user.
1888
     *
1889
     * @param int User id
1890
     *
1891
     * @return bool
1892
     *
1893
     * @uses \UserManager::change_active_state() to actually disable the user
1894
     * @assert (0) === false
1895
     */
1896
    public static function enable($user_id)
1897
    {
1898
        if (empty($user_id)) {
1899
            return false;
1900
        }
1901
        self::change_active_state($user_id, 1);
1902
1903
        return true;
1904
    }
1905
1906
    /**
1907
     * Returns the user's id based on the original id and field name in
1908
     * the extra fields. Returns 0 if no user was found. This function is
1909
     * mostly useful in the context of a web services-based sinchronization.
1910
     *
1911
     * @param string Original user id
1912
     * @param string Original field name
1913
     *
1914
     * @return int User id
1915
     * @assert ('0','---') === 0
1916
     */
1917
    public static function get_user_id_from_original_id(
1918
        $original_user_id_value,
1919
        $original_user_id_name
1920
    ) {
1921
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
1922
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1923
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
1924
1925
        $original_user_id_name = Database::escape_string($original_user_id_name);
1926
        $original_user_id_value = Database::escape_string($original_user_id_value);
1927
1928
        $sql = "SELECT item_id as user_id
1929
                FROM $t_uf uf
1930
                INNER JOIN $t_ufv ufv
1931
                ON ufv.field_id = uf.id
1932
                WHERE
1933
                    variable = '$original_user_id_name' AND
1934
                    value = '$original_user_id_value' AND
1935
                    extra_field_type = $extraFieldType
1936
                ";
1937
        $res = Database::query($sql);
1938
        $row = Database::fetch_object($res);
1939
        if ($row) {
1940
            return $row->user_id;
1941
        }
1942
1943
        return 0;
1944
    }
1945
1946
    /**
1947
     * Check if a username is available.
1948
     *
1949
     * @param string $username the wanted username
1950
     *
1951
     * @return bool true if the wanted username is available
1952
     * @assert ('') === false
1953
     * @assert ('xyzxyzxyz') === true
1954
     */
1955
    public static function is_username_available($username)
1956
    {
1957
        if (empty($username)) {
1958
            return false;
1959
        }
1960
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1961
        $sql = "SELECT username FROM $table_user
1962
                WHERE username = '".Database::escape_string($username)."'";
1963
        $res = Database::query($sql);
1964
1965
        return Database::num_rows($res) == 0;
1966
    }
1967
1968
    /**
1969
     * Creates a username using person's names, i.e. creates jmontoya from Julio Montoya.
1970
     *
1971
     * @param string $firstname the first name of the user
1972
     * @param string $lastname  the last name of the user
1973
     *
1974
     * @return string suggests a username that contains only ASCII-letters and digits,
1975
     *                without check for uniqueness within the system
1976
     *
1977
     * @author Julio Montoya Armas
1978
     * @author Ivan Tcholakov, 2009 - rework about internationalization.
1979
     * @assert ('','') === false
1980
     * @assert ('a','b') === 'ab'
1981
     */
1982
    public static function create_username($firstname, $lastname)
1983
    {
1984
        if (empty($firstname) && empty($lastname)) {
1985
            return false;
1986
        }
1987
1988
        // The first letter only.
1989
        $firstname = api_substr(
1990
            preg_replace(USERNAME_PURIFIER, '', $firstname),
1991
            0,
1992
            1
1993
        );
1994
        //Looking for a space in the lastname
1995
        $pos = api_strpos($lastname, ' ');
1996
        if ($pos !== false) {
1997
            $lastname = api_substr($lastname, 0, $pos);
1998
        }
1999
2000
        $lastname = preg_replace(USERNAME_PURIFIER, '', $lastname);
2001
        $username = $firstname.$lastname;
2002
        if (empty($username)) {
2003
            $username = 'user';
2004
        }
2005
2006
        $username = URLify::transliterate($username);
2007
2008
        return strtolower(substr($username, 0, USERNAME_MAX_LENGTH - 3));
2009
    }
2010
2011
    /**
2012
     * Creates a unique username, using:
2013
     * 1. the first name and the last name of a user;
2014
     * 2. an already created username but not checked for uniqueness yet.
2015
     *
2016
     * @param string $firstname The first name of a given user. If the second parameter $lastname is NULL, then this
2017
     *                          parameter is treated as username which is to be checked f
2018
     *                          or uniqueness and to be modified when it is necessary.
2019
     * @param string $lastname  the last name of the user
2020
     *
2021
     * @return string Returns a username that contains only ASCII-letters and digits and that is unique in the system.
2022
     *                Note: When the method is called several times with same parameters,
2023
     *                its results look like the following sequence: ivan, ivan2, ivan3, ivan4, ...
2024
     *
2025
     * @author Ivan Tcholakov, 2009
2026
     */
2027
    public static function create_unique_username($firstname, $lastname = null)
2028
    {
2029
        if (is_null($lastname)) {
2030
            // In this case the actual input parameter $firstname should contain ASCII-letters and digits only.
2031
            // For making this method tolerant of mistakes,
2032
            // let us transliterate and purify the suggested input username anyway.
2033
            // So, instead of the sentence $username = $firstname; we place the following:
2034
            $username = strtolower(preg_replace(USERNAME_PURIFIER, '', $firstname));
2035
        } else {
2036
            $username = self::create_username($firstname, $lastname);
2037
        }
2038
        if (!self::is_username_available($username)) {
2039
            $i = 2;
2040
            $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
2041
            while (!self::is_username_available($temp_username)) {
2042
                $i++;
2043
                $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
2044
            }
2045
            $username = $temp_username;
2046
        }
2047
2048
        $username = URLify::transliterate($username);
2049
2050
        return $username;
2051
    }
2052
2053
    /**
2054
     * Modifies a given username accordingly to the specification for valid characters and length.
2055
     *
2056
     * @param $username string          The input username
2057
     * @param bool $strict (optional)   When this flag is TRUE, the result is guaranteed for full compliance,
2058
     *                     otherwise compliance may be partial. The default value is FALSE.
2059
     *
2060
     * @return string the resulting purified username
2061
     */
2062
    public static function purify_username($username, $strict = false)
2063
    {
2064
        if ($strict) {
2065
            // 1. Conversion of unacceptable letters (latinian letters with accents for example)
2066
            // into ASCII letters in order they not to be totally removed.
2067
            // 2. Applying the strict purifier.
2068
            // 3. Length limitation.
2069
            if ('true' === api_get_setting('login_is_email')) {
2070
                $return = substr(preg_replace(USERNAME_PURIFIER_MAIL, '', $username), 0, USERNAME_MAX_LENGTH);
2071
            } else {
2072
                $return = substr(preg_replace(USERNAME_PURIFIER, '', $username), 0, USERNAME_MAX_LENGTH);
2073
            }
2074
2075
            return URLify::transliterate($return);
2076
        }
2077
2078
        // 1. Applying the shallow purifier.
2079
        // 2. Length limitation.
2080
        return substr(
2081
            preg_replace(USERNAME_PURIFIER_SHALLOW, '', $username),
2082
            0,
2083
            USERNAME_MAX_LENGTH
2084
        );
2085
    }
2086
2087
    /**
2088
     * Checks whether the user id exists in the database.
2089
     *
2090
     * @param int $userId User id
2091
     *
2092
     * @return bool True if user id was found, false otherwise
2093
     */
2094
    public static function is_user_id_valid($userId)
2095
    {
2096
        $resultData = Database::select(
2097
            'COUNT(1) AS count',
2098
            Database::get_main_table(TABLE_MAIN_USER),
2099
            [
2100
                'where' => ['id = ?' => (int) $userId],
2101
            ],
2102
            'first'
2103
        );
2104
2105
        if ($resultData === false) {
2106
            return false;
2107
        }
2108
2109
        return $resultData['count'] > 0;
2110
    }
2111
2112
    /**
2113
     * Checks whether a given username matches to the specification strictly.
2114
     * The empty username is assumed here as invalid.
2115
     * Mostly this function is to be used in the user interface built-in validation routines
2116
     * for providing feedback while usernames are enterd manually.
2117
     *
2118
     * @param string $username the input username
2119
     *
2120
     * @return bool returns TRUE if the username is valid, FALSE otherwise
2121
     */
2122
    public static function is_username_valid($username)
2123
    {
2124
        return !empty($username) && $username == self::purify_username($username, true);
2125
    }
2126
2127
    /**
2128
     * Checks whether a username is empty. If the username contains whitespace characters,
2129
     * such as spaces, tabulators, newlines, etc.,
2130
     * it is assumed as empty too. This function is safe for validation unpurified data (during importing).
2131
     *
2132
     * @param string $username the given username
2133
     *
2134
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
2135
     */
2136
    public static function is_username_empty($username)
2137
    {
2138
        return strlen(self::purify_username($username, false)) == 0;
2139
    }
2140
2141
    /**
2142
     * Checks whether a username is too long or not.
2143
     *
2144
     * @param string $username the given username, it should contain only ASCII-letters and digits
2145
     *
2146
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
2147
     */
2148
    public static function is_username_too_long($username)
2149
    {
2150
        return strlen($username) > USERNAME_MAX_LENGTH;
2151
    }
2152
2153
    /**
2154
     * Get the users by ID.
2155
     *
2156
     * @param array  $ids    student ids
2157
     * @param string $active
2158
     * @param string $order
2159
     * @param string $limit
2160
     *
2161
     * @return array $result student information
2162
     */
2163
    public static function get_user_list_by_ids($ids = [], $active = null, $order = null, $limit = null)
2164
    {
2165
        if (empty($ids)) {
2166
            return [];
2167
        }
2168
2169
        $ids = is_array($ids) ? $ids : [$ids];
2170
        $ids = array_map('intval', $ids);
2171
        $ids = implode(',', $ids);
2172
2173
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
2174
        $sql = "SELECT * FROM $tbl_user WHERE id IN ($ids)";
2175
        if (!is_null($active)) {
2176
            $sql .= ' AND active='.($active ? '1' : '0');
2177
        }
2178
2179
        if (!is_null($order)) {
2180
            $order = Database::escape_string($order);
2181
            $sql .= ' ORDER BY '.$order;
2182
        }
2183
2184
        if (!is_null($limit)) {
2185
            $limit = Database::escape_string($limit);
2186
            $sql .= ' LIMIT '.$limit;
2187
        }
2188
2189
        $rs = Database::query($sql);
2190
        $result = [];
2191
        while ($row = Database::fetch_array($rs)) {
2192
            $result[] = $row;
2193
        }
2194
2195
        return $result;
2196
    }
2197
2198
    /**
2199
     * Get a list of users of which the given conditions match with an = 'cond'.
2200
     *
2201
     * @param array $conditions a list of condition (example : status=>STUDENT)
2202
     * @param array $order_by   a list of fields on which sort
2203
     *
2204
     * @return array an array with all users of the platform
2205
     *
2206
     * @todo security filter order by
2207
     */
2208
    public static function get_user_list(
2209
        $conditions = [],
2210
        $order_by = [],
2211
        $limit_from = false,
2212
        $limit_to = false,
2213
        $idCampus = null,
2214
        $keyword = null,
2215
        $lastConnectionDate = null,
2216
        $getCount = false,
2217
        $filterUsers = null
2218
    ) {
2219
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2220
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2221
        $return_array = [];
2222
2223
        if ($getCount) {
2224
            $sql = "SELECT count(user.id) as nbUsers FROM $user_table user ";
2225
        } else {
2226
            $sql = "SELECT user.* FROM $user_table user ";
2227
        }
2228
2229
        if (api_is_multiple_url_enabled()) {
2230
            if ($idCampus) {
2231
                $urlId = $idCampus;
2232
            } else {
2233
                $urlId = api_get_current_access_url_id();
2234
            }
2235
            $sql .= " INNER JOIN $userUrlTable url_user
2236
                      ON (user.user_id = url_user.user_id)
2237
                      WHERE url_user.access_url_id = $urlId";
2238
        } else {
2239
            $sql .= " WHERE 1=1 ";
2240
        }
2241
2242
        if (!empty($keyword)) {
2243
            $keyword = trim(Database::escape_string($keyword));
2244
            $keywordParts = array_filter(explode(' ', $keyword));
2245
            $extraKeyword = '';
2246
            if (!empty($keywordParts)) {
2247
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
2248
                if (!empty($keywordPartsFixed)) {
2249
                    $extraKeyword .= " OR
2250
                        CONCAT(user.firstname, ' ', user.lastname) LIKE '%$keywordPartsFixed%' OR
2251
                        CONCAT(user.lastname, ' ', user.firstname) LIKE '%$keywordPartsFixed%' ";
2252
                }
2253
            }
2254
2255
            $sql .= " AND (
2256
                user.username LIKE '%$keyword%' OR
2257
                user.firstname LIKE '%$keyword%' OR
2258
                user.lastname LIKE '%$keyword%' OR
2259
                user.official_code LIKE '%$keyword%' OR
2260
                user.email LIKE '%$keyword%' OR
2261
                CONCAT(user.firstname, ' ', user.lastname) LIKE '%$keyword%' OR
2262
                CONCAT(user.lastname, ' ', user.firstname) LIKE '%$keyword%'
2263
                $extraKeyword
2264
            )";
2265
        }
2266
2267
        if (!empty($lastConnectionDate)) {
2268
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
2269
            $sql .= " AND user.last_login <= '$lastConnectionDate' ";
2270
        }
2271
2272
        if (count($conditions) > 0) {
2273
            foreach ($conditions as $field => $value) {
2274
                $field = Database::escape_string($field);
2275
                $value = Database::escape_string($value);
2276
                $sql .= " AND $field = '$value'";
2277
            }
2278
        }
2279
2280
        if (!empty($filterUsers)) {
2281
            $sql .= " AND user.id IN(".implode(',', $filterUsers).")";
2282
        }
2283
2284
        if (count($order_by) > 0) {
2285
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2286
        }
2287
2288
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
2289
            $limit_from = (int) $limit_from;
2290
            $limit_to = (int) $limit_to;
2291
            $sql .= " LIMIT $limit_from, $limit_to";
2292
        }
2293
        $sql_result = Database::query($sql);
2294
2295
        if ($getCount) {
2296
            $result = Database::fetch_array($sql_result);
2297
2298
            return $result['nbUsers'];
2299
        }
2300
2301
        while ($result = Database::fetch_array($sql_result)) {
2302
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
2303
            $return_array[] = $result;
2304
        }
2305
2306
        return $return_array;
2307
    }
2308
2309
    public static function getUserListExtraConditions(
2310
        $conditions = [],
2311
        $order_by = [],
2312
        $limit_from = false,
2313
        $limit_to = false,
2314
        $idCampus = null,
2315
        $extraConditions = '',
2316
        $getCount = false
2317
    ) {
2318
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2319
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2320
        $return_array = [];
2321
        $sql = "SELECT user.* FROM $user_table user ";
2322
2323
        if ($getCount) {
2324
            $sql = "SELECT count(user.id) count FROM $user_table user ";
2325
        }
2326
2327
        if (api_is_multiple_url_enabled()) {
2328
            if ($idCampus) {
2329
                $urlId = $idCampus;
2330
            } else {
2331
                $urlId = api_get_current_access_url_id();
2332
            }
2333
            $sql .= " INNER JOIN $userUrlTable url_user
2334
                      ON (user.user_id = url_user.user_id)
2335
                      WHERE url_user.access_url_id = $urlId";
2336
        } else {
2337
            $sql .= " WHERE 1=1 ";
2338
        }
2339
2340
        $sql .= " AND status <> ".ANONYMOUS." ";
2341
2342
        if (count($conditions) > 0) {
2343
            foreach ($conditions as $field => $value) {
2344
                $field = Database::escape_string($field);
2345
                $value = Database::escape_string($value);
2346
                $sql .= " AND $field = '$value'";
2347
            }
2348
        }
2349
2350
        $sql .= str_replace("\'", "'", Database::escape_string($extraConditions));
2351
2352
        if (!empty($order_by) && count($order_by) > 0) {
2353
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2354
        }
2355
2356
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
2357
            $limit_from = (int) $limit_from;
2358
            $limit_to = (int) $limit_to;
2359
            $sql .= " LIMIT $limit_from, $limit_to";
2360
        }
2361
2362
        $sql_result = Database::query($sql);
2363
2364
        if ($getCount) {
2365
            $result = Database::fetch_array($sql_result);
2366
2367
            return $result['count'];
2368
        }
2369
2370
        while ($result = Database::fetch_array($sql_result)) {
2371
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
2372
            $return_array[] = $result;
2373
        }
2374
2375
        return $return_array;
2376
    }
2377
2378
    /**
2379
     * Get a list of users of which the given conditions match with a LIKE '%cond%'.
2380
     *
2381
     * @param array  $conditions       a list of condition (exemple : status=>STUDENT)
2382
     * @param array  $order_by         a list of fields on which sort
2383
     * @param bool   $simple_like      Whether we want a simple LIKE 'abc' or a LIKE '%abc%'
2384
     * @param string $condition        Whether we want the filters to be combined by AND or OR
2385
     * @param array  $onlyThisUserList
2386
     *
2387
     * @return array an array with all users of the platform
2388
     *
2389
     * @todo optional course code parameter, optional sorting parameters...
2390
     * @todo security filter order_by
2391
     */
2392
    public static function getUserListLike(
2393
        $conditions = [],
2394
        $order_by = [],
2395
        $simple_like = false,
2396
        $condition = 'AND',
2397
        $onlyThisUserList = []
2398
    ) {
2399
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2400
        $tblAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2401
        $return_array = [];
2402
        $sql_query = "SELECT user.id FROM $user_table user ";
2403
2404
        if (api_is_multiple_url_enabled()) {
2405
            $sql_query .= " INNER JOIN $tblAccessUrlRelUser auru ON auru.user_id = user.id ";
2406
        }
2407
2408
        $sql_query .= ' WHERE 1 = 1 ';
2409
        if (count($conditions) > 0) {
2410
2411
            $andActive = "";
2412
            if (isset($conditions['active'])) {
2413
                $andActive = " AND active = " . (int) $conditions['active'];
2414
                unset($conditions['active']);
2415
            }
2416
2417
            $temp_conditions = [];
2418
            foreach ($conditions as $field => $value) {
2419
                $field = Database::escape_string($field);
2420
                $value = Database::escape_string($value);
2421
                if ($simple_like) {
2422
                    $temp_conditions[] = $field." LIKE '$value%'";
2423
                } else {
2424
                    $temp_conditions[] = $field.' LIKE \'%'.$value.'%\'';
2425
                }
2426
            }
2427
            if (!empty($temp_conditions)) {
2428
                $sql_query .= ' AND ('.implode(' '.$condition.' ', $temp_conditions).') ';
2429
            }
2430
2431
            if (api_is_multiple_url_enabled()) {
2432
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
2433
            }
2434
            $sql_query .= $andActive;
2435
        } else {
2436
            if (api_is_multiple_url_enabled()) {
2437
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
2438
            }
2439
        }
2440
2441
        if (api_is_session_admin() && (api_get_setting('prevent_session_admins_to_manage_all_users') === 'true')) {
2442
            $sql_query .= ' AND user.creator_id = '.api_get_user_id();
2443
        }
2444
2445
        if (!empty($onlyThisUserList)) {
2446
            $onlyThisUserListToString = implode("','", $onlyThisUserList);
2447
            $sql_query .= " AND user.id IN ('$onlyThisUserListToString') ";
2448
        }
2449
2450
        if (count($order_by) > 0) {
2451
            $sql_query .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2452
        }
2453
2454
        $sql_result = Database::query($sql_query);
2455
        while ($result = Database::fetch_array($sql_result)) {
2456
            $userInfo = api_get_user_info($result['id']);
2457
            $return_array[] = $userInfo;
2458
        }
2459
2460
        return $return_array;
2461
    }
2462
2463
    /**
2464
     * Get user picture URL or path from user ID (returns an array).
2465
     * The return format is a complete path, enabling recovery of the directory
2466
     * with dirname() or the file with basename(). This also works for the
2467
     * functions dealing with the user's productions, as they are located in
2468
     * the same directory.
2469
     *
2470
     * @param int    $id       User ID
2471
     * @param string $type     Type of path to return (can be 'system', 'web')
2472
     * @param array  $userInfo user information to avoid query the DB
2473
     *                         returns the /main/img/unknown.jpg image set it at true
2474
     *
2475
     * @return array Array of 2 elements: 'dir' and 'file' which contain
2476
     *               the dir and file as the name implies if image does not exist it will
2477
     *               return the unknow image if anonymous parameter is true if not it returns an empty array
2478
     */
2479
    public static function get_user_picture_path_by_id(
2480
        $id,
2481
        $type = 'web',
2482
        $userInfo = []
2483
    ) {
2484
        switch ($type) {
2485
            case 'system': // Base: absolute system path.
2486
                $base = api_get_path(SYS_CODE_PATH);
2487
                break;
2488
            case 'web': // Base: absolute web path.
2489
            default:
2490
                $base = api_get_path(WEB_CODE_PATH);
2491
                break;
2492
        }
2493
2494
        $anonymousPath = [
2495
            'dir' => $base.'img/',
2496
            'file' => 'unknown.jpg',
2497
            'email' => '',
2498
        ];
2499
2500
        if (empty($id) || empty($type)) {
2501
            return $anonymousPath;
2502
        }
2503
2504
        $id = (int) $id;
2505
        if (empty($userInfo)) {
2506
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
2507
            $sql = "SELECT email, picture_uri FROM $user_table
2508
                    WHERE id = ".$id;
2509
            $res = Database::query($sql);
2510
2511
            if (!Database::num_rows($res)) {
2512
                return $anonymousPath;
2513
            }
2514
            $user = Database::fetch_array($res);
2515
            if (empty($user['picture_uri'])) {
2516
                return $anonymousPath;
2517
            }
2518
        } else {
2519
            $user = $userInfo;
2520
        }
2521
2522
        $pictureFilename = trim($user['picture_uri']);
2523
2524
        $dir = self::getUserPathById($id, $type);
2525
2526
        return [
2527
            'dir' => $dir,
2528
            'file' => $pictureFilename,
2529
            'email' => $user['email'],
2530
        ];
2531
    }
2532
2533
    /**
2534
     * *** READ BEFORE REVIEW THIS FUNCTION ***
2535
     * This function is a exact copy from get_user_picture_path_by_id() and it was create it to avoid
2536
     * a recursive calls for get_user_picture_path_by_id() in another functions when you update a user picture
2537
     * in same script, so you can find this function usage in update_user_picture() function.
2538
     *
2539
     * @param int    $id       User ID
2540
     * @param string $type     Type of path to return (can be 'system', 'web')
2541
     * @param array  $userInfo user information to avoid query the DB
2542
     *                         returns the /main/img/unknown.jpg image set it at true
2543
     *
2544
     * @return array Array of 2 elements: 'dir' and 'file' which contain
2545
     *               the dir and file as the name implies if image does not exist it will
2546
     *               return the unknown image if anonymous parameter is true if not it returns an empty array
2547
     */
2548
    public static function getUserPicturePathById($id, $type = 'web', $userInfo = [])
2549
    {
2550
        switch ($type) {
2551
            case 'system': // Base: absolute system path.
2552
                $base = api_get_path(SYS_CODE_PATH);
2553
                break;
2554
            case 'web': // Base: absolute web path.
2555
            default:
2556
                $base = api_get_path(WEB_CODE_PATH);
2557
                break;
2558
        }
2559
2560
        $anonymousPath = [
2561
            'dir' => $base.'img/',
2562
            'file' => 'unknown.jpg',
2563
            'email' => '',
2564
        ];
2565
2566
        if (empty($id) || empty($type)) {
2567
            return $anonymousPath;
2568
        }
2569
2570
        $id = (int) $id;
2571
        if (empty($userInfo)) {
2572
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
2573
            $sql = "SELECT email, picture_uri FROM $user_table WHERE id = $id";
2574
            $res = Database::query($sql);
2575
2576
            if (!Database::num_rows($res)) {
2577
                return $anonymousPath;
2578
            }
2579
            $user = Database::fetch_array($res);
2580
2581
            if (empty($user['picture_uri'])) {
2582
                return $anonymousPath;
2583
            }
2584
        } else {
2585
            $user = $userInfo;
2586
        }
2587
2588
        $pictureFilename = trim($user['picture_uri']);
2589
        $dir = self::getUserPathById($id, $type);
2590
2591
        return [
2592
            'dir' => $dir,
2593
            'file' => $pictureFilename,
2594
            'email' => $user['email'],
2595
        ];
2596
    }
2597
2598
    /**
2599
     * Get user path from user ID (returns an array).
2600
     * The return format is a complete path to a folder ending with "/"
2601
     * In case the first level of subdirectory of users/ does not exist, the
2602
     * function will attempt to create it. Probably not the right place to do it
2603
     * but at least it avoids headaches in many other places.
2604
     *
2605
     * @param int    $id   User ID
2606
     * @param string $type Type of path to return (can be 'system', 'web', 'last')
2607
     *
2608
     * @return string User folder path (i.e. /var/www/chamilo/app/upload/users/1/1/)
2609
     */
2610
    public static function getUserPathById($id, $type)
2611
    {
2612
        $id = (int) $id;
2613
        if (!$id) {
2614
            return null;
2615
        }
2616
2617
        $userPath = "users/$id/";
2618
        if (api_get_setting('split_users_upload_directory') === 'true') {
2619
            $userPath = 'users/'.substr((string) $id, 0, 1).'/'.$id.'/';
2620
            // In exceptional cases, on some portals, the intermediate base user
2621
            // directory might not have been created. Make sure it is before
2622
            // going further.
2623
2624
            $rootPath = api_get_path(SYS_UPLOAD_PATH).'users/'.substr((string) $id, 0, 1);
2625
            if (!is_dir($rootPath)) {
2626
                $perm = api_get_permissions_for_new_directories();
2627
                try {
2628
                    mkdir($rootPath, $perm);
2629
                } catch (Exception $e) {
2630
                    error_log($e->getMessage());
2631
                }
2632
            }
2633
        }
2634
        switch ($type) {
2635
            case 'system': // Base: absolute system path.
2636
                $userPath = api_get_path(SYS_UPLOAD_PATH).$userPath;
2637
                break;
2638
            case 'web': // Base: absolute web path.
2639
                $userPath = api_get_path(WEB_UPLOAD_PATH).$userPath;
2640
                break;
2641
            case 'last': // Only the last part starting with users/
2642
                break;
2643
        }
2644
2645
        return $userPath;
2646
    }
2647
2648
    /**
2649
     * Gets the current user image.
2650
     *
2651
     * @param string $user_id
2652
     * @param int    $size        it can be USER_IMAGE_SIZE_SMALL,
2653
     *                            USER_IMAGE_SIZE_MEDIUM, USER_IMAGE_SIZE_BIG or  USER_IMAGE_SIZE_ORIGINAL
2654
     * @param bool   $addRandomId
2655
     * @param array  $userInfo    to avoid query the DB
2656
     *
2657
     * @return string
2658
     */
2659
    public static function getUserPicture(
2660
        $user_id,
2661
        $size = USER_IMAGE_SIZE_MEDIUM,
2662
        $addRandomId = true,
2663
        $userInfo = []
2664
    ) {
2665
        // Make sure userInfo is defined. Otherwise, define it!
2666
        if (empty($userInfo) || !is_array($userInfo) || count($userInfo) == 0) {
2667
            if (empty($user_id)) {
2668
                return '';
2669
            } else {
2670
                $userInfo = api_get_user_info($user_id);
2671
            }
2672
        }
2673
2674
        $imageWebPath = self::get_user_picture_path_by_id(
2675
            $user_id,
2676
            'web',
2677
            $userInfo
2678
        );
2679
        $pictureWebFile = $imageWebPath['file'];
2680
        $pictureWebDir = $imageWebPath['dir'];
2681
2682
        $pictureAnonymousSize = '128';
2683
        $gravatarSize = 22;
2684
        $realSizeName = 'small_';
2685
2686
        switch ($size) {
2687
            case USER_IMAGE_SIZE_SMALL:
2688
                $pictureAnonymousSize = '32';
2689
                $realSizeName = 'small_';
2690
                $gravatarSize = 32;
2691
                break;
2692
            case USER_IMAGE_SIZE_MEDIUM:
2693
                $pictureAnonymousSize = '64';
2694
                $realSizeName = 'medium_';
2695
                $gravatarSize = 64;
2696
                break;
2697
            case USER_IMAGE_SIZE_ORIGINAL:
2698
                $pictureAnonymousSize = '128';
2699
                $realSizeName = '';
2700
                $gravatarSize = 128;
2701
                break;
2702
            case USER_IMAGE_SIZE_BIG:
2703
                $pictureAnonymousSize = '128';
2704
                $realSizeName = 'big_';
2705
                $gravatarSize = 128;
2706
                break;
2707
        }
2708
2709
        $gravatarEnabled = api_get_setting('gravatar_enabled');
2710
        $anonymousPath = Display::returnIconPath('unknown.png', $pictureAnonymousSize);
2711
        if ($pictureWebFile == 'unknown.jpg' || empty($pictureWebFile)) {
2712
            if ($gravatarEnabled === 'true') {
2713
                $file = self::getGravatar(
2714
                    $imageWebPath['email'],
2715
                    $gravatarSize,
2716
                    api_get_setting('gravatar_type')
2717
                );
2718
2719
                if ($addRandomId) {
2720
                    $file .= '&rand='.uniqid();
2721
                }
2722
2723
                return $file;
2724
            }
2725
2726
            return $anonymousPath;
2727
        }
2728
2729
        $pictureSysPath = self::get_user_picture_path_by_id($user_id, 'system');
2730
        $file = $pictureSysPath['dir'].$realSizeName.$pictureWebFile;
2731
        $picture = '';
2732
        if (file_exists($file)) {
2733
            $picture = $pictureWebDir.$realSizeName.$pictureWebFile;
2734
        } else {
2735
            $file = $pictureSysPath['dir'].$pictureWebFile;
2736
            if (file_exists($file) && !is_dir($file)) {
2737
                $picture = $pictureWebFile['dir'].$pictureWebFile;
2738
            }
2739
        }
2740
2741
        if (empty($picture)) {
2742
            return $anonymousPath;
2743
        }
2744
2745
        if ($addRandomId) {
2746
            $picture .= '?rand='.uniqid();
2747
        }
2748
2749
        return $picture;
2750
    }
2751
2752
    /**
2753
     * Creates new user photos in various sizes of a user, or deletes user photos.
2754
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2755
     *
2756
     * @param int    $user_id the user internal identification number
2757
     * @param string $file    The common file name for the newly created photos.
2758
     *                        It will be checked and modified for compatibility with the file system.
2759
     *                        If full name is provided, path component is ignored.
2760
     *                        If an empty name is provided, then old user photos are deleted only,
2761
     *
2762
     * @see     UserManager::delete_user_picture() as the prefered way for deletion.
2763
     *
2764
     * @param string $source_file    the full system name of the image from which user photos will be created
2765
     * @param string $cropParameters Optional string that contents "x,y,width,height" of a cropped image format
2766
     *
2767
     * @return bool Returns the resulting common file name of created images which usually should be stored in database.
2768
     *              When deletion is requested returns empty string.
2769
     *              In case of internal error or negative validation returns FALSE.
2770
     */
2771
    public static function update_user_picture(
2772
        $user_id,
2773
        $file = null,
2774
        $source_file = null,
2775
        $cropParameters = ''
2776
    ) {
2777
        if (empty($user_id)) {
2778
            return false;
2779
        }
2780
        $delete = empty($file);
2781
        if (empty($source_file)) {
2782
            $source_file = $file;
2783
        }
2784
2785
        // User-reserved directory where photos have to be placed.
2786
        $path_info = self::getUserPicturePathById($user_id, 'system');
2787
        $path = $path_info['dir'];
2788
2789
        // If this directory does not exist - we create it.
2790
        if (!file_exists($path)) {
2791
            mkdir($path, api_get_permissions_for_new_directories(), true);
2792
        }
2793
2794
        // The old photos (if any).
2795
        $old_file = $path_info['file'];
2796
2797
        // Let us delete them.
2798
        if ($old_file != 'unknown.jpg') {
2799
            if (KEEP_THE_OLD_IMAGE_AFTER_CHANGE) {
2800
                $prefix = 'saved_'.date('Y_m_d_H_i_s').'_'.uniqid('').'_';
2801
                @rename($path.'small_'.$old_file, $path.$prefix.'small_'.$old_file);
2802
                @rename($path.'medium_'.$old_file, $path.$prefix.'medium_'.$old_file);
2803
                @rename($path.'big_'.$old_file, $path.$prefix.'big_'.$old_file);
2804
                @rename($path.$old_file, $path.$prefix.$old_file);
2805
            } else {
2806
                @unlink($path.'small_'.$old_file);
2807
                @unlink($path.'medium_'.$old_file);
2808
                @unlink($path.'big_'.$old_file);
2809
                @unlink($path.$old_file);
2810
            }
2811
        }
2812
2813
        // Exit if only deletion has been requested. Return an empty picture name.
2814
        if ($delete) {
2815
            return '';
2816
        }
2817
2818
        // Validation 2.
2819
        $allowed_types = api_get_supported_image_extensions();
2820
        $file = str_replace('\\', '/', $file);
2821
        $filename = (($pos = strrpos($file, '/')) !== false) ? substr($file, $pos + 1) : $file;
2822
        $extension = strtolower(substr(strrchr($filename, '.'), 1));
2823
        if (!in_array($extension, $allowed_types)) {
2824
            return false;
2825
        }
2826
2827
        // This is the common name for the new photos.
2828
        if (KEEP_THE_NAME_WHEN_CHANGE_IMAGE && $old_file != 'unknown.jpg') {
2829
            $old_extension = strtolower(substr(strrchr($old_file, '.'), 1));
2830
            $filename = in_array($old_extension, $allowed_types) ? substr($old_file, 0, -strlen($old_extension)) : $old_file;
2831
            $filename = (substr($filename, -1) == '.') ? $filename.$extension : $filename.'.'.$extension;
2832
        } else {
2833
            $filename = api_replace_dangerous_char($filename);
2834
            if (PREFIX_IMAGE_FILENAME_WITH_UID) {
2835
                $filename = uniqid('').'_'.$filename;
2836
            }
2837
            // We always prefix user photos with user ids, so on setting
2838
            // api_get_setting('split_users_upload_directory') === 'true'
2839
            // the correspondent directories to be found successfully.
2840
            $filename = $user_id.'_'.$filename;
2841
        }
2842
2843
        if (!file_exists($source_file)) {
2844
            return false;
2845
        }
2846
2847
        $mimeContentType = mime_content_type($source_file);
2848
        if (false === strpos($mimeContentType, 'image')) {
2849
            return false;
2850
        }
2851
2852
        //Crop the image to adjust 1:1 ratio
2853
        $image = new Image($source_file);
2854
        $image->crop($cropParameters);
2855
2856
        // Storing the new photos in 4 versions with various sizes.
2857
        $userPath = self::getUserPathById($user_id, 'system');
2858
2859
        // If this path does not exist - we create it.
2860
        if (!file_exists($userPath)) {
2861
            mkdir($userPath, api_get_permissions_for_new_directories(), true);
2862
        }
2863
        $small = new Image($source_file);
2864
        $small->resize(32);
2865
        $small->send_image($userPath.'small_'.$filename);
2866
        $medium = new Image($source_file);
2867
        $medium->resize(85);
2868
        $medium->send_image($userPath.'medium_'.$filename);
2869
        $normal = new Image($source_file);
2870
        $normal->resize(200);
2871
        $normal->send_image($userPath.$filename);
2872
2873
        $big = new Image($source_file); // This is the original picture.
2874
        $big->send_image($userPath.'big_'.$filename);
2875
2876
        $result = $small && $medium && $normal && $big;
2877
2878
        return $result ? $filename : false;
2879
    }
2880
2881
    /**
2882
     * Update User extra field file type into {user_folder}/{$extra_field}.
2883
     *
2884
     * @param int    $user_id     The user internal identification number
2885
     * @param string $extra_field The $extra_field The extra field name
2886
     * @param null   $file        The filename
2887
     * @param null   $source_file The temporal filename
2888
     *
2889
     * @return bool|null return filename if success, but false
2890
     */
2891
    public static function update_user_extra_file(
2892
        $user_id,
2893
        $extra_field = '',
2894
        $file = null,
2895
        $source_file = null
2896
    ) {
2897
        // Add Filter
2898
        $source_file = Security::filter_filename($source_file);
2899
        $file = Security::filter_filename($file);
2900
2901
        if (empty($user_id)) {
2902
            return false;
2903
        }
2904
2905
        if (empty($source_file)) {
2906
            $source_file = $file;
2907
        }
2908
2909
        // User-reserved directory where extra file have to be placed.
2910
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
2911
        $path = $path_info['dir'];
2912
        if (!empty($extra_field)) {
2913
            $path .= $extra_field.'/';
2914
        }
2915
        // If this directory does not exist - we create it.
2916
        if (!file_exists($path)) {
2917
            @mkdir($path, api_get_permissions_for_new_directories(), true);
2918
        }
2919
2920
        if (filter_extension($file)) {
2921
            if (@move_uploaded_file($source_file, $path.$file)) {
2922
                if ($extra_field) {
2923
                    return $extra_field.'/'.$file;
2924
                } else {
2925
                    return $file;
2926
                }
2927
            }
2928
        }
2929
2930
        return false; // this should be returned if anything went wrong with the upload
2931
    }
2932
2933
    /**
2934
     * Deletes user photos.
2935
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2936
     *
2937
     * @param int $userId the user internal identification number
2938
     *
2939
     * @return mixed returns empty string on success, FALSE on error
2940
     */
2941
    public static function deleteUserPicture($userId)
2942
    {
2943
        return self::update_user_picture($userId);
2944
    }
2945
2946
    /**
2947
     * Returns an XHTML formatted list of productions for a user, or FALSE if he
2948
     * doesn't have any.
2949
     *
2950
     * If there has been a request to remove a production, the function will return
2951
     * without building the list unless forced to do so by the optional second
2952
     * parameter. This increases performance by avoiding to read through the
2953
     * productions on the filesystem before the removal request has been carried
2954
     * out because they'll have to be re-read afterwards anyway.
2955
     *
2956
     * @param int  $user_id    User id
2957
     * @param bool $force      Optional parameter to force building after a removal request
2958
     * @param bool $showDelete
2959
     *
2960
     * @return string A string containing the XHTML code to display the production list, or FALSE
2961
     */
2962
    public static function build_production_list($user_id, $force = false, $showDelete = false)
2963
    {
2964
        if (!$force && !empty($_POST['remove_production'])) {
2965
            return true; // postpone reading from the filesystem
2966
        }
2967
2968
        $productions = self::get_user_productions($user_id);
2969
2970
        if (empty($productions)) {
2971
            return false;
2972
        }
2973
2974
        $production_dir = self::getUserPathById($user_id, 'web');
2975
        $del_image = Display::returnIconPath('delete.png');
2976
        $add_image = Display::returnIconPath('archive.png');
2977
        $del_text = get_lang('Delete');
2978
        $production_list = '';
2979
        if (count($productions) > 0) {
2980
            $production_list = '<div class="files-production"><ul id="productions">';
2981
            foreach ($productions as $file) {
2982
                $production_list .= '<li>
2983
                    <img src="'.$add_image.'" />
2984
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2985
                        '.htmlentities($file).'
2986
                    </a>';
2987
                if ($showDelete) {
2988
                    $production_list .= '&nbsp;&nbsp;
2989
                        <input
2990
                            style="width:16px;"
2991
                            type="image"
2992
                            name="remove_production['.urlencode($file).']"
2993
                            src="'.$del_image.'"
2994
                            alt="'.$del_text.'"
2995
                            title="'.$del_text.' '.htmlentities($file).'"
2996
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2997
                }
2998
            }
2999
            $production_list .= '</ul></div>';
3000
        }
3001
3002
        return $production_list;
3003
    }
3004
3005
    /**
3006
     * Returns an array with the user's productions.
3007
     *
3008
     * @param int $user_id User id
3009
     *
3010
     * @return array An array containing the user's productions
3011
     */
3012
    public static function get_user_productions($user_id)
3013
    {
3014
        $production_repository = self::getUserPathById($user_id, 'system');
3015
        $productions = [];
3016
3017
        if (is_dir($production_repository)) {
3018
            $handle = opendir($production_repository);
3019
            while ($file = readdir($handle)) {
3020
                if ($file == '.' ||
3021
                    $file == '..' ||
3022
                    $file == '.htaccess' ||
3023
                    is_dir($production_repository.$file)
3024
                ) {
3025
                    // skip current/parent directory and .htaccess
3026
                    continue;
3027
                }
3028
3029
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
3030
                    // User's photos should not be listed as productions.
3031
                    continue;
3032
                }
3033
                $productions[] = $file;
3034
            }
3035
        }
3036
3037
        return $productions;
3038
    }
3039
3040
    /**
3041
     * Remove a user production.
3042
     *
3043
     * @param int    $user_id    User id
3044
     * @param string $production The production to remove
3045
     *
3046
     * @return bool
3047
     */
3048
    public static function remove_user_production($user_id, $production)
3049
    {
3050
        $production_path = self::get_user_picture_path_by_id($user_id, 'system');
3051
        $production_file = $production_path['dir'].$production;
3052
        if (is_file($production_file)) {
3053
            unlink($production_file);
3054
3055
            return true;
3056
        }
3057
3058
        return false;
3059
    }
3060
3061
    /**
3062
     * Update an extra field value for a given user.
3063
     *
3064
     * @param int    $userId   User ID
3065
     * @param string $variable Field variable name
3066
     * @param string $value    Field value
3067
     *
3068
     * @return bool true if field updated, false otherwise
3069
     */
3070
    public static function update_extra_field_value($userId, $variable, $value = '')
3071
    {
3072
        $extraFieldValue = new ExtraFieldValue('user');
3073
        $params = [
3074
            'item_id' => $userId,
3075
            'variable' => $variable,
3076
            'value' => $value,
3077
        ];
3078
3079
        return $extraFieldValue->save($params);
3080
    }
3081
3082
    /**
3083
     * Get an array of extra fields with field details (type, default value and options).
3084
     *
3085
     * @param    int    Offset (from which row)
3086
     * @param    int    Number of items
3087
     * @param    int    Column on which sorting is made
3088
     * @param    string    Sorting direction
3089
     * @param    bool    Optional. Whether we get all the fields or just the visible ones
3090
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
3091
     *
3092
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
3093
     */
3094
    public static function get_extra_fields(
3095
        $from = 0,
3096
        $number_of_items = 0,
3097
        $column = 5,
3098
        $direction = 'ASC',
3099
        $all_visibility = true,
3100
        $field_filter = null
3101
    ) {
3102
        $fields = [];
3103
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3104
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
3105
        $columns = [
3106
            'id',
3107
            'variable',
3108
            'field_type',
3109
            'display_text',
3110
            'default_value',
3111
            'field_order',
3112
            'filter',
3113
        ];
3114
        $column = (int) $column;
3115
        $sort_direction = '';
3116
        if (!empty($direction)) {
3117
            if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
3118
                $sort_direction = strtoupper($direction);
3119
            }
3120
        }
3121
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
3122
        $sqlf = "SELECT * FROM $t_uf WHERE extra_field_type = $extraFieldType ";
3123
        if (!$all_visibility) {
3124
            $sqlf .= " AND visible_to_self = 1 ";
3125
        }
3126
        if (!is_null($field_filter)) {
3127
            $field_filter = (int) $field_filter;
3128
            $sqlf .= " AND filter = $field_filter ";
3129
        }
3130
        $sqlf .= " ORDER BY `".$columns[$column]."` $sort_direction ";
3131
        if ($number_of_items != 0) {
3132
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
3133
        }
3134
        $resf = Database::query($sqlf);
3135
        if (Database::num_rows($resf) > 0) {
3136
            while ($rowf = Database::fetch_array($resf)) {
3137
                $fields[$rowf['id']] = [
3138
                    0 => $rowf['id'],
3139
                    1 => $rowf['variable'],
3140
                    2 => $rowf['field_type'],
3141
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
3142
                    4 => $rowf['default_value'],
3143
                    5 => $rowf['field_order'],
3144
                    6 => $rowf['visible_to_self'],
3145
                    7 => $rowf['changeable'],
3146
                    8 => $rowf['filter'],
3147
                    9 => [],
3148
                    10 => '<a name="'.$rowf['id'].'"></a>',
3149
                ];
3150
3151
                $sqlo = "SELECT * FROM $t_ufo
3152
                         WHERE field_id = ".$rowf['id']."
3153
                         ORDER BY option_order ASC";
3154
                $reso = Database::query($sqlo);
3155
                if (Database::num_rows($reso) > 0) {
3156
                    while ($rowo = Database::fetch_array($reso)) {
3157
                        $fields[$rowf['id']][9][$rowo['id']] = [
3158
                            0 => $rowo['id'],
3159
                            1 => $rowo['option_value'],
3160
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
3161
                            3 => $rowo['option_order'],
3162
                        ];
3163
                    }
3164
                }
3165
            }
3166
        }
3167
3168
        return $fields;
3169
    }
3170
3171
    /**
3172
     * Build a list of extra file already uploaded in $user_folder/{$extra_field}/.
3173
     *
3174
     * @param $user_id
3175
     * @param $extra_field
3176
     * @param bool $force
3177
     * @param bool $showDelete
3178
     *
3179
     * @return bool|string
3180
     */
3181
    public static function build_user_extra_file_list(
3182
        $user_id,
3183
        $extra_field,
3184
        $force = false,
3185
        $showDelete = false
3186
    ) {
3187
        if (!$force && !empty($_POST['remove_'.$extra_field])) {
3188
            return true; // postpone reading from the filesystem
3189
        }
3190
3191
        $extra_files = self::get_user_extra_files($user_id, $extra_field);
3192
        if (empty($extra_files)) {
3193
            return false;
3194
        }
3195
3196
        $path_info = self::get_user_picture_path_by_id($user_id, 'web');
3197
        $path = $path_info['dir'];
3198
        $del_image = Display::returnIconPath('delete.png');
3199
3200
        $del_text = get_lang('Delete');
3201
        $extra_file_list = '';
3202
        if (count($extra_files) > 0) {
3203
            $extra_file_list = '<div class="files-production"><ul id="productions">';
3204
            foreach ($extra_files as $file) {
3205
                $filename = substr($file, strlen($extra_field) + 1);
3206
                $extra_file_list .= '<li>'.Display::return_icon('archive.png').
3207
                    '<a href="'.$path.$extra_field.'/'.urlencode($filename).'" target="_blank">
3208
                        '.htmlentities($filename).
3209
                    '</a> ';
3210
                if ($showDelete) {
3211
                    $extra_file_list .= '<input
3212
                        style="width:16px;"
3213
                        type="image"
3214
                        name="remove_extra_'.$extra_field.'['.urlencode($file).']"
3215
                        src="'.$del_image.'"
3216
                        alt="'.$del_text.'"
3217
                        title="'.$del_text.' '.htmlentities($filename).'"
3218
                        onclick="javascript: return confirmation(\''.htmlentities($filename).'\');" /></li>';
3219
                }
3220
            }
3221
            $extra_file_list .= '</ul></div>';
3222
        }
3223
3224
        return $extra_file_list;
3225
    }
3226
3227
    /**
3228
     * Get valid filenames in $user_folder/{$extra_field}/.
3229
     *
3230
     * @param $user_id
3231
     * @param $extra_field
3232
     * @param bool $full_path
3233
     *
3234
     * @return array
3235
     */
3236
    public static function get_user_extra_files($user_id, $extra_field, $full_path = false)
3237
    {
3238
        if (!$full_path) {
3239
            // Nothing to do
3240
        } else {
3241
            $path_info = self::get_user_picture_path_by_id($user_id, 'system');
3242
            $path = $path_info['dir'];
3243
        }
3244
        $extra_data = self::get_extra_user_data_by_field($user_id, $extra_field);
3245
        $extra_files = $extra_data[$extra_field];
3246
3247
        $files = [];
3248
        if (is_array($extra_files)) {
3249
            foreach ($extra_files as $key => $value) {
3250
                if (!$full_path) {
3251
                    // Relative path from user folder
3252
                    $files[] = $value;
3253
                } else {
3254
                    $files[] = $path.$value;
3255
                }
3256
            }
3257
        } elseif (!empty($extra_files)) {
3258
            if (!$full_path) {
3259
                // Relative path from user folder
3260
                $files[] = $extra_files;
3261
            } else {
3262
                $files[] = $path.$extra_files;
3263
            }
3264
        }
3265
3266
        return $files; // can be an empty array
3267
    }
3268
3269
    /**
3270
     * Remove an {$extra_file} from the user folder $user_folder/{$extra_field}/.
3271
     *
3272
     * @param int    $user_id
3273
     * @param string $extra_field
3274
     * @param string $extra_file
3275
     *
3276
     * @return bool
3277
     */
3278
    public static function remove_user_extra_file($user_id, $extra_field, $extra_file)
3279
    {
3280
        $extra_file = Security::filter_filename($extra_file);
3281
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
3282
        if (strpos($extra_file, $extra_field) !== false) {
3283
            $path_extra_file = $path_info['dir'].$extra_file;
3284
        } else {
3285
            $path_extra_file = $path_info['dir'].$extra_field.'/'.$extra_file;
3286
        }
3287
        if (is_file($path_extra_file)) {
3288
            unlink($path_extra_file);
3289
3290
            return true;
3291
        }
3292
3293
        return false;
3294
    }
3295
3296
    /**
3297
     * Creates a new extra field.
3298
     *
3299
     * @param string $variable    Field's internal variable name
3300
     * @param int    $fieldType   Field's type
3301
     * @param string $displayText Field's language var name
3302
     * @param string $default     Field's default value
3303
     *
3304
     * @return int
3305
     */
3306
    public static function create_extra_field(
3307
        $variable,
3308
        $fieldType,
3309
        $displayText,
3310
        $default
3311
    ) {
3312
        $extraField = new ExtraField('user');
3313
        $params = [
3314
            'variable' => $variable,
3315
            'field_type' => $fieldType,
3316
            'display_text' => $displayText,
3317
            'default_value' => $default,
3318
        ];
3319
3320
        return $extraField->save($params);
3321
    }
3322
3323
    /**
3324
     * Check if a field is available.
3325
     *
3326
     * @param string $variable
3327
     *
3328
     * @return bool
3329
     */
3330
    public static function is_extra_field_available($variable)
3331
    {
3332
        $extraField = new ExtraField('user');
3333
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
3334
3335
        return !empty($data) ? true : false;
3336
    }
3337
3338
    /**
3339
     * Gets user extra fields data.
3340
     *
3341
     * @param    int    User ID
3342
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
3343
     * @param    bool    Whether to return invisible fields as well
3344
     * @param    bool    Whether to split multiple-selection fields or not
3345
     * @param    mixed   Whether to filter on the value of filter
3346
     *
3347
     * @return array Array of fields => value for the given user
3348
     */
3349
    public static function get_extra_user_data(
3350
        $user_id,
3351
        $prefix = false,
3352
        $allVisibility = true,
3353
        $splitMultiple = false,
3354
        $fieldFilter = null
3355
    ) {
3356
        $user_id = (int) $user_id;
3357
3358
        if (empty($user_id)) {
3359
            return [];
3360
        }
3361
3362
        $extra_data = [];
3363
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3364
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3365
        $user_id = (int) $user_id;
3366
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
3367
                FROM $t_uf f
3368
                WHERE
3369
                    extra_field_type = ".EntityExtraField::USER_FIELD_TYPE."
3370
                ";
3371
        $filter_cond = '';
3372
3373
        if (!$allVisibility) {
3374
            if (isset($fieldFilter)) {
3375
                $fieldFilter = (int) $fieldFilter;
3376
                $filter_cond .= " AND filter = $fieldFilter ";
3377
            }
3378
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
3379
        } else {
3380
            if (isset($fieldFilter)) {
3381
                $fieldFilter = (int) $fieldFilter;
3382
                $sql .= " AND filter = $fieldFilter ";
3383
            }
3384
        }
3385
3386
        $sql .= ' ORDER BY f.field_order';
3387
3388
        $res = Database::query($sql);
3389
        if (Database::num_rows($res) > 0) {
3390
            while ($row = Database::fetch_array($res)) {
3391
                if ($row['type'] == self::USER_FIELD_TYPE_TAG) {
3392
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
3393
                    $extra_data['extra_'.$row['fvar']] = $tags;
3394
                } else {
3395
                    $sqlu = "SELECT value as fval
3396
                            FROM $t_ufv
3397
                            WHERE field_id = ".$row['id']." AND item_id = ".$user_id;
3398
                    $resu = Database::query($sqlu);
3399
3400
                    if (Database::num_rows($resu) > 0) {
3401
                        $rowu = Database::fetch_array($resu);
3402
                        $fval = $rowu['fval'];
3403
                        if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
3404
                            $fval = explode(';', $rowu['fval']);
3405
                        }
3406
                    } else {
3407
                        // get default value
3408
                        $sql_df = "SELECT default_value as fval_df FROM $t_uf
3409
                               WHERE id = ".$row['id'];
3410
                        $res_df = Database::query($sql_df);
3411
                        $row_df = Database::fetch_array($res_df);
3412
                        $fval = $row_df['fval_df'];
3413
                    }
3414
                    // We get here (and fill the $extra_data array) even if there
3415
                    // is no user with data (we fill it with default values)
3416
                    if ($prefix) {
3417
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
3418
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
3419
                        } else {
3420
                            $extra_data['extra_'.$row['fvar']] = $fval;
3421
                        }
3422
                    } else {
3423
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
3424
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
3425
                        } else {
3426
                            $extra_data[$row['fvar']] = $fval;
3427
                        }
3428
                    }
3429
                }
3430
            }
3431
        }
3432
3433
        return $extra_data;
3434
    }
3435
3436
    /**
3437
     * Get extra user data by field.
3438
     *
3439
     * @param int    user ID
3440
     * @param string the internal variable name of the field
3441
     *
3442
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
3443
     */
3444
    public static function get_extra_user_data_by_field(
3445
        $user_id,
3446
        $field_variable,
3447
        $prefix = false,
3448
        $all_visibility = true,
3449
        $splitmultiple = false
3450
    ) {
3451
        $user_id = (int) $user_id;
3452
3453
        if (empty($user_id)) {
3454
            return [];
3455
        }
3456
3457
        $extra_data = [];
3458
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3459
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3460
3461
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
3462
                FROM $t_uf f
3463
                WHERE f.variable = '$field_variable' ";
3464
3465
        if (!$all_visibility) {
3466
            $sql .= " AND f.visible_to_self = 1 ";
3467
        }
3468
3469
        $sql .= " AND extra_field_type = ".EntityExtraField::USER_FIELD_TYPE;
3470
        $sql .= " ORDER BY f.field_order ";
3471
3472
        $res = Database::query($sql);
3473
        if (Database::num_rows($res) > 0) {
3474
            while ($row = Database::fetch_array($res)) {
3475
                $sqlu = "SELECT value as fval FROM $t_ufv v
3476
                         INNER JOIN $t_uf f
3477
                         ON (v.field_id = f.id)
3478
                         WHERE
3479
                            extra_field_type = ".EntityExtraField::USER_FIELD_TYPE." AND
3480
                            field_id = ".$row['id']." AND
3481
                            item_id = ".$user_id;
3482
                $resu = Database::query($sqlu);
3483
                $fval = '';
3484
                if (Database::num_rows($resu) > 0) {
3485
                    $rowu = Database::fetch_array($resu);
3486
                    $fval = $rowu['fval'];
3487
                    if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
3488
                        $fval = explode(';', $rowu['fval']);
3489
                    }
3490
                }
3491
                if ($prefix) {
3492
                    $extra_data['extra_'.$row['fvar']] = $fval;
3493
                } else {
3494
                    $extra_data[$row['fvar']] = $fval;
3495
                }
3496
            }
3497
        }
3498
3499
        return $extra_data;
3500
    }
3501
3502
    /**
3503
     * Get the extra field information for a certain field (the options as well).
3504
     *
3505
     * @param string $variable The name of the field we want to know everything about
3506
     *
3507
     * @return array Array containing all the information about the extra profile field
3508
     *               (first level of array contains field details, then 'options' sub-array contains options details,
3509
     *               as returned by the database)
3510
     *
3511
     * @author Julio Montoya
3512
     *
3513
     * @since v1.8.6
3514
     */
3515
    public static function get_extra_field_information_by_name($variable)
3516
    {
3517
        $extraField = new ExtraField('user');
3518
3519
        return $extraField->get_handler_field_info_by_field_variable($variable);
3520
    }
3521
3522
    /**
3523
     * Get the extra field information for user tag (the options as well).
3524
     *
3525
     * @param int $variable The name of the field we want to know everything about
3526
     *
3527
     * @return array Array containing all the information about the extra profile field
3528
     *               (first level of array contains field details, then 'options' sub-array contains options details,
3529
     *               as returned by the database)
3530
     *
3531
     * @author José Loguercio
3532
     *
3533
     * @since v1.11.0
3534
     */
3535
    public static function get_extra_field_tags_information_by_name($variable)
3536
    {
3537
        $extraField = new ExtraField('user');
3538
3539
        return $extraField->get_handler_field_info_by_tags($variable);
3540
    }
3541
3542
    /**
3543
     * @param string $type
3544
     *
3545
     * @return array
3546
     */
3547
    public static function get_all_extra_field_by_type($type)
3548
    {
3549
        $extraField = new ExtraField('user');
3550
3551
        return $extraField->get_all_extra_field_by_type($type);
3552
    }
3553
3554
    /**
3555
     * Get all the extra field information of a certain field (also the options).
3556
     *
3557
     * @param int $fieldId the ID of the field we want to know everything of
3558
     *
3559
     * @return array $return containing all th information about the extra profile field
3560
     *
3561
     * @author Julio Montoya
3562
     *
3563
     * @deprecated
3564
     * @since v1.8.6
3565
     */
3566
    public static function get_extra_field_information($fieldId)
3567
    {
3568
        $extraField = new ExtraField('user');
3569
3570
        return $extraField->getFieldInfoByFieldId($fieldId);
3571
    }
3572
3573
    /**
3574
     * Get extra user data by value.
3575
     *
3576
     * @param string $variable the internal variable name of the field
3577
     * @param string $value    the internal value of the field
3578
     * @param bool   $useLike
3579
     *
3580
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
3581
     */
3582
    public static function get_extra_user_data_by_value($variable, $value, $useLike = false)
3583
    {
3584
        $extraFieldValue = new ExtraFieldValue('user');
3585
        $extraField = new ExtraField('user');
3586
3587
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
3588
3589
        if (false === $info) {
3590
            return [];
3591
        }
3592
3593
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
3594
            $variable,
3595
            $value,
3596
            false,
3597
            false,
3598
            true,
3599
            $useLike
3600
        );
3601
3602
        $result = [];
3603
        if (!empty($data)) {
3604
            foreach ($data as $item) {
3605
                $result[] = $item['item_id'];
3606
            }
3607
        }
3608
3609
        return $result;
3610
    }
3611
3612
    /**
3613
     * Get extra user data by tags value.
3614
     *
3615
     * @param int    $fieldId the ID of the field we want to know everything of
3616
     * @param string $tag     the tag name for search
3617
     *
3618
     * @return array with extra data info of a user
3619
     *
3620
     * @author José Loguercio
3621
     *
3622
     * @since v1.11.0
3623
     */
3624
    public static function get_extra_user_data_by_tags($fieldId, $tag)
3625
    {
3626
        $extraField = new ExtraField('user');
3627
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
3628
        $array = [];
3629
        foreach ($result as $index => $user) {
3630
            $array[] = $user['user_id'];
3631
        }
3632
3633
        return $array;
3634
    }
3635
3636
    /**
3637
     * Get extra user data by field variable.
3638
     *
3639
     * @param string $variable field variable
3640
     *
3641
     * @return array data
3642
     */
3643
    public static function get_extra_user_data_by_field_variable($variable)
3644
    {
3645
        $extraInfo = self::get_extra_field_information_by_name($variable);
3646
        $field_id = (int) $extraInfo['id'];
3647
3648
        $extraField = new ExtraFieldValue('user');
3649
        $data = $extraField->getValuesByFieldId($field_id);
3650
3651
        if (!empty($data)) {
3652
            foreach ($data as $row) {
3653
                $user_id = $row['item_id'];
3654
                $data[$user_id] = $row;
3655
            }
3656
        }
3657
3658
        return $data;
3659
    }
3660
3661
    /**
3662
     * Get extra user data tags by field variable.
3663
     *
3664
     * @param string $variable field variable
3665
     *
3666
     * @return array
3667
     */
3668
    public static function get_extra_user_data_for_tags($variable)
3669
    {
3670
        $data = self::get_extra_field_tags_information_by_name($variable);
3671
3672
        return $data;
3673
    }
3674
3675
    /**
3676
     * Gives a list of [session_category][session_id] for the current user.
3677
     *
3678
     * @param int  $user_id
3679
     * @param bool $is_time_over                 whether to fill the first element or not
3680
     *                                           (to give space for courses out of categories)
3681
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
3682
     * @param bool $ignoreTimeLimit              ignore time start/end
3683
     * @param bool $getCount
3684
     *
3685
     * @return array list of statuses [session_category][session_id]
3686
     *
3687
     * @todo ensure multiple access urls are managed correctly
3688
     */
3689
    public static function get_sessions_by_category(
3690
        $user_id,
3691
        $is_time_over = true,
3692
        $ignore_visibility_for_admins = false,
3693
        $ignoreTimeLimit = false,
3694
        $getCount = false
3695
    ) {
3696
        $user_id = (int) $user_id;
3697
3698
        if (empty($user_id)) {
3699
            return [];
3700
        }
3701
3702
        $allowOrder = api_get_configuration_value('session_list_order');
3703
        $position = '';
3704
        if ($allowOrder) {
3705
            $position = ', s.position AS position ';
3706
        }
3707
3708
        // Get the list of sessions per user
3709
        $now = new DateTime('now', new DateTimeZone('UTC'));
3710
3711
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
3712
        // join would not catch session-courses where the user is general
3713
        // session coach but which do not have students nor coaches registered
3714
        $dqlSelect = ' COUNT(DISTINCT s.id) ';
3715
3716
        if (!$getCount) {
3717
            $dqlSelect = " DISTINCT
3718
                s.id,
3719
                s.name,
3720
                s.accessStartDate AS access_start_date,
3721
                s.accessEndDate AS access_end_date,
3722
                s.duration,
3723
                sc.id AS session_category_id,
3724
                sc.name AS session_category_name,
3725
                sc.dateStart AS session_category_date_start,
3726
                sc.dateEnd AS session_category_date_end,
3727
                s.coachAccessStartDate AS coach_access_start_date,
3728
                s.coachAccessEndDate AS coach_access_end_date,
3729
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
3730
                $position
3731
            ";
3732
        }
3733
3734
        $dql = "SELECT $dqlSelect
3735
                FROM ChamiloCoreBundle:Session AS s
3736
                LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
3737
                INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.sessionId = s.id
3738
                LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc ";
3739
3740
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
3741
        // is awfully inefficient for large sets of data (1m25s for 58K
3742
        // sessions, BT#14115) but executing a similar query twice and grouping
3743
        // the results afterwards in PHP takes about 1/1000th of the time
3744
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
3745
        $dqlStudent = $dql.' WHERE scu.user = :user AND url.accessUrlId = :url ';
3746
        $dqlCoach = $dql.' WHERE s.generalCoach = :user AND url.accessUrlId = :url ';
3747
3748
        // Default order
3749
        $order = 'ORDER BY sc.name, s.name';
3750
3751
        // Order by date if showing all sessions
3752
        $showAllSessions = api_get_configuration_value('show_all_sessions_on_my_course_page') === true;
3753
        if ($showAllSessions) {
3754
            $order = 'ORDER BY s.accessStartDate';
3755
        }
3756
3757
        // Order by position
3758
        if ($allowOrder) {
3759
            $order = 'ORDER BY s.position';
3760
        }
3761
3762
        // Order by dates according to settings
3763
        $orderBySettings = api_get_configuration_value('my_courses_session_order');
3764
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
3765
            $field = $orderBySettings['field'];
3766
            $orderSetting = $orderBySettings['order'];
3767
            switch ($field) {
3768
                case 'start_date':
3769
                    $order = " ORDER BY s.accessStartDate $orderSetting";
3770
                    break;
3771
                case 'end_date':
3772
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
3773
                    if ($orderSetting === 'asc') {
3774
                        // Put null values at the end
3775
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
3776
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
3777
                    }
3778
                    break;
3779
                case 'name':
3780
                    $order = " ORDER BY s.name $orderSetting ";
3781
                    break;
3782
            }
3783
        }
3784
3785
        $dqlStudent .= $order;
3786
        $dqlCoach .= $order;
3787
3788
        $accessUrlId = api_get_current_access_url_id();
3789
        $dqlStudent = Database::getManager()
3790
            ->createQuery($dqlStudent)
3791
            ->setParameters(
3792
                ['user' => $user_id, 'url' => $accessUrlId]
3793
            )
3794
        ;
3795
        $dqlCoach = Database::getManager()
3796
            ->createQuery($dqlCoach)
3797
            ->setParameters(
3798
                ['user' => $user_id, 'url' => $accessUrlId]
3799
            )
3800
        ;
3801
3802
        if ($getCount) {
3803
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
3804
        }
3805
3806
        $sessionDataStudent = $dqlStudent->getResult();
3807
        $sessionDataCoach = $dqlCoach->getResult();
3808
3809
        $sessionData = [];
3810
        // First fill $sessionData with student sessions
3811
        if (!empty($sessionDataStudent)) {
3812
            foreach ($sessionDataStudent as $row) {
3813
                $sessionData[$row['id']] = $row;
3814
            }
3815
        }
3816
3817
        // Overwrite session data of the user as a student with session data
3818
        // of the user as a coach.
3819
        // There shouldn't be such duplicate rows, but just in case...
3820
        if (!empty($sessionDataCoach)) {
3821
            foreach ($sessionDataCoach as $row) {
3822
                $sessionData[$row['id']] = $row;
3823
            }
3824
        }
3825
3826
        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
3827
        $extraField = new ExtraFieldValue('session');
3828
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
3829
3830
        if (empty($sessionData)) {
3831
            return [];
3832
        }
3833
3834
        $categories = [];
3835
        foreach ($sessionData as $row) {
3836
            $session_id = $row['id'];
3837
            $coachList = SessionManager::getCoachesBySession($session_id);
3838
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
3839
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
3840
            $courseList = self::get_courses_list_by_session($user_id, $session_id);
3841
3842
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
3843
3844
            // User portal filters:
3845
            if (false === $ignoreTimeLimit) {
3846
                if ($is_time_over) {
3847
                    // History
3848
                    if ($row['duration']) {
3849
                        if ($daysLeft >= 0) {
3850
                            continue;
3851
                        }
3852
                    } else {
3853
                        if (empty($row['access_end_date'])) {
3854
                            continue;
3855
                        } else {
3856
                            if ($row['access_end_date'] > $now) {
3857
                                continue;
3858
                            }
3859
                        }
3860
                    }
3861
                } else {
3862
                    // Current user portal
3863
                    $isGeneralCoach = SessionManager::user_is_general_coach($user_id, $row['id']);
3864
                    $isCoachOfCourse = in_array($user_id, $coachList);
3865
3866
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
3867
                        // Teachers can access the session depending in the access_coach date
3868
                    } else {
3869
                        if ($row['duration']) {
3870
                            if ($daysLeft <= 0) {
3871
                                continue;
3872
                            }
3873
                        } else {
3874
                            if (isset($row['access_end_date']) &&
3875
                                !empty($row['access_end_date'])
3876
                            ) {
3877
                                if ($row['access_end_date'] <= $now) {
3878
                                    continue;
3879
                                }
3880
                            }
3881
                        }
3882
                    }
3883
                }
3884
            }
3885
3886
            $categories[$row['session_category_id']]['session_category'] = [
3887
                'id' => $row['session_category_id'],
3888
                'name' => $row['session_category_name'],
3889
                'date_start' => $categoryStart,
3890
                'date_end' => $categoryEnd,
3891
            ];
3892
3893
            $visibility = api_get_session_visibility(
3894
                $session_id,
3895
                null,
3896
                $ignore_visibility_for_admins
3897
            );
3898
3899
            if ($visibility != SESSION_VISIBLE) {
3900
                // Course Coach session visibility.
3901
                $blockedCourseCount = 0;
3902
                $closedVisibilityList = [
3903
                    COURSE_VISIBILITY_CLOSED,
3904
                    COURSE_VISIBILITY_HIDDEN,
3905
                ];
3906
3907
                foreach ($courseList as $course) {
3908
                    // Checking session visibility
3909
                    $sessionCourseVisibility = api_get_session_visibility(
3910
                        $session_id,
3911
                        $course['real_id'],
3912
                        $ignore_visibility_for_admins
3913
                    );
3914
3915
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
3916
                    if ($courseIsVisible === false || $sessionCourseVisibility == SESSION_INVISIBLE) {
3917
                        $blockedCourseCount++;
3918
                    }
3919
                }
3920
3921
                // If all courses are blocked then no show in the list.
3922
                if ($blockedCourseCount === count($courseList)) {
3923
                    $visibility = SESSION_INVISIBLE;
3924
                } else {
3925
                    $visibility = $sessionCourseVisibility;
3926
                }
3927
            }
3928
3929
            switch ($visibility) {
3930
                case SESSION_VISIBLE_READ_ONLY:
3931
                case SESSION_VISIBLE:
3932
                case SESSION_AVAILABLE:
3933
                    break;
3934
                case SESSION_INVISIBLE:
3935
                    if ($ignore_visibility_for_admins === false) {
3936
                        continue 2;
3937
                    }
3938
            }
3939
3940
            $collapsed = '';
3941
            $collapsedAction = '';
3942
            if ($collapsable) {
3943
                $collapsableData = SessionManager::getCollapsableData(
3944
                    $user_id,
3945
                    $session_id,
3946
                    $extraField,
3947
                    $collapsableLink
3948
                );
3949
                $collapsed = $collapsableData['collapsed'];
3950
                $collapsedAction = $collapsableData['collapsable_link'];
3951
            }
3952
3953
            $categories[$row['session_category_id']]['sessions'][] = [
3954
                'session_name' => $row['name'],
3955
                'session_id' => $row['id'],
3956
                'access_start_date' => $row['access_start_date'] ? $row['access_start_date']->format('Y-m-d H:i:s') : null,
3957
                'access_end_date' => $row['access_end_date'] ? $row['access_end_date']->format('Y-m-d H:i:s') : null,
3958
                'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
3959
                'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
3960
                'courses' => $courseList,
3961
                'collapsed' => $collapsed,
3962
                'collapsable_link' => $collapsedAction,
3963
                'duration' => $row['duration'],
3964
            ];
3965
        }
3966
3967
        return $categories;
3968
    }
3969
3970
    /**
3971
     * Gives a list of [session_id-course_code] => [status] for the current user.
3972
     *
3973
     * @param int $user_id
3974
     * @param int $sessionLimit
3975
     *
3976
     * @return array list of statuses (session_id-course_code => status)
3977
     */
3978
    public static function get_personal_session_course_list($user_id, $sessionLimit = null)
3979
    {
3980
        // Database Table Definitions
3981
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3982
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
3983
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3984
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3985
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3986
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3987
3988
        $user_id = (int) $user_id;
3989
3990
        if (empty($user_id)) {
3991
            return [];
3992
        }
3993
3994
        // We filter the courses from the URL
3995
        $join_access_url = $where_access_url = '';
3996
        if (api_get_multiple_access_url()) {
3997
            $access_url_id = api_get_current_access_url_id();
3998
            if ($access_url_id != -1) {
3999
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
4000
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id";
4001
                $where_access_url = " AND access_url_id = $access_url_id ";
4002
            }
4003
        }
4004
4005
        // Courses in which we subscribed out of any session
4006
        $tbl_user_course_category = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
4007
4008
        $sql = "SELECT
4009
                    course.code,
4010
                    course_rel_user.status course_rel_status,
4011
                    course_rel_user.sort sort,
4012
                    course_rel_user.user_course_cat user_course_cat
4013
                 FROM $tbl_course_user course_rel_user
4014
                 LEFT JOIN $tbl_course course
4015
                 ON course.id = course_rel_user.c_id
4016
                 LEFT JOIN $tbl_user_course_category user_course_category
4017
                 ON course_rel_user.user_course_cat = user_course_category.id
4018
                 $join_access_url
4019
                 WHERE
4020
                    course_rel_user.user_id = '".$user_id."' AND
4021
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
4022
                    $where_access_url
4023
                 ORDER BY user_course_category.sort, course_rel_user.sort, course.title ASC";
4024
4025
        $course_list_sql_result = Database::query($sql);
4026
4027
        $personal_course_list = [];
4028
        if (Database::num_rows($course_list_sql_result) > 0) {
4029
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4030
                $course_info = api_get_course_info($result_row['code']);
4031
                $result_row['course_info'] = $course_info;
4032
                $personal_course_list[] = $result_row;
4033
            }
4034
        }
4035
4036
        $coachCourseConditions = '';
4037
        // Getting sessions that are related to a coach in the session_rel_course_rel_user table
4038
        if (api_is_allowed_to_create_course()) {
4039
            $sessionListFromCourseCoach = [];
4040
            $sql = " SELECT DISTINCT session_id
4041
                    FROM $tbl_session_course_user
4042
                    WHERE user_id = $user_id AND status = 2 ";
4043
4044
            $result = Database::query($sql);
4045
            if (Database::num_rows($result)) {
4046
                $result = Database::store_result($result);
4047
                foreach ($result as $session) {
4048
                    $sessionListFromCourseCoach[] = $session['session_id'];
4049
                }
4050
            }
4051
            if (!empty($sessionListFromCourseCoach)) {
4052
                $condition = implode("','", $sessionListFromCourseCoach);
4053
                $coachCourseConditions = " OR ( s.id IN ('$condition'))";
4054
            }
4055
        }
4056
4057
        // Get the list of sessions where the user is subscribed
4058
        // This is divided into two different queries
4059
        $sessions = [];
4060
        $sessionLimitRestriction = '';
4061
        if (!empty($sessionLimit)) {
4062
            $sessionLimit = (int) $sessionLimit;
4063
            $sessionLimitRestriction = "LIMIT $sessionLimit";
4064
        }
4065
4066
        $sql = "SELECT DISTINCT s.id, name, access_start_date, access_end_date
4067
                FROM $tbl_session_user su INNER JOIN $tbl_session s
4068
                ON (s.id = su.session_id)
4069
                WHERE (
4070
                    su.user_id = $user_id AND
4071
                    su.relation_type <> ".SESSION_RELATION_TYPE_RRHH."
4072
                )
4073
                $coachCourseConditions
4074
                ORDER BY access_start_date, access_end_date, name
4075
                $sessionLimitRestriction
4076
        ";
4077
4078
        $result = Database::query($sql);
4079
        if (Database::num_rows($result) > 0) {
4080
            while ($row = Database::fetch_assoc($result)) {
4081
                $sessions[$row['id']] = $row;
4082
            }
4083
        }
4084
4085
        $sql = "SELECT DISTINCT
4086
                id, name, access_start_date, access_end_date
4087
                FROM $tbl_session s
4088
                WHERE (
4089
                    id_coach = $user_id
4090
                )
4091
                $coachCourseConditions
4092
                ORDER BY access_start_date, access_end_date, name";
4093
4094
        $result = Database::query($sql);
4095
        if (Database::num_rows($result) > 0) {
4096
            while ($row = Database::fetch_assoc($result)) {
4097
                if (empty($sessions[$row['id']])) {
4098
                    $sessions[$row['id']] = $row;
4099
                }
4100
            }
4101
        }
4102
4103
        if (api_is_allowed_to_create_course()) {
4104
            foreach ($sessions as $enreg) {
4105
                $session_id = $enreg['id'];
4106
                $session_visibility = api_get_session_visibility($session_id);
4107
4108
                if ($session_visibility == SESSION_INVISIBLE) {
4109
                    continue;
4110
                }
4111
4112
                // This query is horribly slow when more than a few thousand
4113
                // users and just a few sessions to which they are subscribed
4114
                $sql = "SELECT DISTINCT
4115
                        course.code code,
4116
                        course.title i,
4117
                        ".(api_is_western_name_order() ? "CONCAT(user.firstname,' ',user.lastname)" : "CONCAT(user.lastname,' ',user.firstname)")." t,
4118
                        email, course.course_language l,
4119
                        1 sort,
4120
                        category_code user_course_cat,
4121
                        access_start_date,
4122
                        access_end_date,
4123
                        session.id as session_id,
4124
                        session.name as session_name
4125
                    FROM $tbl_session_course_user as session_course_user
4126
                    INNER JOIN $tbl_course AS course
4127
                        ON course.id = session_course_user.c_id
4128
                    INNER JOIN $tbl_session as session
4129
                        ON session.id = session_course_user.session_id
4130
                    LEFT JOIN $tbl_user as user
4131
                        ON user.id = session_course_user.user_id OR session.id_coach = user.id
4132
                    WHERE
4133
                        session_course_user.session_id = $session_id AND (
4134
                            (session_course_user.user_id = $user_id AND session_course_user.status = 2)
4135
                            OR session.id_coach = $user_id
4136
                        )
4137
                    ORDER BY i";
4138
                $course_list_sql_result = Database::query($sql);
4139
                while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4140
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
4141
                    $key = $result_row['session_id'].' - '.$result_row['code'];
4142
                    $personal_course_list[$key] = $result_row;
4143
                }
4144
            }
4145
        }
4146
4147
        foreach ($sessions as $enreg) {
4148
            $session_id = $enreg['id'];
4149
            $session_visibility = api_get_session_visibility($session_id);
4150
            if ($session_visibility == SESSION_INVISIBLE) {
4151
                continue;
4152
            }
4153
4154
            /* This query is very similar to the above query,
4155
               but it will check the session_rel_course_user table if there are courses registered to our user or not */
4156
            $sql = "SELECT DISTINCT
4157
                course.code code,
4158
                course.title i, CONCAT(user.lastname,' ',user.firstname) t,
4159
                email,
4160
                course.course_language l,
4161
                1 sort,
4162
                category_code user_course_cat,
4163
                access_start_date,
4164
                access_end_date,
4165
                session.id as session_id,
4166
                session.name as session_name,
4167
                IF((session_course_user.user_id = 3 AND session_course_user.status=2),'2', '5')
4168
            FROM $tbl_session_course_user as session_course_user
4169
            INNER JOIN $tbl_course AS course
4170
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
4171
            INNER JOIN $tbl_session as session
4172
            ON session_course_user.session_id = session.id
4173
            LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
4174
            WHERE session_course_user.user_id = $user_id
4175
            ORDER BY i";
4176
4177
            $course_list_sql_result = Database::query($sql);
4178
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4179
                $result_row['course_info'] = api_get_course_info($result_row['code']);
4180
                $key = $result_row['session_id'].' - '.$result_row['code'];
4181
                if (!isset($personal_course_list[$key])) {
4182
                    $personal_course_list[$key] = $result_row;
4183
                }
4184
            }
4185
        }
4186
4187
        return $personal_course_list;
4188
    }
4189
4190
    /**
4191
     * Gives a list of courses for the given user in the given session.
4192
     *
4193
     * @param int $user_id
4194
     * @param int $session_id
4195
     *
4196
     * @return array list of statuses (session_id-course_code => status)
4197
     */
4198
    public static function get_courses_list_by_session($user_id, $session_id)
4199
    {
4200
        // Database Table Definitions
4201
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4202
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
4203
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4204
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
4205
4206
        $user_id = (int) $user_id;
4207
        $session_id = (int) $session_id;
4208
        // We filter the courses from the URL
4209
        $join_access_url = $where_access_url = '';
4210
        if (api_get_multiple_access_url()) {
4211
            $urlId = api_get_current_access_url_id();
4212
            if (-1 != $urlId) {
4213
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4214
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
4215
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
4216
            }
4217
        }
4218
4219
        $exlearnerCondition = "";
4220
        if (false !== api_get_configuration_value('user_edition_extra_field_to_check')) {
4221
            $exlearnerCondition = " AND scu.status NOT IN(".COURSE_EXLEARNER.")";
4222
        }
4223
4224
        /* This query is very similar to the query below, but it will check the
4225
        session_rel_course_user table if there are courses registered
4226
        to our user or not */
4227
        $sql = "SELECT DISTINCT
4228
                    c.title,
4229
                    c.visibility,
4230
                    c.id as real_id,
4231
                    c.code as course_code,
4232
                    c.course_language,
4233
                    sc.position,
4234
                    c.unsubscribe
4235
                FROM $tbl_session_course_user as scu
4236
                INNER JOIN $tbl_session_course sc
4237
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
4238
                INNER JOIN $tableCourse as c
4239
                ON (scu.c_id = c.id)
4240
                $join_access_url
4241
                WHERE
4242
                    scu.user_id = $user_id AND
4243
                    scu.session_id = $session_id
4244
                    $where_access_url
4245
                    $exlearnerCondition
4246
                ORDER BY sc.position ASC";
4247
4248
        $myCourseList = [];
4249
        $courses = [];
4250
        $result = Database::query($sql);
4251
        if (Database::num_rows($result) > 0) {
4252
            while ($result_row = Database::fetch_array($result, 'ASSOC')) {
4253
                $result_row['status'] = 5;
4254
                if (!in_array($result_row['real_id'], $courses)) {
4255
                    $position = $result_row['position'];
4256
                    if (!isset($myCourseList[$position])) {
4257
                        $myCourseList[$position] = $result_row;
4258
                    } else {
4259
                        $myCourseList[] = $result_row;
4260
                    }
4261
                    $courses[] = $result_row['real_id'];
4262
                }
4263
            }
4264
        }
4265
4266
        if (api_is_allowed_to_create_course()) {
4267
            $sql = "SELECT DISTINCT
4268
                        c.title,
4269
                        c.visibility,
4270
                        c.id as real_id,
4271
                        c.code as course_code,
4272
                        c.course_language,
4273
                        sc.position,
4274
                        c.unsubscribe
4275
                    FROM $tbl_session_course_user as scu
4276
                    INNER JOIN $tbl_session as s
4277
                    ON (scu.session_id = s.id)
4278
                    INNER JOIN $tbl_session_course sc
4279
                    ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
4280
                    INNER JOIN $tableCourse as c
4281
                    ON (scu.c_id = c.id)
4282
                    $join_access_url
4283
                    WHERE
4284
                      s.id = $session_id AND
4285
                      (
4286
                        (scu.user_id = $user_id AND scu.status = 2) OR
4287
                        s.id_coach = $user_id
4288
                      )
4289
                    $where_access_url
4290
                    $exlearnerCondition
4291
                    ORDER BY sc.position ASC";
4292
            $result = Database::query($sql);
4293
4294
            if (Database::num_rows($result) > 0) {
4295
                while ($result_row = Database::fetch_array($result, 'ASSOC')) {
4296
                    $result_row['status'] = 2;
4297
                    if (!in_array($result_row['real_id'], $courses)) {
4298
                        $position = $result_row['position'];
4299
                        if (!isset($myCourseList[$position])) {
4300
                            $myCourseList[$position] = $result_row;
4301
                        } else {
4302
                            $myCourseList[] = $result_row;
4303
                        }
4304
                        $courses[] = $result_row['real_id'];
4305
                    }
4306
                }
4307
            }
4308
        }
4309
4310
        if (api_is_drh()) {
4311
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
4312
            $sessionList = array_keys($sessionList);
4313
            if (in_array($session_id, $sessionList)) {
4314
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
4315
                if (!empty($courseList)) {
4316
                    foreach ($courseList as $course) {
4317
                        if (!in_array($course['id'], $courses)) {
4318
                            $position = $course['position'];
4319
                            if (!isset($myCourseList[$position])) {
4320
                                $myCourseList[$position] = $course;
4321
                            } else {
4322
                                $myCourseList[] = $course;
4323
                            }
4324
                        }
4325
                    }
4326
                }
4327
            }
4328
        } else {
4329
            //check if user is general coach for this session
4330
            $sessionInfo = api_get_session_info($session_id);
4331
            if ($sessionInfo['id_coach'] == $user_id) {
4332
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
4333
                if (!empty($courseList)) {
4334
                    foreach ($courseList as $course) {
4335
                        if (!in_array($course['id'], $courses)) {
4336
                            $position = $course['position'];
4337
                            if (!isset($myCourseList[$position])) {
4338
                                $myCourseList[$position] = $course;
4339
                            } else {
4340
                                $myCourseList[] = $course;
4341
                            }
4342
                        }
4343
                    }
4344
                }
4345
            }
4346
        }
4347
4348
        if (!empty($myCourseList)) {
4349
            ksort($myCourseList);
4350
            $checkPosition = array_filter(array_column($myCourseList, 'position'));
4351
            if (empty($checkPosition)) {
4352
                // The session course list doesn't have any position,
4353
                // then order the course list by course code.
4354
                $orderByCode = array_column($myCourseList, 'course_code');
4355
                sort($orderByCode, SORT_NATURAL);
4356
                $newCourseList = [];
4357
                foreach ($orderByCode as $code) {
4358
                    foreach ($myCourseList as $course) {
4359
                        if ($code === $course['course_code']) {
4360
                            $newCourseList[] = $course;
4361
                            break;
4362
                        }
4363
                    }
4364
                }
4365
                $myCourseList = $newCourseList;
4366
            }
4367
        }
4368
4369
        return $myCourseList;
4370
    }
4371
4372
    /**
4373
     * Get user id from a username.
4374
     *
4375
     * @param string $username
4376
     *
4377
     * @return int User ID (or false if not found)
4378
     */
4379
    public static function get_user_id_from_username($username)
4380
    {
4381
        if (empty($username)) {
4382
            return false;
4383
        }
4384
        $username = trim($username);
4385
        $username = Database::escape_string($username);
4386
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
4387
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
4388
        $res = Database::query($sql);
4389
4390
        if ($res === false) {
4391
            return false;
4392
        }
4393
        if (Database::num_rows($res) !== 1) {
4394
            return false;
4395
        }
4396
        $row = Database::fetch_array($res);
4397
4398
        return $row['id'];
4399
    }
4400
4401
    /**
4402
     * Get the users files upload from his share_folder.
4403
     *
4404
     * @param string $user_id      User ID
4405
     * @param string $course       course directory
4406
     * @param string $resourceType resource type: images, all
4407
     *
4408
     * @return string
4409
     */
4410
    public static function get_user_upload_files_by_course(
4411
        $user_id,
4412
        $course,
4413
        $resourceType = 'all'
4414
    ) {
4415
        $return = '';
4416
        $user_id = (int) $user_id;
4417
4418
        if (!empty($user_id) && !empty($course)) {
4419
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
4420
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
4421
            $file_list = [];
4422
4423
            if (is_dir($path)) {
4424
                $handle = opendir($path);
4425
                while ($file = readdir($handle)) {
4426
                    if ($file == '.' || $file == '..' || $file == '.htaccess' || is_dir($path.$file)) {
4427
                        continue; // skip current/parent directory and .htaccess
4428
                    }
4429
                    $file_list[] = $file;
4430
                }
4431
                if (count($file_list) > 0) {
4432
                    $return = "<h4>$course</h4>";
4433
                    $return .= '<ul class="thumbnails">';
4434
                }
4435
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
4436
                foreach ($file_list as $file) {
4437
                    if ($resourceType == 'all') {
4438
                        $return .= '<li>
4439
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
4440
                    } elseif ($resourceType == 'images') {
4441
                        //get extension
4442
                        $ext = explode('.', $file);
4443
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
4444
                            $return .= '<li class="span2">
4445
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
4446
                                                <img src="'.$web_path.urlencode($file).'" >
4447
                                            </a>
4448
                                        </li>';
4449
                        }
4450
                    }
4451
                }
4452
                if (count($file_list) > 0) {
4453
                    $return .= '</ul>';
4454
                }
4455
            }
4456
        }
4457
4458
        return $return;
4459
    }
4460
4461
    /**
4462
     * Gets the API key (or keys) and return them into an array.
4463
     *
4464
     * @param int     Optional user id (defaults to the result of api_get_user_id())
4465
     * @param string $api_service
4466
     *
4467
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
4468
     */
4469
    public static function get_api_keys($user_id = null, $api_service = 'dokeos')
4470
    {
4471
        if ($user_id != strval(intval($user_id))) {
4472
            return false;
4473
        }
4474
        if (empty($user_id)) {
4475
            $user_id = api_get_user_id();
4476
        }
4477
        if ($user_id === false) {
4478
            return false;
4479
        }
4480
        $service_name = Database::escape_string($api_service);
4481
        if (is_string($service_name) === false) {
4482
            return false;
4483
        }
4484
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4485
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
4486
        $res = Database::query($sql);
4487
        if ($res === false) {
4488
            return false;
4489
        } //error during query
4490
        $num = Database::num_rows($res);
4491
        if ($num == 0) {
4492
            return false;
4493
        }
4494
        $list = [];
4495
        while ($row = Database::fetch_array($res)) {
4496
            $list[$row['id']] = $row['api_key'];
4497
        }
4498
4499
        return $list;
4500
    }
4501
4502
    /**
4503
     * Adds a new API key to the users' account.
4504
     *
4505
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
4506
     * @param string $api_service
4507
     *
4508
     * @return bool True on success, false on failure
4509
     */
4510
    public static function add_api_key($user_id = null, $api_service = 'dokeos')
4511
    {
4512
        if ($user_id != strval(intval($user_id))) {
4513
            return false;
4514
        }
4515
        if (empty($user_id)) {
4516
            $user_id = api_get_user_id();
4517
        }
4518
        if ($user_id === false) {
4519
            return false;
4520
        }
4521
        $service_name = Database::escape_string($api_service);
4522
        if (is_string($service_name) === false) {
4523
            return false;
4524
        }
4525
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4526
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
4527
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
4528
        $res = Database::query($sql);
4529
        if ($res === false) {
4530
            return false;
4531
        } //error during query
4532
        $num = Database::insert_id();
4533
4534
        return $num == 0 ? false : $num;
4535
    }
4536
4537
    /**
4538
     * Deletes an API key from the user's account.
4539
     *
4540
     * @param   int     API key's internal ID
4541
     *
4542
     * @return bool True on success, false on failure
4543
     */
4544
    public static function delete_api_key($key_id)
4545
    {
4546
        if ($key_id != strval(intval($key_id))) {
4547
            return false;
4548
        }
4549
        if ($key_id === false) {
4550
            return false;
4551
        }
4552
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4553
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
4554
        $res = Database::query($sql);
4555
        if ($res === false) {
4556
            return false;
4557
        } //error during query
4558
        $num = Database::num_rows($res);
4559
        if ($num !== 1) {
4560
            return false;
4561
        }
4562
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
4563
        $res = Database::query($sql);
4564
        if ($res === false) {
4565
            return false;
4566
        } //error during query
4567
4568
        return true;
4569
    }
4570
4571
    /**
4572
     * Regenerate an API key from the user's account.
4573
     *
4574
     * @param   int     user ID (defaults to the results of api_get_user_id())
4575
     * @param   string  API key's internal ID
4576
     *
4577
     * @return int num
4578
     */
4579
    public static function update_api_key($user_id, $api_service)
4580
    {
4581
        if ($user_id != strval(intval($user_id))) {
4582
            return false;
4583
        }
4584
        if ($user_id === false) {
4585
            return false;
4586
        }
4587
        $service_name = Database::escape_string($api_service);
4588
        if (is_string($service_name) === false) {
4589
            return false;
4590
        }
4591
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4592
        $sql = "SELECT id FROM $t_api
4593
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
4594
        $res = Database::query($sql);
4595
        $num = Database::num_rows($res);
4596
        if ($num == 1) {
4597
            $id_key = Database::fetch_array($res, 'ASSOC');
4598
            self::delete_api_key($id_key['id']);
4599
            $num = self::add_api_key($user_id, $api_service);
4600
        } elseif ($num == 0) {
4601
            $num = self::add_api_key($user_id, $api_service);
4602
        }
4603
4604
        return $num;
4605
    }
4606
4607
    /**
4608
     * @param   int     user ID (defaults to the results of api_get_user_id())
4609
     * @param   string    API key's internal ID
4610
     *
4611
     * @return int row ID, or return false if not found
4612
     */
4613
    public static function get_api_key_id($user_id, $api_service)
4614
    {
4615
        if ($user_id != strval(intval($user_id))) {
4616
            return false;
4617
        }
4618
        if ($user_id === false) {
4619
            return false;
4620
        }
4621
        if (empty($api_service)) {
4622
            return false;
4623
        }
4624
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4625
        $api_service = Database::escape_string($api_service);
4626
        $sql = "SELECT id FROM $t_api
4627
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
4628
        $res = Database::query($sql);
4629
        if (Database::num_rows($res) < 1) {
4630
            return false;
4631
        }
4632
        $row = Database::fetch_array($res, 'ASSOC');
4633
4634
        return $row['id'];
4635
    }
4636
4637
    /**
4638
     * Checks if a user_id is platform admin.
4639
     *
4640
     * @param   int user ID
4641
     *
4642
     * @return bool True if is admin, false otherwise
4643
     *
4644
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
4645
     */
4646
    public static function is_admin($user_id)
4647
    {
4648
        $user_id = (int) $user_id;
4649
        if (empty($user_id)) {
4650
            return false;
4651
        }
4652
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
4653
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
4654
        $res = Database::query($sql);
4655
4656
        return Database::num_rows($res) === 1;
4657
    }
4658
4659
    /**
4660
     * Get the total count of users.
4661
     *
4662
     * @param int|null $status        Status of users to be counted
4663
     * @param int|null $access_url_id Access URL ID (optional)
4664
     *
4665
     * @return mixed Number of users or false on error
4666
     */
4667
    public static function get_number_of_users(
4668
        int $status = null,
4669
        int $access_url_id = null,
4670
        int $active = null,
4671
        string $dateFrom = null,
4672
        string $dateUntil = null
4673
    ) {
4674
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
4675
        $tableAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4676
4677
        if (empty($access_url_id)) {
4678
            $access_url_id = api_get_current_access_url_id();
4679
        }
4680
4681
        if (api_is_multiple_url_enabled()) {
4682
            $sql = "SELECT count(u.id)
4683
                    FROM $tableUser u
4684
                    INNER JOIN $tableAccessUrlRelUser url_user
4685
                    ON (u.id = url_user.user_id)
4686
                    WHERE url_user.access_url_id = $access_url_id
4687
            ";
4688
        } else {
4689
            $sql = "SELECT count(u.id)
4690
                    FROM $tableUser u
4691
                    WHERE 1 = 1 ";
4692
        }
4693
4694
        if (is_int($status) && $status > 0) {
4695
            $status = (int) $status;
4696
            $sql .= " AND u.status = $status ";
4697
        }
4698
4699
        if (isset($active)) {
4700
            $active = (int) $active;
4701
            $sql .= " AND u.active = $active ";
4702
        }
4703
4704
        if (!empty($dateFrom)) {
4705
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
4706
            $sql .= " AND u.registration_date >= '$dateFrom' ";
4707
        }
4708
4709
        if (!empty($dateUntil)) {
4710
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
4711
            $sql .= " AND u.registration_date <= '$dateUntil' ";
4712
        }
4713
4714
        $res = Database::query($sql);
4715
        if (Database::num_rows($res) === 1) {
4716
            return (int) Database::result($res, 0, 0);
4717
        }
4718
4719
        return false;
4720
    }
4721
4722
    /**
4723
     * Gets the tags of a specific field_id
4724
     * USER TAGS.
4725
     *
4726
     * Instructions to create a new user tag by Julio Montoya <[email protected]>
4727
     *
4728
     * 1. Create a new extra field in main/admin/user_fields.php with the "TAG" field type make it available and visible.
4729
     *    Called it "books" for example.
4730
     * 2. Go to profile main/auth/profile.php There you will see a special input (facebook style) that will show suggestions of tags.
4731
     * 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
4732
     * 4. Tags are independent this means that tags can't be shared between tags + book + hobbies.
4733
     * 5. Test and enjoy.
4734
     *
4735
     * @param string $tag
4736
     * @param int    $field_id      field_id
4737
     * @param string $return_format how we are going to result value in array or in a string (json)
4738
     * @param $limit
4739
     *
4740
     * @return mixed
4741
     */
4742
    public static function get_tags($tag, $field_id, $return_format = 'json', $limit = 10)
4743
    {
4744
        // database table definition
4745
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4746
        $field_id = (int) $field_id;
4747
        $limit = (int) $limit;
4748
        $tag = trim(Database::escape_string($tag));
4749
4750
        // all the information of the field
4751
        $sql = "SELECT DISTINCT id, tag from $table_user_tag
4752
                WHERE field_id = $field_id AND tag LIKE '$tag%'
4753
                ORDER BY tag
4754
                LIMIT $limit";
4755
        $result = Database::query($sql);
4756
        $return = [];
4757
        if (Database::num_rows($result) > 0) {
4758
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4759
                $return[] = ['id' => $row['tag'], 'text' => $row['tag']];
4760
            }
4761
        }
4762
        if ($return_format === 'json') {
4763
            $return = json_encode($return);
4764
        }
4765
4766
        return $return;
4767
    }
4768
4769
    /**
4770
     * @param int $field_id
4771
     * @param int $limit
4772
     *
4773
     * @return array
4774
     */
4775
    public static function get_top_tags($field_id, $limit = 100)
4776
    {
4777
        // database table definition
4778
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4779
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4780
        $field_id = (int) $field_id;
4781
        $limit = (int) $limit;
4782
        // all the information of the field
4783
        $sql = "SELECT count(*) count, tag FROM $table_user_tag_values  uv
4784
                INNER JOIN $table_user_tag ut
4785
                ON (ut.id = uv.tag_id)
4786
                WHERE field_id = $field_id
4787
                GROUP BY tag_id
4788
                ORDER BY count DESC
4789
                LIMIT $limit";
4790
        $result = Database::query($sql);
4791
        $return = [];
4792
        if (Database::num_rows($result) > 0) {
4793
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4794
                $return[] = $row;
4795
            }
4796
        }
4797
4798
        return $return;
4799
    }
4800
4801
    /**
4802
     * Get user's tags.
4803
     *
4804
     * @param int $user_id
4805
     * @param int $field_id
4806
     *
4807
     * @return array
4808
     */
4809
    public static function get_user_tags($user_id, $field_id)
4810
    {
4811
        // database table definition
4812
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4813
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4814
        $field_id = (int) $field_id;
4815
        $user_id = (int) $user_id;
4816
4817
        // all the information of the field
4818
        $sql = "SELECT ut.id, tag, count
4819
                FROM $table_user_tag ut
4820
                INNER JOIN $table_user_tag_values uv
4821
                ON (uv.tag_id=ut.ID)
4822
                WHERE field_id = $field_id AND user_id = $user_id
4823
                ORDER BY tag";
4824
        $result = Database::query($sql);
4825
        $return = [];
4826
        if (Database::num_rows($result) > 0) {
4827
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4828
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4829
            }
4830
        }
4831
4832
        return $return;
4833
    }
4834
4835
    /**
4836
     * Get user's tags.
4837
     *
4838
     * @param int  $user_id
4839
     * @param int  $field_id
4840
     * @param bool $show_links show links or not
4841
     *
4842
     * @return string
4843
     */
4844
    public static function get_user_tags_to_string($user_id, $field_id, $show_links = true)
4845
    {
4846
        // database table definition
4847
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4848
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4849
        $field_id = (int) $field_id;
4850
        $user_id = (int) $user_id;
4851
4852
        // all the information of the field
4853
        $sql = "SELECT ut.id, tag,count FROM $table_user_tag ut
4854
                INNER JOIN $table_user_tag_values uv
4855
                ON (uv.tag_id = ut.id)
4856
                WHERE field_id = $field_id AND user_id = $user_id
4857
                ORDER BY tag";
4858
4859
        $result = Database::query($sql);
4860
        $return = [];
4861
        if (Database::num_rows($result) > 0) {
4862
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4863
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4864
            }
4865
        }
4866
        $user_tags = $return;
4867
        $tag_tmp = [];
4868
        foreach ($user_tags as $tag) {
4869
            if ($show_links) {
4870
                $tag_tmp[] = '<a href="'.api_get_path(WEB_PATH).'main/search/index.php?q='.$tag['tag'].'">'.
4871
                    $tag['tag'].
4872
                '</a>';
4873
            } else {
4874
                $tag_tmp[] = $tag['tag'];
4875
            }
4876
        }
4877
4878
        if (is_array($user_tags) && count($user_tags) > 0) {
4879
            return implode(', ', $tag_tmp);
4880
        } else {
4881
            return '';
4882
        }
4883
    }
4884
4885
    /**
4886
     * Get the tag id.
4887
     *
4888
     * @param int $tag
4889
     * @param int $field_id
4890
     *
4891
     * @return int returns 0 if fails otherwise the tag id
4892
     */
4893
    public static function get_tag_id($tag, $field_id)
4894
    {
4895
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4896
        $tag = Database::escape_string($tag);
4897
        $field_id = (int) $field_id;
4898
        //with COLLATE latin1_bin to select query in a case sensitive mode
4899
        $sql = "SELECT id FROM $table_user_tag
4900
                WHERE tag LIKE '$tag' AND field_id = $field_id";
4901
        $result = Database::query($sql);
4902
        if (Database::num_rows($result) > 0) {
4903
            $row = Database::fetch_array($result, 'ASSOC');
4904
4905
            return $row['id'];
4906
        } else {
4907
            return 0;
4908
        }
4909
    }
4910
4911
    /**
4912
     * Get the tag id.
4913
     *
4914
     * @param int $tag_id
4915
     * @param int $field_id
4916
     *
4917
     * @return int 0 if fails otherwise the tag id
4918
     */
4919
    public static function get_tag_id_from_id($tag_id, $field_id)
4920
    {
4921
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4922
        $tag_id = (int) $tag_id;
4923
        $field_id = (int) $field_id;
4924
        $sql = "SELECT id FROM $table_user_tag
4925
                WHERE id = '$tag_id' AND field_id = $field_id";
4926
        $result = Database::query($sql);
4927
        if (Database::num_rows($result) > 0) {
4928
            $row = Database::fetch_array($result, 'ASSOC');
4929
4930
            return $row['id'];
4931
        } else {
4932
            return false;
4933
        }
4934
    }
4935
4936
    /**
4937
     * Adds a user-tag value.
4938
     *
4939
     * @param mixed $tag
4940
     * @param int   $user_id
4941
     * @param int   $field_id field id of the tag
4942
     *
4943
     * @return bool True if the tag was inserted or updated. False otherwise.
4944
     *              The return value doesn't take into account *values* added to the tag.
4945
     *              Only the creation/update of the tag field itself.
4946
     */
4947
    public static function add_tag($tag, $user_id, $field_id)
4948
    {
4949
        // database table definition
4950
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4951
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4952
        $tag = trim(Database::escape_string($tag));
4953
        $user_id = (int) $user_id;
4954
        $field_id = (int) $field_id;
4955
4956
        $tag_id = self::get_tag_id($tag, $field_id);
4957
4958
        /* IMPORTANT
4959
         *  @todo we don't create tags with numbers
4960
         *
4961
         */
4962
        if (is_numeric($tag)) {
4963
            //the form is sending an id this means that the user select it from the list so it MUST exists
4964
            /* $new_tag_id = self::get_tag_id_from_id($tag,$field_id);
4965
              if ($new_tag_id !== false) {
4966
              $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $new_tag_id";
4967
              $result = Database::query($sql);
4968
              $last_insert_id = $new_tag_id;
4969
              } else {
4970
              $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4971
              $result = Database::query($sql);
4972
              $last_insert_id = Database::insert_id();
4973
              } */
4974
        }
4975
4976
        //this is a new tag
4977
        if ($tag_id == 0) {
4978
            //the tag doesn't exist
4979
            $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4980
            Database::query($sql);
4981
            $last_insert_id = Database::insert_id();
4982
        } else {
4983
            //the tag exists we update it
4984
            $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $tag_id";
4985
            Database::query($sql);
4986
            $last_insert_id = $tag_id;
4987
        }
4988
4989
        if (!empty($last_insert_id) && ($last_insert_id != 0)) {
4990
            //we insert the relationship user-tag
4991
            $sql = "SELECT tag_id FROM $table_user_tag_values
4992
                    WHERE user_id = $user_id AND tag_id = $last_insert_id ";
4993
            $result = Database::query($sql);
4994
            //if the relationship does not exist we create it
4995
            if (Database::num_rows($result) == 0) {
4996
                $sql = "INSERT INTO $table_user_tag_values SET user_id = $user_id, tag_id = $last_insert_id";
4997
                Database::query($sql);
4998
            }
4999
5000
            return true;
5001
        }
5002
5003
        return false;
5004
    }
5005
5006
    /**
5007
     * Deletes an user tag.
5008
     *
5009
     * @param int $user_id
5010
     * @param int $field_id
5011
     */
5012
    public static function delete_user_tags($user_id, $field_id)
5013
    {
5014
        // database table definition
5015
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
5016
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
5017
        $user_id = (int) $user_id;
5018
5019
        $tags = self::get_user_tags($user_id, $field_id);
5020
        if (is_array($tags) && count($tags) > 0) {
5021
            foreach ($tags as $key => $tag) {
5022
                if ($tag['count'] > '0') {
5023
                    $sql = "UPDATE $table_user_tag SET count = count - 1  WHERE id = $key ";
5024
                    Database::query($sql);
5025
                }
5026
                $sql = "DELETE FROM $table_user_tag_values
5027
                        WHERE user_id = $user_id AND tag_id = $key";
5028
                Database::query($sql);
5029
            }
5030
        }
5031
    }
5032
5033
    /**
5034
     * Process the tag list comes from the UserManager::update_extra_field_value() function.
5035
     *
5036
     * @param array $tags     the tag list that will be added
5037
     * @param int   $user_id
5038
     * @param int   $field_id
5039
     *
5040
     * @return bool
5041
     */
5042
    public static function process_tags($tags, $user_id, $field_id)
5043
    {
5044
        // We loop the tags and add it to the DB
5045
        if (is_array($tags)) {
5046
            foreach ($tags as $tag) {
5047
                self::add_tag($tag, $user_id, $field_id);
5048
            }
5049
        } else {
5050
            self::add_tag($tags, $user_id, $field_id);
5051
        }
5052
5053
        return true;
5054
    }
5055
5056
    /**
5057
     * Returns a list of all administrators.
5058
     *
5059
     * @return array
5060
     */
5061
    public static function get_all_administrators()
5062
    {
5063
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
5064
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
5065
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5066
        $access_url_id = api_get_current_access_url_id();
5067
        if (api_get_multiple_access_url()) {
5068
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
5069
                    FROM $tbl_url_rel_user as url
5070
                    INNER JOIN $table_admin as admin
5071
                    ON (admin.user_id=url.user_id)
5072
                    INNER JOIN $table_user u
5073
                    ON (u.id=admin.user_id)
5074
                    WHERE access_url_id ='".$access_url_id."'";
5075
        } else {
5076
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
5077
                    FROM $table_admin as admin
5078
                    INNER JOIN $table_user u
5079
                    ON (u.id=admin.user_id)";
5080
        }
5081
        $result = Database::query($sql);
5082
        $return = [];
5083
        if (Database::num_rows($result) > 0) {
5084
            while ($row = Database::fetch_array($result, 'ASSOC')) {
5085
                $return[$row['user_id']] = $row;
5086
            }
5087
        }
5088
5089
        return $return;
5090
    }
5091
5092
    /**
5093
     * Search an user (tags, first name, last name and email ).
5094
     *
5095
     * @param string $tag
5096
     * @param int    $field_id        field id of the tag
5097
     * @param int    $from            where to start in the query
5098
     * @param int    $number_of_items
5099
     * @param bool   $getCount        get count or not
5100
     *
5101
     * @return array
5102
     */
5103
    public static function get_all_user_tags(
5104
        $tag,
5105
        $field_id = 0,
5106
        $from = 0,
5107
        $number_of_items = 10,
5108
        $getCount = false
5109
    ) {
5110
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
5111
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
5112
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
5113
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5114
5115
        $field_id = intval($field_id);
5116
        $from = intval($from);
5117
        $number_of_items = intval($number_of_items);
5118
5119
        $where_field = "";
5120
        $where_extra_fields = self::get_search_form_where_extra_fields();
5121
        if ($field_id != 0) {
5122
            $where_field = " field_id = $field_id AND ";
5123
        }
5124
5125
        // all the information of the field
5126
        if ($getCount) {
5127
            $select = "SELECT count(DISTINCT u.id) count";
5128
        } else {
5129
            $select = "SELECT DISTINCT u.id, u.username, firstname, lastname, email, picture_uri";
5130
        }
5131
5132
        $sql = " $select
5133
                FROM $user_table u
5134
                INNER JOIN $access_url_rel_user_table url_rel_user
5135
                ON (u.id = url_rel_user.user_id)
5136
                LEFT JOIN $table_user_tag_values uv
5137
                ON (u.id AND uv.user_id AND uv.user_id = url_rel_user.user_id)
5138
                LEFT JOIN $table_user_tag ut ON (uv.tag_id = ut.id)
5139
                WHERE
5140
                    ($where_field tag LIKE '".Database::escape_string($tag."%")."') OR
5141
                    (
5142
                        u.firstname LIKE '".Database::escape_string("%".$tag."%")."' OR
5143
                        u.lastname LIKE '".Database::escape_string("%".$tag."%")."' OR
5144
                        u.username LIKE '".Database::escape_string("%".$tag."%")."' OR
5145
                        concat(u.firstname, ' ', u.lastname) LIKE '".Database::escape_string("%".$tag."%")."' OR
5146
                        concat(u.lastname, ' ', u.firstname) LIKE '".Database::escape_string("%".$tag."%")."'
5147
                     )
5148
                     ".(!empty($where_extra_fields) ? $where_extra_fields : '')."
5149
                     AND url_rel_user.access_url_id=".api_get_current_access_url_id();
5150
5151
        $keyword_active = true;
5152
        // only active users
5153
        if ($keyword_active) {
5154
            $sql .= " AND u.active='1'";
5155
        }
5156
        // avoid anonymous
5157
        $sql .= " AND u.status <> 6 ";
5158
        $sql .= " ORDER BY username";
5159
        $sql .= " LIMIT $from , $number_of_items";
5160
5161
        $result = Database::query($sql);
5162
        $return = [];
5163
5164
        if (Database::num_rows($result) > 0) {
5165
            if ($getCount) {
5166
                $row = Database::fetch_array($result, 'ASSOC');
5167
5168
                return $row['count'];
5169
            }
5170
            while ($row = Database::fetch_array($result, 'ASSOC')) {
5171
                $return[$row['id']] = $row;
5172
            }
5173
        }
5174
5175
        return $return;
5176
    }
5177
5178
    /**
5179
     * Get extra filterable user fields (only type select).
5180
     *
5181
     * @return array Array of extra fields as [int => ['name' => ..., 'variable' => ..., 'data' => ...]] (
5182
     *               or empty array if no extra field)
5183
     */
5184
    public static function getExtraFilterableFields()
5185
    {
5186
        $extraFieldList = self::get_extra_fields();
5187
        $fields = [];
5188
        if (is_array($extraFieldList)) {
5189
            foreach ($extraFieldList as $extraField) {
5190
                // If is enabled to filter and is a "<select>" field type
5191
                if ($extraField[8] == 1 && $extraField[2] == 4) {
5192
                    $fields[] = [
5193
                        'name' => $extraField[3],
5194
                        'variable' => $extraField[1],
5195
                        'data' => $extraField[9],
5196
                    ];
5197
                }
5198
            }
5199
        }
5200
5201
        return $fields;
5202
    }
5203
5204
    /**
5205
     * Get extra where clauses for finding users based on extra filterable user fields (type select).
5206
     *
5207
     * @return string With AND clauses based on user's ID which have the values to search in extra user fields
5208
     *                (or empty if no extra field exists)
5209
     */
5210
    public static function get_search_form_where_extra_fields()
5211
    {
5212
        $useExtraFields = false;
5213
        $extraFields = self::getExtraFilterableFields();
5214
        $extraFieldResult = [];
5215
        if (is_array($extraFields) && count($extraFields) > 0) {
5216
            foreach ($extraFields as $extraField) {
5217
                $varName = 'field_'.$extraField['variable'];
5218
                if (self::is_extra_field_available($extraField['variable'])) {
5219
                    if (isset($_GET[$varName]) && $_GET[$varName] != '0') {
5220
                        $useExtraFields = true;
5221
                        $extraFieldResult[] = self::get_extra_user_data_by_value(
5222
                            $extraField['variable'],
5223
                            $_GET[$varName]
5224
                        );
5225
                    }
5226
                }
5227
            }
5228
        }
5229
5230
        if ($useExtraFields) {
5231
            $finalResult = [];
5232
            if (count($extraFieldResult) > 1) {
5233
                for ($i = 0; $i < count($extraFieldResult) - 1; $i++) {
5234
                    if (is_array($extraFieldResult[$i]) && is_array($extraFieldResult[$i + 1])) {
5235
                        $finalResult = array_intersect($extraFieldResult[$i], $extraFieldResult[$i + 1]);
5236
                    }
5237
                }
5238
            } else {
5239
                $finalResult = $extraFieldResult[0];
5240
            }
5241
5242
            if (is_array($finalResult) && count($finalResult) > 0) {
5243
                $whereFilter = " AND u.id IN  ('".implode("','", $finalResult)."') ";
5244
            } else {
5245
                //no results
5246
                $whereFilter = " AND u.id  = -1 ";
5247
            }
5248
5249
            return $whereFilter;
5250
        }
5251
5252
        return '';
5253
    }
5254
5255
    /**
5256
     * Show the search form.
5257
     *
5258
     * @param string $query the value of the search box
5259
     *
5260
     * @throws Exception
5261
     *
5262
     * @return string HTML form
5263
     */
5264
    public static function get_search_form($query, $defaultParams = [])
5265
    {
5266
        $searchType = isset($_GET['search_type']) ? $_GET['search_type'] : null;
5267
        $form = new FormValidator(
5268
            'search_user',
5269
            'get',
5270
            api_get_path(WEB_PATH).'main/social/search.php',
5271
            '',
5272
            [],
5273
            FormValidator::LAYOUT_HORIZONTAL
5274
        );
5275
5276
        $query = Security::remove_XSS($query);
5277
5278
        if (!empty($query)) {
5279
            $form->addHeader(get_lang('Results').' "'.$query.'"');
5280
        }
5281
5282
        $form->addText(
5283
            'q',
5284
            get_lang('UsersGroups'),
5285
            false,
5286
            [
5287
                'id' => 'q',
5288
            ]
5289
        );
5290
        $options = [
5291
            0 => get_lang('Select'),
5292
            1 => get_lang('User'),
5293
            2 => get_lang('Group'),
5294
        ];
5295
        $form->addSelect(
5296
            'search_type',
5297
            get_lang('Type'),
5298
            $options,
5299
            ['onchange' => 'javascript: extra_field_toogle();', 'id' => 'search_type']
5300
        );
5301
5302
        // Extra fields
5303
        $extraFields = self::getExtraFilterableFields();
5304
        $defaults = [];
5305
        if (is_array($extraFields) && count($extraFields) > 0) {
5306
            foreach ($extraFields as $extraField) {
5307
                $varName = 'field_'.$extraField['variable'];
5308
                $options = [
5309
                    0 => get_lang('Select'),
5310
                ];
5311
                foreach ($extraField['data'] as $option) {
5312
                    if (isset($_GET[$varName])) {
5313
                        if ($_GET[$varName] == $option[1]) {
5314
                            $defaults[$option[1]] = true;
5315
                        }
5316
                    }
5317
5318
                    $options[$option[1]] = $option[1];
5319
                }
5320
                $form->addSelect($varName, $extraField['name'], $options);
5321
            }
5322
        }
5323
5324
        $defaults['search_type'] = (int) $searchType;
5325
        $defaults['q'] = $query;
5326
5327
        if (!empty($defaultParams)) {
5328
            $defaults = array_merge($defaults, $defaultParams);
5329
        }
5330
        $form->setDefaults($defaults);
5331
        $form->addButtonSearch(get_lang('Search'));
5332
5333
        $js = '<script>
5334
        extra_field_toogle();
5335
        function extra_field_toogle() {
5336
            if (jQuery("select[name=search_type]").val() != "1") {
5337
                jQuery(".extra_field").hide();
5338
            } else {
5339
                jQuery(".extra_field").show();
5340
            }
5341
        }
5342
        </script>';
5343
5344
        return $js.$form->returnForm();
5345
    }
5346
5347
    /**
5348
     * Shows the user menu.
5349
     */
5350
    public static function show_menu()
5351
    {
5352
        echo '<div class="actions">';
5353
        echo '<a href="/main/auth/profile.php">'.
5354
            Display::return_icon('profile.png').' '.get_lang('PersonalData').'</a>';
5355
        echo '<a href="/main/messages/inbox.php">'.
5356
            Display::return_icon('inbox.png').' '.get_lang('Inbox').'</a>';
5357
        echo '<a href="/main/messages/outbox.php">'.
5358
            Display::return_icon('outbox.png').' '.get_lang('Outbox').'</a>';
5359
        echo '<span style="float:right; padding-top:7px;">'.
5360
        '<a href="/main/auth/profile.php?show=1">'.
5361
            Display::return_icon('edit.gif').' '.get_lang('Configuration').'</a>';
5362
        echo '</span>';
5363
        echo '</div>';
5364
    }
5365
5366
    /**
5367
     * Allow to register contact to social network.
5368
     *
5369
     * @param int $friend_id     user friend id
5370
     * @param int $my_user_id    user id
5371
     * @param int $relation_type relation between users see constants definition
5372
     *
5373
     * @return bool
5374
     */
5375
    public static function relate_users($friend_id, $my_user_id, $relation_type)
5376
    {
5377
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5378
5379
        $friend_id = (int) $friend_id;
5380
        $my_user_id = (int) $my_user_id;
5381
        $relation_type = (int) $relation_type;
5382
5383
        $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
5384
                WHERE
5385
                    friend_user_id='.$friend_id.' AND
5386
                    user_id='.$my_user_id.' AND
5387
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
5388
        $result = Database::query($sql);
5389
        $row = Database::fetch_array($result, 'ASSOC');
5390
        $current_date = api_get_utc_datetime();
5391
5392
        if ($row['count'] == 0) {
5393
            $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
5394
                    VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
5395
            Database::query($sql);
5396
5397
            return true;
5398
        }
5399
5400
        $sql = 'SELECT COUNT(*) as count, relation_type  FROM '.$tbl_my_friend.'
5401
                WHERE
5402
                    friend_user_id='.$friend_id.' AND
5403
                    user_id='.$my_user_id.' AND
5404
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
5405
        $result = Database::query($sql);
5406
        $row = Database::fetch_array($result, 'ASSOC');
5407
5408
        if ($row['count'] == 1) {
5409
            //only for the case of a RRHH or a Student BOSS
5410
            if ($row['relation_type'] != $relation_type &&
5411
                ($relation_type == USER_RELATION_TYPE_RRHH || $relation_type == USER_RELATION_TYPE_BOSS)
5412
            ) {
5413
                $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
5414
                        VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
5415
            } else {
5416
                $sql = 'UPDATE '.$tbl_my_friend.' SET relation_type='.$relation_type.'
5417
                        WHERE friend_user_id='.$friend_id.' AND user_id='.$my_user_id;
5418
            }
5419
            Database::query($sql);
5420
5421
            return true;
5422
        }
5423
5424
        return false;
5425
    }
5426
5427
    /**
5428
     * Deletes a contact.
5429
     *
5430
     * @param bool   $friend_id
5431
     * @param bool   $real_removed          true will delete ALL friends relationship
5432
     * @param string $with_status_condition
5433
     *
5434
     * @author isaac flores paz <[email protected]>
5435
     * @author Julio Montoya <[email protected]> Cleaning code
5436
     */
5437
    public static function remove_user_rel_user(
5438
        $friend_id,
5439
        $real_removed = false,
5440
        $with_status_condition = ''
5441
    ) {
5442
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5443
        $tbl_my_message = Database::get_main_table(TABLE_MESSAGE);
5444
        $friend_id = (int) $friend_id;
5445
        $user_id = api_get_user_id();
5446
5447
        if ($real_removed) {
5448
            $extra_condition = '';
5449
            if ($with_status_condition != '') {
5450
                $extra_condition = ' AND relation_type = '.intval($with_status_condition);
5451
            }
5452
            $sql = 'DELETE FROM '.$tbl_my_friend.'
5453
                    WHERE
5454
                        relation_type <> '.USER_RELATION_TYPE_RRHH.' AND
5455
                        friend_user_id='.$friend_id.' '.$extra_condition;
5456
            Database::query($sql);
5457
            $sql = 'DELETE FROM '.$tbl_my_friend.'
5458
                   WHERE
5459
                    relation_type <> '.USER_RELATION_TYPE_RRHH.' AND
5460
                    user_id='.$friend_id.' '.$extra_condition;
5461
            Database::query($sql);
5462
        } else {
5463
            $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
5464
                    WHERE
5465
                        user_id='.$user_id.' AND
5466
                        relation_type NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.') AND
5467
                        friend_user_id='.$friend_id;
5468
            $result = Database::query($sql);
5469
            $row = Database::fetch_array($result, 'ASSOC');
5470
            if ($row['count'] == 1) {
5471
                //Delete user rel user
5472
                $sql_i = 'UPDATE '.$tbl_my_friend.' SET relation_type='.USER_RELATION_TYPE_DELETED.'
5473
                          WHERE user_id='.$user_id.' AND friend_user_id='.$friend_id;
5474
5475
                $sql_j = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
5476
                          WHERE
5477
                                user_receiver_id='.$user_id.' AND
5478
                                user_sender_id='.$friend_id.' AND update_date="0000-00-00 00:00:00" ';
5479
                // Delete user
5480
                $sql_ij = 'UPDATE '.$tbl_my_friend.'  SET relation_type='.USER_RELATION_TYPE_DELETED.'
5481
                           WHERE user_id='.$friend_id.' AND friend_user_id='.$user_id;
5482
                $sql_ji = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
5483
                           WHERE
5484
                                user_receiver_id='.$friend_id.' AND
5485
                                user_sender_id='.$user_id.' AND
5486
                                update_date="0000-00-00 00:00:00" ';
5487
                Database::query($sql_i);
5488
                Database::query($sql_j);
5489
                Database::query($sql_ij);
5490
                Database::query($sql_ji);
5491
            }
5492
        }
5493
5494
        // Delete accepted invitations
5495
        $sql = "DELETE FROM $tbl_my_message
5496
                WHERE
5497
                    msg_status = ".MESSAGE_STATUS_INVITATION_ACCEPTED." AND
5498
                    (
5499
                        user_receiver_id = $user_id AND
5500
                        user_sender_id = $friend_id
5501
                    ) OR
5502
                    (
5503
                        user_sender_id = $user_id AND
5504
                        user_receiver_id = $friend_id
5505
                    )
5506
        ";
5507
        Database::query($sql);
5508
    }
5509
5510
    /**
5511
     * @param int $userId
5512
     *
5513
     * @return array
5514
     */
5515
    public static function getDrhListFromUser($userId)
5516
    {
5517
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
5518
        $tblUserRelUser = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5519
        $tblUserRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5520
        $userId = (int) $userId;
5521
5522
        $orderBy = null;
5523
        if (api_is_western_name_order()) {
5524
            $orderBy .= ' ORDER BY firstname, lastname ';
5525
        } else {
5526
            $orderBy .= ' ORDER BY lastname, firstname ';
5527
        }
5528
5529
        $sql = "SELECT u.id, username, u.firstname, u.lastname
5530
                FROM $tblUser u
5531
                INNER JOIN $tblUserRelUser uru ON (uru.friend_user_id = u.id)
5532
                INNER JOIN $tblUserRelAccessUrl a ON (a.user_id = u.id)
5533
                WHERE
5534
                    access_url_id = ".api_get_current_access_url_id()." AND
5535
                    uru.user_id = '$userId' AND
5536
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5537
                $orderBy
5538
                ";
5539
        $result = Database::query($sql);
5540
5541
        return Database::store_result($result);
5542
    }
5543
5544
    /**
5545
     * get users followed by human resource manager.
5546
     *
5547
     * @param int    $userId
5548
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
5549
     * @param bool   $getOnlyUserId
5550
     * @param bool   $getSql
5551
     * @param bool   $getCount
5552
     * @param int    $from
5553
     * @param int    $numberItems
5554
     * @param int    $column
5555
     * @param string $direction
5556
     * @param int    $active
5557
     * @param string $lastConnectionDate
5558
     *
5559
     * @return array users
5560
     */
5561
    public static function get_users_followed_by_drh(
5562
        $userId,
5563
        $userStatus = 0,
5564
        $getOnlyUserId = false,
5565
        $getSql = false,
5566
        $getCount = false,
5567
        $from = null,
5568
        $numberItems = null,
5569
        $column = null,
5570
        $direction = null,
5571
        $active = null,
5572
        $lastConnectionDate = null
5573
    ) {
5574
        return self::getUsersFollowedByUser(
5575
            $userId,
5576
            $userStatus,
5577
            $getOnlyUserId,
5578
            $getSql,
5579
            $getCount,
5580
            $from,
5581
            $numberItems,
5582
            $column,
5583
            $direction,
5584
            $active,
5585
            $lastConnectionDate,
5586
            DRH
5587
        );
5588
    }
5589
5590
    /**
5591
     * Get users followed by human resource manager.
5592
     *
5593
     * @param int    $userId
5594
     * @param int    $userStatus             Filter users by status (STUDENT, COURSEMANAGER, etc)
5595
     * @param bool   $getOnlyUserId
5596
     * @param bool   $getSql
5597
     * @param bool   $getCount
5598
     * @param int    $from
5599
     * @param int    $numberItems
5600
     * @param int    $column
5601
     * @param string $direction
5602
     * @param int    $active
5603
     * @param string $lastConnectionDate
5604
     * @param int    $status                 the function is called by who? COURSEMANAGER, DRH?
5605
     * @param string $keyword
5606
     * @param bool   $checkSessionVisibility
5607
     *
5608
     * @return mixed Users list (array) or the SQL query if $getSQL was set to true
5609
     */
5610
    public static function getUsersFollowedByUser(
5611
        $userId,
5612
        $userStatus = null,
5613
        $getOnlyUserId = false,
5614
        $getSql = false,
5615
        $getCount = false,
5616
        $from = null,
5617
        $numberItems = null,
5618
        $column = null,
5619
        $direction = null,
5620
        $active = null,
5621
        $lastConnectionDate = null,
5622
        $status = null,
5623
        $keyword = null,
5624
        $checkSessionVisibility = false,
5625
        $filterUsers = null
5626
    ) {
5627
        // Database Table Definitions
5628
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
5629
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5630
        $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5631
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
5632
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
5633
        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
5634
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
5635
        $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
5636
5637
        $userId = (int) $userId;
5638
        $limitCondition = '';
5639
5640
        if (isset($from) && isset($numberItems)) {
5641
            $from = (int) $from;
5642
            $numberItems = (int) $numberItems;
5643
            $limitCondition = "LIMIT $from, $numberItems";
5644
        }
5645
5646
        $column = Database::escape_string($column);
5647
        $direction = in_array(strtolower($direction), ['asc', 'desc']) ? $direction : null;
5648
5649
        $userConditions = '';
5650
        if (!empty($userStatus)) {
5651
            $userConditions .= ' AND u.status = '.intval($userStatus);
5652
        }
5653
5654
        $select = " SELECT DISTINCT u.id user_id, u.username, u.lastname, u.firstname, u.email ";
5655
        if ($getOnlyUserId) {
5656
            $select = " SELECT DISTINCT u.id user_id";
5657
        }
5658
5659
        $masterSelect = "SELECT DISTINCT * FROM ";
5660
5661
        if ($getCount) {
5662
            $masterSelect = "SELECT COUNT(DISTINCT(user_id)) as count FROM ";
5663
            $select = " SELECT DISTINCT(u.id) user_id";
5664
        }
5665
5666
        if (!is_null($active)) {
5667
            $active = intval($active);
5668
            $userConditions .= " AND u.active = $active ";
5669
        }
5670
5671
        if (!empty($keyword)) {
5672
            $keyword = trim(Database::escape_string($keyword));
5673
            $keywordParts = array_filter(explode(' ', $keyword));
5674
            $extraKeyword = '';
5675
            if (!empty($keywordParts)) {
5676
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
5677
                if (!empty($keywordPartsFixed)) {
5678
                    $extraKeyword .= " OR
5679
                        CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keywordPartsFixed%' OR
5680
                        CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keywordPartsFixed%' ";
5681
                }
5682
            }
5683
5684
            $userConditions .= " AND (
5685
                u.username LIKE '%$keyword%' OR
5686
                u.firstname LIKE '%$keyword%' OR
5687
                u.lastname LIKE '%$keyword%' OR
5688
                u.official_code LIKE '%$keyword%' OR
5689
                u.email LIKE '%$keyword%' OR
5690
                CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keyword%' OR
5691
                CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keyword%'
5692
                $extraKeyword
5693
            )";
5694
        }
5695
5696
        if (!empty($lastConnectionDate)) {
5697
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
5698
            $userConditions .= " AND u.last_login <= '$lastConnectionDate' ";
5699
        }
5700
5701
        if (!empty($filterUsers)) {
5702
            $userConditions .= " AND u.id IN(".implode(',', $filterUsers).")";
5703
        }
5704
5705
        $sessionConditionsCoach = null;
5706
        $dateCondition = '';
5707
        $drhConditions = null;
5708
        $teacherSelect = null;
5709
5710
        $urlId = api_get_current_access_url_id();
5711
5712
        switch ($status) {
5713
            case DRH:
5714
                $drhConditions .= " AND
5715
                    friend_user_id = '$userId' AND
5716
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5717
                ";
5718
                break;
5719
            case COURSEMANAGER:
5720
                $drhConditions .= " AND
5721
                    friend_user_id = '$userId' AND
5722
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5723
                ";
5724
5725
                $sessionConditionsCoach .= " AND
5726
                    (s.id_coach = '$userId')
5727
                ";
5728
5729
                $sessionConditionsTeacher = " AND
5730
                    (scu.status = 2 AND scu.user_id = '$userId')
5731
                ";
5732
5733
                if ($checkSessionVisibility) {
5734
                    $today = api_strtotime('now', 'UTC');
5735
                    $today = date('Y-m-d', $today);
5736
                    $dateCondition = "
5737
                        AND
5738
                        (
5739
                            (s.access_start_date <= '$today' AND '$today' <= s.access_end_date) OR
5740
                            (s.access_start_date IS NULL AND s.access_end_date IS NULL) OR
5741
                            (s.access_start_date <= '$today' AND s.access_end_date IS NULL) OR
5742
                            ('$today' <= s.access_end_date AND s.access_start_date IS NULL)
5743
                        )
5744
					";
5745
                }
5746
5747
                // Use $tbl_session_rel_course_rel_user instead of $tbl_session_rel_user
5748
                /*
5749
                INNER JOIN $tbl_session_rel_user sru
5750
                ON (sru.user_id = u.id)
5751
                INNER JOIN $tbl_session_rel_course_rel_user scu
5752
                ON (scu.user_id = u.id AND scu.c_id IS NOT NULL AND visibility = 1)*/
5753
                $teacherSelect =
5754
                "UNION ALL (
5755
                        $select
5756
                        FROM $tbl_user u
5757
                        INNER JOIN $tbl_session_rel_user sru ON (sru.user_id = u.id)
5758
                        WHERE
5759
                            (
5760
                                sru.session_id IN (
5761
                                    SELECT DISTINCT(s.id) FROM $tbl_session s INNER JOIN
5762
                                    $tbl_session_rel_access_url session_rel_access_rel_user
5763
                                    ON session_rel_access_rel_user.session_id = s.id
5764
                                    WHERE access_url_id = ".$urlId."
5765
                                    $sessionConditionsCoach
5766
                                ) OR sru.session_id IN (
5767
                                    SELECT DISTINCT(s.id) FROM $tbl_session s
5768
                                    INNER JOIN $tbl_session_rel_access_url url
5769
                                    ON (url.session_id = s.id)
5770
                                    INNER JOIN $tbl_session_rel_course_rel_user scu
5771
                                    ON (scu.session_id = s.id)
5772
                                    WHERE access_url_id = ".$urlId."
5773
                                    $sessionConditionsTeacher
5774
                                    $dateCondition
5775
                                )
5776
                            )
5777
                            $userConditions
5778
                    )
5779
                    UNION ALL(
5780
                        $select
5781
                        FROM $tbl_user u
5782
                        INNER JOIN $tbl_course_user cu ON (cu.user_id = u.id)
5783
                        WHERE cu.c_id IN (
5784
                            SELECT DISTINCT(c_id) FROM $tbl_course_user
5785
                            WHERE user_id = $userId AND status = ".COURSEMANAGER."
5786
                        )
5787
                        $userConditions
5788
                    )"
5789
                ;
5790
                break;
5791
            case STUDENT_BOSS:
5792
                $drhConditions = " AND friend_user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
5793
                break;
5794
            case HRM_REQUEST:
5795
                $drhConditions .= " AND
5796
                    friend_user_id = '$userId' AND
5797
                    relation_type = '".USER_RELATION_TYPE_HRM_REQUEST."'
5798
                ";
5799
                break;
5800
        }
5801
5802
        $join = null;
5803
        $sql = " $masterSelect
5804
                (
5805
                    (
5806
                        $select
5807
                        FROM $tbl_user u
5808
                        INNER JOIN $tbl_user_rel_user uru ON (uru.user_id = u.id)
5809
                        LEFT JOIN $tbl_user_rel_access_url a ON (a.user_id = u.id)
5810
                        $join
5811
                        WHERE
5812
                            access_url_id = ".$urlId."
5813
                            $drhConditions
5814
                            $userConditions
5815
                    )
5816
                    $teacherSelect
5817
                ) as t1";
5818
5819
        if ($getSql) {
5820
            return $sql;
5821
        }
5822
5823
        if ($getCount) {
5824
            $result = Database::query($sql);
5825
            $row = Database::fetch_array($result);
5826
5827
            return $row['count'];
5828
        }
5829
5830
        $orderBy = null;
5831
        if ($getOnlyUserId == false) {
5832
            if (api_is_western_name_order()) {
5833
                $orderBy .= " ORDER BY firstname, lastname ";
5834
            } else {
5835
                $orderBy .= " ORDER BY lastname, firstname ";
5836
            }
5837
5838
            if (!empty($column) && !empty($direction)) {
5839
                // Fixing order due the UNIONs
5840
                $column = str_replace('u.', '', $column);
5841
                $column = trim($column);
5842
                $orderBy = " ORDER BY `$column` $direction ";
5843
            }
5844
        }
5845
5846
        $sql .= $orderBy;
5847
        $sql .= $limitCondition;
5848
5849
        $result = Database::query($sql);
5850
        $users = [];
5851
        if (Database::num_rows($result) > 0) {
5852
            while ($row = Database::fetch_array($result)) {
5853
                $users[$row['user_id']] = $row;
5854
            }
5855
        }
5856
5857
        return $users;
5858
    }
5859
5860
    /**
5861
     * Subscribes users to human resource manager (Dashboard feature).
5862
     *
5863
     * @param int   $hr_dept_id
5864
     * @param array $users_id
5865
     * @param bool  $deleteOtherAssignedUsers
5866
     *
5867
     * @return int
5868
     */
5869
    public static function subscribeUsersToHRManager(
5870
        $hr_dept_id,
5871
        $users_id,
5872
        $deleteOtherAssignedUsers = true
5873
    ) {
5874
        return self::subscribeUsersToUser(
5875
            $hr_dept_id,
5876
            $users_id,
5877
            USER_RELATION_TYPE_RRHH,
5878
            false,
5879
            $deleteOtherAssignedUsers
5880
        );
5881
    }
5882
5883
    /**
5884
     * Register request to assign users to HRM.
5885
     *
5886
     * @param int   $hrmId   The HRM ID
5887
     * @param array $usersId The users IDs
5888
     *
5889
     * @return int
5890
     */
5891
    public static function requestUsersToHRManager($hrmId, $usersId)
5892
    {
5893
        return self::subscribeUsersToUser(
5894
            $hrmId,
5895
            $usersId,
5896
            USER_RELATION_TYPE_HRM_REQUEST,
5897
            false,
5898
            false
5899
        );
5900
    }
5901
5902
    /**
5903
     * Remove the requests for assign a user to a HRM.
5904
     *
5905
     * @param array $usersId List of user IDs from whom to remove all relations requests with HRM
5906
     */
5907
    public static function clearHrmRequestsForUser(User $hrmId, $usersId)
5908
    {
5909
        $users = implode(', ', $usersId);
5910
        Database::getManager()
5911
            ->createQuery('
5912
                DELETE FROM ChamiloCoreBundle:UserRelUser uru
5913
                WHERE uru.friendUserId = :hrm_id AND uru.relationType = :relation_type AND uru.userId IN (:users_ids)
5914
            ')
5915
            ->execute(['hrm_id' => $hrmId, 'relation_type' => USER_RELATION_TYPE_HRM_REQUEST, 'users_ids' => $users]);
5916
    }
5917
5918
    /**
5919
     * Add subscribed users to a user by relation type.
5920
     *
5921
     * @param int    $userId                   The user id
5922
     * @param array  $subscribedUsersId        The id of subscribed users
5923
     * @param string $relationType             The relation type
5924
     * @param bool   $deleteUsersBeforeInsert
5925
     * @param bool   $deleteOtherAssignedUsers
5926
     *
5927
     * @return int
5928
     */
5929
    public static function subscribeUsersToUser(
5930
        $userId,
5931
        $subscribedUsersId,
5932
        $relationType,
5933
        $deleteUsersBeforeInsert = false,
5934
        $deleteOtherAssignedUsers = true
5935
    ) {
5936
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5937
        $userRelAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5938
5939
        $userId = (int) $userId;
5940
        $relationType = (int) $relationType;
5941
        $affectedRows = 0;
5942
5943
        if ($deleteOtherAssignedUsers) {
5944
            if (api_get_multiple_access_url()) {
5945
                // Deleting assigned users to hrm_id
5946
                $sql = "SELECT s.user_id
5947
                        FROM $userRelUserTable s
5948
                        INNER JOIN $userRelAccessUrlTable a
5949
                        ON (a.user_id = s.user_id)
5950
                        WHERE
5951
                            friend_user_id = $userId AND
5952
                            relation_type = $relationType AND
5953
                            access_url_id = ".api_get_current_access_url_id();
5954
            } else {
5955
                $sql = "SELECT user_id
5956
                        FROM $userRelUserTable
5957
                        WHERE
5958
                            friend_user_id = $userId AND
5959
                            relation_type = $relationType";
5960
            }
5961
            $result = Database::query($sql);
5962
5963
            if (Database::num_rows($result) > 0) {
5964
                while ($row = Database::fetch_array($result)) {
5965
                    $sql = "DELETE FROM $userRelUserTable
5966
                            WHERE
5967
                                user_id = {$row['user_id']} AND
5968
                                friend_user_id = $userId AND
5969
                                relation_type = $relationType";
5970
                    Database::query($sql);
5971
                }
5972
            }
5973
        }
5974
5975
        if ($deleteUsersBeforeInsert) {
5976
            $sql = "DELETE FROM $userRelUserTable
5977
                    WHERE
5978
                        user_id = $userId AND
5979
                        relation_type = $relationType";
5980
            Database::query($sql);
5981
        }
5982
5983
        // Inserting new user list
5984
        if (is_array($subscribedUsersId)) {
5985
            foreach ($subscribedUsersId as $subscribedUserId) {
5986
                $subscribedUserId = (int) $subscribedUserId;
5987
                $sql = "SELECT id
5988
                        FROM $userRelUserTable
5989
                        WHERE
5990
                            user_id = $subscribedUserId AND
5991
                            friend_user_id = $userId AND
5992
                            relation_type = $relationType";
5993
5994
                $result = Database::query($sql);
5995
                $num = Database::num_rows($result);
5996
                if ($num === 0) {
5997
                    $date = api_get_utc_datetime();
5998
                    $sql = "INSERT INTO $userRelUserTable (user_id, friend_user_id, relation_type, last_edit)
5999
                            VALUES ($subscribedUserId, $userId, $relationType, '$date')";
6000
                    $result = Database::query($sql);
6001
                    $affectedRows += Database::affected_rows($result);
6002
                }
6003
            }
6004
        }
6005
6006
        return $affectedRows;
6007
    }
6008
6009
    /**
6010
     * This function check if an user is followed by human resources manager.
6011
     *
6012
     * @param int $user_id
6013
     * @param int $hr_dept_id Human resources manager
6014
     *
6015
     * @return bool
6016
     */
6017
    public static function is_user_followed_by_drh($user_id, $hr_dept_id)
6018
    {
6019
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6020
        $user_id = (int) $user_id;
6021
        $hr_dept_id = (int) $hr_dept_id;
6022
        $result = false;
6023
6024
        $sql = "SELECT user_id FROM $tbl_user_rel_user
6025
                WHERE
6026
                    user_id = $user_id AND
6027
                    friend_user_id = $hr_dept_id AND
6028
                    relation_type = ".USER_RELATION_TYPE_RRHH;
6029
        $rs = Database::query($sql);
6030
        if (Database::num_rows($rs) > 0) {
6031
            $result = true;
6032
        }
6033
6034
        return $result;
6035
    }
6036
6037
    /**
6038
     * Return the user id of teacher or session administrator.
6039
     *
6040
     * @return int|bool The user id, or false if the session ID was negative
6041
     */
6042
    public static function get_user_id_of_course_admin_or_session_admin(array $courseInfo)
6043
    {
6044
        $session = api_get_session_id();
6045
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6046
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6047
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6048
6049
        if (empty($courseInfo)) {
6050
            return false;
6051
        }
6052
6053
        $courseId = $courseInfo['real_id'];
6054
6055
        if (empty($session)) {
6056
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
6057
                    INNER JOIN '.$table_course_user.' ru
6058
                    ON ru.user_id = u.id
6059
                    WHERE
6060
                        ru.status = '.COURSEMANAGER.' AND
6061
                        ru.c_id = "'.$courseId.'" ';
6062
        } else {
6063
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
6064
                    INNER JOIN '.$table_session_course_user.' sru
6065
                    ON sru.user_id=u.id
6066
                    WHERE
6067
                        sru.c_id="'.$courseId.'" AND
6068
                        sru.session_id="'.$session.'" AND
6069
                        sru.status = '.SessionEntity::COACH;
6070
        }
6071
6072
        $rs = Database::query($sql);
6073
        $num_rows = Database::num_rows($rs);
6074
6075
        if (0 === $num_rows) {
6076
            return false;
6077
        }
6078
6079
        if (1 === $num_rows) {
6080
            $row = Database::fetch_array($rs);
6081
6082
            return (int) $row['uid'];
6083
        }
6084
6085
        $my_num_rows = $num_rows;
6086
        $my_user_id = Database::result($rs, $my_num_rows - 1, 'uid');
6087
6088
        return (int) $my_user_id;
6089
    }
6090
6091
    /**
6092
     * Determines if a user is a gradebook certified.
6093
     *
6094
     * @param int $cat_id  The category id of gradebook
6095
     * @param int $user_id The user id
6096
     *
6097
     * @return bool
6098
     */
6099
    public static function is_user_certified($cat_id, $user_id)
6100
    {
6101
        $cat_id = (int) $cat_id;
6102
        $user_id = (int) $user_id;
6103
6104
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
6105
        $sql = 'SELECT path_certificate
6106
                FROM '.$table.'
6107
                WHERE
6108
                    cat_id = "'.$cat_id.'" AND
6109
                    user_id = "'.$user_id.'"';
6110
        $rs = Database::query($sql);
6111
        $row = Database::fetch_array($rs);
6112
6113
        if ($row['path_certificate'] == '' || is_null($row['path_certificate'])) {
6114
            return false;
6115
        }
6116
6117
        return true;
6118
    }
6119
6120
    /**
6121
     * Gets the info about a gradebook certificate for a user by course.
6122
     *
6123
     * @param string $course_code The course code
6124
     * @param int    $session_id
6125
     * @param int    $user_id     The user id
6126
     * @param string $startDate   date string
6127
     * @param string $endDate     date string
6128
     *
6129
     * @return array if there is not information return false
6130
     */
6131
    public static function get_info_gradebook_certificate(
6132
        $course_code,
6133
        $session_id,
6134
        $user_id,
6135
        $startDate = null,
6136
        $endDate = null
6137
    ) {
6138
        $tbl_grade_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
6139
        $tbl_grade_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
6140
        $session_id = (int) $session_id;
6141
        $user_id = (int) $user_id;
6142
6143
        if (empty($session_id)) {
6144
            $session_condition = ' AND (session_id = "" OR session_id = 0 OR session_id IS NULL )';
6145
        } else {
6146
            $session_condition = " AND session_id = $session_id";
6147
        }
6148
6149
        $dateConditions = "";
6150
        if (!empty($startDate)) {
6151
            $startDate = api_get_utc_datetime($startDate, false, true);
6152
            $dateConditions .= " AND created_at >= '".$startDate->format('Y-m-d 00:00:00')."' ";
6153
        }
6154
        if (!empty($endDate)) {
6155
            $endDate = api_get_utc_datetime($endDate, false, true);
6156
            $dateConditions .= " AND created_at <= '".$endDate->format('Y-m-d 23:59:59')."' ";
6157
        }
6158
6159
        $sql = 'SELECT * FROM '.$tbl_grade_certificate.'
6160
                WHERE cat_id = (
6161
                    SELECT id FROM '.$tbl_grade_category.'
6162
                    WHERE
6163
                        course_code = "'.Database::escape_string($course_code).'" '.$session_condition.' '.$dateConditions.'
6164
                    LIMIT 1
6165
                ) AND user_id='.$user_id;
6166
6167
        $rs = Database::query($sql);
6168
        if (Database::num_rows($rs) > 0) {
6169
            $row = Database::fetch_array($rs, 'ASSOC');
6170
            $score = $row['score_certificate'];
6171
            $category_id = $row['cat_id'];
6172
            $cat = Category::load($category_id);
6173
            $displayscore = ScoreDisplay::instance();
6174
            if (isset($cat) && $displayscore->is_custom()) {
6175
                $grade = $displayscore->display_score(
6176
                    [$score, $cat[0]->get_weight()],
6177
                    SCORE_DIV_PERCENT_WITH_CUSTOM
6178
                );
6179
            } else {
6180
                $grade = $displayscore->display_score(
6181
                    [$score, $cat[0]->get_weight()]
6182
                );
6183
            }
6184
            $row['grade'] = $grade;
6185
6186
            return $row;
6187
        }
6188
6189
        return false;
6190
    }
6191
6192
    /**
6193
     * This function check if the user is a coach inside session course.
6194
     *
6195
     * @param int $user_id    User id
6196
     * @param int $courseId
6197
     * @param int $session_id
6198
     *
6199
     * @return bool True if the user is a coach
6200
     */
6201
    public static function is_session_course_coach($user_id, $courseId, $session_id)
6202
    {
6203
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6204
        // Protect data
6205
        $user_id = intval($user_id);
6206
        $courseId = intval($courseId);
6207
        $session_id = intval($session_id);
6208
        $result = false;
6209
6210
        $sql = "SELECT session_id FROM $table
6211
                WHERE
6212
                  session_id = $session_id AND
6213
                  c_id = $courseId AND
6214
                  user_id = $user_id AND
6215
                  status = 2 ";
6216
        $res = Database::query($sql);
6217
6218
        if (Database::num_rows($res) > 0) {
6219
            $result = true;
6220
        }
6221
6222
        return $result;
6223
    }
6224
6225
    /**
6226
     * This function returns an icon path that represents the favicon of the website of which the url given.
6227
     * Defaults to the current Chamilo favicon.
6228
     *
6229
     * @param string $url1 URL of website where to look for favicon.ico
6230
     * @param string $url2 Optional second URL of website where to look for favicon.ico
6231
     *
6232
     * @return string Path of icon to load
6233
     */
6234
    public static function get_favicon_from_url($url1, $url2 = null)
6235
    {
6236
        $icon_link = '';
6237
        $url = $url1;
6238
        if (empty($url1)) {
6239
            $url = $url2;
6240
            if (empty($url)) {
6241
                $url = api_get_access_url(api_get_current_access_url_id());
6242
                $url = $url[0];
6243
            }
6244
        }
6245
        if (!empty($url)) {
6246
            $pieces = parse_url($url);
6247
            $icon_link = $pieces['scheme'].'://'.$pieces['host'].'/favicon.ico';
6248
        }
6249
6250
        return $icon_link;
6251
    }
6252
6253
    public static function addUserAsAdmin(User $user)
6254
    {
6255
        if ($user) {
6256
            $userId = $user->getId();
6257
            if (!self::is_admin($userId)) {
6258
                $table = Database::get_main_table(TABLE_MAIN_ADMIN);
6259
                $sql = "INSERT INTO $table SET user_id = $userId";
6260
                Database::query($sql);
6261
            }
6262
6263
            $user->addRole('ROLE_SUPER_ADMIN');
6264
            self::getManager()->updateUser($user, true);
6265
        }
6266
    }
6267
6268
    public static function removeUserAdmin(User $user)
6269
    {
6270
        $userId = (int) $user->getId();
6271
        if (self::is_admin($userId)) {
6272
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
6273
            $sql = "DELETE FROM $table WHERE user_id = $userId";
6274
            Database::query($sql);
6275
            $user->removeRole('ROLE_SUPER_ADMIN');
6276
            self::getManager()->updateUser($user, true);
6277
        }
6278
    }
6279
6280
    /**
6281
     * @param string $from
6282
     * @param string $to
6283
     */
6284
    public static function update_all_user_languages($from, $to)
6285
    {
6286
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6287
        $from = Database::escape_string($from);
6288
        $to = Database::escape_string($to);
6289
6290
        if (!empty($to) && !empty($from)) {
6291
            $sql = "UPDATE $table_user SET language = '$to'
6292
                    WHERE language = '$from'";
6293
            Database::query($sql);
6294
        }
6295
    }
6296
6297
    /**
6298
     * Subscribe boss to students.
6299
     *
6300
     * @param int   $bossId                   The boss id
6301
     * @param array $usersId                  The users array
6302
     * @param bool  $deleteOtherAssignedUsers
6303
     *
6304
     * @return int Affected rows
6305
     */
6306
    public static function subscribeBossToUsers($bossId, $usersId, $deleteOtherAssignedUsers = true)
6307
    {
6308
        return self::subscribeUsersToUser(
6309
            $bossId,
6310
            $usersId,
6311
            USER_RELATION_TYPE_BOSS,
6312
            false,
6313
            $deleteOtherAssignedUsers
6314
        );
6315
    }
6316
6317
    /**
6318
     * @param int $userId
6319
     *
6320
     * @return bool
6321
     */
6322
    public static function removeAllBossFromStudent($userId)
6323
    {
6324
        $userId = (int) $userId;
6325
6326
        if (empty($userId)) {
6327
            return false;
6328
        }
6329
6330
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6331
        $sql = "DELETE FROM $userRelUserTable
6332
                WHERE user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
6333
        Database::query($sql);
6334
6335
        return true;
6336
    }
6337
6338
    /**
6339
     * It updates course relation type as EX-LEARNER if project name (extra field from user_edition_extra_field_to_check) is changed.
6340
     *
6341
     * @param $userId
6342
     * @param $extraValue
6343
     *
6344
     * @throws \Doctrine\DBAL\Exception
6345
     */
6346
    public static function updateCourseRelationTypeExLearner($userId, $extraValue)
6347
    {
6348
        if (false !== api_get_configuration_value('user_edition_extra_field_to_check')) {
6349
            $extraToCheck = api_get_configuration_value('user_edition_extra_field_to_check');
6350
6351
            // Get the old user extra value to check
6352
            $userExtra = UserManager::get_extra_user_data_by_field($userId, $extraToCheck);
6353
            if (isset($userExtra[$extraToCheck]) && $userExtra[$extraToCheck] != $extraValue) {
6354
                // it searchs the courses with the user old extravalue
6355
                $extraFieldValues = new ExtraFieldValue('course');
6356
                $extraItems = $extraFieldValues->get_item_id_from_field_variable_and_field_value($extraToCheck, $userExtra[$extraToCheck], false, false, true);
6357
                $coursesTocheck = [];
6358
                if (!empty($extraItems)) {
6359
                    foreach ($extraItems as $items) {
6360
                        $coursesTocheck[] = $items['item_id'];
6361
                    }
6362
                }
6363
6364
                $tblUserGroupRelUser = Database::get_main_table(TABLE_USERGROUP_REL_USER);
6365
                $tblUserGroupRelCourse = Database::get_main_table(TABLE_USERGROUP_REL_COURSE);
6366
                $tblUserGroupRelSession = Database::get_main_table(TABLE_USERGROUP_REL_SESSION);
6367
                $tblSessionUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
6368
                $tblCourseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6369
6370
                // To check in main course
6371
                if (!empty($coursesTocheck)) {
6372
                    foreach ($coursesTocheck as $courseId) {
6373
                        $sql = "SELECT id FROM $tblCourseUser
6374
                                WHERE user_id = $userId AND c_id = $courseId";
6375
                        $rs = Database::query($sql);
6376
                        if (Database::num_rows($rs) > 0) {
6377
                            $id = Database::result($rs, 0, 0);
6378
                            $sql = "UPDATE $tblCourseUser SET relation_type = ".COURSE_EXLEARNER."
6379
                                    WHERE id = $id";
6380
                            Database::query($sql);
6381
                        }
6382
                    }
6383
                }
6384
6385
                // To check in sessions
6386
                if (!empty($coursesTocheck)) {
6387
                    $tblSessionCourseUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6388
                    $sessionsToCheck = [];
6389
                    foreach ($coursesTocheck as $courseId) {
6390
                        $sql = "SELECT id, session_id FROM $tblSessionCourseUser
6391
                                WHERE user_id = $userId AND c_id = $courseId";
6392
                        $rs = Database::query($sql);
6393
                        if (Database::num_rows($rs) > 0) {
6394
                            $row = Database::fetch_array($rs);
6395
                            $id = $row['id'];
6396
                            $sessionId = $row['session_id'];
6397
                            $sql = "UPDATE $tblSessionCourseUser SET status = ".COURSE_EXLEARNER."
6398
                                    WHERE id = $id";
6399
                            Database::query($sql);
6400
                            $sessionsToCheck[] = $sessionId;
6401
                        }
6402
                    }
6403
                    // It checks if user is ex-learner in all courses in the session to update the session relation type
6404
                    if (!empty($sessionsToCheck)) {
6405
                        $sessionsToCheck = array_unique($sessionsToCheck);
6406
                        foreach ($sessionsToCheck as $sessionId) {
6407
                            $checkAll = Database::query("SELECT count(id) FROM $tblSessionCourseUser WHERE user_id = $userId AND session_id = $sessionId");
6408
                            $countAll = Database::result($checkAll, 0, 0);
6409
                            $checkExLearner = Database::query("SELECT count(id) FROM $tblSessionCourseUser WHERE status = ".COURSE_EXLEARNER." AND user_id = $userId AND session_id = $sessionId");
6410
                            $countExLearner = Database::result($checkExLearner, 0, 0);
6411
                            if ($countAll > 0 && $countAll == $countExLearner) {
6412
                                $sql = "UPDATE $tblSessionUser SET relation_type = ".COURSE_EXLEARNER."
6413
                                    WHERE user_id = $userId AND session_id = $sessionId";
6414
                                Database::query($sql);
6415
                            }
6416
                        }
6417
                    }
6418
                }
6419
                // To check users inside a class
6420
                $rsUser = Database::query("SELECT usergroup_id FROM $tblUserGroupRelUser WHERE user_id = $userId");
6421
                if (Database::num_rows($rsUser) > 0) {
6422
                    while ($rowUser = Database::fetch_array($rsUser)) {
6423
                        $usergroupId = $rowUser['usergroup_id'];
6424
6425
                        // Count courses with exlearners
6426
                        $sqlC1 = "SELECT count(id) FROM $tblUserGroupRelCourse WHERE usergroup_id = $usergroupId";
6427
                        $rsCourses = Database::query($sqlC1);
6428
                        $countGroupCourses = Database::result($rsCourses, 0, 0);
6429
6430
                        $sqlC2 = "SELECT count(cu.id)
6431
                                FROM $tblCourseUser cu
6432
                                INNER JOIN $tblUserGroupRelCourse gc
6433
                                    ON gc.course_id = cu.c_id
6434
                                WHERE
6435
                                    cu.user_id = $userId AND
6436
                                    usergroup_id = $usergroupId AND
6437
                                    relation_type = ".COURSE_EXLEARNER;
6438
                        $rsExCourses = Database::query($sqlC2);
6439
                        $countExCourses = Database::result($rsExCourses, 0, 0);
6440
                        $checkedExCourses = $countGroupCourses > 0 && ($countExCourses == $countGroupCourses);
6441
6442
                        // Count sessions with exlearners
6443
                        $sqlS1 = "SELECT count(id) FROM $tblUserGroupRelSession WHERE usergroup_id = $usergroupId";
6444
                        $rsSessions = Database::query($sqlS1);
6445
                        $countGroupSessions = Database::result($rsSessions, 0, 0);
6446
6447
                        $sqlS2 = "SELECT count(su.id)
6448
                                FROM $tblSessionUser su
6449
                                INNER JOIN $tblUserGroupRelSession gs
6450
                                    ON gs.session_id = su.session_id
6451
                                WHERE
6452
                                    su.user_id = $userId AND
6453
                                    usergroup_id = $usergroupId AND
6454
                                    relation_type = ".COURSE_EXLEARNER;
6455
                        $rsExSessions = Database::query($sqlS2);
6456
                        $countExSessions = Database::result($rsExSessions, 0, 0);
6457
                        $checkedExSessions = $countGroupSessions > 0 && ($countExSessions == $countGroupSessions);
6458
6459
                        // it checks if usergroup user should be set to EXLEARNER
6460
                        $checkedExClassLearner = false;
6461
                        if ($countGroupCourses > 0 && $countGroupSessions == 0) {
6462
                            $checkedExClassLearner = $checkedExCourses;
6463
                        } elseif ($countGroupCourses == 0 && $countGroupSessions > 0) {
6464
                            $checkedExClassLearner = $checkedExSessions;
6465
                        } elseif ($countGroupCourses > 0 && $countGroupSessions > 0) {
6466
                            $checkedExClassLearner = ($checkedExCourses && $checkedExSessions);
6467
                        }
6468
6469
                        if ($checkedExClassLearner) {
6470
                            Database::query("UPDATE $tblUserGroupRelUser SET relation_type = ".COURSE_EXLEARNER." WHERE user_id = $userId AND usergroup_id = $usergroupId");
6471
                        }
6472
                    }
6473
                }
6474
            }
6475
        }
6476
    }
6477
6478
    /**
6479
     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
6480
     *
6481
     * @param int   $studentId
6482
     * @param array $bossList
6483
     * @param bool  $sendNotification
6484
     *
6485
     * @return mixed Affected rows or false on failure
6486
     */
6487
    public static function subscribeUserToBossList(
6488
        $studentId,
6489
        $bossList,
6490
        $sendNotification = false
6491
    ) {
6492
        $inserted = 0;
6493
        if (!empty($bossList)) {
6494
            sort($bossList);
6495
            $studentId = (int) $studentId;
6496
            $studentInfo = api_get_user_info($studentId);
6497
6498
            if (empty($studentInfo)) {
6499
                return false;
6500
            }
6501
6502
            $previousBossList = self::getStudentBossList($studentId);
6503
            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
6504
            sort($previousBossList);
6505
6506
            // Boss list is the same, nothing changed.
6507
            if ($bossList == $previousBossList) {
6508
                return false;
6509
            }
6510
6511
            $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6512
            self::removeAllBossFromStudent($studentId);
6513
6514
            foreach ($bossList as $bossId) {
6515
                $bossId = (int) $bossId;
6516
                $bossInfo = api_get_user_info($bossId);
6517
6518
                if (empty($bossInfo)) {
6519
                    continue;
6520
                }
6521
6522
                $bossLanguage = $bossInfo['language'];
6523
6524
                $sql = "INSERT IGNORE INTO $userRelUserTable (user_id, friend_user_id, relation_type)
6525
                        VALUES ($studentId, $bossId, ".USER_RELATION_TYPE_BOSS.")";
6526
                $insertId = Database::query($sql);
6527
6528
                if ($insertId) {
6529
                    if ($sendNotification) {
6530
                        $name = $studentInfo['complete_name'];
6531
                        $url = api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$studentId;
6532
                        $url = Display::url($url, $url);
6533
                        $subject = sprintf(
6534
                            get_lang('UserXHasBeenAssignedToBoss', false, $bossLanguage),
6535
                            $name
6536
                        );
6537
                        $message = sprintf(
6538
                            get_lang('UserXHasBeenAssignedToBossWithUrlX', false, $bossLanguage),
6539
                            $name,
6540
                            $url
6541
                        );
6542
                        MessageManager::send_message_simple(
6543
                            $bossId,
6544
                            $subject,
6545
                            $message
6546
                        );
6547
                    }
6548
                    $inserted++;
6549
                }
6550
            }
6551
        } else {
6552
            self::removeAllBossFromStudent($studentId);
6553
        }
6554
6555
        return $inserted;
6556
    }
6557
6558
    /**
6559
     * Get users followed by student boss.
6560
     *
6561
     * @param int    $userId
6562
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
6563
     * @param bool   $getOnlyUserId
6564
     * @param bool   $getSql
6565
     * @param bool   $getCount
6566
     * @param int    $from
6567
     * @param int    $numberItems
6568
     * @param int    $column
6569
     * @param string $direction
6570
     * @param int    $active
6571
     * @param string $lastConnectionDate
6572
     *
6573
     * @return array users
6574
     */
6575
    public static function getUsersFollowedByStudentBoss(
6576
        $userId,
6577
        $userStatus = 0,
6578
        $getOnlyUserId = false,
6579
        $getSql = false,
6580
        $getCount = false,
6581
        $from = null,
6582
        $numberItems = null,
6583
        $column = null,
6584
        $direction = null,
6585
        $active = null,
6586
        $lastConnectionDate = null
6587
    ) {
6588
        return self::getUsersFollowedByUser(
6589
            $userId,
6590
            $userStatus,
6591
            $getOnlyUserId,
6592
            $getSql,
6593
            $getCount,
6594
            $from,
6595
            $numberItems,
6596
            $column,
6597
            $direction,
6598
            $active,
6599
            $lastConnectionDate,
6600
            STUDENT_BOSS
6601
        );
6602
    }
6603
6604
    /**
6605
     * @return array
6606
     */
6607
    public static function getOfficialCodeGrouped()
6608
    {
6609
        $user = Database::get_main_table(TABLE_MAIN_USER);
6610
        $sql = "SELECT DISTINCT official_code
6611
                FROM $user
6612
                GROUP BY official_code";
6613
        $result = Database::query($sql);
6614
        $values = Database::store_result($result, 'ASSOC');
6615
        $result = [];
6616
        foreach ($values as $value) {
6617
            $result[$value['official_code']] = $value['official_code'];
6618
        }
6619
6620
        return $result;
6621
    }
6622
6623
    /**
6624
     * @param string $officialCode
6625
     *
6626
     * @return array
6627
     */
6628
    public static function getUsersByOfficialCode($officialCode)
6629
    {
6630
        $user = Database::get_main_table(TABLE_MAIN_USER);
6631
        $officialCode = Database::escape_string($officialCode);
6632
6633
        $sql = "SELECT DISTINCT id
6634
                FROM $user
6635
                WHERE official_code = '$officialCode'
6636
                ";
6637
        $result = Database::query($sql);
6638
6639
        $users = [];
6640
        while ($row = Database::fetch_array($result)) {
6641
            $users[] = $row['id'];
6642
        }
6643
6644
        return $users;
6645
    }
6646
6647
    /**
6648
     * Calc the expended time (in seconds) by a user in a course.
6649
     *
6650
     * @param int    $userId    The user id
6651
     * @param int    $courseId  The course id
6652
     * @param int    $sessionId Optional. The session id
6653
     * @param string $from      Optional. From date
6654
     * @param string $until     Optional. Until date
6655
     *
6656
     * @return int The time
6657
     */
6658
    public static function getTimeSpentInCourses(
6659
        $userId,
6660
        $courseId,
6661
        $sessionId = 0,
6662
        $from = '',
6663
        $until = ''
6664
    ) {
6665
        $userId = (int) $userId;
6666
        $sessionId = (int) $sessionId;
6667
6668
        $trackCourseAccessTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
6669
        $whereConditions = [
6670
            'user_id = ? ' => $userId,
6671
            'AND c_id = ? ' => $courseId,
6672
            'AND session_id = ? ' => $sessionId,
6673
        ];
6674
6675
        if (!empty($from) && !empty($until)) {
6676
            $whereConditions["AND (login_course_date >= '?' "] = $from;
6677
            $whereConditions["AND logout_course_date <= DATE_ADD('?', INTERVAL 1 DAY)) "] = $until;
6678
        }
6679
6680
        $trackResult = Database::select(
6681
            'SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as total_time',
6682
            $trackCourseAccessTable,
6683
            [
6684
                'where' => $whereConditions,
6685
            ],
6686
            'first'
6687
        );
6688
6689
        if ($trackResult != false) {
6690
            return $trackResult['total_time'] ? $trackResult['total_time'] : 0;
6691
        }
6692
6693
        return 0;
6694
    }
6695
6696
    /**
6697
     * Get the boss user ID from a followed user id.
6698
     *
6699
     * @param $userId
6700
     *
6701
     * @return bool
6702
     */
6703
    public static function getFirstStudentBoss($userId)
6704
    {
6705
        $userId = (int) $userId;
6706
        if ($userId > 0) {
6707
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6708
            $row = Database::select(
6709
                'DISTINCT friend_user_id AS boss_id',
6710
                $userRelTable,
6711
                [
6712
                    'where' => [
6713
                        'user_id = ? AND relation_type = ? LIMIT 1' => [
6714
                            $userId,
6715
                            USER_RELATION_TYPE_BOSS,
6716
                        ],
6717
                    ],
6718
                ]
6719
            );
6720
            if (!empty($row)) {
6721
                return $row[0]['boss_id'];
6722
            }
6723
        }
6724
6725
        return false;
6726
    }
6727
6728
    /**
6729
     * Get the boss user ID from a followed user id.
6730
     *
6731
     * @param int $userId student id
6732
     *
6733
     * @return array
6734
     */
6735
    public static function getStudentBossList($userId)
6736
    {
6737
        $userId = (int) $userId;
6738
6739
        if ($userId > 0) {
6740
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6741
6742
            return Database::select(
6743
                'DISTINCT friend_user_id AS boss_id',
6744
                $userRelTable,
6745
                [
6746
                    'where' => [
6747
                        'user_id = ? AND relation_type = ? ' => [
6748
                            $userId,
6749
                            USER_RELATION_TYPE_BOSS,
6750
                        ],
6751
                    ],
6752
                ]
6753
            );
6754
        }
6755
6756
        return [];
6757
    }
6758
6759
    /**
6760
     * @param int $bossId
6761
     * @param int $studentId
6762
     *
6763
     * @return bool
6764
     */
6765
    public static function userIsBossOfStudent($bossId, $studentId)
6766
    {
6767
        $result = false;
6768
        $bossList = self::getStudentBossList($studentId);
6769
        if (!empty($bossList)) {
6770
            $bossList = array_column($bossList, 'boss_id');
6771
            if (in_array($bossId, $bossList)) {
6772
                $result = true;
6773
            }
6774
        }
6775
6776
        return $result;
6777
    }
6778
6779
    /**
6780
     * Displays the name of the user and makes the link to the user profile.
6781
     *
6782
     * @param array $userInfo
6783
     *
6784
     * @return string
6785
     */
6786
    public static function getUserProfileLink($userInfo)
6787
    {
6788
        if (isset($userInfo) && isset($userInfo['user_id'])) {
6789
            return Display::url(
6790
                $userInfo['complete_name_with_username'],
6791
                $userInfo['profile_url']
6792
            );
6793
        }
6794
6795
        return get_lang('Anonymous');
6796
    }
6797
6798
    /**
6799
     * Get users whose name matches $firstname and $lastname.
6800
     *
6801
     * @param string $firstname Firstname to search
6802
     * @param string $lastname  Lastname to search
6803
     *
6804
     * @return array The user list
6805
     */
6806
    public static function getUsersByName($firstname, $lastname)
6807
    {
6808
        $firstname = Database::escape_string($firstname);
6809
        $lastname = Database::escape_string($lastname);
6810
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
6811
6812
        $sql = <<<SQL
6813
            SELECT id, username, lastname, firstname
6814
            FROM $userTable
6815
            WHERE
6816
                firstname LIKE '$firstname%' AND
6817
                lastname LIKE '$lastname%'
6818
SQL;
6819
        $result = Database::query($sql);
6820
        $users = [];
6821
        while ($resultData = Database::fetch_object($result)) {
6822
            $users[] = $resultData;
6823
        }
6824
6825
        return $users;
6826
    }
6827
6828
    /**
6829
     * @param int $optionSelected
6830
     *
6831
     * @return string
6832
     */
6833
    public static function getUserSubscriptionTab($optionSelected = 1)
6834
    {
6835
        $allowAdmin = api_get_setting('allow_user_course_subscription_by_course_admin');
6836
        if (($allowAdmin === 'true' && api_is_allowed_to_edit()) ||
6837
            api_is_platform_admin()
6838
        ) {
6839
            $userPath = api_get_path(WEB_CODE_PATH).'user/';
6840
6841
            $headers = [
6842
                [
6843
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.STUDENT,
6844
                    'content' => get_lang('Students'),
6845
                ],
6846
                [
6847
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.COURSEMANAGER,
6848
                    'content' => get_lang('Teachers'),
6849
                ],
6850
                /*[
6851
                    'url' => $userPath.'subscribe_user.php?'.api_get_cidreq(),
6852
                    'content' => get_lang('Students'),
6853
                ],
6854
                [
6855
                    'url' => $userPath.'subscribe_user.php?type=teacher&'.api_get_cidreq(),
6856
                    'content' => get_lang('Teachers'),
6857
                ],*/
6858
                [
6859
                    'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
6860
                    'content' => get_lang('Groups'),
6861
                ],
6862
                'classes' => [
6863
                    'url' => $userPath.'class.php?'.api_get_cidreq(),
6864
                    'content' => get_lang('Classes'),
6865
                ],
6866
            ];
6867
6868
            if (api_get_configuration_value('session_classes_tab_disable')
6869
                && !api_is_platform_admin()
6870
                && api_get_session_id()
6871
            ) {
6872
                unset($headers['classes']);
6873
            }
6874
6875
            return Display::tabsOnlyLink($headers, $optionSelected);
6876
        }
6877
6878
        return '';
6879
    }
6880
6881
    /**
6882
     * Make sure this function is protected because it does NOT check password!
6883
     *
6884
     * This function defines globals.
6885
     *
6886
     * @param int  $userId
6887
     * @param bool $checkIfUserCanLoginAs
6888
     *
6889
     * @return bool
6890
     *
6891
     * @author Evie Embrechts
6892
     * @author Yannick Warnier <[email protected]>
6893
     */
6894
    public static function loginAsUser($userId, $checkIfUserCanLoginAs = true)
6895
    {
6896
        $userId = (int) $userId;
6897
        $userInfo = api_get_user_info($userId);
6898
6899
        // Check if the user is allowed to 'login_as'
6900
        $canLoginAs = true;
6901
        if ($checkIfUserCanLoginAs) {
6902
            $canLoginAs = api_can_login_as($userId);
6903
        }
6904
6905
        if (!$canLoginAs || empty($userInfo)) {
6906
            return false;
6907
        }
6908
6909
        if ($userId) {
6910
            $logInfo = [
6911
                'tool' => 'logout',
6912
                'tool_id' => 0,
6913
                'tool_id_detail' => 0,
6914
                'action' => '',
6915
                'info' => 'Change user (login as)',
6916
            ];
6917
            Event::registerLog($logInfo);
6918
6919
            // Logout the current user
6920
            self::loginDelete(api_get_user_id());
6921
6922
            Session::erase('_user');
6923
            Session::erase('is_platformAdmin');
6924
            Session::erase('is_allowedCreateCourse');
6925
            Session::erase('_uid');
6926
6927
            // Cleaning session variables
6928
            $_user['firstName'] = $userInfo['firstname'];
6929
            $_user['lastName'] = $userInfo['lastname'];
6930
            $_user['mail'] = $userInfo['email'];
6931
            $_user['official_code'] = $userInfo['official_code'];
6932
            $_user['picture_uri'] = $userInfo['picture_uri'];
6933
            $_user['user_id'] = $userId;
6934
            $_user['id'] = $userId;
6935
            $_user['status'] = $userInfo['status'];
6936
6937
            // Filling session variables with new data
6938
            Session::write('_uid', $userId);
6939
            Session::write('_user', $userInfo);
6940
            Session::write('is_platformAdmin', (bool) self::is_admin($userId));
6941
            Session::write('is_allowedCreateCourse', $userInfo['status'] == 1);
6942
            // will be useful later to know if the user is actually an admin or not (example reporting)
6943
            Session::write('login_as', true);
6944
            $logInfo = [
6945
                'tool' => 'login',
6946
                'tool_id' => 0,
6947
                'tool_id_detail' => 0,
6948
                'info' => $userId,
6949
            ];
6950
            Event::registerLog($logInfo);
6951
6952
            return true;
6953
        }
6954
6955
        return false;
6956
    }
6957
6958
    /**
6959
     * Remove all login records from the track_e_online stats table,
6960
     * for the given user ID.
6961
     *
6962
     * @param int $userId User ID
6963
     */
6964
    public static function loginDelete($userId)
6965
    {
6966
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
6967
        $userId = (int) $userId;
6968
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
6969
        Database::query($query);
6970
    }
6971
6972
    /**
6973
     * Login as first admin user registered in the platform.
6974
     *
6975
     * @return array
6976
     */
6977
    public static function logInAsFirstAdmin()
6978
    {
6979
        $adminList = self::get_all_administrators();
6980
6981
        if (!empty($adminList)) {
6982
            $userInfo = current($adminList);
6983
            if (!empty($userInfo)) {
6984
                $result = self::loginAsUser($userInfo['user_id'], false);
6985
                if ($result && api_is_platform_admin()) {
6986
                    return api_get_user_info();
6987
                }
6988
            }
6989
        }
6990
6991
        return [];
6992
    }
6993
6994
    public static function blockIfMaxLoginAttempts(array $userInfo)
6995
    {
6996
        if (false === (bool) $userInfo['active'] || null === $userInfo['last_login']) {
6997
            return;
6998
        }
6999
7000
        $maxAllowed = (int) api_get_configuration_value('login_max_attempt_before_blocking_account');
7001
7002
        if ($maxAllowed <= 0) {
7003
            return;
7004
        }
7005
7006
        $em = Database::getManager();
7007
7008
        $countFailedAttempts = $em
7009
            ->getRepository(TrackELoginAttempt::class)
7010
            ->createQueryBuilder('la')
7011
            ->select('COUNT(la)')
7012
            ->where('la.username = :username')
7013
            ->andWhere('la.loginDate >= :last_login')
7014
            ->andWhere('la.success <> TRUE')
7015
            ->setParameters(
7016
                [
7017
                    'username' => $userInfo['username'],
7018
                    'last_login' => $userInfo['last_login'],
7019
                ]
7020
            )
7021
            ->getQuery()
7022
            ->getSingleScalarResult()
7023
        ;
7024
7025
        if ($countFailedAttempts >= $maxAllowed) {
7026
            Database::update(
7027
                Database::get_main_table(TABLE_MAIN_USER),
7028
                ['active' => false],
7029
                ['username = ?' => $userInfo['username']]
7030
            );
7031
7032
            Display::addFlash(
7033
                Display::return_message(
7034
                    sprintf(
7035
                        get_lang('XAccountDisabledByYAttempts'),
7036
                        $userInfo['username'],
7037
                        $countFailedAttempts
7038
                    ),
7039
                    'error',
7040
                    false
7041
                )
7042
            );
7043
        }
7044
    }
7045
7046
    /**
7047
     * Check if user is teacher of a student based in their courses.
7048
     *
7049
     * @param $teacherId
7050
     * @param $studentId
7051
     *
7052
     * @return array
7053
     */
7054
    public static function getCommonCoursesBetweenTeacherAndStudent($teacherId, $studentId)
7055
    {
7056
        $courses = CourseManager::getCoursesFollowedByUser(
7057
            $teacherId,
7058
            COURSEMANAGER
7059
        );
7060
        if (empty($courses)) {
7061
            return false;
7062
        }
7063
7064
        $coursesFromUser = CourseManager::get_courses_list_by_user_id($studentId);
7065
        if (empty($coursesFromUser)) {
7066
            return false;
7067
        }
7068
7069
        $coursesCodeList = array_column($courses, 'code');
7070
        $coursesCodeFromUserList = array_column($coursesFromUser, 'code');
7071
        $commonCourses = array_intersect($coursesCodeList, $coursesCodeFromUserList);
7072
        $commonCourses = array_filter($commonCourses);
7073
7074
        if (!empty($commonCourses)) {
7075
            return $commonCourses;
7076
        }
7077
7078
        return [];
7079
    }
7080
7081
    /**
7082
     * @param int $teacherId
7083
     * @param int $studentId
7084
     *
7085
     * @return bool
7086
     */
7087
    public static function isTeacherOfStudent($teacherId, $studentId)
7088
    {
7089
        $courses = self::getCommonCoursesBetweenTeacherAndStudent(
7090
            $teacherId,
7091
            $studentId
7092
        );
7093
7094
        if (!empty($courses)) {
7095
            return true;
7096
        }
7097
7098
        return false;
7099
    }
7100
7101
    /**
7102
     * Send user confirmation mail.
7103
     *
7104
     * @throws Exception
7105
     */
7106
    public static function sendUserConfirmationMail(User $user)
7107
    {
7108
        $uniqueId = api_get_unique_id();
7109
        $user->setConfirmationToken($uniqueId);
7110
7111
        Database::getManager()->persist($user);
7112
        Database::getManager()->flush();
7113
7114
        $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
7115
7116
        // Check if the user was originally set for an automated subscription to a course or session
7117
        $courseCodeToRedirect = Session::read('course_redirect');
7118
        $sessionToRedirect = Session::read('session_redirect');
7119
        if (!empty($courseCodeToRedirect)) {
7120
            $url .= '&c='.$courseCodeToRedirect;
7121
        }
7122
        if (!empty($sessionToRedirect)) {
7123
            $url .= '&s='.$sessionToRedirect;
7124
        }
7125
        $mailSubject = get_lang('RegistrationConfirmation');
7126
        $mailBody = get_lang('RegistrationConfirmationEmailMessage')
7127
            .PHP_EOL
7128
            .Display::url($url, $url);
7129
7130
        api_mail_html(
7131
            self::formatUserFullName($user),
7132
            $user->getEmail(),
7133
            $mailSubject,
7134
            $mailBody
7135
        );
7136
        Display::addFlash(Display::return_message(get_lang('CheckYourEmailAndFollowInstructions')));
7137
    }
7138
7139
    /**
7140
     * Anonymize a user. Replace personal info by anonymous info.
7141
     *
7142
     * @param int  $userId   User id
7143
     * @param bool $deleteIP Whether to replace the IP address in logs tables by 127.0.0.1 or to leave as is
7144
     *
7145
     * @throws \Exception
7146
     *
7147
     * @return bool
7148
     * @assert (0) === false
7149
     */
7150
    public static function anonymize($userId, $deleteIP = true)
7151
    {
7152
        global $debug;
7153
7154
        $userId = (int) $userId;
7155
7156
        if (empty($userId)) {
7157
            return false;
7158
        }
7159
7160
        $em = Database::getManager();
7161
        $user = api_get_user_entity($userId);
7162
        $uniqueId = uniqid('anon', true);
7163
        $user
7164
            ->setFirstname($uniqueId)
7165
            ->setLastname($uniqueId)
7166
            ->setBiography('')
7167
            ->setAddress('')
7168
            ->setCurriculumItems(null)
7169
            ->setDateOfBirth(null)
7170
            ->setCompetences('')
7171
            ->setDiplomas('')
7172
            ->setOpenarea('')
7173
            ->setTeach('')
7174
            ->setProductions(null)
7175
            ->setOpenid('')
7176
            ->setEmailCanonical($uniqueId.'@example.com')
7177
            ->setEmail($uniqueId.'@example.com')
7178
            ->setUsername($uniqueId)
7179
            ->setUsernameCanonical($uniqueId)
7180
            ->setPhone('')
7181
            ->setOfficialCode('')
7182
        ;
7183
7184
        self::deleteUserPicture($userId);
7185
        self::cleanUserRequestsOfRemoval($userId);
7186
7187
        // The IP address is a border-case personal data, as it does
7188
        // not directly allow for personal identification (it is not
7189
        // a completely safe value in most countries - the IP could
7190
        // be used by neighbours and crackers)
7191
        if ($deleteIP) {
7192
            $substitute = '127.0.0.1';
7193
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
7194
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE access_user_id = $userId";
7195
            $res = Database::query($sql);
7196
            if ($res === false && $debug > 0) {
7197
                error_log("Could not anonymize IP address for user $userId ($sql)");
7198
            }
7199
7200
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
7201
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7202
            $res = Database::query($sql);
7203
            if ($res === false && $debug > 0) {
7204
                error_log("Could not anonymize IP address for user $userId ($sql)");
7205
            }
7206
7207
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7208
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE exe_user_id = $userId";
7209
            $res = Database::query($sql);
7210
            if ($res === false && $debug > 0) {
7211
                error_log("Could not anonymize IP address for user $userId ($sql)");
7212
            }
7213
7214
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
7215
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
7216
            $res = Database::query($sql);
7217
            if ($res === false && $debug > 0) {
7218
                error_log("Could not anonymize IP address for user $userId ($sql)");
7219
            }
7220
7221
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
7222
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
7223
            $res = Database::query($sql);
7224
            if ($res === false && $debug > 0) {
7225
                error_log("Could not anonymize IP address for user $userId ($sql)");
7226
            }
7227
7228
            $table = Database::get_course_table(TABLE_WIKI);
7229
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7230
            $res = Database::query($sql);
7231
            if ($res === false && $debug > 0) {
7232
                error_log("Could not anonymize IP address for user $userId ($sql)");
7233
            }
7234
7235
            $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
7236
            $sql = "UPDATE $table set ip_address = '$substitute' WHERE sys_insert_user_id = $userId";
7237
            $res = Database::query($sql);
7238
            if ($res === false && $debug > 0) {
7239
                error_log("Could not anonymize IP address for user $userId ($sql)");
7240
            }
7241
7242
            $table = Database::get_course_table(TABLE_WIKI);
7243
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7244
            $res = Database::query($sql);
7245
            if ($res === false && $debug > 0) {
7246
                error_log("Could not anonymize IP address for user $userId ($sql)");
7247
            }
7248
        }
7249
        $em->persist($user);
7250
        $em->flush($user);
7251
        Event::addEvent(LOG_USER_ANONYMIZE, LOG_USER_ID, $userId);
7252
7253
        return true;
7254
    }
7255
7256
    /**
7257
     * @param int $userId
7258
     *
7259
     * @throws Exception
7260
     *
7261
     * @return string
7262
     */
7263
    public static function anonymizeUserWithVerification($userId)
7264
    {
7265
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
7266
7267
        $message = '';
7268
        if (api_is_platform_admin() ||
7269
            ($allowDelete && api_is_session_admin())
7270
        ) {
7271
            $userToUpdateInfo = api_get_user_info($userId);
7272
            $currentUserId = api_get_user_id();
7273
7274
            if ($userToUpdateInfo &&
7275
                api_global_admin_can_edit_admin($userId, null, $allowDelete)
7276
            ) {
7277
                if ($userId != $currentUserId &&
7278
                    self::anonymize($userId)
7279
                ) {
7280
                    $message = Display::return_message(
7281
                        sprintf(get_lang('UserXAnonymized'), $userToUpdateInfo['complete_name_with_username']),
7282
                        'confirmation'
7283
                    );
7284
                } else {
7285
                    $message = Display::return_message(
7286
                        sprintf(get_lang('CannotAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
7287
                        'error'
7288
                    );
7289
                }
7290
            } else {
7291
                $message = Display::return_message(
7292
                    sprintf(get_lang('NoPermissionToAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
7293
                    'error'
7294
                );
7295
            }
7296
        }
7297
7298
        return $message;
7299
    }
7300
7301
    /**
7302
     * @param int $userId
7303
     *
7304
     * @throws Exception
7305
     *
7306
     * @return string
7307
     */
7308
    public static function deleteUserWithVerification($userId)
7309
    {
7310
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
7311
        $message = Display::return_message(get_lang('CannotDeleteUser'), 'error');
7312
        $userToUpdateInfo = api_get_user_info($userId);
7313
7314
        // User must exist.
7315
        if (empty($userToUpdateInfo)) {
7316
            return $message;
7317
        }
7318
7319
        $currentUserId = api_get_user_id();
7320
7321
        // Cannot delete myself.
7322
        if ($userId == $currentUserId) {
7323
            return $message;
7324
        }
7325
7326
        if (api_is_platform_admin() ||
7327
            ($allowDelete && api_is_session_admin())
7328
        ) {
7329
            if (api_global_admin_can_edit_admin($userId, null, $allowDelete)) {
7330
                if (self::delete_user($userId)) {
7331
                    $message = Display::return_message(
7332
                        get_lang('UserDeleted').': '.$userToUpdateInfo['complete_name_with_username'],
7333
                        'confirmation'
7334
                    );
7335
                } else {
7336
                    $message = Display::return_message(get_lang('CannotDeleteUserBecauseOwnsCourse'), 'error');
7337
                }
7338
            }
7339
        }
7340
7341
        return $message;
7342
    }
7343
7344
    /**
7345
     * @return array
7346
     */
7347
    public static function createDataPrivacyExtraFields()
7348
    {
7349
        self::create_extra_field(
7350
            'request_for_legal_agreement_consent_removal_justification',
7351
            1, //text
7352
            'Request for legal agreement consent removal justification	',
7353
            ''
7354
        );
7355
7356
        self::create_extra_field(
7357
            'request_for_delete_account_justification',
7358
            1, //text
7359
            'Request for delete account justification',
7360
            ''
7361
        );
7362
7363
        $extraFieldId = self::create_extra_field(
7364
            'request_for_legal_agreement_consent_removal',
7365
            1, //text
7366
            'Request for legal agreement consent removal',
7367
            ''
7368
        );
7369
7370
        $extraFieldIdDeleteAccount = self::create_extra_field(
7371
            'request_for_delete_account',
7372
            1, //text
7373
            'Request for delete user account',
7374
            ''
7375
        );
7376
7377
        return [
7378
            'delete_account_extra_field' => $extraFieldIdDeleteAccount,
7379
            'delete_legal' => $extraFieldId,
7380
        ];
7381
    }
7382
7383
    /**
7384
     * @param int $userId
7385
     */
7386
    public static function cleanUserRequestsOfRemoval($userId)
7387
    {
7388
        $userId = (int) $userId;
7389
7390
        $extraFieldValue = new ExtraFieldValue('user');
7391
        $extraFieldsToDelete = [
7392
            'legal_accept',
7393
            'request_for_legal_agreement_consent_removal',
7394
            'request_for_legal_agreement_consent_removal_justification',
7395
            'request_for_delete_account_justification', // just in case delete also this
7396
            'request_for_delete_account',
7397
        ];
7398
7399
        foreach ($extraFieldsToDelete as $variable) {
7400
            $value = $extraFieldValue->get_values_by_handler_and_field_variable(
7401
                $userId,
7402
                $variable
7403
            );
7404
            if ($value && isset($value['id'])) {
7405
                $extraFieldValue->delete($value['id']);
7406
            }
7407
        }
7408
    }
7409
7410
    /**
7411
     * @param int $searchYear
7412
     *
7413
     * @throws Exception
7414
     *
7415
     * @return array
7416
     */
7417
    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
7418
    {
7419
        $timezone = new DateTimeZone(api_get_timezone());
7420
7421
        $sessions = [];
7422
        if (DRH == $userInfo['status']) {
7423
            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
7424
        } elseif (api_is_platform_admin(true)) {
7425
            $sessions = SessionManager::getSessionsForAdmin($userInfo['id']);
7426
        } else {
7427
            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
7428
            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
7429
7430
            foreach ($sessionsByCategory as $sessionsInCategory) {
7431
                $sessions = array_merge($sessions, $sessionsInCategory);
7432
            }
7433
        }
7434
7435
        $sessions = array_map(
7436
            function ($sessionInfo) {
7437
                if (!isset($sessionInfo['session_id'])) {
7438
                    $sessionInfo['session_id'] = $sessionInfo['id'];
7439
                }
7440
                if (!isset($sessionInfo['session_name'])) {
7441
                    $sessionInfo['session_name'] = $sessionInfo['name'];
7442
                }
7443
7444
                return $sessionInfo;
7445
            },
7446
            $sessions
7447
        );
7448
7449
        $calendarSessions = [];
7450
7451
        foreach ($sessions as $sessionInfo) {
7452
            if (!empty($sessionInfo['duration'])) {
7453
                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
7454
                    $sessionInfo['session_id'],
7455
                    $userInfo['id']
7456
                );
7457
7458
                if (empty($courseAccess)) {
7459
                    continue;
7460
                }
7461
7462
                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
7463
                $lastAccessDate = clone $firstAcessDate;
7464
                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
7465
7466
                $firstAccessYear = (int) $firstAcessDate->format('Y');
7467
                $lastAccessYear = (int) $lastAccessDate->format('Y');
7468
7469
                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
7470
                    $calendarSessions[$sessionInfo['session_id']] = [
7471
                        'name' => $sessionInfo['session_name'],
7472
                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
7473
                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
7474
                    ];
7475
                }
7476
7477
                continue;
7478
            }
7479
7480
            $accessStartDate = !empty($sessionInfo['access_start_date'])
7481
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
7482
                : null;
7483
            $accessEndDate = !empty($sessionInfo['access_end_date'])
7484
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
7485
                : null;
7486
            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
7487
            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
7488
7489
            $isValid = false;
7490
7491
            if ($accessStartYear && $accessEndYear) {
7492
                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
7493
                    $isValid = true;
7494
                }
7495
            }
7496
7497
            if ($accessStartYear && !$accessEndYear) {
7498
                if ($accessStartYear == $searchYear) {
7499
                    $isValid = true;
7500
                }
7501
            }
7502
7503
            if (!$accessStartYear && $accessEndYear) {
7504
                if ($accessEndYear == $searchYear) {
7505
                    $isValid = true;
7506
                }
7507
            }
7508
7509
            if ($isValid) {
7510
                $calendarSessions[$sessionInfo['session_id']] = [
7511
                    'name' => $sessionInfo['session_name'],
7512
                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
7513
                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
7514
                ];
7515
            }
7516
        }
7517
7518
        return $calendarSessions;
7519
    }
7520
7521
    /**
7522
     * Get sessions info for planification calendar.
7523
     *
7524
     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
7525
     * @param int   $searchYear
7526
     *
7527
     * @throws Exception
7528
     *
7529
     * @return array
7530
     */
7531
    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
7532
    {
7533
        $timezone = new DateTimeZone(api_get_timezone());
7534
        $calendar = [];
7535
7536
        foreach ($sessionsList as $sessionId => $sessionInfo) {
7537
            $startDate = $sessionInfo['access_start_date']
7538
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
7539
                : null;
7540
            $endDate = $sessionInfo['access_end_date']
7541
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
7542
                : null;
7543
7544
            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
7545
            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
7546
            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
7547
            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
7548
            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
7549
            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
7550
7551
            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
7552
            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
7553
7554
            $calendar[] = [
7555
                'id' => $sessionId,
7556
                'name' => $sessionInfo['name'],
7557
                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
7558
                'start_in_last_year' => $startYear < $searchYear,
7559
                'end_in_next_year' => $endYear > $searchYear,
7560
                'no_start' => !$startWeek,
7561
                'no_end' => !$endWeek,
7562
                'start' => $start,
7563
                'duration' => $duration > 0 ? $duration : 1,
7564
            ];
7565
        }
7566
7567
        usort(
7568
            $calendar,
7569
            function ($sA, $sB) {
7570
                if ($sA['start'] == $sB['start']) {
7571
                    return 0;
7572
                }
7573
7574
                if ($sA['start'] < $sB['start']) {
7575
                    return -1;
7576
                }
7577
7578
                return 1;
7579
            }
7580
        );
7581
7582
        return $calendar;
7583
    }
7584
7585
    /**
7586
     * Return the user's full name. Optionally with the username.
7587
     *
7588
     * @param bool $includeUsername Optional. By default username is not included.
7589
     *
7590
     * @return string
7591
     */
7592
    public static function formatUserFullName(User $user, $includeUsername = false)
7593
    {
7594
        $fullName = api_get_person_name($user->getFirstname(), $user->getLastname());
7595
7596
        if ($includeUsername && api_get_configuration_value('hide_username_with_complete_name') !== true) {
7597
            $username = $user->getUsername();
7598
7599
            return "$fullName ($username)";
7600
        }
7601
7602
        return $fullName;
7603
    }
7604
7605
    /**
7606
     * @param int $userId
7607
     *
7608
     * @return array
7609
     */
7610
    public static function getUserCareers($userId)
7611
    {
7612
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7613
        $tableCareer = Database::get_main_table(TABLE_CAREER);
7614
        $userId = (int) $userId;
7615
7616
        $sql = "SELECT c.id, c.name
7617
                FROM $table uc
7618
                INNER JOIN $tableCareer c
7619
                ON uc.career_id = c.id
7620
                WHERE user_id = $userId
7621
                ORDER BY uc.created_at
7622
                ";
7623
        $result = Database::query($sql);
7624
7625
        return Database::store_result($result, 'ASSOC');
7626
    }
7627
7628
    /**
7629
     * @param int $userId
7630
     * @param int $careerId
7631
     */
7632
    public static function addUserCareer($userId, $careerId)
7633
    {
7634
        if (!api_get_configuration_value('allow_career_users')) {
7635
            return false;
7636
        }
7637
7638
        if (self::userHasCareer($userId, $careerId) === false) {
7639
            $params = [
7640
                'user_id' => $userId,
7641
                'career_id' => $careerId,
7642
                'created_at' => api_get_utc_datetime(),
7643
                'updated_at' => api_get_utc_datetime(),
7644
            ];
7645
            $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7646
            Database::insert($table, $params);
7647
        }
7648
7649
        return true;
7650
    }
7651
7652
    /**
7653
     * @param int   $userCareerId
7654
     * @param array $data
7655
     *
7656
     * @return bool
7657
     */
7658
    public static function updateUserCareer($userCareerId, $data)
7659
    {
7660
        if (!api_get_configuration_value('allow_career_users')) {
7661
            return false;
7662
        }
7663
7664
        $params = ['extra_data' => $data, 'updated_at' => api_get_utc_datetime()];
7665
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7666
        Database::update(
7667
            $table,
7668
            $params,
7669
            ['id = ?' => (int) $userCareerId]
7670
        );
7671
7672
        return true;
7673
    }
7674
7675
    /**
7676
     * @param int $userId
7677
     * @param int $careerId
7678
     *
7679
     * @return array
7680
     */
7681
    public static function getUserCareer($userId, $careerId)
7682
    {
7683
        $userId = (int) $userId;
7684
        $careerId = (int) $careerId;
7685
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7686
7687
        $sql = "SELECT * FROM $table WHERE user_id = $userId AND career_id = $careerId";
7688
        $result = Database::query($sql);
7689
7690
        return Database::fetch_array($result, 'ASSOC');
7691
    }
7692
7693
    /**
7694
     * @param int $userId
7695
     * @param int $careerId
7696
     *
7697
     * @return bool
7698
     */
7699
    public static function userHasCareer($userId, $careerId)
7700
    {
7701
        $userId = (int) $userId;
7702
        $careerId = (int) $careerId;
7703
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7704
7705
        $sql = "SELECT id FROM $table WHERE user_id = $userId AND career_id = $careerId";
7706
        $result = Database::query($sql);
7707
7708
        return Database::num_rows($result) > 0;
7709
    }
7710
7711
    /**
7712
     * @param int $userId
7713
     *
7714
     * @throws Exception
7715
     */
7716
    public static function deleteUserFiles($userId)
7717
    {
7718
        $path = self::getUserPathById($userId, 'system');
7719
7720
        $fs = new Filesystem();
7721
        $fs->remove($path);
7722
    }
7723
7724
    public static function redirectToResetPassword($userId)
7725
    {
7726
        $forceRenew = api_get_configuration_value('force_renew_password_at_first_login');
7727
7728
        if ($forceRenew) {
7729
            $askPassword = self::get_extra_user_data_by_field(
7730
                $userId,
7731
                'ask_new_password'
7732
            );
7733
7734
            if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
7735
                1 === (int) $askPassword['ask_new_password']
7736
            ) {
7737
                $uniqueId = api_get_unique_id();
7738
                $userObj = api_get_user_entity($userId);
7739
7740
                $userObj->setConfirmationToken($uniqueId);
7741
                $userObj->setPasswordRequestedAt(new \DateTime());
7742
7743
                Database::getManager()->persist($userObj);
7744
                Database::getManager()->flush();
7745
7746
                $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
7747
                api_location($url);
7748
            }
7749
        }
7750
7751
        $forceRotateDays = api_get_configuration_value('security_password_rotate_days');
7752
        $forceRotate = false;
7753
7754
        if ($forceRotateDays > 0) {
7755
            // get the date of the last password update recorded
7756
            $lastUpdate = self::get_extra_user_data_by_field(
7757
                $userId,
7758
                'password_updated_at'
7759
            );
7760
7761
            if (empty($lastUpdate) or empty($lastUpdate['password_updated_at'])) {
7762
                $userObj = api_get_user_entity($userId);
7763
                $registrationDate = $userObj->getRegistrationDate();
7764
                $now = new \DateTime(null, new DateTimeZone('UTC'));
7765
                $interval = $now->diff($registrationDate);
7766
                $daysSince = $interval->format('%a');
7767
                if ($daysSince > $forceRotateDays) {
7768
                    $forceRotate = true;
7769
                }
7770
            } else {
7771
                $now = new \DateTime(null, new DateTimeZone('UTC'));
7772
                // In some cases, old records might contain an incomplete Y-m-d H:i:s format
7773
                if (strlen($lastUpdate['password_updated_at']) == 10) {
7774
                    $lastUpdate['password_updated_at'] .= ' 00:00:00';
7775
                }
7776
                if (strlen($lastUpdate['password_updated_at']) == 16) {
7777
                    $lastUpdate['password_updated_at'] .= ':00';
7778
                }
7779
                $date = \DateTime::createFromFormat('Y-m-d H:i:s', $lastUpdate['password_updated_at'], new DateTimeZone('UTC'));
7780
                $interval = $now->diff($date);
7781
                $daysSince = $interval->format('%a');
7782
                if ($daysSince > $forceRotateDays) {
7783
                    $forceRotate = true;
7784
                }
7785
            }
7786
            if ($forceRotate) {
7787
                $uniqueId = api_get_unique_id();
7788
                $userObj = api_get_user_entity($userId);
7789
7790
                $userObj->setConfirmationToken($uniqueId);
7791
                $userObj->setPasswordRequestedAt(new \DateTime());
7792
7793
                Database::getManager()->persist($userObj);
7794
                Database::getManager()->flush();
7795
7796
                $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId.'&rotate=1';
7797
                api_location($url);
7798
            }
7799
        }
7800
    }
7801
7802
    /**
7803
     * It returns the list of user status available.
7804
     *
7805
     * @return array
7806
     */
7807
    public static function getUserStatusList()
7808
    {
7809
        $userStatusConfig = [];
7810
        // it gets the roles to show in creation/edition user
7811
        if (true === api_get_configuration_value('user_status_show_options_enabled')) {
7812
            $userStatusConfig = api_get_configuration_value('user_status_show_option');
7813
        }
7814
        // it gets the roles to show in creation/edition user (only for admins)
7815
        if (true === api_get_configuration_value('user_status_option_only_for_admin_enabled') && api_is_platform_admin()) {
7816
            $userStatusConfig = api_get_configuration_value('user_status_option_show_only_for_admin');
7817
        }
7818
7819
        $status = [];
7820
        if (!empty($userStatusConfig)) {
7821
            $statusLang = api_get_status_langvars();
7822
            foreach ($userStatusConfig as $role => $enabled) {
7823
                if ($enabled) {
7824
                    $constStatus = constant($role);
7825
                    $status[$constStatus] = $statusLang[$constStatus];
7826
                }
7827
            }
7828
        } else {
7829
            $status[COURSEMANAGER] = get_lang('Teacher');
7830
            $status[STUDENT] = get_lang('Learner');
7831
            $status[DRH] = get_lang('Drh');
7832
            $status[SESSIONADMIN] = get_lang('SessionsAdmin');
7833
            $status[STUDENT_BOSS] = get_lang('RoleStudentBoss');
7834
            $status[INVITEE] = get_lang('Invitee');
7835
        }
7836
7837
        return $status;
7838
    }
7839
7840
    /**
7841
     * Get the expiration date by user status from configuration value.
7842
     *
7843
     * @param $status
7844
     *
7845
     * @throws Exception
7846
     *
7847
     * @return array
7848
     */
7849
    public static function getExpirationDateByRole($status)
7850
    {
7851
        $status = (int) $status;
7852
        $nbDaysByRole = api_get_configuration_value('user_number_of_days_for_default_expiration_date_per_role');
7853
        $dates = [];
7854
        if (!empty($nbDaysByRole)) {
7855
            $date = new DateTime();
7856
            foreach ($nbDaysByRole as $strVariable => $nDays) {
7857
                $constStatus = constant($strVariable);
7858
                if ($status == $constStatus) {
7859
                    $duration = "P{$nDays}D";
7860
                    $date->add(new DateInterval($duration));
7861
                    $newExpirationDate = $date->format('Y-m-d H:i');
7862
                    $formatted = api_format_date($newExpirationDate, DATE_TIME_FORMAT_LONG_24H);
7863
                    $dates = ['formatted' => $formatted, 'date' => $newExpirationDate];
7864
                }
7865
            }
7866
        }
7867
7868
        return $dates;
7869
    }
7870
7871
    public static function getAllowedRolesAsTeacher(): array
7872
    {
7873
        if (api_get_configuration_value('course_allow_student_role_to_be_teacher')) {
7874
            return [
7875
                STUDENT,
7876
                COURSEMANAGER,
7877
                SESSIONADMIN,
7878
            ];
7879
        } else {
7880
            return [
7881
                COURSEMANAGER,
7882
                SESSIONADMIN,
7883
            ];
7884
        }
7885
    }
7886
7887
    /**
7888
     * Count users in courses and if they have certificate.
7889
     * This function is resource intensive.
7890
     *
7891
     * @return array
7892
     */
7893
    public static function countUsersWhoFinishedCourses()
7894
    {
7895
        $courses = [];
7896
        $currentAccessUrlId = api_get_current_access_url_id();
7897
        $sql = "SELECT course.code, cru.user_id
7898
                FROM course_rel_user cru
7899
                    JOIN course ON cru.c_id = course.id
7900
                    JOIN access_url_rel_user auru on cru.user_id = auru.user_id
7901
                    JOIN access_url_rel_course ON course.id = access_url_rel_course.c_id
7902
                WHERE access_url_rel_course.access_url_id = $currentAccessUrlId
7903
                ORDER BY course.code
7904
        ";
7905
7906
        $res = Database::query($sql);
7907
        if (Database::num_rows($res) > 0) {
7908
            while ($row = Database::fetch_array($res)) {
7909
                if (!isset($courses[$row['code']])) {
7910
                    $courses[$row['code']] = [
7911
                        'subscribed' => 0,
7912
                        'finished' => 0,
7913
                    ];
7914
                }
7915
7916
                $courses[$row['code']]['subscribed']++;
7917
                $entityManager = Database::getManager();
7918
                $repository = $entityManager->getRepository('ChamiloCoreBundle:GradebookCategory');
7919
                //todo check when have more than 1 gradebook
7920
                /** @var \Chamilo\CoreBundle\Entity\GradebookCategory $gradebook */
7921
                $gradebook = $repository->findOneBy(['courseCode' => $row['code']]);
7922
7923
                if (!empty($gradebook)) {
7924
                    $finished = 0;
7925
                    $gb = Category::createCategoryObjectFromEntity($gradebook);
7926
                    $finished = $gb->is_certificate_available($row['user_id']);
7927
                    if (!empty($finished)) {
7928
                        $courses[$row['code']]['finished']++;
7929
                    }
7930
                }
7931
            }
7932
        }
7933
7934
        return $courses;
7935
    }
7936
7937
    /**
7938
     * Count users in sessions and if they have certificate.
7939
     * This function is resource intensive.
7940
     *
7941
     * @return array
7942
     */
7943
    public static function countUsersWhoFinishedCoursesInSessions()
7944
    {
7945
        $coursesInSessions = [];
7946
        $currentAccessUrlId = api_get_current_access_url_id();
7947
        $sql = "SELECT course.code, srcru.session_id, srcru.user_id, session.name
7948
                FROM session_rel_course_rel_user srcru
7949
                    JOIN course ON srcru.c_id = course.id
7950
                    JOIN access_url_rel_session aurs on srcru.session_id = aurs.session_id
7951
                    JOIN session ON srcru.session_id = session.id
7952
                WHERE aurs.access_url_id = $currentAccessUrlId
7953
                ORDER BY course.code, session.name
7954
        ";
7955
7956
        $res = Database::query($sql);
7957
        if (Database::num_rows($res) > 0) {
7958
            while ($row = Database::fetch_array($res)) {
7959
                $index = $row['code'].' ('.$row['name'].')';
7960
                if (!isset($coursesInSessions[$index])) {
7961
                    $coursesInSessions[$index] = [
7962
                        'subscribed' => 0,
7963
                        'finished' => 0,
7964
                    ];
7965
                }
7966
7967
                $coursesInSessions[$index]['subscribed']++;
7968
                $entityManager = Database::getManager();
7969
                $repository = $entityManager->getRepository('ChamiloCoreBundle:GradebookCategory');
7970
                /** @var \Chamilo\CoreBundle\Entity\GradebookCategory $gradebook */
7971
                $gradebook = $repository->findOneBy(
7972
                    [
7973
                        'courseCode' => $row['code'],
7974
                        'sessionId' => $row['session_id'],
7975
                    ]
7976
                );
7977
7978
                if (!empty($gradebook)) {
7979
                    $finished = 0;
7980
                    $gb = Category::createCategoryObjectFromEntity($gradebook);
7981
                    $finished = $gb->is_certificate_available($row['user_id']);
7982
                    if (!empty($finished)) {
7983
                        $coursesInSessions[$index]['finished']++;
7984
                    }
7985
                }
7986
            }
7987
        }
7988
7989
        return $coursesInSessions;
7990
    }
7991
7992
    /**
7993
     * Build the active-column of the table to lock or unlock a certain user
7994
     * lock = the user can no longer use this account.
7995
     *
7996
     * @author Patrick Cool <[email protected]>, Ghent University
7997
     *
7998
     * @return string Some HTML-code with the lock/unlock button
7999
     */
8000
    public static function getActiveFilterForTable(string $active, string $params, array $row): string
8001
    {
8002
        if ('1' == $active) {
8003
            $action = 'Lock';
8004
            $image = 'accept';
8005
        } elseif ('-1' == $active) {
8006
            $action = 'edit';
8007
            $image = 'warning';
8008
        } elseif ('0' == $active) {
8009
            $action = 'Unlock';
8010
            $image = 'error';
8011
        }
8012
8013
        if ('edit' === $action) {
8014
            $langAccountExpired = get_lang('AccountExpired');
8015
8016
            return Display::return_icon(
8017
                $image.'.png',
8018
                    $langAccountExpired,
8019
                [],
8020
                ICON_SIZE_TINY
8021
            ).'<span class="sr-only" aria-hidden="true">'.$langAccountExpired.'</span>';
8022
        }
8023
8024
        if ($row['0'] != api_get_user_id()) {
8025
            $langAction = get_lang(ucfirst($action));
8026
            // you cannot lock yourself out otherwise you could disable all the
8027
            // accounts including your own => everybody is locked out and nobody
8028
            // can change it anymore.
8029
            return Display::return_icon(
8030
                $image.'.png',
8031
                $langAction,
8032
                ['onclick' => 'active_user(this);', 'id' => 'img_'.$row['0'], 'style' => 'cursor: pointer;'],
8033
                ICON_SIZE_TINY
8034
                ).'<span class="sr-only" aria-hidden="true">'.$langAction.'</span>';
8035
        }
8036
8037
        return '';
8038
    }
8039
8040
    public static function getScriptFunctionForActiveFilter(): string
8041
    {
8042
        return 'function active_user(element_div) {
8043
            id_image = $(element_div).attr("id");
8044
            image_clicked = $(element_div).attr("src");
8045
            image_clicked_info = image_clicked.split("/");
8046
            image_real_clicked = image_clicked_info[image_clicked_info.length-1];
8047
            var status = 1;
8048
            if (image_real_clicked == "accept.png") {
8049
                status = 0;
8050
            }
8051
            user_id = id_image.split("_");
8052
            ident = "#img_"+user_id[1];
8053
            if (confirm("'.get_lang('AreYouSureToEditTheUserStatus', '').'")) {
8054
                 $.ajax({
8055
                    contentType: "application/x-www-form-urlencoded",
8056
                    beforeSend: function(myObject) {
8057
                        $(ident).attr("src","'.Display::returnIconPath('loading1.gif').'"); //candy eye stuff
8058
                    },
8059
                    type: "GET",
8060
                    url: _p.web_ajax + "user_manager.ajax.php?a=active_user",
8061
                    data: "user_id=" + user_id[1] + "&status=" + status,
8062
                    success: function(data) {
8063
                        if (data == 1) {
8064
                            $(ident).attr("src", "'.Display::returnIconPath('accept.png', ICON_SIZE_TINY).'");
8065
                            $(ident).attr("title","'.get_lang('Lock').'");
8066
                        }
8067
                        if (data == 0) {
8068
                            $(ident).attr("src","'.Display::returnIconPath('error.png').'");
8069
                            $(ident).attr("title","'.get_lang('Unlock').'");
8070
                        }
8071
                        if (data == -1) {
8072
                            $(ident).attr("src", "'.Display::returnIconPath('warning.png').'");
8073
                            $(ident).attr("title","'.get_lang('ActionNotAllowed').'");
8074
                        }
8075
                    }
8076
                });
8077
            }
8078
        }';
8079
    }
8080
8081
    /**
8082
     * Get a list of users with the given e-mail address + their "active" field value (0 or 1).
8083
     *
8084
     * @param string $mail User id
8085
     *
8086
     * @return array List of users e-mails + active field
8087
     */
8088
    public static function getUsersByMail(string $mail): array
8089
    {
8090
        $resultData = Database::select(
8091
            'id, active',
8092
            Database::get_main_table(TABLE_MAIN_USER),
8093
            [
8094
                'where' => ['email = ?' => $mail],
8095
            ],
8096
            'all',
8097
            null
8098
        );
8099
8100
        if ($resultData === false) {
8101
            return [];
8102
        }
8103
8104
        return $resultData;
8105
    }
8106
8107
    /**
8108
     * Get whether we can send an e-mail or not.
8109
     * If the e-mail is not in the database, send the mail.
8110
     * If the e-mail is in the database but none of its occurences is active, don't send.
8111
     *
8112
     * @param string $mail The e-mail address to check
8113
     *
8114
     * @return bool Whether we can send an e-mail or not
8115
     */
8116
    public function isEmailingAllowed(string $mail): bool
8117
    {
8118
        $list = self::getUsersByMail($mail);
8119
        if (empty($list)) {
8120
            // No e-mail matches, send the mail
8121
            return true;
8122
        }
8123
        $send = false;
8124
        foreach ($list as $id => $user) {
8125
            if ($user['active'] == 1) {
8126
                // as soon as we find at least one active user, send the mail
8127
                return true;
8128
            }
8129
        }
8130
8131
        return false;
8132
    }
8133
8134
    /**
8135
     * @return EncoderFactory
8136
     */
8137
    private static function getEncoderFactory()
8138
    {
8139
        $encryption = self::getPasswordEncryption();
8140
        $encoders = [
8141
            'Chamilo\\UserBundle\\Entity\\User' => new \Chamilo\UserBundle\Security\Encoder($encryption),
8142
        ];
8143
8144
        return new EncoderFactory($encoders);
8145
    }
8146
8147
    /**
8148
     * @return \Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface
8149
     */
8150
    private static function getEncoder(User $user)
8151
    {
8152
        $encoderFactory = self::getEncoderFactory();
8153
8154
        return $encoderFactory->getEncoder($user);
8155
    }
8156
8157
    /**
8158
     * Disables or enables a user.
8159
     *
8160
     * @param int $user_id
8161
     * @param int $active  Enable or disable
8162
     *
8163
     * @return bool True on success, false on failure
8164
     * @assert (-1,0) === false
8165
     * @assert (1,1) === true
8166
     */
8167
    private static function change_active_state($user_id, $active)
8168
    {
8169
        $user_id = (int) $user_id;
8170
        $active = (int) $active;
8171
8172
        if (empty($user_id)) {
8173
            return false;
8174
        }
8175
8176
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
8177
        $sql = "UPDATE $table_user SET active = '$active' WHERE id = $user_id";
8178
        $r = Database::query($sql);
8179
        $ev = LOG_USER_DISABLE;
8180
        if ($active == 1) {
8181
            $ev = LOG_USER_ENABLE;
8182
        }
8183
        if ($r !== false) {
8184
            Event::addEvent($ev, LOG_USER_ID, $user_id);
8185
        }
8186
8187
        return $r;
8188
    }
8189
8190
    /**
8191
     * Get either a Gravatar URL or complete image tag for a specified email address.
8192
     *
8193
     * @param string $email The email address
8194
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
8195
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
8196
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
8197
     * @param bool   $img   True to return a complete IMG tag False for just the URL
8198
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
8199
     *
8200
     * @return string containing either just a URL or a complete image tag
8201
     * @source http://gravatar.com/site/implement/images/php/
8202
     */
8203
    private static function getGravatar(
8204
        $email,
8205
        $s = 80,
8206
        $d = 'mm',
8207
        $r = 'g',
8208
        $img = false,
8209
        $atts = []
8210
    ) {
8211
        $url = 'http://www.gravatar.com/avatar/';
8212
        if (!empty($_SERVER['HTTPS'])) {
8213
            $url = 'https://secure.gravatar.com/avatar/';
8214
        }
8215
        $url .= md5(strtolower(trim($email)));
8216
        $url .= "?s=$s&d=$d&r=$r";
8217
        if ($img) {
8218
            $url = '<img src="'.$url.'"';
8219
            foreach ($atts as $key => $val) {
8220
                $url .= ' '.$key.'="'.$val.'"';
8221
            }
8222
            $url .= ' />';
8223
        }
8224
8225
        return $url;
8226
    }
8227
8228
   /**
8229
    * return user hash based on user_id and loggedin user's salt
8230
    *
8231
    * @param int user_id id of the user for whom we need the hash
8232
    *
8233
    * @return string containing the hash
8234
    */
8235
    public static function generateUserHash(int $user_id): string
8236
    {
8237
        $currentUserId = api_get_user_id();
8238
        $userManager = self::getManager();
8239
        /** @var User $user */
8240
        $user = self::getRepository()->find($currentUserId);
8241
        if (empty($user)) {
8242
            return false;
8243
        }
8244
        return rawurlencode(api_encrypt_hash($user_id, $user->getSalt()));
8245
    }
8246
8247
   /**
8248
    * return decrypted hash or false
8249
    *
8250
    * @param string hash    hash that is to be decrypted
8251
    *
8252
    * @return string
8253
    */
8254
    public static function decryptUserHash(string $hash): string
8255
    {
8256
        $currentUserId = api_get_user_id();
8257
        $userManager = self::getManager();
8258
        /** @var User $user */
8259
        $user = self::getRepository()->find($currentUserId);
8260
        if (empty($user)) {
8261
            return false;
8262
        }
8263
        return api_decrypt_hash(rawurldecode($hash), $user->getSalt());
8264
    }
8265
}
8266