Completed
Push — master ( f41061...196bfc )
by Julito
09:03
created

UserManager::get_user_list()   B

Complexity

Conditions 8
Paths 32

Size

Total Lines 44
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 26
nc 32
nop 4
dl 0
loc 44
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField;
5
use Chamilo\CoreBundle\Entity\SkillRelUser;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SkillRelUser. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use Chamilo\CoreBundle\Entity\SkillRelUserComment;
7
use Chamilo\CoreBundle\Framework\Container;
8
use Chamilo\CoreBundle\Repository\AccessUrlRepository;
9
use Chamilo\UserBundle\Entity\User;
10
use Chamilo\UserBundle\Repository\UserRepository;
11
use ChamiloSession as Session;
12
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
13
14
/**
15
 * Class UserManager.
16
 *
17
 * This library provides functions for user management.
18
 * Include/require it in your code to use its functionality.
19
 *
20
 * @package chamilo.library
21
 *
22
 * @author Julio Montoya <[email protected]> Social network groups added 2009/12
23
 */
24
class UserManager
25
{
26
    // This constants are deprecated use the constants located in ExtraField
27
    public const USER_FIELD_TYPE_TEXT = 1;
28
    public const USER_FIELD_TYPE_TEXTAREA = 2;
29
    public const USER_FIELD_TYPE_RADIO = 3;
30
    public const USER_FIELD_TYPE_SELECT = 4;
31
    public const USER_FIELD_TYPE_SELECT_MULTIPLE = 5;
32
    public const USER_FIELD_TYPE_DATE = 6;
33
    public const USER_FIELD_TYPE_DATETIME = 7;
34
    public const USER_FIELD_TYPE_DOUBLE_SELECT = 8;
35
    public const USER_FIELD_TYPE_DIVIDER = 9;
36
    public const USER_FIELD_TYPE_TAG = 10;
37
    public const USER_FIELD_TYPE_TIMEZONE = 11;
38
    public const USER_FIELD_TYPE_SOCIAL_PROFILE = 12;
39
    public const USER_FIELD_TYPE_FILE = 13;
40
    public const USER_FIELD_TYPE_MOBILE_PHONE_NUMBER = 14;
41
42
    private static $encryptionMethod;
43
44
    /**
45
     * Constructor.
46
     *
47
     * @assert () === null
48
     */
49
    public function __construct()
50
    {
51
    }
52
53
    /**
54
     * Repository is use to query the DB, selects, etc.
55
     *
56
     * @return UserRepository
57
     */
58
    public static function getRepository()
59
    {
60
        return Database::getManager()->getRepository('ChamiloUserBundle:User');
61
    }
62
63
    /**
64
     * Create/update/delete methods are available in the UserManager
65
     * (based in the Sonata\UserBundle\Entity\UserManager).
66
     *
67
     * @return \Sonata\UserBundle\Entity\UserManager
68
     */
69
    public static function getManager()
70
    {
71
        return Container::getUserManager();
72
    }
73
74
    /**
75
     * @param string $encryptionMethod
76
     */
77
    public static function setPasswordEncryption($encryptionMethod)
78
    {
79
        self::$encryptionMethod = $encryptionMethod;
80
    }
81
82
    /**
83
     * @return bool|mixed
84
     */
85
    public static function getPasswordEncryption()
86
    {
87
        $encryptionMethod = self::$encryptionMethod;
88
        if (empty($encryptionMethod)) {
89
            $encryptionMethod = api_get_configuration_value('password_encryption');
90
        }
91
92
        return $encryptionMethod;
93
    }
94
95
    /**
96
     * Validates the password.
97
     *
98
     * @param $encoded
99
     * @param $raw
100
     * @param $salt
101
     *
102
     * @return bool
103
     */
104
    public static function isPasswordValid($encoded, $raw, $salt)
105
    {
106
        $encoder = new \Chamilo\UserBundle\Security\Encoder(self::getPasswordEncryption());
0 ignored issues
show
Bug introduced by
It seems like self::getPasswordEncryption() can also be of type boolean; however, parameter $passwordEncrypt of Chamilo\UserBundle\Security\Encoder::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

106
        $encoder = new \Chamilo\UserBundle\Security\Encoder(/** @scrutinizer ignore-type */ self::getPasswordEncryption());
Loading history...
107
        $validPassword = $encoder->isPasswordValid($encoded, $raw, $salt);
108
109
        return $validPassword;
110
    }
111
112
    /**
113
     * @param string $raw
114
     * @param User   $user
115
     *
116
     * @return string
117
     */
118
    public static function encryptPassword($raw, User $user)
119
    {
120
        $encoder = self::getEncoder($user);
121
        $encodedPassword = $encoder->encodePassword(
122
            $raw,
123
            $user->getSalt()
124
        );
125
126
        return $encodedPassword;
127
    }
128
129
    /**
130
     * @param int    $userId
131
     * @param string $password
132
     */
133
    public static function updatePassword($userId, $password)
134
    {
135
        $repository = self::getRepository();
136
        /** @var User $user */
137
        $user = $repository->find($userId);
138
        $userManager = self::getManager();
139
        $user->setPlainPassword($password);
140
        $userManager->updateUser($user, true);
141
    }
142
143
    /**
144
     * Creates a new user for the platform.
145
     *
146
     * @author Hugues Peeters <[email protected]>,
147
     * @author Roan Embrechts <[email protected]>
148
     *
149
     * @param string        $firstName
150
     * @param string        $lastName
151
     * @param int           $status               (1 for course tutor, 5 for student, 6 for anonymous)
152
     * @param string        $email
153
     * @param string        $loginName
154
     * @param string        $password
155
     * @param string        $official_code        Any official code (optional)
156
     * @param string        $language             User language    (optional)
157
     * @param string        $phone                Phone number    (optional)
158
     * @param string        $picture_uri          Picture URI        (optional)
159
     * @param string        $authSource           Authentication source (defaults to 'platform', dependind on constant)
160
     * @param string        $expirationDate       Account expiration date (optional, defaults to null)
161
     * @param int           $active               Whether the account is enabled or disabled by default
162
     * @param int           $hr_dept_id           The department of HR in which the user is registered (defaults to 0)
163
     * @param array         $extra                Extra fields
164
     * @param string        $encrypt_method       Used if password is given encrypted. Set to an empty string by default
165
     * @param bool          $send_mail
166
     * @param bool          $isAdmin
167
     * @param string        $address
168
     * @param bool          $sendEmailToAllAdmins
169
     * @param FormValidator $form
170
     * @param int           $creatorId
171
     * @param array         $emailTemplate
172
     *
173
     * @return mixed new user id - if the new user creation succeeds, false otherwise
174
     * @desc The function tries to retrieve user id from the session.
175
     * If it exists, the current user id is the creator id. If a problem arises,
176
     * @assert ('Sam','Gamegie',5,'[email protected]','jo','jo') > 1
177
     * @assert ('Pippin','Took',null,null,'jo','jo') === false
178
     */
179
    public static function create_user(
180
        $firstName,
181
        $lastName,
182
        $status,
183
        $email,
184
        $loginName,
185
        $password,
186
        $official_code = '',
187
        $language = '',
188
        $phone = '',
189
        $picture_uri = '',
190
        $authSource = PLATFORM_AUTH_SOURCE,
191
        $expirationDate = null,
192
        $active = 1,
193
        $hr_dept_id = 0,
194
        $extra = [],
195
        $encrypt_method = '',
0 ignored issues
show
Unused Code introduced by
The parameter $encrypt_method is not used and could be removed. ( Ignorable by Annotation )

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

195
        /** @scrutinizer ignore-unused */ $encrypt_method = '',

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
196
        $send_mail = false,
197
        $isAdmin = false,
198
        $address = '',
199
        $sendEmailToAllAdmins = false,
200
        $form = null,
201
        $creatorId = 0,
202
        $emailTemplate = []
203
    ) {
204
        $creatorId = empty($creatorId) ? api_get_user_id() : 0;
205
        $hook = HookCreateUser::create();
206
        if (!empty($hook)) {
207
            $hook->notifyCreateUser(HOOK_EVENT_TYPE_PRE);
208
        }
209
210
        // First check wether the login already exists
211
        if (!self::is_username_available($loginName)) {
212
            Display::addFlash(
213
                Display::return_message(get_lang('LoginAlreadyTaken'))
214
            );
215
216
            return false;
217
        }
218
219
        global $_configuration;
220
        $original_password = $password;
221
222
        $access_url_id = 1;
223
        if (api_get_multiple_access_url()) {
224
            $access_url_id = api_get_current_access_url_id();
225
        } else {
226
            // In some cases, the first access_url ID might be different from 1
227
            // for example when using a DB cluster or hacking the DB manually.
228
            // In this case, we want the first row, not necessarily "1".
229
            $dbm = Database::getManager();
230
            /** @var AccessUrlRepository $accessUrlRepository */
231
            $accessUrlRepository = $dbm->getRepository('ChamiloCoreBundle:AccessUrl');
232
            $accessUrl = $accessUrlRepository->getFirstId();
233
            if (!empty($accessUrl[0]) && !empty($accessUrl[0][1])) {
234
                $access_url_id = $accessUrl[0][1];
235
            }
236
        }
237
238
        if (isset($_configuration[$access_url_id]) &&
239
            is_array($_configuration[$access_url_id]) &&
240
            isset($_configuration[$access_url_id]['hosting_limit_users']) &&
241
            $_configuration[$access_url_id]['hosting_limit_users'] > 0) {
242
            $num = self::get_number_of_users();
243
            if ($num >= $_configuration[$access_url_id]['hosting_limit_users']) {
244
                api_warn_hosting_contact('hosting_limit_users');
245
                Display::addFlash(
246
                    Display::return_message(
247
                        get_lang('PortalUsersLimitReached'),
248
                        'warning'
249
                    )
250
                );
251
252
                return false;
253
            }
254
        }
255
256
        if ($status === 1 &&
257
            isset($_configuration[$access_url_id]) &&
258
            is_array($_configuration[$access_url_id]) &&
259
            isset($_configuration[$access_url_id]['hosting_limit_teachers']) &&
260
            $_configuration[$access_url_id]['hosting_limit_teachers'] > 0
261
        ) {
262
            $num = self::get_number_of_users(1);
263
            if ($num >= $_configuration[$access_url_id]['hosting_limit_teachers']) {
264
                Display::addFlash(
265
                    Display::return_message(
266
                        get_lang('PortalTeachersLimitReached'),
267
                        'warning'
268
                    )
269
                );
270
                api_warn_hosting_contact('hosting_limit_teachers');
271
272
                return false;
273
            }
274
        }
275
276
        if (empty($password)) {
277
            if ($authSource === PLATFORM_AUTH_SOURCE) {
278
                Display::addFlash(
279
                    Display::return_message(
280
                        get_lang('ThisFieldIsRequired').': '.get_lang(
281
                            'Password'
282
                        ),
283
                        'warning'
284
                    )
285
                );
286
287
                return false;
288
            }
289
290
            // We use the authSource as password.
291
            // The real validation will be by processed by the auth
292
            // source not Chamilo
293
            $password = $authSource;
294
        }
295
296
        // database table definition
297
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
298
299
        // Checking the user language
300
        $languages = api_get_languages();
301
        $language = strtolower($language);
302
        if (isset($languages)) {
303
            if (!in_array($language, $languages)) {
304
                $language = api_get_setting('platformLanguage');
305
            }
306
        }
307
308
        $currentDate = api_get_utc_datetime();
309
        $now = new DateTime();
310
311
        if (empty($expirationDate) || $expirationDate == '0000-00-00 00:00:00') {
312
            // Default expiration date
313
            // if there is a default duration of a valid account then
314
            // we have to change the expiration_date accordingly
315
            // Accept 0000-00-00 00:00:00 as a null value to avoid issues with
316
            // third party code using this method with the previous (pre-1.10)
317
            // value of 0000...
318
            if (api_get_setting('account_valid_duration') != '') {
319
                $expirationDate = new DateTime($currentDate);
320
                $days = (int) api_get_setting('account_valid_duration');
321
                $expirationDate->modify('+'.$days.' day');
322
            }
323
        } else {
324
            $expirationDate = api_get_utc_datetime($expirationDate);
325
            $expirationDate = new \DateTime($expirationDate, new DateTimeZone('UTC'));
326
        }
327
328
        $userManager = self::getManager();
329
330
        /** @var User $user */
331
        $user = $userManager->createUser();
332
333
        $user
334
            ->setLastname($lastName)
335
            ->setFirstname($firstName)
336
            ->setUsername($loginName)
337
            ->setStatus($status)
338
            ->setPlainPassword($password)
339
            ->setEmail($email)
340
            ->setOfficialCode($official_code)
341
            ->setPictureUri($picture_uri)
342
            ->setCreatorId($creatorId)
343
            ->setAuthSource($authSource)
344
            ->setPhone($phone)
345
            ->setAddress($address)
346
            ->setLanguage($language)
347
            ->setRegistrationDate($now)
348
            ->setHrDeptId($hr_dept_id)
349
            ->setActive($active)
350
            ->setEnabled($active)
351
        ;
352
353
        if (!empty($expirationDate)) {
354
            $user->setExpirationDate($expirationDate);
0 ignored issues
show
Bug introduced by
It seems like $expirationDate can also be of type string; however, parameter $expirationDate of Chamilo\UserBundle\Entit...er::setExpirationDate() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

354
            $user->setExpirationDate(/** @scrutinizer ignore-type */ $expirationDate);
Loading history...
355
        }
356
        try {
357
            $userManager->updateUser($user);
358
            $userId = $user->getId();
359
360
            // Add user to a group
361
            $statusToGroup = [
362
                COURSEMANAGER => 'TEACHER',
363
                STUDENT => 'STUDENT',
364
                DRH => 'RRHH',
365
                SESSIONADMIN => 'SESSION_ADMIN',
366
                STUDENT_BOSS => 'STUDENT_BOSS',
367
                INVITEE => 'INVITEE',
368
            ];
369
370
            $group = Container::$container->get('fos_user.group_manager')->findGroupBy(['code' => $statusToGroup[$status]]);
371
            if ($group) {
372
                $user->addGroup($group);
373
                $userManager->updateUser($user);
374
            }
375
        } catch (Exception $e) {
376
            error_log($e->getMessage());
377
        }
378
379
        if (!empty($userId)) {
380
            $return = $userId;
381
            $sql = "UPDATE $table_user SET user_id = $return WHERE id = $return";
382
            Database::query($sql);
383
384
            if ($isAdmin) {
385
                self::add_user_as_admin($user);
386
            }
387
388
            if (api_get_multiple_access_url()) {
389
                UrlManager::add_user_to_url($userId, api_get_current_access_url_id());
390
            } else {
391
                //we are adding by default the access_url_user table with access_url_id = 1
392
                UrlManager::add_user_to_url($userId, 1);
393
            }
394
395
            $extra['item_id'] = $userId;
396
397
            if (is_array($extra) && count($extra) > 0) {
398
                $courseFieldValue = new ExtraFieldValue('user');
399
                $courseFieldValue->saveFieldValues($extra);
400
            } else {
401
                // Create notify settings by default
402
                self::update_extra_field_value(
403
                    $userId,
404
                    'mail_notify_invitation',
405
                    '1'
406
                );
407
                self::update_extra_field_value(
408
                    $userId,
409
                    'mail_notify_message',
410
                    '1'
411
                );
412
                self::update_extra_field_value(
413
                    $userId,
414
                    'mail_notify_group_message',
415
                    '1'
416
                );
417
            }
418
419
            self::update_extra_field_value(
420
                $userId,
421
                'already_logged_in',
422
                'false'
423
            );
424
425
            if (!empty($email) && $send_mail) {
426
                $recipient_name = api_get_person_name(
427
                    $firstName,
428
                    $lastName,
429
                    null,
430
                    PERSON_NAME_EMAIL_ADDRESS
431
                );
432
                $tplSubject = new Template(
433
                    null,
434
                    false,
435
                    false,
436
                    false,
437
                    false,
438
                    false
439
                );
440
                $layoutSubject = $tplSubject->get_template('mail/subject_registration_platform.tpl');
441
                $emailSubject = $tplSubject->fetch($layoutSubject);
442
                $sender_name = api_get_person_name(
443
                    api_get_setting('administratorName'),
444
                    api_get_setting('administratorSurname'),
445
                    null,
446
                    PERSON_NAME_EMAIL_ADDRESS
447
                );
448
                $email_admin = api_get_setting('emailAdministrator');
449
450
                $url = api_get_path(WEB_PATH);
451
                if (api_is_multiple_url_enabled()) {
452
                    $access_url_id = api_get_current_access_url_id();
453
                    if ($access_url_id != -1) {
454
                        $urlInfo = api_get_access_url($access_url_id);
455
                        if ($urlInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
456
                            $url = $urlInfo['url'];
457
                        }
458
                    }
459
                }
460
461
                $tplContent = new Template(
462
                    null,
463
                    false,
464
                    false,
465
                    false,
466
                    false,
467
                    false
468
                );
469
                // variables for the default template
470
                $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
471
                $tplContent->assign('login_name', $loginName);
472
                $tplContent->assign('original_password', stripslashes($original_password));
473
                $tplContent->assign('mailWebPath', $url);
474
                $tplContent->assign('new_user', $user);
475
476
                $layoutContent = $tplContent->get_template('mail/content_registration_platform.tpl');
477
                $emailBody = $tplContent->fetch($layoutContent);
478
479
                $userInfo = api_get_user_info($userId);
480
                $mailTemplateManager = new MailTemplateManager();
481
482
                $phoneNumber = isset($extra['mobile_phone_number']) ? $extra['mobile_phone_number'] : null;
483
484
                $additionalParameters = [
485
                    'smsType' => SmsPlugin::WELCOME_LOGIN_PASSWORD,
486
                    'userId' => $return,
487
                    'mobilePhoneNumber' => $phoneNumber,
488
                    'password' => $original_password,
489
                ];
490
491
                $emailBodyTemplate = '';
492
                if (!empty($emailTemplate)) {
493
                    if (isset($emailTemplate['content_registration_platform.tpl']) &&
494
                        !empty($emailTemplate['content_registration_platform.tpl'])
495
                    ) {
496
                        $emailBodyTemplate = $mailTemplateManager->parseTemplate(
497
                            $emailTemplate['content_registration_platform.tpl'],
498
                            $userInfo
499
                        );
500
                    }
501
                }
502
503
                $twoEmail = api_get_configuration_value('send_two_inscription_confirmation_mail');
504
                if ($twoEmail === true) {
505
                    $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
506
                    $emailBody = $tplContent->fetch($layoutContent);
507
508
                    if (!empty($emailBodyTemplate) &&
509
                        isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
510
                        !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
511
                    ) {
512
                        $emailBody = $mailTemplateManager->parseTemplate(
513
                            $emailTemplate['new_user_first_email_confirmation.tpl'],
514
                            $userInfo
515
                        );
516
                    }
517
518
                    api_mail_html(
519
                        $recipient_name,
520
                        $email,
521
                        $emailSubject,
522
                        $emailBody,
523
                        $sender_name,
524
                        $email_admin,
525
                        null,
526
                        null,
527
                        null,
528
                        $additionalParameters
529
                    );
530
531
                    $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
532
                    $emailBody = $tplContent->fetch($layoutContent);
533
534
                    if (!empty($emailBodyTemplate) &&
535
                        isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
536
                        !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
537
                    ) {
538
                        $emailBody = $mailTemplateManager->parseTemplate(
539
                            $emailTemplate['new_user_second_email_confirmation.tpl'],
540
                            $userInfo
541
                        );
542
                    }
543
544
                    api_mail_html(
545
                        $recipient_name,
546
                        $email,
547
                        $emailSubject,
548
                        $emailBody,
549
                        $sender_name,
550
                        $email_admin,
551
                        null,
552
                        null,
553
                        null,
554
                        $additionalParameters
555
                    );
556
                } else {
557
                    if (!empty($emailBodyTemplate)) {
558
                        $emailBody = $emailBodyTemplate;
559
                    }
560
                    $sendToInbox = api_get_configuration_value('send_inscription_msg_to_inbox');
561
                    if ($sendToInbox) {
562
                        $adminList = self::get_all_administrators();
563
                        $senderId = 1;
564
                        if (!empty($adminList)) {
565
                            $adminInfo = current($adminList);
566
                            $senderId = $adminInfo['user_id'];
567
                        }
568
569
                        MessageManager::send_message_simple(
570
                            $userId,
571
                            $emailSubject,
572
                            $emailBody,
573
                            $senderId
574
                        );
575
                    } else {
576
                        api_mail_html(
577
                            $recipient_name,
578
                            $email,
579
                            $emailSubject,
580
                            $emailBody,
581
                            $sender_name,
582
                            $email_admin,
583
                            null,
584
                            null,
585
                            null,
586
                            $additionalParameters
587
                        );
588
                    }
589
                }
590
591
                $notification = api_get_configuration_value('send_notification_when_user_added');
592
                if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {
593
                    foreach ($notification['admins'] as $adminId) {
594
                        $emailSubjectToAdmin = get_lang('UserAdded').': '.api_get_person_name($firstName, $lastName);
595
                        MessageManager::send_message_simple($adminId, $emailSubjectToAdmin, $emailBody, $userId);
0 ignored issues
show
Bug introduced by
It seems like $emailBody can also be of type false; however, parameter $message of MessageManager::send_message_simple() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

595
                        MessageManager::send_message_simple($adminId, $emailSubjectToAdmin, /** @scrutinizer ignore-type */ $emailBody, $userId);
Loading history...
596
                    }
597
                }
598
599
                if ($sendEmailToAllAdmins) {
600
                    $adminList = self::get_all_administrators();
601
602
                    $tplContent = new Template(
603
                        null,
604
                        false,
605
                        false,
606
                        false,
607
                        false,
608
                        false
609
                    );
610
                    // variables for the default template
611
                    $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
612
                    $tplContent->assign('user_added', $user);
613
                    $renderer = FormValidator::getDefaultRenderer();
614
                    // Form template
615
                    $elementTemplate = ' {label}: {element} <br />';
616
                    $renderer->setElementTemplate($elementTemplate);
617
                    /** @var FormValidator $form */
618
                    $form->freeze(null, $elementTemplate);
619
                    $form->removeElement('submit');
620
                    $formData = $form->returnForm();
621
                    $url = api_get_path(WEB_CODE_PATH).'admin/user_information.php?user_id='.$user->getId();
622
                    $tplContent->assign('link', Display::url($url, $url));
623
                    $tplContent->assign('form', $formData);
624
625
                    $layoutContent = $tplContent->get_template('mail/content_registration_platform_to_admin.tpl');
626
                    $emailBody = $tplContent->fetch($layoutContent);
627
628
                    if (!empty($emailBodyTemplate) &&
629
                        isset($emailTemplate['content_registration_platform_to_admin.tpl']) &&
630
                        !empty($emailTemplate['content_registration_platform_to_admin.tpl'])
631
                    ) {
632
                        $emailBody = $mailTemplateManager->parseTemplate(
633
                            $emailTemplate['content_registration_platform_to_admin.tpl'],
634
                            $userInfo
635
                        );
636
                    }
637
638
                    $subject = get_lang('UserAdded');
639
640
                    foreach ($adminList as $adminId => $data) {
641
                        MessageManager::send_message_simple(
642
                            $adminId,
643
                            $subject,
644
                            $emailBody,
645
                            $userId
646
                        );
647
                    }
648
                }
649
                /* ENDS MANAGE EVENT WITH MAIL */
650
            }
651
652
            if (!empty($hook)) {
653
                $hook->setEventData([
654
                    'return' => $userId,
655
                    'originalPassword' => $original_password,
656
                ]);
657
                $hook->notifyCreateUser(HOOK_EVENT_TYPE_POST);
658
            }
659
            Event::addEvent(LOG_USER_CREATE, LOG_USER_ID, $userId, null, $creatorId);
0 ignored issues
show
Bug introduced by
The method addEvent() does not exist on Event. ( Ignorable by Annotation )

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
660
        } else {
661
            Display::addFlash(
662
                Display::return_message(get_lang('ErrorContactPlatformAdmin'))
663
            );
664
665
            return false;
666
        }
667
668
        return $return;
669
    }
670
671
    /**
672
     * Can user be deleted? This function checks whether there's a course
673
     * in which the given user is the
674
     * only course administrator. If that is the case, the user can't be
675
     * deleted because the course would remain without a course admin.
676
     *
677
     * @param int $user_id The user id
678
     *
679
     * @return bool true if user can be deleted
680
     *
681
     * @assert (null) === false
682
     * @assert (-1) === false
683
     * @assert ('abc') === false
684
     */
685
    public static function canDeleteUser($user_id)
686
    {
687
        $deny = api_get_configuration_value('deny_delete_users');
688
689
        if ($deny) {
690
            return false;
691
        }
692
693
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
694
        if ($user_id != strval(intval($user_id))) {
695
            return false;
696
        }
697
        if ($user_id === false) {
698
            return false;
699
        }
700
        $sql = "SELECT * FROM $table_course_user
701
                WHERE status = 1 AND user_id = ".$user_id;
702
        $res = Database::query($sql);
703
        while ($course = Database::fetch_object($res)) {
704
            $sql = "SELECT id FROM $table_course_user
705
                    WHERE status=1 AND c_id = ".intval($course->c_id);
706
            $res2 = Database::query($sql);
707
            if (Database::num_rows($res2) == 1) {
708
                return false;
709
            }
710
        }
711
712
        return true;
713
    }
714
715
    /**
716
     * Delete a user from the platform, and all its belongings. This is a
717
     * very dangerous function that should only be accessible by
718
     * super-admins. Other roles should only be able to disable a user,
719
     * which removes access to the platform but doesn't delete anything.
720
     *
721
     * @param int The ID of th user to be deleted
722
     *
723
     * @throws Exception
724
     *
725
     * @return bool true if user is successfully deleted, false otherwise
726
     * @assert (null) === false
727
     * @assert ('abc') === false
728
     */
729
    public static function delete_user($user_id)
730
    {
731
        $user_id = (int) $user_id;
732
733
        if (empty($user_id)) {
734
            return false;
735
        }
736
737
        if (!self::canDeleteUser($user_id)) {
738
            return false;
739
        }
740
741
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
742
        $usergroup_rel_user = Database::get_main_table(TABLE_USERGROUP_REL_USER);
743
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
744
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
745
        $table_session = Database::get_main_table(TABLE_MAIN_SESSION);
746
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
747
        $table_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
748
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
749
        $table_group = Database::get_course_table(TABLE_GROUP_USER);
750
        $table_work = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
751
752
        $user = api_get_user_entity($user_id);
753
754
        // Unsubscribe the user from all groups in all his courses
755
        $sql = "SELECT c.id 
756
                FROM $table_course c 
757
                INNER JOIN $table_course_user cu
758
                ON (c.id = cu.c_id)
759
                WHERE
760
                    cu.user_id = '".$user_id."' AND
761
                    relation_type<>".COURSE_RELATION_TYPE_RRHH."
762
                ";
763
764
        $res = Database::query($sql);
765
        while ($course = Database::fetch_object($res)) {
766
            $sql = "DELETE FROM $table_group
767
                    WHERE c_id = {$course->id} AND user_id = $user_id";
768
            Database::query($sql);
769
        }
770
771
        // Unsubscribe user from usergroup_rel_user
772
        $sql = "DELETE FROM $usergroup_rel_user WHERE user_id = '".$user_id."'";
773
        Database::query($sql);
774
775
        // Unsubscribe user from all courses
776
        $sql = "DELETE FROM $table_course_user WHERE user_id = '".$user_id."'";
777
        Database::query($sql);
778
779
        // Unsubscribe user from all courses in sessions
780
        $sql = "DELETE FROM $table_session_course_user WHERE user_id = '".$user_id."'";
781
        Database::query($sql);
782
783
        // If the user was added as a id_coach then set the current admin as coach see BT#
784
        $currentUserId = api_get_user_id();
785
        $sql = "UPDATE $table_session SET id_coach = $currentUserId
786
                WHERE id_coach = '".$user_id."'";
787
        Database::query($sql);
788
789
        $sql = "UPDATE $table_session SET id_coach = $currentUserId
790
                WHERE session_admin_id = '".$user_id."'";
791
        Database::query($sql);
792
793
        // Unsubscribe user from all sessions
794
        $sql = "DELETE FROM $table_session_user
795
                WHERE user_id = '".$user_id."'";
796
        Database::query($sql);
797
798
        // Delete user picture
799
        /* TODO: Logic about api_get_setting('split_users_upload_directory') == 'true'
800
        a user has 4 different sized photos to be deleted. */
801
        $user_info = api_get_user_info($user_id);
802
803
        if (strlen($user_info['picture_uri']) > 0) {
804
            $path = self::getUserPathById($user_id, 'system');
805
            $img_path = $path.$user_info['picture_uri'];
806
            if (file_exists($img_path)) {
807
                unlink($img_path);
808
            }
809
        }
810
811
        // Delete the personal course categories
812
        $course_cat_table = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
813
        $sql = "DELETE FROM $course_cat_table WHERE user_id = '".$user_id."'";
814
        Database::query($sql);
815
816
        // Delete user from the admin table
817
        $sql = "DELETE FROM $table_admin WHERE user_id = '".$user_id."'";
818
        Database::query($sql);
819
820
        // Delete the personal agenda-items from this user
821
        $agenda_table = Database::get_main_table(TABLE_PERSONAL_AGENDA);
822
        $sql = "DELETE FROM $agenda_table WHERE user = '".$user_id."'";
823
        Database::query($sql);
824
825
        $gradebook_results_table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
826
        $sql = 'DELETE FROM '.$gradebook_results_table.' WHERE user_id = '.$user_id;
827
        Database::query($sql);
828
829
        $extraFieldValue = new ExtraFieldValue('user');
830
        $extraFieldValue->deleteValuesByItem($user_id);
831
832
        UrlManager::deleteUserFromAllUrls($user_id);
833
834
        if (api_get_setting('allow_social_tool') === 'true') {
835
            $userGroup = new UserGroup();
836
            //Delete user from portal groups
837
            $group_list = $userGroup->get_groups_by_user($user_id);
838
            if (!empty($group_list)) {
839
                foreach ($group_list as $group_id => $data) {
840
                    $userGroup->delete_user_rel_group($user_id, $group_id);
841
                }
842
            }
843
844
            // Delete user from friend lists
845
            SocialManager::remove_user_rel_user($user_id, true);
846
        }
847
848
        // Removing survey invitation
849
        SurveyManager::delete_all_survey_invitations_by_user($user_id);
850
851
        // Delete students works
852
        $sql = "DELETE FROM $table_work WHERE user_id = $user_id AND c_id <> 0";
853
        Database::query($sql);
854
855
        $sql = "UPDATE c_item_property SET to_user_id = NULL
856
                WHERE to_user_id = '".$user_id."'";
857
        Database::query($sql);
858
859
        $sql = "UPDATE c_item_property SET insert_user_id = NULL
860
                WHERE insert_user_id = '".$user_id."'";
861
        Database::query($sql);
862
863
        $sql = "UPDATE c_item_property SET lastedit_user_id = NULL
864
                WHERE lastedit_user_id = '".$user_id."'";
865
        Database::query($sql);
866
867
        // Skills
868
        $em = Database::getManager();
869
870
        $criteria = ['user' => $user_id];
871
        $skills = $em->getRepository('ChamiloCoreBundle:SkillRelUser')->findBy($criteria);
872
        if ($skills) {
873
            /** @var SkillRelUser $skill */
874
            foreach ($skills as $skill) {
875
                $comments = $skill->getComments();
876
                if ($comments) {
877
                    /** @var SkillRelUserComment $comment */
878
                    foreach ($comments as $comment) {
879
                        $em->remove($comment);
880
                    }
881
                }
882
                $em->remove($skill);
883
            }
884
            $em->flush();
885
        }
886
887
        // ExtraFieldSavedSearch
888
        $criteria = ['user' => $user_id];
889
        $searchList = $em->getRepository('ChamiloCoreBundle:ExtraFieldSavedSearch')->findBy($criteria);
890
        if ($searchList) {
891
            foreach ($searchList as $search) {
892
                $em->remove($search);
893
            }
894
            $em->flush();
895
        }
896
897
        $connection = Database::getManager()->getConnection();
898
        $tableExists = $connection->getSchemaManager()->tablesExist(['plugin_bbb_room']);
899
        if ($tableExists) {
900
            // Delete user from database
901
            $sql = "DELETE FROM plugin_bbb_room WHERE participant_id = $user_id";
902
            Database::query($sql);
903
        }
904
905
        // Delete user/ticket relationships :(
906
        $tableExists = $connection->getSchemaManager()->tablesExist(['ticket_ticket']);
907
        if ($tableExists) {
908
            TicketManager::deleteUserFromTicketSystem($user_id);
909
        }
910
911
        $tableExists = $connection->getSchemaManager()->tablesExist(['c_lp_category_user']);
912
        if ($tableExists) {
913
            $sql = "DELETE FROM c_lp_category_user WHERE user_id = $user_id";
914
            Database::query($sql);
915
        }
916
917
        $app_plugin = new AppPlugin();
918
        $app_plugin->performActionsWhenDeletingItem('user', $user_id);
919
920
        // Delete user from database
921
        $em->remove($user);
922
923
        $em->flush();
924
925
        // Add event to system log
926
        $user_id_manager = api_get_user_id();
927
928
        Event::addEvent(
929
            LOG_USER_DELETE,
930
            LOG_USER_ID,
931
            $user_id,
932
            api_get_utc_datetime(),
933
            $user_id_manager
934
        );
935
936
        Event::addEvent(
937
            LOG_USER_DELETE,
938
            LOG_USER_OBJECT,
939
            $user_info,
940
            api_get_utc_datetime(),
941
            $user_id_manager
942
        );
943
        $cacheAvailable = api_get_configuration_value('apc');
944
        if ($cacheAvailable === true) {
945
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
946
            if (apcu_exists($apcVar)) {
947
                apcu_delete($apcVar);
948
            }
949
        }
950
951
        return true;
952
    }
953
954
    /**
955
     * Deletes users completely. Can be called either as:
956
     * - UserManager::delete_users(1, 2, 3); or
957
     * - UserManager::delete_users(array(1, 2, 3));.
958
     *
959
     * @param array|int $ids
960
     *
961
     * @return bool True if at least one user was successfuly deleted. False otherwise.
962
     *
963
     * @author Laurent Opprecht
964
     *
965
     * @uses \UserManager::delete_user() to actually delete each user
966
     * @assert (null) === false
967
     * @assert (-1) === false
968
     * @assert (array(-1)) === false
969
     */
970
    public static function delete_users($ids = [])
971
    {
972
        $result = false;
973
        $ids = is_array($ids) ? $ids : func_get_args();
974
        if (!is_array($ids) || count($ids) == 0) {
975
            return false;
976
        }
977
        $ids = array_map('intval', $ids);
978
        foreach ($ids as $id) {
979
            if (empty($id) || $id < 1) {
980
                continue;
981
            }
982
            $deleted = self::delete_user($id);
983
            $result = $deleted || $result;
984
        }
985
986
        return $result;
987
    }
988
989
    /**
990
     * Disable users. Can be called either as:
991
     * - UserManager::deactivate_users(1, 2, 3);
992
     * - UserManager::deactivate_users(array(1, 2, 3));.
993
     *
994
     * @param array|int $ids
995
     *
996
     * @return bool
997
     *
998
     * @author Laurent Opprecht
999
     * @assert (null) === false
1000
     * @assert (array(-1)) === false
1001
     */
1002
    public static function deactivate_users($ids = [])
1003
    {
1004
        if (empty($ids)) {
1005
            return false;
1006
        }
1007
1008
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1009
1010
        $ids = is_array($ids) ? $ids : func_get_args();
1011
        $ids = array_map('intval', $ids);
1012
        $ids = implode(',', $ids);
1013
1014
        $sql = "UPDATE $table_user SET active = 0 WHERE id IN ($ids)";
1015
        $r = Database::query($sql);
1016
        if ($r !== false) {
1017
            Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $ids);
1018
1019
            return true;
1020
        }
1021
1022
        return false;
1023
    }
1024
1025
    /**
1026
     * Enable users. Can be called either as:
1027
     * - UserManager::activate_users(1, 2, 3);
1028
     * - UserManager::activate_users(array(1, 2, 3));.
1029
     *
1030
     * @param array|int IDs of the users to enable
1031
     *
1032
     * @return bool
1033
     *
1034
     * @author Laurent Opprecht
1035
     * @assert (null) === false
1036
     * @assert (array(-1)) === false
1037
     */
1038
    public static function activate_users($ids = [])
1039
    {
1040
        if (empty($ids)) {
1041
            return false;
1042
        }
1043
1044
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1045
1046
        $ids = is_array($ids) ? $ids : func_get_args();
1047
        $ids = array_map('intval', $ids);
1048
        $ids = implode(',', $ids);
1049
1050
        $sql = "UPDATE $table_user SET active = 1 WHERE id IN ($ids)";
1051
        $r = Database::query($sql);
1052
        if ($r !== false) {
1053
            Event::addEvent(LOG_USER_ENABLE, LOG_USER_ID, $ids);
1054
1055
            return true;
1056
        }
1057
1058
        return false;
1059
    }
1060
1061
    /**
1062
     * Update user information with all the parameters passed to this function.
1063
     *
1064
     * @param int    $user_id         The ID of the user to be updated
1065
     * @param string $firstname       The user's firstname
1066
     * @param string $lastname        The user's lastname
1067
     * @param string $username        The user's username (login)
1068
     * @param string $password        The user's password
1069
     * @param string $auth_source     The authentication source (default: "platform")
1070
     * @param string $email           The user's e-mail address
1071
     * @param int    $status          The user's status
1072
     * @param string $official_code   The user's official code (usually just an internal institutional code)
1073
     * @param string $phone           The user's phone number
1074
     * @param string $picture_uri     The user's picture URL (internal to the Chamilo directory)
1075
     * @param string $expiration_date The date at which this user will be automatically disabled
1076
     * @param int    $active          Whether this account needs to be enabled (1) or disabled (0)
1077
     * @param int    $creator_id      The user ID of the person who registered this user (optional, defaults to null)
1078
     * @param int    $hr_dept_id      The department of HR in which the user is registered (optional, defaults to 0)
1079
     * @param array  $extra           Additional fields to add to this user as extra fields (defaults to null)
1080
     * @param string $language        The language to which the user account will be set
1081
     * @param string $encrypt_method  The cipher method. This parameter is deprecated. It will use the system's default
1082
     * @param bool   $send_email      Whether to send an e-mail to the user after the update is complete
1083
     * @param int    $reset_password  Method used to reset password (0, 1, 2 or 3 - see usage examples for details)
1084
     * @param string $address
1085
     * @param array  $emailTemplate
1086
     *
1087
     * @return bool|int False on error, or the user ID if the user information was updated
1088
     * @assert (false, false, false, false, false, false, false, false, false, false, false, false, false) === false
1089
     */
1090
    public static function update_user(
1091
        $user_id,
1092
        $firstname,
1093
        $lastname,
1094
        $username,
1095
        $password = null,
1096
        $auth_source = null,
1097
        $email,
1098
        $status,
1099
        $official_code,
1100
        $phone,
1101
        $picture_uri,
1102
        $expiration_date,
1103
        $active,
1104
        $creator_id = null,
0 ignored issues
show
Unused Code introduced by
The parameter $creator_id is not used and could be removed. ( Ignorable by Annotation )

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

1104
        /** @scrutinizer ignore-unused */ $creator_id = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1105
        $hr_dept_id = 0,
1106
        $extra = null,
1107
        $language = 'english',
1108
        $encrypt_method = '',
0 ignored issues
show
Unused Code introduced by
The parameter $encrypt_method is not used and could be removed. ( Ignorable by Annotation )

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

1108
        /** @scrutinizer ignore-unused */ $encrypt_method = '',

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1109
        $send_email = false,
1110
        $reset_password = 0,
1111
        $address = null,
1112
        $emailTemplate = []
1113
    ) {
1114
        $hook = HookUpdateUser::create();
1115
        if (!empty($hook)) {
1116
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_PRE);
1117
        }
1118
        $original_password = $password;
1119
1120
        if ($user_id != strval(intval($user_id))) {
1121
            return false;
1122
        }
1123
1124
        if (empty($user_id)) {
1125
            return false;
1126
        }
1127
1128
        $userManager = self::getManager();
1129
        /** @var User $user */
1130
        $user = self::getRepository()->find($user_id);
1131
1132
        if (empty($user)) {
1133
            return false;
1134
        }
1135
1136
        if ($reset_password == 0) {
1137
            $password = null;
1138
            $auth_source = $user->getAuthSource();
1139
        } elseif ($reset_password == 1) {
1140
            $original_password = $password = api_generate_password();
1141
            $auth_source = PLATFORM_AUTH_SOURCE;
1142
        } elseif ($reset_password == 2) {
1143
            $password = $password;
1144
            $auth_source = PLATFORM_AUTH_SOURCE;
1145
        } elseif ($reset_password == 3) {
1146
            $password = $password;
1147
            $auth_source = $auth_source;
1148
        }
1149
1150
        // Checking the user language
1151
        $languages = array_keys(api_get_languages());
1152
        if (!in_array($language, $languages)) {
1153
            $language = api_get_setting('platformLanguage');
1154
        }
1155
1156
        $change_active = 0;
1157
        $isUserActive = $user->getActive();
1158
        if ($isUserActive != $active) {
1159
            $change_active = 1;
1160
        }
1161
1162
        $originalUsername = $user->getUsername();
1163
1164
        // If username is different from original then check if it exists.
1165
        if ($originalUsername !== $username) {
1166
            $available = self::is_username_available($username);
1167
            if ($available === false) {
1168
                return false;
1169
            }
1170
        }
1171
1172
        if (!empty($expiration_date)) {
1173
            $expiration_date = api_get_utc_datetime($expiration_date);
1174
            $expiration_date = new \DateTime(
1175
                $expiration_date,
1176
                new DateTimeZone('UTC')
1177
            );
1178
        }
1179
1180
        $user
1181
            ->setLastname($lastname)
1182
            ->setFirstname($firstname)
1183
            ->setUsername($username)
1184
            ->setStatus($status)
1185
            ->setAuthSource($auth_source)
1186
            ->setLanguage($language)
1187
            ->setLocale($language)
1188
            ->setEmail($email)
1189
            ->setOfficialCode($official_code)
1190
            ->setPhone($phone)
1191
            ->setAddress($address)
1192
            ->setPictureUri($picture_uri)
1193
            ->setExpirationDate($expiration_date)
1194
            ->setActive($active)
1195
            ->setEnabled($active)
1196
            ->setHrDeptId($hr_dept_id)
1197
        ;
1198
1199
        if (!is_null($password)) {
1200
            $user->setPlainPassword($password);
1201
        }
1202
1203
        $userManager->updateUser($user, true);
1204
1205
        if ($change_active == 1) {
1206
            if ($active == 1) {
1207
                $event_title = LOG_USER_ENABLE;
1208
            } else {
1209
                $event_title = LOG_USER_DISABLE;
1210
            }
1211
            Event::addEvent($event_title, LOG_USER_ID, $user_id);
1212
        }
1213
1214
        if (is_array($extra) && count($extra) > 0) {
1215
            $res = true;
1216
            foreach ($extra as $fname => $fvalue) {
1217
                $res = $res && self::update_extra_field_value(
1218
                    $user_id,
1219
                    $fname,
1220
                    $fvalue
1221
                );
1222
            }
1223
        }
1224
1225
        if (!empty($email) && $send_email) {
1226
            $recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1227
            $emailsubject = '['.api_get_setting('siteName').'] '.get_lang('YourReg').' '.api_get_setting('siteName');
1228
            $sender_name = api_get_person_name(
1229
                api_get_setting('administratorName'),
1230
                api_get_setting('administratorSurname'),
1231
                null,
1232
                PERSON_NAME_EMAIL_ADDRESS
1233
            );
1234
            $email_admin = api_get_setting('emailAdministrator');
1235
            $url = api_get_path(WEB_PATH);
1236
            if (api_is_multiple_url_enabled()) {
1237
                $access_url_id = api_get_current_access_url_id();
1238
                if ($access_url_id != -1) {
1239
                    $url = api_get_access_url($access_url_id);
1240
                    $url = $url['url'];
1241
                }
1242
            }
1243
1244
            $tplContent = new Template(
1245
                null,
1246
                false,
1247
                false,
1248
                false,
1249
                false,
1250
                false
1251
            );
1252
            // variables for the default template
1253
            $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstname, $lastname)));
1254
            $tplContent->assign('login_name', $username);
1255
1256
            $originalPassword = '';
1257
            if ($reset_password > 0) {
1258
                $originalPassword = stripslashes($original_password);
1259
            }
1260
            $tplContent->assign('original_password', $originalPassword);
1261
            $tplContent->assign('portal_url', $url);
1262
1263
            $layoutContent = $tplContent->get_template('mail/user_edit_content.tpl');
1264
            $emailBody = $tplContent->fetch($layoutContent);
1265
1266
            $mailTemplateManager = new MailTemplateManager();
1267
1268
            if (!empty($emailTemplate) &&
1269
                isset($emailTemplate['user_edit_content.tpl']) &&
1270
                !empty($emailTemplate['user_edit_content.tpl'])
1271
            ) {
1272
                $userInfo = api_get_user_info($user_id);
1273
                $emailBody = $mailTemplateManager->parseTemplate($emailTemplate['user_edit_content.tpl'], $userInfo);
1274
            }
1275
            api_mail_html(
1276
                $recipient_name,
1277
                $email,
1278
                $emailsubject,
1279
                $emailBody,
1280
                $sender_name,
1281
                $email_admin
1282
            );
1283
        }
1284
1285
        if (!empty($hook)) {
1286
            $hook->setEventData(['user' => $user]);
1287
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_POST);
1288
        }
1289
1290
        $cacheAvailable = api_get_configuration_value('apc');
1291
        if ($cacheAvailable === true) {
1292
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
1293
            if (apcu_exists($apcVar)) {
1294
                apcu_delete($apcVar);
1295
            }
1296
        }
1297
1298
        return $user->getId();
1299
    }
1300
1301
    /**
1302
     * Disables a user.
1303
     *
1304
     * @param int User id
1305
     *
1306
     * @return bool
1307
     *
1308
     * @uses \UserManager::change_active_state() to actually disable the user
1309
     * @assert (0) === false
1310
     */
1311
    public static function disable($user_id)
1312
    {
1313
        if (empty($user_id)) {
1314
            return false;
1315
        }
1316
        self::change_active_state($user_id, 0);
1317
1318
        return true;
1319
    }
1320
1321
    /**
1322
     * Enable a user.
1323
     *
1324
     * @param int User id
1325
     *
1326
     * @return bool
1327
     *
1328
     * @uses \UserManager::change_active_state() to actually disable the user
1329
     * @assert (0) === false
1330
     */
1331
    public static function enable($user_id)
1332
    {
1333
        if (empty($user_id)) {
1334
            return false;
1335
        }
1336
        self::change_active_state($user_id, 1);
1337
1338
        return true;
1339
    }
1340
1341
    /**
1342
     * Returns the user's id based on the original id and field name in
1343
     * the extra fields. Returns 0 if no user was found. This function is
1344
     * mostly useful in the context of a web services-based sinchronization.
1345
     *
1346
     * @param string Original user id
1347
     * @param string Original field name
1348
     *
1349
     * @return int User id
1350
     * @assert ('0','---') === 0
1351
     */
1352
    public static function get_user_id_from_original_id(
1353
        $original_user_id_value,
1354
        $original_user_id_name
1355
    ) {
1356
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
1357
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1358
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
1359
        $sql = "SELECT item_id as user_id
1360
                FROM $t_uf uf
1361
                INNER JOIN $t_ufv ufv
1362
                ON ufv.field_id=uf.id
1363
                WHERE
1364
                    variable='$original_user_id_name' AND
1365
                    value='$original_user_id_value' AND
1366
                    extra_field_type = $extraFieldType
1367
                ";
1368
        $res = Database::query($sql);
1369
        $row = Database::fetch_object($res);
1370
        if ($row) {
1371
            return $row->user_id;
1372
        } else {
1373
            return 0;
1374
        }
1375
    }
1376
1377
    /**
1378
     * Check if a username is available.
1379
     *
1380
     * @param string $username the wanted username
1381
     *
1382
     * @return bool true if the wanted username is available
1383
     * @assert ('') === false
1384
     * @assert ('xyzxyzxyz') === true
1385
     */
1386
    public static function is_username_available($username)
1387
    {
1388
        if (empty($username)) {
1389
            return false;
1390
        }
1391
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1392
        $sql = "SELECT username FROM $table_user
1393
                WHERE username = '".Database::escape_string($username)."'";
1394
        $res = Database::query($sql);
1395
1396
        return Database::num_rows($res) == 0;
1397
    }
1398
1399
    /**
1400
     * Creates a username using person's names, i.e. creates jmontoya from Julio Montoya.
1401
     *
1402
     * @param string $firstname the first name of the user
1403
     * @param string $lastname  the last name of the user
1404
     *
1405
     * @return string suggests a username that contains only ASCII-letters and digits,
1406
     *                without check for uniqueness within the system
1407
     *
1408
     * @author Julio Montoya Armas
1409
     * @author Ivan Tcholakov, 2009 - rework about internationalization.
1410
     * @assert ('','') === false
1411
     * @assert ('a','b') === 'ab'
1412
     */
1413
    public static function create_username($firstname, $lastname)
1414
    {
1415
        if (empty($firstname) && empty($lastname)) {
1416
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1417
        }
1418
1419
        // The first letter only.
1420
        $firstname = api_substr(
1421
            preg_replace(USERNAME_PURIFIER, '', $firstname),
1422
            0,
1423
            1
1424
        );
1425
        //Looking for a space in the lastname
1426
        $pos = api_strpos($lastname, ' ');
1427
        if ($pos !== false) {
1428
            $lastname = api_substr($lastname, 0, $pos);
1429
        }
1430
1431
        $lastname = preg_replace(USERNAME_PURIFIER, '', $lastname);
1432
        $username = $firstname.$lastname;
1433
        if (empty($username)) {
1434
            $username = 'user';
1435
        }
1436
1437
        $username = URLify::transliterate($username);
1438
1439
        return strtolower(substr($username, 0, USERNAME_MAX_LENGTH - 3));
1440
    }
1441
1442
    /**
1443
     * Creates a unique username, using:
1444
     * 1. the first name and the last name of a user;
1445
     * 2. an already created username but not checked for uniqueness yet.
1446
     *
1447
     * @param string $firstname The first name of a given user. If the second parameter $lastname is NULL, then this
1448
     *                          parameter is treated as username which is to be checked f
1449
     *                          or uniqueness and to be modified when it is necessary.
1450
     * @param string $lastname  the last name of the user
1451
     *
1452
     * @return string Returns a username that contains only ASCII-letters and digits and that is unique in the system.
1453
     *                Note: When the method is called several times with same parameters,
1454
     *                its results look like the following sequence: ivan, ivan2, ivan3, ivan4, ...
1455
     *
1456
     * @author Ivan Tcholakov, 2009
1457
     */
1458
    public static function create_unique_username($firstname, $lastname = null)
1459
    {
1460
        if (is_null($lastname)) {
1461
            // In this case the actual input parameter $firstname should contain ASCII-letters and digits only.
1462
            // For making this method tolerant of mistakes,
1463
            // let us transliterate and purify the suggested input username anyway.
1464
            // So, instead of the sentence $username = $firstname; we place the following:
1465
            $username = strtolower(preg_replace(USERNAME_PURIFIER, '', $firstname));
1466
        } else {
1467
            $username = self::create_username($firstname, $lastname);
1468
        }
1469
        if (!self::is_username_available($username)) {
1470
            $i = 2;
1471
            $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
1472
            while (!self::is_username_available($temp_username)) {
1473
                $i++;
1474
                $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
1475
            }
1476
            $username = $temp_username;
1477
        }
1478
1479
        $username = URLify::transliterate($username);
1480
1481
        return $username;
1482
    }
1483
1484
    /**
1485
     * Modifies a given username accordingly to the specification for valid characters and length.
1486
     *
1487
     * @param $username string          The input username
1488
     * @param bool $strict (optional)   When this flag is TRUE, the result is guaranteed for full compliance,
1489
     *                     otherwise compliance may be partial. The default value is FALSE.
1490
     *
1491
     * @return string the resulting purified username
1492
     */
1493
    public static function purify_username($username, $strict = false)
1494
    {
1495
        if ($strict) {
1496
            // 1. Conversion of unacceptable letters (latinian letters with accents for example)
1497
            // into ASCII letters in order they not to be totally removed.
1498
            // 2. Applying the strict purifier.
1499
            // 3. Length limitation.
1500
            $return = api_get_setting('login_is_email') === 'true' ? substr(preg_replace(USERNAME_PURIFIER_MAIL, '', $username), 0, USERNAME_MAX_LENGTH) : substr(preg_replace(USERNAME_PURIFIER, '', $username), 0, USERNAME_MAX_LENGTH);
1501
            $return = URLify::transliterate($return);
1502
1503
            return $return;
1504
        }
1505
1506
        // 1. Applying the shallow purifier.
1507
        // 2. Length limitation.
1508
        return substr(
1509
            preg_replace(USERNAME_PURIFIER_SHALLOW, '', $username),
1510
            0,
1511
            USERNAME_MAX_LENGTH
1512
        );
1513
    }
1514
1515
    /**
1516
     * Checks whether the user id exists in the database.
1517
     *
1518
     * @param int $userId User id
1519
     *
1520
     * @return bool True if user id was found, false otherwise
1521
     */
1522
    public static function is_user_id_valid($userId)
1523
    {
1524
        $resultData = Database::select(
1525
            'COUNT(1) AS count',
1526
            Database::get_main_table(TABLE_MAIN_USER),
1527
            [
1528
                'where' => ['id = ?' => (int) $userId],
1529
            ],
1530
            'first'
1531
        );
1532
1533
        if ($resultData === false) {
1534
            return false;
1535
        }
1536
1537
        return $resultData['count'] > 0;
1538
    }
1539
1540
    /**
1541
     * Checks whether a given username matches to the specification strictly.
1542
     * The empty username is assumed here as invalid.
1543
     * Mostly this function is to be used in the user interface built-in validation routines
1544
     * for providing feedback while usernames are enterd manually.
1545
     *
1546
     * @param string $username the input username
1547
     *
1548
     * @return bool returns TRUE if the username is valid, FALSE otherwise
1549
     */
1550
    public static function is_username_valid($username)
1551
    {
1552
        return !empty($username) && $username == self::purify_username($username, true);
1553
    }
1554
1555
    /**
1556
     * Checks whether a username is empty. If the username contains whitespace characters,
1557
     * such as spaces, tabulators, newlines, etc.,
1558
     * it is assumed as empty too. This function is safe for validation unpurified data (during importing).
1559
     *
1560
     * @param string $username the given username
1561
     *
1562
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
1563
     */
1564
    public static function is_username_empty($username)
1565
    {
1566
        return strlen(self::purify_username($username, false)) == 0;
1567
    }
1568
1569
    /**
1570
     * Checks whether a username is too long or not.
1571
     *
1572
     * @param string $username the given username, it should contain only ASCII-letters and digits
1573
     *
1574
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
1575
     */
1576
    public static function is_username_too_long($username)
1577
    {
1578
        return strlen($username) > USERNAME_MAX_LENGTH;
1579
    }
1580
1581
    /**
1582
     * Get the users by ID.
1583
     *
1584
     * @param array  $ids    student ids
1585
     * @param string $active
1586
     * @param string $order
1587
     * @param string $limit
1588
     *
1589
     * @return array $result student information
1590
     */
1591
    public static function get_user_list_by_ids($ids = [], $active = null, $order = null, $limit = null)
1592
    {
1593
        if (empty($ids)) {
1594
            return [];
1595
        }
1596
1597
        $ids = is_array($ids) ? $ids : [$ids];
1598
        $ids = array_map('intval', $ids);
1599
        $ids = implode(',', $ids);
1600
1601
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
1602
        $sql = "SELECT * FROM $tbl_user WHERE id IN ($ids)";
1603
        if (!is_null($active)) {
1604
            $sql .= ' AND active='.($active ? '1' : '0');
1605
        }
1606
1607
        if (!is_null($order)) {
1608
            $order = Database::escape_string($order);
1609
            $sql .= ' ORDER BY '.$order;
1610
        }
1611
1612
        if (!is_null($limit)) {
1613
            $limit = Database::escape_string($limit);
1614
            $sql .= ' LIMIT '.$limit;
1615
        }
1616
1617
        $rs = Database::query($sql);
1618
        $result = [];
1619
        while ($row = Database::fetch_array($rs)) {
1620
            $result[] = $row;
1621
        }
1622
1623
        return $result;
1624
    }
1625
1626
    /**
1627
     * Get a list of users of which the given conditions match with an = 'cond'.
1628
     *
1629
     * @param array $conditions a list of condition (example : status=>STUDENT)
1630
     * @param array $order_by   a list of fields on which sort
1631
     *
1632
     * @return array an array with all users of the platform
1633
     *
1634
     * @todo security filter order by
1635
     */
1636
    public static function get_user_list(
1637
        $conditions = [],
1638
        $order_by = [],
1639
        $limit_from = false,
1640
        $limit_to = false
1641
    ) {
1642
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1643
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1644
        $return_array = [];
1645
        $sql = "SELECT user.* FROM $user_table user ";
1646
1647
        if (api_is_multiple_url_enabled()) {
1648
            $urlId = api_get_current_access_url_id();
1649
            $sql .= " INNER JOIN $userUrlTable url_user
1650
                      ON (user.user_id = url_user.user_id)
1651
                      WHERE url_user.access_url_id = $urlId";
1652
        } else {
1653
            $sql .= " WHERE 1=1 ";
1654
        }
1655
1656
        if (count($conditions) > 0) {
1657
            foreach ($conditions as $field => $value) {
1658
                $field = Database::escape_string($field);
1659
                $value = Database::escape_string($value);
1660
                $sql .= " AND $field = '$value'";
1661
            }
1662
        }
1663
1664
        if (count($order_by) > 0) {
1665
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1666
        }
1667
1668
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1669
            $limit_from = (int) $limit_from;
1670
            $limit_to = (int) $limit_to;
1671
            $sql .= " LIMIT $limit_from, $limit_to";
1672
        }
1673
        $sql_result = Database::query($sql);
1674
        while ($result = Database::fetch_array($sql_result)) {
1675
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1676
            $return_array[] = $result;
1677
        }
1678
1679
        return $return_array;
1680
    }
1681
1682
    /**
1683
     * Get a list of users of which the given conditions match with a LIKE '%cond%'.
1684
     *
1685
     * @param array  $conditions       a list of condition (exemple : status=>STUDENT)
1686
     * @param array  $order_by         a list of fields on which sort
1687
     * @param bool   $simple_like      Whether we want a simple LIKE 'abc' or a LIKE '%abc%'
1688
     * @param string $condition        Whether we want the filters to be combined by AND or OR
1689
     * @param array  $onlyThisUserList
1690
     *
1691
     * @return array an array with all users of the platform
1692
     *
1693
     * @todo optional course code parameter, optional sorting parameters...
1694
     * @todo security filter order_by
1695
     */
1696
    public static function getUserListLike(
1697
        $conditions = [],
1698
        $order_by = [],
1699
        $simple_like = false,
1700
        $condition = 'AND',
1701
        $onlyThisUserList = []
1702
    ) {
1703
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1704
        $tblAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1705
        $return_array = [];
1706
        $sql_query = "SELECT user.id FROM $user_table user ";
1707
1708
        if (api_is_multiple_url_enabled()) {
1709
            $sql_query .= " INNER JOIN $tblAccessUrlRelUser auru ON auru.user_id = user.id ";
1710
        }
1711
1712
        $sql_query .= ' WHERE 1 = 1 ';
1713
        if (count($conditions) > 0) {
1714
            $temp_conditions = [];
1715
            foreach ($conditions as $field => $value) {
1716
                $field = Database::escape_string($field);
1717
                $value = Database::escape_string($value);
1718
                if ($simple_like) {
1719
                    $temp_conditions[] = $field." LIKE '$value%'";
1720
                } else {
1721
                    $temp_conditions[] = $field.' LIKE \'%'.$value.'%\'';
1722
                }
1723
            }
1724
            if (!empty($temp_conditions)) {
1725
                $sql_query .= ' AND '.implode(' '.$condition.' ', $temp_conditions);
1726
            }
1727
1728
            if (api_is_multiple_url_enabled()) {
1729
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1730
            }
1731
        } else {
1732
            if (api_is_multiple_url_enabled()) {
1733
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
1734
            }
1735
        }
1736
1737
        if (!empty($onlyThisUserList)) {
1738
            $onlyThisUserListToString = implode("','", $onlyThisUserList);
1739
            $sql_query .= " AND user.id IN ('$onlyThisUserListToString') ";
1740
        }
1741
1742
        if (count($order_by) > 0) {
1743
            $sql_query .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1744
        }
1745
1746
        $sql_result = Database::query($sql_query);
1747
        while ($result = Database::fetch_array($sql_result)) {
1748
            $userInfo = api_get_user_info($result['id']);
1749
            $return_array[] = $userInfo;
1750
        }
1751
1752
        return $return_array;
1753
    }
1754
1755
    /**
1756
     * Get user picture URL or path from user ID (returns an array).
1757
     * The return format is a complete path, enabling recovery of the directory
1758
     * with dirname() or the file with basename(). This also works for the
1759
     * functions dealing with the user's productions, as they are located in
1760
     * the same directory.
1761
     *
1762
     * @param int    $id       User ID
1763
     * @param string $type     Type of path to return (can be 'system', 'web')
1764
     * @param array  $userInfo user information to avoid query the DB
1765
     *                         returns the /main/img/unknown.jpg image set it at true
1766
     *
1767
     * @return array Array of 2 elements: 'dir' and 'file' which contain
1768
     *               the dir and file as the name implies if image does not exist it will
1769
     *               return the unknow image if anonymous parameter is true if not it returns an empty array
1770
     */
1771
    public static function get_user_picture_path_by_id(
1772
        $id,
1773
        $type = 'web',
1774
        $userInfo = []
1775
    ) {
1776
        switch ($type) {
1777
            case 'system': // Base: absolute system path.
1778
                $base = api_get_path(SYS_CODE_PATH);
1779
                break;
1780
            case 'web': // Base: absolute web path.
1781
            default:
1782
                $base = api_get_path(WEB_CODE_PATH);
1783
                break;
1784
        }
1785
1786
        $anonymousPath = [
1787
            'dir' => $base.'img/',
1788
            'file' => 'unknown.jpg',
1789
            'email' => '',
1790
        ];
1791
1792
        if (empty($id) || empty($type)) {
1793
            return $anonymousPath;
1794
        }
1795
1796
        $id = (int) $id;
1797
        if (empty($userInfo)) {
1798
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
1799
            $sql = "SELECT email, picture_uri FROM $user_table
1800
                    WHERE id=".$id;
1801
            $res = Database::query($sql);
1802
1803
            if (!Database::num_rows($res)) {
1804
                return $anonymousPath;
1805
            }
1806
            $user = Database::fetch_array($res);
1807
            if (empty($user['picture_uri'])) {
1808
                return $anonymousPath;
1809
            }
1810
        } else {
1811
            $user = $userInfo;
1812
        }
1813
1814
        $pictureFilename = trim($user['picture_uri']);
1815
1816
        $dir = self::getUserPathById($id, $type);
1817
1818
        return [
1819
            'dir' => $dir,
1820
            'file' => $pictureFilename,
1821
            'email' => $user['email'],
1822
        ];
1823
    }
1824
1825
    /**
1826
     * *** READ BEFORE REVIEW THIS FUNCTION ***
1827
     * This function is a exact copy from get_user_picture_path_by_id() and it was create it to avoid
1828
     * a recursive calls for get_user_picture_path_by_id() in another functions when you update a user picture
1829
     * in same script, so you can find this function usage in update_user_picture() function.
1830
     *
1831
     * @param int    $id       User ID
1832
     * @param string $type     Type of path to return (can be 'system', 'web')
1833
     * @param array  $userInfo user information to avoid query the DB
1834
     *                         returns the /main/img/unknown.jpg image set it at true
1835
     *
1836
     * @return array Array of 2 elements: 'dir' and 'file' which contain
1837
     *               the dir and file as the name implies if image does not exist it will
1838
     *               return the unknown image if anonymous parameter is true if not it returns an empty array
1839
     */
1840
    public static function getUserPicturePathById($id, $type = 'web', $userInfo = [])
1841
    {
1842
        switch ($type) {
1843
            case 'system': // Base: absolute system path.
1844
                $base = api_get_path(SYS_CODE_PATH);
1845
                break;
1846
            case 'web': // Base: absolute web path.
1847
            default:
1848
                $base = api_get_path(WEB_CODE_PATH);
1849
                break;
1850
        }
1851
1852
        $anonymousPath = [
1853
            'dir' => $base.'img/',
1854
            'file' => 'unknown.jpg',
1855
            'email' => '',
1856
        ];
1857
1858
        if (empty($id) || empty($type)) {
1859
            return $anonymousPath;
1860
        }
1861
1862
        $id = (int) $id;
1863
1864
        if (empty($userInfo)) {
1865
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
1866
            $sql = "SELECT email, picture_uri FROM $user_table WHERE id = $id";
1867
            $res = Database::query($sql);
1868
1869
            if (!Database::num_rows($res)) {
1870
                return $anonymousPath;
1871
            }
1872
            $user = Database::fetch_array($res);
1873
1874
            if (empty($user['picture_uri'])) {
1875
                return $anonymousPath;
1876
            }
1877
        } else {
1878
            $user = $userInfo;
1879
        }
1880
1881
        $pictureFilename = trim($user['picture_uri']);
1882
        $dir = self::getUserPathById($id, $type);
1883
1884
        return [
1885
            'dir' => $dir,
1886
            'file' => $pictureFilename,
1887
            'email' => $user['email'],
1888
        ];
1889
    }
1890
1891
    /**
1892
     * Get user path from user ID (returns an array).
1893
     * The return format is a complete path to a folder ending with "/"
1894
     * In case the first level of subdirectory of users/ does not exist, the
1895
     * function will attempt to create it. Probably not the right place to do it
1896
     * but at least it avoids headaches in many other places.
1897
     *
1898
     * @param int    $id   User ID
1899
     * @param string $type Type of path to return (can be 'system', 'web', 'last')
1900
     *
1901
     * @return string User folder path (i.e. /var/www/chamilo/app/upload/users/1/1/)
1902
     */
1903
    public static function getUserPathById($id, $type)
1904
    {
1905
        $id = (int) $id;
1906
        if (!$id) {
1907
            return null;
1908
        }
1909
1910
        $userPath = "users/$id/";
1911
        if (api_get_setting('split_users_upload_directory') === 'true') {
1912
            $userPath = 'users/'.substr((string) $id, 0, 1).'/'.$id.'/';
1913
            // In exceptional cases, on some portals, the intermediate base user
1914
            // directory might not have been created. Make sure it is before
1915
            // going further.
1916
1917
            $rootPath = api_get_path(SYS_UPLOAD_PATH).'users/'.substr((string) $id, 0, 1);
1918
            if (!is_dir($rootPath)) {
1919
                $perm = api_get_permissions_for_new_directories();
1920
                try {
1921
                    mkdir($rootPath, $perm);
1922
                } catch (Exception $e) {
1923
                    error_log($e->getMessage());
1924
                }
1925
            }
1926
        }
1927
        switch ($type) {
1928
            case 'system': // Base: absolute system path.
1929
                $userPath = api_get_path(SYS_UPLOAD_PATH).$userPath;
1930
                break;
1931
            case 'web': // Base: absolute web path.
1932
                $userPath = api_get_path(WEB_UPLOAD_PATH).$userPath;
1933
                break;
1934
            case 'last': // Only the last part starting with users/
1935
                break;
1936
        }
1937
1938
        return $userPath;
1939
    }
1940
1941
    /**
1942
     * Gets the current user image.
1943
     *
1944
     * @param string $user_id
1945
     * @param int    $size        it can be USER_IMAGE_SIZE_SMALL,
1946
     *                            USER_IMAGE_SIZE_MEDIUM, USER_IMAGE_SIZE_BIG or  USER_IMAGE_SIZE_ORIGINAL
1947
     * @param bool   $addRandomId
1948
     * @param array  $userInfo    to avoid query the DB
1949
     *
1950
     * @return string
1951
     */
1952
    public static function getUserPicture(
1953
        $user_id,
1954
        $size = USER_IMAGE_SIZE_MEDIUM,
1955
        $addRandomId = true,
1956
        $userInfo = []
1957
    ) {
1958
        // Make sure userInfo is defined. Otherwise, define it!
1959
        if (empty($userInfo) || !is_array($userInfo) || count($userInfo) == 0) {
1960
            if (empty($user_id)) {
1961
                return '';
1962
            } else {
1963
                $userInfo = api_get_user_info($user_id);
1964
            }
1965
        }
1966
1967
        $imageWebPath = self::get_user_picture_path_by_id(
1968
            $user_id,
1969
            'web',
1970
            $userInfo
1971
        );
1972
        $pictureWebFile = $imageWebPath['file'];
1973
        $pictureWebDir = $imageWebPath['dir'];
1974
1975
        $pictureAnonymousSize = '128';
1976
        $gravatarSize = 22;
1977
        $realSizeName = 'small_';
1978
1979
        switch ($size) {
1980
            case USER_IMAGE_SIZE_SMALL:
1981
                $pictureAnonymousSize = '32';
1982
                $realSizeName = 'small_';
1983
                $gravatarSize = 32;
1984
                break;
1985
            case USER_IMAGE_SIZE_MEDIUM:
1986
                $pictureAnonymousSize = '64';
1987
                $realSizeName = 'medium_';
1988
                $gravatarSize = 64;
1989
                break;
1990
            case USER_IMAGE_SIZE_ORIGINAL:
1991
                $pictureAnonymousSize = '128';
1992
                $realSizeName = '';
1993
                $gravatarSize = 128;
1994
                break;
1995
            case USER_IMAGE_SIZE_BIG:
1996
                $pictureAnonymousSize = '128';
1997
                $realSizeName = 'big_';
1998
                $gravatarSize = 128;
1999
                break;
2000
        }
2001
2002
        $gravatarEnabled = api_get_setting('gravatar_enabled');
2003
        $anonymousPath = Display::returnIconPath('unknown.png', $pictureAnonymousSize);
2004
        if ($pictureWebFile == 'unknown.jpg' || empty($pictureWebFile)) {
2005
            if ($gravatarEnabled === 'true') {
2006
                $file = self::getGravatar(
2007
                    $imageWebPath['email'],
2008
                    $gravatarSize,
2009
                    api_get_setting('gravatar_type')
2010
                );
2011
2012
                if ($addRandomId) {
2013
                    $file .= '&rand='.uniqid();
2014
                }
2015
2016
                return $file;
2017
            }
2018
2019
            return $anonymousPath;
2020
        }
2021
2022
        $pictureSysPath = self::get_user_picture_path_by_id($user_id, 'system');
2023
        $file = $pictureSysPath['dir'].$realSizeName.$pictureWebFile;
2024
        $picture = '';
2025
        if (file_exists($file)) {
2026
            $picture = $pictureWebDir.$realSizeName.$pictureWebFile;
2027
        } else {
2028
            $file = $pictureSysPath['dir'].$pictureWebFile;
2029
            if (file_exists($file) && !is_dir($file)) {
2030
                $picture = $pictureWebFile['dir'].$pictureWebFile;
2031
            }
2032
        }
2033
2034
        if (empty($picture)) {
2035
            return $anonymousPath;
2036
        }
2037
2038
        if ($addRandomId) {
2039
            $picture .= '?rand='.uniqid();
2040
        }
2041
2042
        return $picture;
2043
    }
2044
2045
    /**
2046
     * Creates new user photos in various sizes of a user, or deletes user photos.
2047
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2048
     *
2049
     * @param int    $user_id the user internal identification number
2050
     * @param string $file    The common file name for the newly created photos.
2051
     *                        It will be checked and modified for compatibility with the file system.
2052
     *                        If full name is provided, path component is ignored.
2053
     *                        If an empty name is provided, then old user photos are deleted only,
2054
     *
2055
     * @see     UserManager::delete_user_picture() as the prefered way for deletion.
2056
     *
2057
     * @param string $source_file    the full system name of the image from which user photos will be created
2058
     * @param string $cropParameters Optional string that contents "x,y,width,height" of a cropped image format
2059
     *
2060
     * @return bool Returns the resulting common file name of created images which usually should be stored in database.
2061
     *              When deletion is requested returns empty string.
2062
     *              In case of internal error or negative validation returns FALSE.
2063
     */
2064
    public static function update_user_picture(
2065
        $user_id,
2066
        $file = null,
2067
        $source_file = null,
2068
        $cropParameters = ''
2069
    ) {
2070
        if (empty($user_id)) {
2071
            return false;
2072
        }
2073
        $delete = empty($file);
2074
        if (empty($source_file)) {
2075
            $source_file = $file;
2076
        }
2077
2078
        // User-reserved directory where photos have to be placed.
2079
        $path_info = self::getUserPicturePathById($user_id, 'system');
2080
        $path = $path_info['dir'];
2081
2082
        // If this directory does not exist - we create it.
2083
        if (!file_exists($path)) {
2084
            mkdir($path, api_get_permissions_for_new_directories(), true);
2085
        }
2086
2087
        // The old photos (if any).
2088
        $old_file = $path_info['file'];
2089
2090
        // Let us delete them.
2091
        if ($old_file != 'unknown.jpg') {
2092
            if (api_get_setting('platform.keep_old_images_after_delete') == 'true') {
2093
                $prefix = 'saved_'.date('Y_m_d_H_i_s').'_'.uniqid('').'_';
2094
                @rename($path.'small_'.$old_file, $path.$prefix.'small_'.$old_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2094
                /** @scrutinizer ignore-unhandled */ @rename($path.'small_'.$old_file, $path.$prefix.'small_'.$old_file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2095
                @rename($path.'medium_'.$old_file, $path.$prefix.'medium_'.$old_file);
2096
                @rename($path.'big_'.$old_file, $path.$prefix.'big_'.$old_file);
2097
                @rename($path.$old_file, $path.$prefix.$old_file);
2098
            } else {
2099
                @unlink($path.'small_'.$old_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2099
                /** @scrutinizer ignore-unhandled */ @unlink($path.'small_'.$old_file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2100
                @unlink($path.'medium_'.$old_file);
2101
                @unlink($path.'big_'.$old_file);
2102
                @unlink($path.$old_file);
2103
            }
2104
        }
2105
2106
        // Exit if only deletion has been requested. Return an empty picture name.
2107
        if ($delete) {
2108
            return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type boolean.
Loading history...
2109
        }
2110
2111
        // Validation 2.
2112
        $allowed_types = api_get_supported_image_extensions();
2113
        $file = str_replace('\\', '/', $file);
2114
        $filename = (($pos = strrpos($file, '/')) !== false) ? substr($file, $pos + 1) : $file;
2115
        $extension = strtolower(substr(strrchr($filename, '.'), 1));
2116
        if (!in_array($extension, $allowed_types)) {
2117
            return false;
2118
        }
2119
2120
        // This is the common name for the new photos.
2121
        if ($old_file != 'unknown.jpg') {
2122
            $old_extension = strtolower(substr(strrchr($old_file, '.'), 1));
2123
            $filename = in_array($old_extension, $allowed_types) ? substr($old_file, 0, -strlen($old_extension)) : $old_file;
2124
            $filename = (substr($filename, -1) == '.') ? $filename.$extension : $filename.'.'.$extension;
2125
        } else {
2126
            $filename = api_replace_dangerous_char($filename);
2127
            $filename = uniqid('').'_'.$filename;
2128
            // We always prefix user photos with user ids, so on setting
2129
            // api_get_setting('split_users_upload_directory') === 'true'
2130
            // the correspondent directories to be found successfully.
2131
            $filename = $user_id.'_'.$filename;
2132
        }
2133
2134
        //Crop the image to adjust 1:1 ratio
2135
        $image = new Image($source_file);
2136
        $image->crop($cropParameters);
2137
2138
        // Storing the new photos in 4 versions with various sizes.
2139
        $userPath = self::getUserPathById($user_id, 'system');
2140
2141
        // If this path does not exist - we create it.
2142
        if (!file_exists($userPath)) {
2143
            mkdir($userPath, api_get_permissions_for_new_directories(), true);
2144
        }
2145
        $small = new Image($source_file);
2146
        $small->resize(32);
2147
        $small->send_image($userPath.'small_'.$filename);
2148
        $medium = new Image($source_file);
2149
        $medium->resize(85);
2150
        $medium->send_image($userPath.'medium_'.$filename);
2151
        $normal = new Image($source_file);
2152
        $normal->resize(200);
2153
        $normal->send_image($userPath.$filename);
2154
2155
        $big = new Image($source_file); // This is the original picture.
2156
        $big->send_image($userPath.'big_'.$filename);
2157
2158
        $result = $small && $medium && $normal && $big;
0 ignored issues
show
introduced by
$big is of type Image, thus it always evaluated to true.
Loading history...
introduced by
$medium is of type Image, thus it always evaluated to true.
Loading history...
introduced by
$normal is of type Image, thus it always evaluated to true.
Loading history...
2159
2160
        return $result ? $filename : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result ? $filename : false returns the type string which is incompatible with the documented return type boolean.
Loading history...
2161
    }
2162
2163
    /**
2164
     * Update User extra field file type into {user_folder}/{$extra_field}.
2165
     *
2166
     * @param int    $user_id     The user internal identification number
2167
     * @param string $extra_field The $extra_field The extra field name
2168
     * @param null   $file        The filename
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $file is correct as it would always require null to be passed?
Loading history...
2169
     * @param null   $source_file The temporal filename
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $source_file is correct as it would always require null to be passed?
Loading history...
2170
     *
2171
     * @return bool|null return filename if success, but false
2172
     */
2173
    public static function update_user_extra_file(
2174
        $user_id,
2175
        $extra_field = '',
2176
        $file = null,
2177
        $source_file = null
2178
    ) {
2179
        // Add Filter
2180
        $source_file = Security::filter_filename($source_file);
2181
        $file = Security::filter_filename($file);
2182
2183
        if (empty($user_id)) {
2184
            return false;
2185
        }
2186
2187
        if (empty($source_file)) {
2188
            $source_file = $file;
2189
        }
2190
2191
        // User-reserved directory where extra file have to be placed.
2192
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
2193
        $path = $path_info['dir'];
2194
        if (!empty($extra_field)) {
2195
            $path .= $extra_field.'/';
2196
        }
2197
        // If this directory does not exist - we create it.
2198
        if (!file_exists($path)) {
2199
            @mkdir($path, api_get_permissions_for_new_directories(), true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

2199
            /** @scrutinizer ignore-unhandled */ @mkdir($path, api_get_permissions_for_new_directories(), true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2200
        }
2201
2202
        if (filter_extension($file)) {
2203
            if (@move_uploaded_file($source_file, $path.$file)) {
2204
                if ($extra_field) {
2205
                    return $extra_field.'/'.$file;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extra_field . '/' . $file returns the type string which is incompatible with the documented return type boolean|null.
Loading history...
2206
                } else {
2207
                    return $file;
2208
                }
2209
            }
2210
        }
2211
2212
        return false; // this should be returned if anything went wrong with the upload
2213
    }
2214
2215
    /**
2216
     * Deletes user photos.
2217
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2218
     *
2219
     * @param int $userId the user internal identification number
2220
     *
2221
     * @return mixed returns empty string on success, FALSE on error
2222
     */
2223
    public static function deleteUserPicture($userId)
2224
    {
2225
        return self::update_user_picture($userId);
2226
    }
2227
2228
    /**
2229
     * Returns an XHTML formatted list of productions for a user, or FALSE if he
2230
     * doesn't have any.
2231
     *
2232
     * If there has been a request to remove a production, the function will return
2233
     * without building the list unless forced to do so by the optional second
2234
     * parameter. This increases performance by avoiding to read through the
2235
     * productions on the filesystem before the removal request has been carried
2236
     * out because they'll have to be re-read afterwards anyway.
2237
     *
2238
     * @param int  $user_id    User id
2239
     * @param bool $force      Optional parameter to force building after a removal request
2240
     * @param bool $showDelete
2241
     *
2242
     * @return string A string containing the XHTML code to display the production list, or FALSE
2243
     */
2244
    public static function build_production_list($user_id, $force = false, $showDelete = false)
2245
    {
2246
        if (!$force && !empty($_POST['remove_production'])) {
2247
            return true; // postpone reading from the filesystem
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
2248
        }
2249
2250
        $productions = self::get_user_productions($user_id);
2251
2252
        if (empty($productions)) {
2253
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2254
        }
2255
2256
        $production_dir = self::getUserPathById($user_id, 'web');
2257
        $del_image = Display::returnIconPath('delete.png');
2258
        $add_image = Display::returnIconPath('archive.png');
2259
        $del_text = get_lang('Delete');
2260
        $production_list = '';
2261
        if (count($productions) > 0) {
2262
            $production_list = '<div class="files-production"><ul id="productions">';
2263
            foreach ($productions as $file) {
2264
                $production_list .= '<li>
2265
                    <img src="'.$add_image.'" />
2266
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2267
                        '.htmlentities($file).'
2268
                    </a>';
2269
                if ($showDelete) {
2270
                    $production_list .= '&nbsp;&nbsp;
2271
                        <input 
2272
                            style="width:16px;" 
2273
                            type="image" 
2274
                            name="remove_production['.urlencode($file).']" 
2275
                            src="'.$del_image.'" 
2276
                            alt="'.$del_text.'" 
2277
                            title="'.$del_text.' '.htmlentities($file).'" 
2278
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2279
                }
2280
            }
2281
            $production_list .= '</ul></div>';
2282
        }
2283
2284
        return $production_list;
2285
    }
2286
2287
    /**
2288
     * Returns an array with the user's productions.
2289
     *
2290
     * @param int $user_id User id
2291
     *
2292
     * @return array An array containing the user's productions
2293
     */
2294
    public static function get_user_productions($user_id)
2295
    {
2296
        $production_repository = self::getUserPathById($user_id, 'system');
2297
        $productions = [];
2298
2299
        if (is_dir($production_repository)) {
2300
            $handle = opendir($production_repository);
2301
            while ($file = readdir($handle)) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

2301
            while ($file = readdir(/** @scrutinizer ignore-type */ $handle)) {
Loading history...
2302
                if ($file == '.' ||
2303
                    $file == '..' ||
2304
                    $file == '.htaccess' ||
2305
                    is_dir($production_repository.$file)
2306
                ) {
2307
                    // skip current/parent directory and .htaccess
2308
                    continue;
2309
                }
2310
2311
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
2312
                    // User's photos should not be listed as productions.
2313
                    continue;
2314
                }
2315
                $productions[] = $file;
2316
            }
2317
        }
2318
2319
        return $productions;
2320
    }
2321
2322
    /**
2323
     * Remove a user production.
2324
     *
2325
     * @param int    $user_id    User id
2326
     * @param string $production The production to remove
2327
     *
2328
     * @return bool
2329
     */
2330
    public static function remove_user_production($user_id, $production)
2331
    {
2332
        $production_path = self::get_user_picture_path_by_id($user_id, 'system');
2333
        $production_file = $production_path['dir'].$production;
2334
        if (is_file($production_file)) {
2335
            unlink($production_file);
2336
2337
            return true;
2338
        }
2339
2340
        return false;
2341
    }
2342
2343
    /**
2344
     * Update an extra field value for a given user.
2345
     *
2346
     * @param int    $userId   User ID
2347
     * @param string $variable Field variable name
2348
     * @param string $value    Field value
2349
     *
2350
     * @return bool true if field updated, false otherwise
2351
     */
2352
    public static function update_extra_field_value($userId, $variable, $value = '')
2353
    {
2354
        $extraFieldValue = new ExtraFieldValue('user');
2355
        $params = [
2356
            'item_id' => $userId,
2357
            'variable' => $variable,
2358
            'value' => $value,
2359
        ];
2360
2361
        return $extraFieldValue->save($params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraFieldValue->save($params) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
2362
    }
2363
2364
    /**
2365
     * Get an array of extra fields with field details (type, default value and options).
2366
     *
2367
     * @param    int    Offset (from which row)
2368
     * @param    int    Number of items
2369
     * @param    int    Column on which sorting is made
2370
     * @param    string    Sorting direction
2371
     * @param    bool    Optional. Whether we get all the fields or just the visible ones
0 ignored issues
show
Documentation Bug introduced by
The doc comment Optional. at position 0 could not be parsed: Unknown type name 'Optional.' at position 0 in Optional..
Loading history...
2372
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
2373
     *
2374
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
2375
     */
2376
    public static function get_extra_fields(
2377
        $from = 0,
2378
        $number_of_items = 0,
2379
        $column = 5,
2380
        $direction = 'ASC',
2381
        $all_visibility = true,
2382
        $field_filter = null
2383
    ) {
2384
        $fields = [];
2385
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2386
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
2387
        $columns = [
2388
            'id',
2389
            'variable',
2390
            'field_type',
2391
            'display_text',
2392
            'default_value',
2393
            'field_order',
2394
            'filter',
2395
        ];
2396
        $column = (int) $column;
2397
        $sort_direction = '';
2398
        if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
2399
            $sort_direction = strtoupper($direction);
2400
        }
2401
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
2402
        $sqlf = "SELECT * FROM $t_uf WHERE extra_field_type = $extraFieldType ";
2403
        if (!$all_visibility) {
2404
            $sqlf .= " AND visible_to_self = 1 ";
2405
        }
2406
        if (!is_null($field_filter)) {
2407
            $field_filter = (int) $field_filter;
2408
            $sqlf .= " AND filter = $field_filter ";
2409
        }
2410
        $sqlf .= " ORDER BY ".$columns[$column]." $sort_direction ";
2411
        if ($number_of_items != 0) {
2412
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
2413
        }
2414
        $resf = Database::query($sqlf);
2415
        if (Database::num_rows($resf) > 0) {
2416
            while ($rowf = Database::fetch_array($resf)) {
2417
                $fields[$rowf['id']] = [
2418
                    0 => $rowf['id'],
2419
                    1 => $rowf['variable'],
2420
                    2 => $rowf['field_type'],
2421
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
2422
                    4 => $rowf['default_value'],
2423
                    5 => $rowf['field_order'],
2424
                    6 => $rowf['visible_to_self'],
2425
                    7 => $rowf['changeable'],
2426
                    8 => $rowf['filter'],
2427
                    9 => [],
2428
                    10 => '<a name="'.$rowf['id'].'"></a>',
2429
                ];
2430
2431
                $sqlo = "SELECT * FROM $t_ufo
2432
                         WHERE field_id = ".$rowf['id']."
2433
                         ORDER BY option_order ASC";
2434
                $reso = Database::query($sqlo);
2435
                if (Database::num_rows($reso) > 0) {
2436
                    while ($rowo = Database::fetch_array($reso)) {
2437
                        $fields[$rowf['id']][9][$rowo['id']] = [
2438
                            0 => $rowo['id'],
2439
                            1 => $rowo['option_value'],
2440
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
2441
                            3 => $rowo['option_order'],
2442
                        ];
2443
                    }
2444
                }
2445
            }
2446
        }
2447
2448
        return $fields;
2449
    }
2450
2451
    /**
2452
     * Build a list of extra file already uploaded in $user_folder/{$extra_field}/.
2453
     *
2454
     * @param $user_id
2455
     * @param $extra_field
2456
     * @param bool $force
2457
     * @param bool $showDelete
2458
     *
2459
     * @return bool|string
2460
     */
2461
    public static function build_user_extra_file_list(
2462
        $user_id,
2463
        $extra_field,
2464
        $force = false,
2465
        $showDelete = false
2466
    ) {
2467
        if (!$force && !empty($_POST['remove_'.$extra_field])) {
2468
            return true; // postpone reading from the filesystem
2469
        }
2470
2471
        $extra_files = self::get_user_extra_files($user_id, $extra_field);
2472
        if (empty($extra_files)) {
2473
            return false;
2474
        }
2475
2476
        $path_info = self::get_user_picture_path_by_id($user_id, 'web');
2477
        $path = $path_info['dir'];
2478
        $del_image = Display::returnIconPath('delete.png');
2479
2480
        $del_text = get_lang('Delete');
2481
        $extra_file_list = '';
2482
        if (count($extra_files) > 0) {
2483
            $extra_file_list = '<div class="files-production"><ul id="productions">';
2484
            foreach ($extra_files as $file) {
2485
                $filename = substr($file, strlen($extra_field) + 1);
2486
                $extra_file_list .= '<li>'.Display::return_icon('archive.png').
2487
                    '<a href="'.$path.$extra_field.'/'.urlencode($filename).'" target="_blank">
2488
                        '.htmlentities($filename).
2489
                    '</a> ';
2490
                if ($showDelete) {
2491
                    $extra_file_list .= '<input 
2492
                        style="width:16px;" 
2493
                        type="image" 
2494
                        name="remove_extra_'.$extra_field.'['.urlencode($file).']" 
2495
                        src="'.$del_image.'" 
2496
                        alt="'.$del_text.'"
2497
                        title="'.$del_text.' '.htmlentities($filename).'" 
2498
                        onclick="javascript: return confirmation(\''.htmlentities($filename).'\');" /></li>';
2499
                }
2500
            }
2501
            $extra_file_list .= '</ul></div>';
2502
        }
2503
2504
        return $extra_file_list;
2505
    }
2506
2507
    /**
2508
     * Get valid filenames in $user_folder/{$extra_field}/.
2509
     *
2510
     * @param $user_id
2511
     * @param $extra_field
2512
     * @param bool $full_path
2513
     *
2514
     * @return array
2515
     */
2516
    public static function get_user_extra_files($user_id, $extra_field, $full_path = false)
2517
    {
2518
        if (!$full_path) {
2519
            // Nothing to do
2520
        } else {
2521
            $path_info = self::get_user_picture_path_by_id($user_id, 'system');
2522
            $path = $path_info['dir'];
2523
        }
2524
        $extra_data = self::get_extra_user_data_by_field($user_id, $extra_field);
2525
        $extra_files = $extra_data[$extra_field];
2526
        if (is_array($extra_files)) {
2527
            foreach ($extra_files as $key => $value) {
2528
                if (!$full_path) {
2529
                    // Relative path from user folder
2530
                    $files[] = $value;
2531
                } else {
2532
                    $files[] = $path.$value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $path does not seem to be defined for all execution paths leading up to this point.
Loading history...
2533
                }
2534
            }
2535
        } elseif (!empty($extra_files)) {
2536
            if (!$full_path) {
2537
                // Relative path from user folder
2538
                $files[] = $extra_files;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$files was never initialized. Although not strictly required by PHP, it is generally a good practice to add $files = array(); before regardless.
Loading history...
2539
            } else {
2540
                $files[] = $path.$extra_files;
2541
            }
2542
        }
2543
2544
        return $files; // can be an empty array
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $files does not seem to be defined for all execution paths leading up to this point.
Loading history...
2545
    }
2546
2547
    /**
2548
     * Remove an {$extra_file} from the user folder $user_folder/{$extra_field}/.
2549
     *
2550
     * @param int    $user_id
2551
     * @param string $extra_field
2552
     * @param string $extra_file
2553
     *
2554
     * @return bool
2555
     */
2556
    public static function remove_user_extra_file($user_id, $extra_field, $extra_file)
2557
    {
2558
        $extra_file = Security::filter_filename($extra_file);
2559
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
2560
        if (strpos($extra_file, $extra_field) !== false) {
2561
            $path_extra_file = $path_info['dir'].$extra_file;
2562
        } else {
2563
            $path_extra_file = $path_info['dir'].$extra_field.'/'.$extra_file;
2564
        }
2565
        if (is_file($path_extra_file)) {
2566
            unlink($path_extra_file);
2567
2568
            return true;
2569
        }
2570
2571
        return false;
2572
    }
2573
2574
    /**
2575
     * Creates a new extra field.
2576
     *
2577
     * @param string $variable    Field's internal variable name
2578
     * @param int    $fieldType   Field's type
2579
     * @param string $displayText Field's language var name
2580
     * @param string $default     Field's default value
2581
     *
2582
     * @return int
2583
     */
2584
    public static function create_extra_field(
2585
        $variable,
2586
        $fieldType,
2587
        $displayText,
2588
        $default
2589
    ) {
2590
        $extraField = new ExtraField('user');
2591
        $params = [
2592
            'variable' => $variable,
2593
            'field_type' => $fieldType,
2594
            'display_text' => $displayText,
2595
            'default_value' => $default,
2596
        ];
2597
2598
        return $extraField->save($params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->save($params) also could return the type boolean which is incompatible with the documented return type integer.
Loading history...
2599
    }
2600
2601
    /**
2602
     * Check if a field is available.
2603
     *
2604
     * @param string $variable
2605
     *
2606
     * @return bool
2607
     */
2608
    public static function is_extra_field_available($variable)
2609
    {
2610
        $extraField = new ExtraField('user');
2611
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
2612
2613
        return !empty($data) ? true : false;
2614
    }
2615
2616
    /**
2617
     * Gets user extra fields data.
2618
     *
2619
     * @param    int    User ID
2620
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
2621
     * @param    bool    Whether to return invisible fields as well
2622
     * @param    bool    Whether to split multiple-selection fields or not
2623
     *
2624
     * @return array Array of fields => value for the given user
2625
     */
2626
    public static function get_extra_user_data(
2627
        $user_id,
2628
        $prefix = false,
2629
        $allVisibility = true,
2630
        $splitMultiple = false,
0 ignored issues
show
Unused Code introduced by
The parameter $splitMultiple is not used and could be removed. ( Ignorable by Annotation )

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

2630
        /** @scrutinizer ignore-unused */ $splitMultiple = false,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2631
        $fieldFilter = null
2632
    ) {
2633
        $user_id = (int) $user_id;
2634
2635
        if (empty($user_id)) {
2636
            return [];
2637
        }
2638
2639
        $extra_data = [];
2640
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2641
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2642
        $user_id = (int) $user_id;
2643
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
2644
                FROM $t_uf f
2645
                WHERE
2646
                    extra_field_type = ".EntityExtraField::USER_FIELD_TYPE."
2647
                ";
2648
        $filter_cond = '';
2649
2650
        if (!$allVisibility) {
2651
            if (isset($fieldFilter)) {
2652
                $fieldFilter = (int) $fieldFilter;
2653
                $filter_cond .= " AND filter = $fieldFilter ";
2654
            }
2655
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
2656
        } else {
2657
            if (isset($fieldFilter)) {
2658
                $fieldFilter = (int) $fieldFilter;
2659
                $sql .= " AND filter = $fieldFilter ";
2660
            }
2661
        }
2662
2663
        $sql .= ' ORDER BY f.field_order';
2664
2665
        $res = Database::query($sql);
2666
        if (Database::num_rows($res) > 0) {
2667
            while ($row = Database::fetch_array($res)) {
2668
                if ($row['type'] == self::USER_FIELD_TYPE_TAG) {
2669
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
2670
                    $extra_data['extra_'.$row['fvar']] = $tags;
2671
                } else {
2672
                    $sqlu = "SELECT value as fval
2673
                            FROM $t_ufv
2674
                            WHERE field_id=".$row['id']." AND item_id = ".$user_id;
2675
                    $resu = Database::query($sqlu);
2676
                    // get default value
2677
                    $sql_df = "SELECT default_value as fval_df FROM $t_uf
2678
                               WHERE id=".$row['id'];
2679
                    $res_df = Database::query($sql_df);
2680
2681
                    if (Database::num_rows($resu) > 0) {
2682
                        $rowu = Database::fetch_array($resu);
2683
                        $fval = $rowu['fval'];
2684
                        if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
2685
                            $fval = explode(';', $rowu['fval']);
2686
                        }
2687
                    } else {
2688
                        $row_df = Database::fetch_array($res_df);
2689
                        $fval = $row_df['fval_df'];
2690
                    }
2691
                    // We get here (and fill the $extra_data array) even if there
2692
                    // is no user with data (we fill it with default values)
2693
                    if ($prefix) {
2694
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
2695
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2696
                        } else {
2697
                            $extra_data['extra_'.$row['fvar']] = $fval;
2698
                        }
2699
                    } else {
2700
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
2701
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2702
                        } else {
2703
                            $extra_data[$row['fvar']] = $fval;
2704
                        }
2705
                    }
2706
                }
2707
            }
2708
        }
2709
2710
        return $extra_data;
2711
    }
2712
2713
    /** Get extra user data by field
2714
     * @param int    user ID
2715
     * @param string the internal variable name of the field
2716
     *
2717
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2718
     */
2719
    public static function get_extra_user_data_by_field(
2720
        $user_id,
2721
        $field_variable,
2722
        $prefix = false,
2723
        $all_visibility = true,
2724
        $splitmultiple = false
0 ignored issues
show
Unused Code introduced by
The parameter $splitmultiple is not used and could be removed. ( Ignorable by Annotation )

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

2724
        /** @scrutinizer ignore-unused */ $splitmultiple = false

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2725
    ) {
2726
        // A sanity check.
2727
        if (empty($user_id)) {
2728
            $user_id = 0;
2729
        } else {
2730
            if ($user_id != strval(intval($user_id))) {
2731
                return [];
2732
            }
2733
        }
2734
        $extra_data = [];
2735
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2736
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2737
        $user_id = (int) $user_id;
2738
2739
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
2740
                FROM $t_uf f
2741
                WHERE f.variable = '$field_variable' ";
2742
2743
        if (!$all_visibility) {
2744
            $sql .= " AND f.visible_to_self = 1 ";
2745
        }
2746
2747
        $sql .= " AND extra_field_type = ".EntityExtraField::USER_FIELD_TYPE;
2748
        $sql .= " ORDER BY f.field_order";
2749
2750
        $res = Database::query($sql);
2751
        if (Database::num_rows($res) > 0) {
2752
            while ($row = Database::fetch_array($res)) {
2753
                $sqlu = "SELECT value as fval FROM $t_ufv v 
2754
                         INNER JOIN $t_uf f
2755
                         ON (v.field_id = f.id)
2756
                         WHERE
2757
                            extra_field_type = ".EntityExtraField::USER_FIELD_TYPE." AND
2758
                            field_id = ".$row['id']." AND
2759
                            item_id = ".$user_id;
2760
                $resu = Database::query($sqlu);
2761
                $fval = '';
2762
                if (Database::num_rows($resu) > 0) {
2763
                    $rowu = Database::fetch_array($resu);
2764
                    $fval = $rowu['fval'];
2765
                    if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
2766
                        $fval = explode(';', $rowu['fval']);
2767
                    }
2768
                }
2769
                if ($prefix) {
2770
                    $extra_data['extra_'.$row['fvar']] = $fval;
2771
                } else {
2772
                    $extra_data[$row['fvar']] = $fval;
2773
                }
2774
            }
2775
        }
2776
2777
        return $extra_data;
2778
    }
2779
2780
    /**
2781
     * Get the extra field information for a certain field (the options as well).
2782
     *
2783
     * @param int $variable The name of the field we want to know everything about
2784
     *
2785
     * @return array Array containing all the information about the extra profile field
2786
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2787
     *               as returned by the database)
2788
     *
2789
     * @author Julio Montoya
2790
     *
2791
     * @since v1.8.6
2792
     */
2793
    public static function get_extra_field_information_by_name($variable)
2794
    {
2795
        $extraField = new ExtraField('user');
2796
2797
        return $extraField->get_handler_field_info_by_field_variable($variable);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->get_...eld_variable($variable) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2798
    }
2799
2800
    /**
2801
     * Get the extra field information for user tag (the options as well).
2802
     *
2803
     * @param int $variable The name of the field we want to know everything about
2804
     *
2805
     * @return array Array containing all the information about the extra profile field
2806
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2807
     *               as returned by the database)
2808
     *
2809
     * @author José Loguercio
2810
     *
2811
     * @since v1.11.0
2812
     */
2813
    public static function get_extra_field_tags_information_by_name($variable)
2814
    {
2815
        $extraField = new ExtraField('user');
2816
2817
        return $extraField->get_handler_field_info_by_tags($variable);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->get_...info_by_tags($variable) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2818
    }
2819
2820
    /**
2821
     * @param string $type
2822
     *
2823
     * @return array
2824
     */
2825
    public static function get_all_extra_field_by_type($type)
2826
    {
2827
        $extraField = new ExtraField('user');
2828
2829
        return $extraField->get_all_extra_field_by_type($type);
2830
    }
2831
2832
    /**
2833
     * Get all the extra field information of a certain field (also the options).
2834
     *
2835
     * @param int $fieldId the ID of the field we want to know everything of
2836
     *
2837
     * @return array $return containing all th information about the extra profile field
2838
     *
2839
     * @author Julio Montoya
2840
     *
2841
     * @deprecated
2842
     * @since v1.8.6
2843
     */
2844
    public static function get_extra_field_information($fieldId)
2845
    {
2846
        $extraField = new ExtraField('user');
2847
2848
        return $extraField->getFieldInfoByFieldId($fieldId);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extraField->getF...InfoByFieldId($fieldId) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
2849
    }
2850
2851
    /**
2852
     * Get extra user data by value.
2853
     *
2854
     * @param string $variable       the internal variable name of the field
2855
     * @param string $value          the internal value of the field
2856
     * @param bool   $all_visibility
2857
     *
2858
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2859
     */
2860
    public static function get_extra_user_data_by_value($variable, $value, $all_visibility = true)
0 ignored issues
show
Unused Code introduced by
The parameter $all_visibility is not used and could be removed. ( Ignorable by Annotation )

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

2860
    public static function get_extra_user_data_by_value($variable, $value, /** @scrutinizer ignore-unused */ $all_visibility = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2861
    {
2862
        $extraFieldValue = new ExtraFieldValue('user');
2863
        $extraField = new ExtraField('user');
2864
2865
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
2866
2867
        if (false === $info) {
2868
            return [];
2869
        }
2870
2871
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
2872
            $variable,
2873
            $value,
2874
            false,
2875
            false,
2876
            true
2877
        );
2878
2879
        $result = [];
2880
        if (!empty($data)) {
2881
            foreach ($data as $item) {
2882
                $result[] = $item['item_id'];
2883
            }
2884
        }
2885
2886
        return $result;
2887
    }
2888
2889
    /**
2890
     * Get extra user data by tags value.
2891
     *
2892
     * @param int    $fieldId the ID of the field we want to know everything of
2893
     * @param string $tag     the tag name for search
2894
     *
2895
     * @return array with extra data info of a user
2896
     *
2897
     * @author José Loguercio
2898
     *
2899
     * @since v1.11.0
2900
     */
2901
    public static function get_extra_user_data_by_tags($fieldId, $tag)
2902
    {
2903
        $extraField = new ExtraField('user');
2904
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
2905
        $array = [];
2906
        foreach ($result as $index => $user) {
2907
            $array[] = $user['user_id'];
2908
        }
2909
2910
        return $array;
2911
    }
2912
2913
    /**
2914
     * Get extra user data by field variable.
2915
     *
2916
     * @param string $variable field variable
2917
     *
2918
     * @return array data
2919
     */
2920
    public static function get_extra_user_data_by_field_variable($variable)
2921
    {
2922
        $extraInfo = self::get_extra_field_information_by_name($variable);
2923
        $field_id = (int) $extraInfo['id'];
2924
2925
        $extraField = new ExtraFieldValue('user');
2926
        $data = $extraField->getValuesByFieldId($field_id);
2927
2928
        if (!empty($data)) {
2929
            foreach ($data as $row) {
2930
                $user_id = $row['item_id'];
2931
                $data[$user_id] = $row;
2932
            }
2933
        }
2934
2935
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

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

Loading history...
2936
    }
2937
2938
    /**
2939
     * Get extra user data tags by field variable.
2940
     *
2941
     * @param string $variable field variable
2942
     *
2943
     * @return array
2944
     */
2945
    public static function get_extra_user_data_for_tags($variable)
2946
    {
2947
        $data = self::get_extra_field_tags_information_by_name($variable);
2948
2949
        return $data;
2950
    }
2951
2952
    /**
2953
     * Gives a list of [session_category][session_id] for the current user.
2954
     *
2955
     * @param int  $user_id
2956
     * @param bool $is_time_over                 whether to fill the first element or not
2957
     *                                           (to give space for courses out of categories)
2958
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
2959
     * @param bool $ignoreTimeLimit              ignore time start/end
2960
     *
2961
     * @return array list of statuses [session_category][session_id]
2962
     *
2963
     * @todo ensure multiple access urls are managed correctly
2964
     */
2965
    public static function get_sessions_by_category(
2966
        $user_id,
2967
        $is_time_over = true,
2968
        $ignore_visibility_for_admins = false,
2969
        $ignoreTimeLimit = false,
2970
        $getCount = false
2971
    ) {
2972
        if ($user_id != strval(intval($user_id))) {
2973
            return [];
2974
        }
2975
2976
        $allowOrder = api_get_configuration_value('session_list_order');
2977
        $position = '';
2978
        if ($allowOrder) {
2979
            $position = ', s.position AS position ';
2980
        }
2981
2982
        // Get the list of sessions per user
2983
        $now = new DateTime('now', new DateTimeZone('UTC'));
2984
2985
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
2986
        // join would not catch session-courses where the user is general
2987
        // session coach but which do not have students nor coaches registered
2988
        $dqlSelect = " COUNT(DISTINCT s.id) ";
2989
2990
        if (!$getCount) {
2991
            $dqlSelect = " DISTINCT
2992
                s.id,
2993
                s.name,
2994
                s.accessStartDate AS access_start_date,
2995
                s.accessEndDate AS access_end_date,
2996
                s.duration,
2997
                sc.id AS session_category_id,
2998
                sc.name AS session_category_name,
2999
                sc.dateStart AS session_category_date_start,
3000
                sc.dateEnd AS session_category_date_end,
3001
                s.coachAccessStartDate AS coach_access_start_date,
3002
                s.coachAccessEndDate AS coach_access_end_date,
3003
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
3004
                $position
3005
            ";
3006
        }
3007
3008
        $dql = "SELECT $dqlSelect
3009
                FROM ChamiloCoreBundle:Session AS s
3010
                LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
3011
                INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
3012
                LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc ";
3013
3014
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
3015
        // is awfully inefficient for large sets of data (1m25s for 58K
3016
        // sessions, BT#14115) but executing a similar query twice and grouping
3017
        // the results afterwards in PHP takes about 1/1000th of the time
3018
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
3019
        $dqlStudent = $dql." WHERE scu.user = :user AND url.url = :url ";
3020
        $dqlCoach = $dql." WHERE s.generalCoach = :user AND url.url = :url ";
3021
3022
        // Default order
3023
        $order = 'ORDER BY sc.name, s.name';
3024
3025
        // Order by date if showing all sessions
3026
        $showAllSessions = api_get_configuration_value('show_all_sessions_on_my_course_page') === true;
3027
        if ($showAllSessions) {
3028
            $order = 'ORDER BY s.accessStartDate';
3029
        }
3030
3031
        // Order by position
3032
        if ($allowOrder) {
3033
            $order = 'ORDER BY s.position';
3034
        }
3035
3036
        // Order by dates according to settings
3037
        $orderBySettings = api_get_configuration_value('my_courses_session_order');
3038
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
3039
            $field = $orderBySettings['field'];
3040
            $orderSetting = $orderBySettings['order'];
3041
            switch ($field) {
3042
                case 'start_date':
3043
                    $order = " ORDER BY s.accessStartDate $orderSetting";
3044
                    break;
3045
                case 'end_date':
3046
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
3047
                    if ($orderSetting == 'asc') {
3048
                        // Put null values at the end
3049
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
3050
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
3051
                    }
3052
                    break;
3053
            }
3054
        }
3055
3056
        $dqlStudent .= $order;
3057
        $dqlCoach .= $order;
3058
3059
        $accessUrlId = api_get_current_access_url_id();
3060
        $dqlStudent = Database::getManager()
3061
            ->createQuery($dqlStudent)
3062
            ->setParameters(
3063
                ['user' => $user_id, 'url' => $accessUrlId]
3064
            )
3065
        ;
3066
        $dqlCoach = Database::getManager()
3067
            ->createQuery($dqlCoach)
3068
            ->setParameters(
3069
                ['user' => $user_id, 'url' => $accessUrlId]
3070
            )
3071
        ;
3072
3073
        if ($getCount) {
3074
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
3075
        }
3076
3077
        $sessionDataStudent = $dqlStudent->getResult();
3078
        $sessionDataCoach = $dqlCoach->getResult();
3079
3080
        $sessionData = [];
3081
        // First fill $sessionData with student sessions
3082
        foreach ($sessionDataStudent as $row) {
3083
            $sessionData[$row['id']] = $row;
3084
        }
3085
        // Overwrite session data of the user as a student with session data
3086
        // of the user as a coach.
3087
        // There shouldn't be such duplicate rows, but just in case...
3088
        foreach ($sessionDataCoach as $row) {
3089
            $sessionData[$row['id']] = $row;
3090
        }
3091
3092
        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
3093
        $extraField = new ExtraFieldValue('session');
3094
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
3095
3096
        $categories = [];
3097
        foreach ($sessionData as $row) {
3098
            $session_id = $row['id'];
3099
            $coachList = SessionManager::getCoachesBySession($session_id);
3100
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
3101
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
3102
            $courseList = self::get_courses_list_by_session(
3103
                $user_id,
3104
                $session_id
3105
            );
3106
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
3107
3108
            // User portal filters:
3109
            if ($ignoreTimeLimit === false) {
3110
                if ($is_time_over) {
3111
                    // History
3112
                    if ($row['duration']) {
3113
                        if ($daysLeft >= 0) {
3114
                            continue;
3115
                        }
3116
                    } else {
3117
                        if (empty($row['access_end_date'])) {
3118
                            continue;
3119
                        } else {
3120
                            if ($row['access_end_date'] > $now) {
3121
                                continue;
3122
                            }
3123
                        }
3124
                    }
3125
                } else {
3126
                    // Current user portal
3127
                    $isGeneralCoach = SessionManager::user_is_general_coach($user_id, $row['id']);
3128
                    $isCoachOfCourse = in_array($user_id, $coachList);
3129
3130
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
3131
                        // Teachers can access the session depending in the access_coach date
3132
                    } else {
3133
                        if ($row['duration']) {
3134
                            if ($daysLeft <= 0) {
3135
                                continue;
3136
                            }
3137
                        } else {
3138
                            if (isset($row['access_end_date']) &&
3139
                                !empty($row['access_end_date'])
3140
                            ) {
3141
                                if ($row['access_end_date'] <= $now) {
3142
                                    continue;
3143
                                }
3144
                            }
3145
                        }
3146
                    }
3147
                }
3148
            }
3149
3150
            $categories[$row['session_category_id']]['session_category'] = [
3151
                'id' => $row['session_category_id'],
3152
                'name' => $row['session_category_name'],
3153
                'date_start' => $categoryStart,
3154
                'date_end' => $categoryEnd,
3155
            ];
3156
3157
            $visibility = api_get_session_visibility(
3158
                $session_id,
3159
                null,
3160
                $ignore_visibility_for_admins
3161
            );
3162
3163
            if ($visibility != SESSION_VISIBLE) {
3164
                // Course Coach session visibility.
3165
                $blockedCourseCount = 0;
3166
                $closedVisibilityList = [
3167
                    COURSE_VISIBILITY_CLOSED,
3168
                    COURSE_VISIBILITY_HIDDEN,
3169
                ];
3170
3171
                foreach ($courseList as $course) {
3172
                    // Checking session visibility
3173
                    $sessionCourseVisibility = api_get_session_visibility(
3174
                        $session_id,
3175
                        $course['real_id'],
3176
                        $ignore_visibility_for_admins
3177
                    );
3178
3179
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
3180
                    if ($courseIsVisible === false || $sessionCourseVisibility == SESSION_INVISIBLE) {
3181
                        $blockedCourseCount++;
3182
                    }
3183
                }
3184
3185
                // If all courses are blocked then no show in the list.
3186
                if ($blockedCourseCount === count($courseList)) {
3187
                    $visibility = SESSION_INVISIBLE;
3188
                } else {
3189
                    $visibility = $sessionCourseVisibility;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionCourseVisibility does not seem to be defined for all execution paths leading up to this point.
Loading history...
3190
                }
3191
            }
3192
3193
            switch ($visibility) {
3194
                case SESSION_VISIBLE_READ_ONLY:
3195
                case SESSION_VISIBLE:
3196
                case SESSION_AVAILABLE:
3197
                    break;
3198
                case SESSION_INVISIBLE:
3199
                    if ($ignore_visibility_for_admins === false) {
3200
                        continue 2;
3201
                    }
3202
            }
3203
3204
            $collapsed = '';
3205
            $collapsedAction = '';
3206
            if ($collapsable) {
3207
                $collapsableData = Sessionmanager::getCollapsableData(
3208
                    $user_id,
3209
                    $session_id,
3210
                    $extraField,
3211
                    $collapsableLink
3212
                );
3213
                $collapsed = $collapsableData['collapsed'];
3214
                $collapsedAction = $collapsableData['collapsable_link'];
3215
            }
3216
3217
            $categories[$row['session_category_id']]['sessions'][] = [
3218
                'session_name' => $row['name'],
3219
                'session_id' => $row['id'],
3220
                'access_start_date' => $row['access_start_date'] ? $row['access_start_date']->format('Y-m-d H:i:s') : null,
3221
                'access_end_date' => $row['access_end_date'] ? $row['access_end_date']->format('Y-m-d H:i:s') : null,
3222
                'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
3223
                'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
3224
                'courses' => $courseList,
3225
                'collapsed' => $collapsed,
3226
                'collapsable_link' => $collapsedAction,
3227
                'duration' => $row['duration'],
3228
            ];
3229
        }
3230
3231
        return $categories;
3232
    }
3233
3234
    /**
3235
     * Gives a list of [session_id-course_code] => [status] for the current user.
3236
     *
3237
     * @param int $user_id
3238
     * @param int $sessionLimit
3239
     *
3240
     * @return array list of statuses (session_id-course_code => status)
3241
     */
3242
    public static function get_personal_session_course_list($user_id, $sessionLimit = null)
3243
    {
3244
        // Database Table Definitions
3245
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3246
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
3247
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3248
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3249
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3250
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3251
3252
        if ($user_id != strval(intval($user_id))) {
3253
            return [];
3254
        }
3255
3256
        // We filter the courses from the URL
3257
        $join_access_url = $where_access_url = '';
3258
3259
        if (api_get_multiple_access_url()) {
3260
            $access_url_id = api_get_current_access_url_id();
3261
            if ($access_url_id != -1) {
3262
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3263
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id";
3264
                $where_access_url = " AND access_url_id = $access_url_id ";
3265
            }
3266
        }
3267
3268
        // Courses in which we subscribed out of any session
3269
        $tbl_user_course_category = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
3270
3271
        $sql = "SELECT
3272
                    course.code,
3273
                    course_rel_user.status course_rel_status,
3274
                    course_rel_user.sort sort,
3275
                    course_rel_user.user_course_cat user_course_cat
3276
                 FROM $tbl_course_user course_rel_user
3277
                 LEFT JOIN $tbl_course course
3278
                 ON course.id = course_rel_user.c_id
3279
                 LEFT JOIN $tbl_user_course_category user_course_category
3280
                 ON course_rel_user.user_course_cat = user_course_category.id
3281
                 $join_access_url
3282
                 WHERE
3283
                    course_rel_user.user_id = '".$user_id."' AND
3284
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
3285
                    $where_access_url
3286
                 ORDER BY user_course_category.sort, course_rel_user.sort, course.title ASC";
3287
3288
        $course_list_sql_result = Database::query($sql);
3289
3290
        $personal_course_list = [];
3291
        if (Database::num_rows($course_list_sql_result) > 0) {
3292
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
3293
                $course_info = api_get_course_info($result_row['code']);
3294
                $result_row['course_info'] = $course_info;
3295
                $personal_course_list[] = $result_row;
3296
            }
3297
        }
3298
3299
        $coachCourseConditions = '';
3300
        // Getting sessions that are related to a coach in the session_rel_course_rel_user table
3301
        if (api_is_allowed_to_create_course()) {
3302
            $sessionListFromCourseCoach = [];
3303
            $sql = " SELECT DISTINCT session_id
3304
                    FROM $tbl_session_course_user
3305
                    WHERE user_id = $user_id AND status = 2 ";
3306
3307
            $result = Database::query($sql);
3308
            if (Database::num_rows($result)) {
3309
                $result = Database::store_result($result);
3310
                foreach ($result as $session) {
3311
                    $sessionListFromCourseCoach[] = $session['session_id'];
3312
                }
3313
            }
3314
            if (!empty($sessionListFromCourseCoach)) {
3315
                $condition = implode("','", $sessionListFromCourseCoach);
3316
                $coachCourseConditions = " OR ( s.id IN ('$condition'))";
3317
            }
3318
        }
3319
3320
        // Get the list of sessions where the user is subscribed
3321
        // This is divided into two different queries
3322
        $sessions = [];
3323
        $sessionLimitRestriction = '';
3324
        if (!empty($sessionLimit)) {
3325
            $sessionLimit = (int) $sessionLimit;
3326
            $sessionLimitRestriction = "LIMIT $sessionLimit";
3327
        }
3328
3329
        $sql = "SELECT DISTINCT s.id, name, access_start_date, access_end_date
3330
                FROM $tbl_session_user su INNER JOIN $tbl_session s
3331
                ON (s.id = su.session_id)
3332
                WHERE (
3333
                    su.user_id = $user_id AND
3334
                    su.relation_type <> ".SESSION_RELATION_TYPE_RRHH."
3335
                )
3336
                $coachCourseConditions
3337
                ORDER BY access_start_date, access_end_date, name
3338
                $sessionLimitRestriction
3339
        ";
3340
3341
        $result = Database::query($sql);
3342
        if (Database::num_rows($result) > 0) {
3343
            while ($row = Database::fetch_assoc($result)) {
3344
                $sessions[$row['id']] = $row;
3345
            }
3346
        }
3347
3348
        $sql = "SELECT DISTINCT
3349
                id, name, access_start_date, access_end_date
3350
                FROM $tbl_session s
3351
                WHERE (
3352
                    id_coach = $user_id
3353
                )
3354
                $coachCourseConditions
3355
                ORDER BY access_start_date, access_end_date, name";
3356
3357
        $result = Database::query($sql);
3358
        if (Database::num_rows($result) > 0) {
3359
            while ($row = Database::fetch_assoc($result)) {
3360
                if (empty($sessions[$row['id']])) {
3361
                    $sessions[$row['id']] = $row;
3362
                }
3363
            }
3364
        }
3365
3366
        if (api_is_allowed_to_create_course()) {
3367
            foreach ($sessions as $enreg) {
3368
                $session_id = $enreg['id'];
3369
                $session_visibility = api_get_session_visibility($session_id);
3370
3371
                if ($session_visibility == SESSION_INVISIBLE) {
3372
                    continue;
3373
                }
3374
3375
                // This query is horribly slow when more than a few thousand
3376
                // users and just a few sessions to which they are subscribed
3377
                $sql = "SELECT DISTINCT
3378
                        course.code code,
3379
                        course.title i,
3380
                        ".(api_is_western_name_order() ? "CONCAT(user.firstname,' ',user.lastname)" : "CONCAT(user.lastname,' ',user.firstname)")." t,
3381
                        email, course.course_language l,
3382
                        1 sort,
3383
                        category_code user_course_cat,
3384
                        access_start_date,
3385
                        access_end_date,
3386
                        session.id as session_id,
3387
                        session.name as session_name
3388
                    FROM $tbl_session_course_user as session_course_user
3389
                    INNER JOIN $tbl_course AS course
3390
                        ON course.id = session_course_user.c_id
3391
                    INNER JOIN $tbl_session as session
3392
                        ON session.id = session_course_user.session_id
3393
                    LEFT JOIN $tbl_user as user
3394
                        ON user.id = session_course_user.user_id OR session.id_coach = user.id
3395
                    WHERE
3396
                        session_course_user.session_id = $session_id AND (
3397
                            (session_course_user.user_id = $user_id AND session_course_user.status = 2)
3398
                            OR session.id_coach = $user_id
3399
                        )
3400
                    ORDER BY i";
3401
                $course_list_sql_result = Database::query($sql);
3402
                while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
3403
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
3404
                    $key = $result_row['session_id'].' - '.$result_row['code'];
3405
                    $personal_course_list[$key] = $result_row;
3406
                }
3407
            }
3408
        }
3409
3410
        foreach ($sessions as $enreg) {
3411
            $session_id = $enreg['id'];
3412
            $session_visibility = api_get_session_visibility($session_id);
3413
            if ($session_visibility == SESSION_INVISIBLE) {
3414
                continue;
3415
            }
3416
3417
            /* This query is very similar to the above query,
3418
               but it will check the session_rel_course_user table if there are courses registered to our user or not */
3419
            $sql = "SELECT DISTINCT
3420
                course.code code,
3421
                course.title i, CONCAT(user.lastname,' ',user.firstname) t,
3422
                email,
3423
                course.course_language l,
3424
                1 sort,
3425
                category_code user_course_cat,
3426
                access_start_date,
3427
                access_end_date,
3428
                session.id as session_id,
3429
                session.name as session_name,
3430
                IF((session_course_user.user_id = 3 AND session_course_user.status=2),'2', '5')
3431
            FROM $tbl_session_course_user as session_course_user
3432
            INNER JOIN $tbl_course AS course
3433
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
3434
            INNER JOIN $tbl_session as session 
3435
            ON session_course_user.session_id = session.id
3436
            LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
3437
            WHERE session_course_user.user_id = $user_id
3438
            ORDER BY i";
3439
3440
            $course_list_sql_result = Database::query($sql);
3441
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
3442
                $result_row['course_info'] = api_get_course_info($result_row['code']);
3443
                $key = $result_row['session_id'].' - '.$result_row['code'];
3444
                if (!isset($personal_course_list[$key])) {
3445
                    $personal_course_list[$key] = $result_row;
3446
                }
3447
            }
3448
        }
3449
3450
        return $personal_course_list;
3451
    }
3452
3453
    /**
3454
     * Gives a list of courses for the given user in the given session.
3455
     *
3456
     * @param int $user_id
3457
     * @param int $session_id
3458
     *
3459
     * @return array list of statuses (session_id-course_code => status)
3460
     */
3461
    public static function get_courses_list_by_session($user_id, $session_id)
3462
    {
3463
        // Database Table Definitions
3464
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3465
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
3466
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3467
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3468
3469
        $user_id = (int) $user_id;
3470
        $session_id = (int) $session_id;
3471
        //we filter the courses from the URL
3472
        $join_access_url = $where_access_url = '';
3473
3474
        if (api_get_multiple_access_url()) {
3475
            $urlId = api_get_current_access_url_id();
3476
            if ($urlId != -1) {
3477
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3478
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
3479
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
3480
            }
3481
        }
3482
3483
        /* This query is very similar to the query below, but it will check the
3484
        session_rel_course_user table if there are courses registered
3485
        to our user or not */
3486
        $sql = "SELECT DISTINCT
3487
                    c.visibility,
3488
                    c.id as real_id,
3489
                    c.code as course_code,
3490
                    sc.position
3491
                FROM $tbl_session_course_user as scu
3492
                INNER JOIN $tbl_session_course sc
3493
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
3494
                INNER JOIN $tableCourse as c
3495
                ON (scu.c_id = c.id)
3496
                $join_access_url
3497
                WHERE
3498
                    scu.user_id = $user_id AND
3499
                    scu.session_id = $session_id
3500
                    $where_access_url
3501
                ORDER BY sc.position ASC";
3502
3503
        $myCourseList = [];
3504
        $courses = [];
3505
        $result = Database::query($sql);
3506
        if (Database::num_rows($result) > 0) {
3507
            while ($result_row = Database::fetch_array($result, 'ASSOC')) {
3508
                $result_row['status'] = 5;
3509
                if (!in_array($result_row['real_id'], $courses)) {
3510
                    $position = $result_row['position'];
3511
                    if (!isset($myCourseList[$position])) {
3512
                        $myCourseList[$position] = $result_row;
3513
                    } else {
3514
                        $myCourseList[] = $result_row;
3515
                    }
3516
                    $courses[] = $result_row['real_id'];
3517
                }
3518
            }
3519
        }
3520
3521
        if (api_is_allowed_to_create_course()) {
3522
            $sql = "SELECT DISTINCT
3523
                        c.visibility, 
3524
                        c.id as real_id,
3525
                        c.code as course_code,
3526
                        sc.position
3527
                    FROM $tbl_session_course_user as scu
3528
                    INNER JOIN $tbl_session as s
3529
                    ON (scu.session_id = s.id)
3530
                    INNER JOIN $tbl_session_course sc
3531
                    ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
3532
                    INNER JOIN $tableCourse as c
3533
                    ON (scu.c_id = c.id)
3534
                    $join_access_url
3535
                    WHERE
3536
                      s.id = $session_id AND
3537
                      (
3538
                        (scu.user_id = $user_id AND scu.status = 2) OR
3539
                        s.id_coach = $user_id
3540
                      )
3541
                    $where_access_url
3542
                    ORDER BY sc.position ASC";
3543
            $result = Database::query($sql);
3544
3545
            if (Database::num_rows($result) > 0) {
3546
                while ($result_row = Database::fetch_array($result, 'ASSOC')) {
3547
                    $result_row['status'] = 2;
3548
                    if (!in_array($result_row['real_id'], $courses)) {
3549
                        $position = $result_row['position'];
3550
                        if (!isset($myCourseList[$position])) {
3551
                            $myCourseList[$position] = $result_row;
3552
                        } else {
3553
                            $myCourseList[] = $result_row;
3554
                        }
3555
                        $courses[] = $result_row['real_id'];
3556
                    }
3557
                }
3558
            }
3559
        }
3560
3561
        if (api_is_drh()) {
3562
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
3563
            $sessionList = array_keys($sessionList);
3564
            if (in_array($session_id, $sessionList)) {
3565
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3566
                if (!empty($courseList)) {
3567
                    foreach ($courseList as $course) {
3568
                        if (!in_array($course['id'], $courses)) {
3569
                            $position = $course['position'];
3570
                            if (!isset($myCourseList[$position])) {
3571
                                $myCourseList[$position] = $course;
3572
                            } else {
3573
                                $myCourseList[] = $course;
3574
                            }
3575
                        }
3576
                    }
3577
                }
3578
            }
3579
        } else {
3580
            //check if user is general coach for this session
3581
            $sessionInfo = api_get_session_info($session_id);
3582
            if ($sessionInfo['id_coach'] == $user_id) {
3583
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3584
                if (!empty($courseList)) {
3585
                    foreach ($courseList as $course) {
3586
                        if (!in_array($course['id'], $courses)) {
3587
                            $position = $course['position'];
3588
                            if (!isset($myCourseList[$position])) {
3589
                                $myCourseList[$position] = $course;
3590
                            } else {
3591
                                $myCourseList[] = $course;
3592
                            }
3593
                        }
3594
                    }
3595
                }
3596
            }
3597
        }
3598
3599
        if (!empty($myCourseList)) {
3600
            ksort($myCourseList);
3601
        }
3602
3603
        return $myCourseList;
3604
    }
3605
3606
    /**
3607
     * Get user id from a username.
3608
     *
3609
     * @param string $username
3610
     *
3611
     * @return int User ID (or false if not found)
3612
     */
3613
    public static function get_user_id_from_username($username)
3614
    {
3615
        if (empty($username)) {
3616
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3617
        }
3618
        $username = trim($username);
3619
        $username = Database::escape_string($username);
3620
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
3621
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
3622
        $res = Database::query($sql);
3623
3624
        if ($res === false) {
3625
            return false;
3626
        }
3627
        if (Database::num_rows($res) !== 1) {
3628
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3629
        }
3630
        $row = Database::fetch_array($res);
3631
3632
        return $row['id'];
3633
    }
3634
3635
    /**
3636
     * Get the users files upload from his share_folder.
3637
     *
3638
     * @param string $user_id      User ID
3639
     * @param string $course       course directory
3640
     * @param string $resourceType resource type: images, all
3641
     *
3642
     * @return string
3643
     */
3644
    public static function get_user_upload_files_by_course(
3645
        $user_id,
3646
        $course,
3647
        $resourceType = 'all'
3648
    ) {
3649
        $return = '';
3650
        if (!empty($user_id) && !empty($course)) {
3651
            $user_id = (int) $user_id;
3652
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3653
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3654
            $file_list = [];
3655
3656
            if (is_dir($path)) {
3657
                $handle = opendir($path);
3658
                while ($file = readdir($handle)) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

3658
                while ($file = readdir(/** @scrutinizer ignore-type */ $handle)) {
Loading history...
3659
                    if ($file == '.' || $file == '..' || $file == '.htaccess' || is_dir($path.$file)) {
3660
                        continue; // skip current/parent directory and .htaccess
3661
                    }
3662
                    $file_list[] = $file;
3663
                }
3664
                if (count($file_list) > 0) {
3665
                    $return = "<h4>$course</h4>";
3666
                    $return .= '<ul class="thumbnails">';
3667
                }
3668
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
3669
                foreach ($file_list as $file) {
3670
                    if ($resourceType == 'all') {
3671
                        $return .= '<li>
3672
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
3673
                    } elseif ($resourceType == 'images') {
3674
                        //get extension
3675
                        $ext = explode('.', $file);
3676
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
3677
                            $return .= '<li class="span2">
3678
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
3679
                                                <img src="'.$web_path.urlencode($file).'" >
3680
                                            </a>
3681
                                        </li>';
3682
                        }
3683
                    }
3684
                }
3685
                if (count($file_list) > 0) {
3686
                    $return .= '</ul>';
3687
                }
3688
            }
3689
        }
3690
3691
        return $return;
3692
    }
3693
3694
    /**
3695
     * Gets the API key (or keys) and return them into an array.
3696
     *
3697
     * @param int     Optional user id (defaults to the result of api_get_user_id())
3698
     * @param string $api_service
3699
     *
3700
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
3701
     */
3702
    public static function get_api_keys($user_id = null, $api_service = 'dokeos')
3703
    {
3704
        if ($user_id != strval(intval($user_id))) {
3705
            return false;
3706
        }
3707
        if (empty($user_id)) {
3708
            $user_id = api_get_user_id();
3709
        }
3710
        if ($user_id === false) {
3711
            return false;
3712
        }
3713
        $service_name = Database::escape_string($api_service);
3714
        if (is_string($service_name) === false) {
3715
            return false;
3716
        }
3717
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3718
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
3719
        $res = Database::query($sql);
3720
        if ($res === false) {
3721
            return false;
3722
        } //error during query
3723
        $num = Database::num_rows($res);
3724
        if ($num == 0) {
3725
            return false;
3726
        }
3727
        $list = [];
3728
        while ($row = Database::fetch_array($res)) {
3729
            $list[$row['id']] = $row['api_key'];
3730
        }
3731
3732
        return $list;
3733
    }
3734
3735
    /**
3736
     * Adds a new API key to the users' account.
3737
     *
3738
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
3739
     * @param string $api_service
3740
     *
3741
     * @return bool True on success, false on failure
3742
     */
3743
    public static function add_api_key($user_id = null, $api_service = 'dokeos')
3744
    {
3745
        if ($user_id != strval(intval($user_id))) {
3746
            return false;
3747
        }
3748
        if (empty($user_id)) {
3749
            $user_id = api_get_user_id();
3750
        }
3751
        if ($user_id === false) {
3752
            return false;
3753
        }
3754
        $service_name = Database::escape_string($api_service);
3755
        if (is_string($service_name) === false) {
3756
            return false;
3757
        }
3758
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3759
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
3760
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
3761
        $res = Database::query($sql);
3762
        if ($res === false) {
3763
            return false;
3764
        } //error during query
3765
        $num = Database::insert_id();
3766
3767
        return $num == 0 ? false : $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $num == 0 ? false : $num also could return the type string which is incompatible with the documented return type boolean.
Loading history...
3768
    }
3769
3770
    /**
3771
     * Deletes an API key from the user's account.
3772
     *
3773
     * @param   int     API key's internal ID
3774
     *
3775
     * @return bool True on success, false on failure
3776
     */
3777
    public static function delete_api_key($key_id)
3778
    {
3779
        if ($key_id != strval(intval($key_id))) {
3780
            return false;
3781
        }
3782
        if ($key_id === false) {
3783
            return false;
3784
        }
3785
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3786
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
3787
        $res = Database::query($sql);
3788
        if ($res === false) {
3789
            return false;
3790
        } //error during query
3791
        $num = Database::num_rows($res);
3792
        if ($num !== 1) {
3793
            return false;
3794
        }
3795
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
3796
        $res = Database::query($sql);
3797
        if ($res === false) {
3798
            return false;
3799
        } //error during query
3800
3801
        return true;
3802
    }
3803
3804
    /**
3805
     * Regenerate an API key from the user's account.
3806
     *
3807
     * @param   int     user ID (defaults to the results of api_get_user_id())
3808
     * @param   string  API key's internal ID
3809
     *
3810
     * @return int num
3811
     */
3812
    public static function update_api_key($user_id, $api_service)
3813
    {
3814
        if ($user_id != strval(intval($user_id))) {
3815
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3816
        }
3817
        if ($user_id === false) {
3818
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3819
        }
3820
        $service_name = Database::escape_string($api_service);
3821
        if (is_string($service_name) === false) {
3822
            return false;
3823
        }
3824
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3825
        $sql = "SELECT id FROM $t_api 
3826
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3827
        $res = Database::query($sql);
3828
        $num = Database::num_rows($res);
3829
        if ($num == 1) {
3830
            $id_key = Database::fetch_array($res, 'ASSOC');
3831
            self::delete_api_key($id_key['id']);
3832
            $num = self::add_api_key($user_id, $api_service);
3833
        } elseif ($num == 0) {
3834
            $num = self::add_api_key($user_id, $api_service);
3835
        }
3836
3837
        return $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $num also could return the type boolean which is incompatible with the documented return type integer.
Loading history...
3838
    }
3839
3840
    /**
3841
     * @param   int     user ID (defaults to the results of api_get_user_id())
3842
     * @param   string    API key's internal ID
3843
     *
3844
     * @return int row ID, or return false if not found
3845
     */
3846
    public static function get_api_key_id($user_id, $api_service)
3847
    {
3848
        if ($user_id != strval(intval($user_id))) {
3849
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3850
        }
3851
        if ($user_id === false) {
3852
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3853
        }
3854
        if (empty($api_service)) {
3855
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3856
        }
3857
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3858
        $api_service = Database::escape_string($api_service);
3859
        $sql = "SELECT id FROM $t_api 
3860
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3861
        $res = Database::query($sql);
3862
        if (Database::num_rows($res) < 1) {
3863
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3864
        }
3865
        $row = Database::fetch_array($res, 'ASSOC');
3866
3867
        return $row['id'];
3868
    }
3869
3870
    /**
3871
     * Checks if a user_id is platform admin.
3872
     *
3873
     * @param   int user ID
3874
     *
3875
     * @return bool True if is admin, false otherwise
3876
     *
3877
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
3878
     */
3879
    public static function is_admin($user_id)
3880
    {
3881
        if (empty($user_id) || $user_id != strval(intval($user_id))) {
3882
            return false;
3883
        }
3884
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
3885
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
3886
        $res = Database::query($sql);
3887
3888
        return Database::num_rows($res) === 1;
3889
    }
3890
3891
    /**
3892
     * Get the total count of users.
3893
     *
3894
     * @param   int     Status of users to be counted
3895
     * @param   int     Access URL ID (optional)
3896
     *
3897
     * @return mixed Number of users or false on error
3898
     */
3899
    public static function get_number_of_users($status = 0, $access_url_id = 1)
3900
    {
3901
        $t_u = Database::get_main_table(TABLE_MAIN_USER);
3902
        $t_a = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3903
3904
        if (api_is_multiple_url_enabled()) {
3905
            $sql = "SELECT count(u.id) 
3906
                    FROM $t_u u 
3907
                    INNER JOIN $t_a url_user
3908
                    ON (u.id = url_user.user_id)
3909
                    WHERE url_user.access_url_id = $access_url_id                
3910
            ";
3911
        } else {
3912
            $sql = "SELECT count(u.id) 
3913
                    FROM $t_u u
3914
                    WHERE 1 = 1 ";
3915
        }
3916
        if (is_int($status) && $status > 0) {
3917
            $sql .= " AND u.status = $status ";
3918
        }
3919
        $res = Database::query($sql);
3920
        if (Database::num_rows($res) === 1) {
3921
            return (int) Database::result($res, 0, 0);
3922
        }
3923
3924
        return false;
3925
    }
3926
3927
    /**
3928
     * Gets the tags of a specific field_id
3929
     * USER TAGS.
3930
     *
3931
     * Instructions to create a new user tag by Julio Montoya <[email protected]>
3932
     *
3933
     * 1. Create a new extra field in main/admin/user_fields.php with the "TAG" field type make it available and visible.
3934
     *    Called it "books" for example.
3935
     * 2. Go to profile main/auth/profile.php There you will see a special input (facebook style) that will show suggestions of tags.
3936
     * 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
3937
     * 4. Tags are independent this means that tags can't be shared between tags + book + hobbies.
3938
     * 5. Test and enjoy.
3939
     *
3940
     * @param string $tag
3941
     * @param int    $field_id      field_id
3942
     * @param string $return_format how we are going to result value in array or in a string (json)
3943
     * @param $limit
3944
     *
3945
     * @return mixed
3946
     */
3947
    public static function get_tags($tag, $field_id, $return_format = 'json', $limit = 10)
3948
    {
3949
        // database table definition
3950
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3951
        $field_id = (int) $field_id;
3952
        $limit = (int) $limit;
3953
        $tag = trim(Database::escape_string($tag));
3954
3955
        // all the information of the field
3956
        $sql = "SELECT DISTINCT id, tag from $table_user_tag
3957
                WHERE field_id = $field_id AND tag LIKE '$tag%' ORDER BY tag LIMIT $limit";
3958
        $result = Database::query($sql);
3959
        $return = [];
3960
        if (Database::num_rows($result) > 0) {
3961
            while ($row = Database::fetch_array($result, 'ASSOC')) {
3962
                $return[] = ['id' => $row['tag'], 'text' => $row['tag']];
3963
            }
3964
        }
3965
        if ($return_format === 'json') {
3966
            $return = json_encode($return);
3967
        }
3968
3969
        return $return;
3970
    }
3971
3972
    /**
3973
     * @param int $field_id
3974
     * @param int $limit
3975
     *
3976
     * @return array
3977
     */
3978
    public static function get_top_tags($field_id, $limit = 100)
3979
    {
3980
        // database table definition
3981
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
3982
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
3983
        $field_id = (int) $field_id;
3984
        $limit = (int) $limit;
3985
        // all the information of the field
3986
        $sql = "SELECT count(*) count, tag FROM $table_user_tag_values  uv
3987
                INNER JOIN $table_user_tag ut
3988
                ON (ut.id = uv.tag_id)
3989
                WHERE field_id = $field_id
3990
                GROUP BY tag_id
3991
                ORDER BY count DESC
3992
                LIMIT $limit";
3993
        $result = Database::query($sql);
3994
        $return = [];
3995
        if (Database::num_rows($result) > 0) {
3996
            while ($row = Database::fetch_array($result, 'ASSOC')) {
3997
                $return[] = $row;
3998
            }
3999
        }
4000
4001
        return $return;
4002
    }
4003
4004
    /**
4005
     * Get user's tags.
4006
     *
4007
     * @param int $user_id
4008
     * @param int $field_id
4009
     *
4010
     * @return array
4011
     */
4012
    public static function get_user_tags($user_id, $field_id)
4013
    {
4014
        // database table definition
4015
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4016
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4017
        $field_id = (int) $field_id;
4018
        $user_id = (int) $user_id;
4019
4020
        // all the information of the field
4021
        $sql = "SELECT ut.id, tag, count
4022
                FROM $table_user_tag ut
4023
                INNER JOIN $table_user_tag_values uv
4024
                ON (uv.tag_id=ut.ID)
4025
                WHERE field_id = $field_id AND user_id = $user_id
4026
                ORDER BY tag";
4027
        $result = Database::query($sql);
4028
        $return = [];
4029
        if (Database::num_rows($result) > 0) {
4030
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4031
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4032
            }
4033
        }
4034
4035
        return $return;
4036
    }
4037
4038
    /**
4039
     * Get user's tags.
4040
     *
4041
     * @param int  $user_id
4042
     * @param int  $field_id
4043
     * @param bool $show_links show links or not
4044
     *
4045
     * @return string
4046
     */
4047
    public static function get_user_tags_to_string($user_id, $field_id, $show_links = true)
4048
    {
4049
        // database table definition
4050
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4051
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4052
        $field_id = (int) $field_id;
4053
        $user_id = (int) $user_id;
4054
4055
        // all the information of the field
4056
        $sql = "SELECT ut.id, tag,count FROM $table_user_tag ut
4057
                INNER JOIN $table_user_tag_values uv
4058
                ON (uv.tag_id = ut.id)
4059
                WHERE field_id = $field_id AND user_id = $user_id
4060
                ORDER BY tag";
4061
4062
        $result = Database::query($sql);
4063
        $return = [];
4064
        if (Database::num_rows($result) > 0) {
4065
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4066
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4067
            }
4068
        }
4069
        $user_tags = $return;
4070
        $tag_tmp = [];
4071
        foreach ($user_tags as $tag) {
4072
            if ($show_links) {
4073
                $tag_tmp[] = '<a href="'.api_get_path(WEB_PATH).'main/search/index.php?q='.$tag['tag'].'">'.
4074
                    $tag['tag'].
4075
                '</a>';
4076
            } else {
4077
                $tag_tmp[] = $tag['tag'];
4078
            }
4079
        }
4080
4081
        if (is_array($user_tags) && count($user_tags) > 0) {
4082
            return implode(', ', $tag_tmp);
4083
        } else {
4084
            return '';
4085
        }
4086
    }
4087
4088
    /**
4089
     * Get the tag id.
4090
     *
4091
     * @param int $tag
4092
     * @param int $field_id
4093
     *
4094
     * @return int returns 0 if fails otherwise the tag id
4095
     */
4096
    public static function get_tag_id($tag, $field_id)
4097
    {
4098
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4099
        $tag = Database::escape_string($tag);
4100
        $field_id = (int) $field_id;
4101
        //with COLLATE latin1_bin to select query in a case sensitive mode
4102
        $sql = "SELECT id FROM $table_user_tag
4103
                WHERE tag LIKE '$tag' AND field_id = $field_id";
4104
        $result = Database::query($sql);
4105
        if (Database::num_rows($result) > 0) {
4106
            $row = Database::fetch_array($result, 'ASSOC');
4107
4108
            return $row['id'];
4109
        } else {
4110
            return 0;
4111
        }
4112
    }
4113
4114
    /**
4115
     * Get the tag id.
4116
     *
4117
     * @param int $tag_id
4118
     * @param int $field_id
4119
     *
4120
     * @return int 0 if fails otherwise the tag id
4121
     */
4122
    public static function get_tag_id_from_id($tag_id, $field_id)
4123
    {
4124
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4125
        $tag_id = (int) $tag_id;
4126
        $field_id = (int) $field_id;
4127
        $sql = "SELECT id FROM $table_user_tag
4128
                WHERE id = '$tag_id' AND field_id = $field_id";
4129
        $result = Database::query($sql);
4130
        if (Database::num_rows($result) > 0) {
4131
            $row = Database::fetch_array($result, 'ASSOC');
4132
4133
            return $row['id'];
4134
        } else {
4135
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
4136
        }
4137
    }
4138
4139
    /**
4140
     * Adds a user-tag value.
4141
     *
4142
     * @param mixed $tag
4143
     * @param int   $user_id
4144
     * @param int   $field_id field id of the tag
4145
     *
4146
     * @return bool True if the tag was inserted or updated. False otherwise.
4147
     *              The return value doesn't take into account *values* added to the tag.
4148
     *              Only the creation/update of the tag field itself.
4149
     */
4150
    public static function add_tag($tag, $user_id, $field_id)
4151
    {
4152
        // database table definition
4153
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4154
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4155
        $tag = trim(Database::escape_string($tag));
4156
        $user_id = (int) $user_id;
4157
        $field_id = (int) $field_id;
4158
4159
        $tag_id = self::get_tag_id($tag, $field_id);
4160
4161
        /* IMPORTANT
4162
         *  @todo we don't create tags with numbers
4163
         *
4164
         */
4165
        if (is_numeric($tag)) {
4166
            //the form is sending an id this means that the user select it from the list so it MUST exists
4167
            /* $new_tag_id = self::get_tag_id_from_id($tag,$field_id);
4168
              if ($new_tag_id !== false) {
4169
              $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $new_tag_id";
4170
              $result = Database::query($sql);
4171
              $last_insert_id = $new_tag_id;
4172
              } else {
4173
              $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4174
              $result = Database::query($sql);
4175
              $last_insert_id = Database::insert_id();
4176
              } */
4177
        }
4178
4179
        //this is a new tag
4180
        if ($tag_id == 0) {
4181
            //the tag doesn't exist
4182
            $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4183
            Database::query($sql);
4184
            $last_insert_id = Database::insert_id();
4185
        } else {
4186
            //the tag exists we update it
4187
            $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $tag_id";
4188
            Database::query($sql);
4189
            $last_insert_id = $tag_id;
4190
        }
4191
4192
        if (!empty($last_insert_id) && ($last_insert_id != 0)) {
4193
            //we insert the relationship user-tag
4194
            $sql = "SELECT tag_id FROM $table_user_tag_values
4195
                    WHERE user_id = $user_id AND tag_id = $last_insert_id ";
4196
            $result = Database::query($sql);
4197
            //if the relationship does not exist we create it
4198
            if (Database::num_rows($result) == 0) {
4199
                $sql = "INSERT INTO $table_user_tag_values SET user_id = $user_id, tag_id = $last_insert_id";
4200
                Database::query($sql);
4201
            }
4202
4203
            return true;
4204
        }
4205
4206
        return false;
4207
    }
4208
4209
    /**
4210
     * Deletes an user tag.
4211
     *
4212
     * @param int $user_id
4213
     * @param int $field_id
4214
     */
4215
    public static function delete_user_tags($user_id, $field_id)
4216
    {
4217
        // database table definition
4218
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4219
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4220
        $tags = self::get_user_tags($user_id, $field_id);
4221
        if (is_array($tags) && count($tags) > 0) {
4222
            foreach ($tags as $key => $tag) {
4223
                if ($tag['count'] > '0') {
4224
                    $sql = "UPDATE $table_user_tag SET count = count - 1  WHERE id = $key ";
4225
                    Database::query($sql);
4226
                }
4227
                $sql = "DELETE FROM $table_user_tag_values
4228
                        WHERE user_id = $user_id AND tag_id = $key";
4229
                Database::query($sql);
4230
            }
4231
        }
4232
    }
4233
4234
    /**
4235
     * Process the tag list comes from the UserManager::update_extra_field_value() function.
4236
     *
4237
     * @param array $tags     the tag list that will be added
4238
     * @param int   $user_id
4239
     * @param int   $field_id
4240
     *
4241
     * @return bool
4242
     */
4243
    public static function process_tags($tags, $user_id, $field_id)
4244
    {
4245
        // We loop the tags and add it to the DB
4246
        if (is_array($tags)) {
4247
            foreach ($tags as $tag) {
4248
                self::add_tag($tag, $user_id, $field_id);
4249
            }
4250
        } else {
4251
            self::add_tag($tags, $user_id, $field_id);
4252
        }
4253
4254
        return true;
4255
    }
4256
4257
    /**
4258
     * Returns a list of all administrators.
4259
     *
4260
     * @return array
4261
     */
4262
    public static function get_all_administrators()
4263
    {
4264
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
4265
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
4266
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4267
        $access_url_id = api_get_current_access_url_id();
4268
        if (api_get_multiple_access_url()) {
4269
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
4270
                    FROM $tbl_url_rel_user as url
4271
                    INNER JOIN $table_admin as admin
4272
                    ON (admin.user_id=url.user_id)
4273
                    INNER JOIN $table_user u
4274
                    ON (u.id=admin.user_id)
4275
                    WHERE access_url_id ='".$access_url_id."'";
4276
        } else {
4277
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
4278
                    FROM $table_admin as admin
4279
                    INNER JOIN $table_user u
4280
                    ON (u.id=admin.user_id)";
4281
        }
4282
        $result = Database::query($sql);
4283
        $return = [];
4284
        if (Database::num_rows($result) > 0) {
4285
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4286
                $return[$row['user_id']] = $row;
4287
            }
4288
        }
4289
4290
        return $return;
4291
    }
4292
4293
    /**
4294
     * Search an user (tags, first name, last name and email ).
4295
     *
4296
     * @param string $tag
4297
     * @param int    $field_id        field id of the tag
4298
     * @param int    $from            where to start in the query
4299
     * @param int    $number_of_items
4300
     * @param bool   $getCount        get count or not
4301
     *
4302
     * @return array
4303
     */
4304
    public static function get_all_user_tags(
4305
        $tag,
4306
        $field_id = 0,
4307
        $from = 0,
4308
        $number_of_items = 10,
4309
        $getCount = false
4310
    ) {
4311
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
4312
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4313
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4314
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4315
4316
        $field_id = intval($field_id);
4317
        $from = intval($from);
4318
        $number_of_items = intval($number_of_items);
4319
4320
        $where_field = "";
4321
        $where_extra_fields = self::get_search_form_where_extra_fields();
4322
        if ($field_id != 0) {
4323
            $where_field = " field_id = $field_id AND ";
4324
        }
4325
4326
        // all the information of the field
4327
        if ($getCount) {
4328
            $select = "SELECT count(DISTINCT u.id) count";
4329
        } else {
4330
            $select = "SELECT DISTINCT u.id, u.username, firstname, lastname, email, tag, picture_uri";
4331
        }
4332
4333
        $sql = " $select
4334
                FROM $user_table u
4335
                INNER JOIN $access_url_rel_user_table url_rel_user
4336
                ON (u.id = url_rel_user.user_id)
4337
                LEFT JOIN $table_user_tag_values uv
4338
                ON (u.id AND uv.user_id AND uv.user_id = url_rel_user.user_id)
4339
                LEFT JOIN $table_user_tag ut ON (uv.tag_id = ut.id)
4340
                WHERE
4341
                    ($where_field tag LIKE '".Database::escape_string($tag."%")."') OR
4342
                    (
4343
                        u.firstname LIKE '".Database::escape_string("%".$tag."%")."' OR
4344
                        u.lastname LIKE '".Database::escape_string("%".$tag."%")."' OR
4345
                        u.username LIKE '".Database::escape_string("%".$tag."%")."' OR
4346
                        concat(u.firstname, ' ', u.lastname) LIKE '".Database::escape_string("%".$tag."%")."' OR
4347
                        concat(u.lastname, ' ', u.firstname) LIKE '".Database::escape_string("%".$tag."%")."'
4348
                     )
4349
                     ".(!empty($where_extra_fields) ? $where_extra_fields : '')."
4350
                     AND url_rel_user.access_url_id=".api_get_current_access_url_id();
4351
4352
        $keyword_active = true;
4353
        // only active users
4354
        if ($keyword_active) {
4355
            $sql .= " AND u.active='1'";
4356
        }
4357
        // avoid anonymous
4358
        $sql .= " AND u.status <> 6 ";
4359
        $sql .= " ORDER BY username";
4360
        $sql .= " LIMIT $from , $number_of_items";
4361
4362
        $result = Database::query($sql);
4363
        $return = [];
4364
4365
        if (Database::num_rows($result) > 0) {
4366
            if ($getCount) {
4367
                $row = Database::fetch_array($result, 'ASSOC');
4368
4369
                return $row['count'];
4370
            }
4371
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4372
                if (isset($return[$row['id']]) &&
4373
                    !empty($return[$row['id']]['tag'])
4374
                ) {
4375
                    $url = Display::url(
4376
                        $row['tag'],
4377
                        api_get_path(WEB_PATH).'main/social/search.php?q='.$row['tag'],
4378
                        ['class' => 'tag']
4379
                    );
4380
                    $row['tag'] = $url;
4381
                }
4382
                $return[$row['id']] = $row;
4383
            }
4384
        }
4385
4386
        return $return;
4387
    }
4388
4389
    /**
4390
     * Get extra filterable user fields (only type select).
4391
     *
4392
     * @return array Array of extra fields as [int => ['name' => ..., 'variable' => ..., 'data' => ...]] (
4393
     *               or empty array if no extra field)
4394
     */
4395
    public static function getExtraFilterableFields()
4396
    {
4397
        $extraFieldList = self::get_extra_fields();
4398
        $fields = [];
4399
        if (is_array($extraFieldList)) {
4400
            foreach ($extraFieldList as $extraField) {
4401
                // If is enabled to filter and is a "<select>" field type
4402
                if ($extraField[8] == 1 && $extraField[2] == 4) {
4403
                    $fields[] = [
4404
                        'name' => $extraField[3],
4405
                        'variable' => $extraField[1],
4406
                        'data' => $extraField[9],
4407
                    ];
4408
                }
4409
            }
4410
        }
4411
4412
        return $fields;
4413
    }
4414
4415
    /**
4416
     * Get extra where clauses for finding users based on extra filterable user fields (type select).
4417
     *
4418
     * @return string With AND clauses based on user's ID which have the values to search in extra user fields
4419
     *                (or empty if no extra field exists)
4420
     */
4421
    public static function get_search_form_where_extra_fields()
4422
    {
4423
        $useExtraFields = false;
4424
        $extraFields = self::getExtraFilterableFields();
4425
        $extraFieldResult = [];
4426
        if (is_array($extraFields) && count($extraFields) > 0) {
4427
            foreach ($extraFields as $extraField) {
4428
                $varName = 'field_'.$extraField['variable'];
4429
                if (self::is_extra_field_available($extraField['variable'])) {
4430
                    if (isset($_GET[$varName]) && $_GET[$varName] != '0') {
4431
                        $useExtraFields = true;
4432
                        $extraFieldResult[] = self::get_extra_user_data_by_value(
4433
                            $extraField['variable'],
4434
                            $_GET[$varName]
4435
                        );
4436
                    }
4437
                }
4438
            }
4439
        }
4440
4441
        if ($useExtraFields) {
4442
            $finalResult = [];
4443
            if (count($extraFieldResult) > 1) {
4444
                for ($i = 0; $i < count($extraFieldResult) - 1; $i++) {
4445
                    if (is_array($extraFieldResult[$i]) && is_array($extraFieldResult[$i + 1])) {
4446
                        $finalResult = array_intersect($extraFieldResult[$i], $extraFieldResult[$i + 1]);
4447
                    }
4448
                }
4449
            } else {
4450
                $finalResult = $extraFieldResult[0];
4451
            }
4452
4453
            if (is_array($finalResult) && count($finalResult) > 0) {
4454
                $whereFilter = " AND u.id IN  ('".implode("','", $finalResult)."') ";
4455
            } else {
4456
                //no results
4457
                $whereFilter = " AND u.id  = -1 ";
4458
            }
4459
4460
            return $whereFilter;
4461
        }
4462
4463
        return '';
4464
    }
4465
4466
    /**
4467
     * Show the search form.
4468
     *
4469
     * @param string $query the value of the search box
4470
     *
4471
     * @throws Exception
4472
     *
4473
     * @return string HTML form
4474
     */
4475
    public static function getSearchForm($query, $defaultParams = [])
4476
    {
4477
        $searchType = isset($_GET['search_type']) ? $_GET['search_type'] : null;
4478
        $form = new FormValidator(
4479
            'search_user',
4480
            'get',
4481
            api_get_path(WEB_PATH).'main/social/search.php',
4482
            '',
4483
            [],
4484
            'box-no-label'
4485
        );
4486
4487
        $form->addText('q', get_lang('UsersGroups'), false, [
4488
            "id" => "q",
4489
        ]);
4490
        $options = [
4491
            0 => get_lang('Select'),
4492
            1 => get_lang('User'),
4493
            2 => get_lang('Group'),
4494
        ];
4495
        $form->addSelect(
4496
            'search_type',
4497
            get_lang('Type'),
4498
            $options,
4499
            ['onchange' => 'javascript: extra_field_toogle();', 'id' => 'search_type']
4500
        );
4501
4502
        // Extra fields
4503
        $extraFields = self::getExtraFilterableFields();
4504
        $defaults = [];
4505
        if (is_array($extraFields) && count($extraFields) > 0) {
4506
            foreach ($extraFields as $extraField) {
4507
                $varName = 'field_'.$extraField['variable'];
4508
                $options = [
4509
                    0 => get_lang('Select'),
4510
                ];
4511
                foreach ($extraField['data'] as $option) {
4512
                    $checked = '';
4513
                    if (isset($_GET[$varName])) {
4514
                        if ($_GET[$varName] == $option[1]) {
4515
                            $defaults[$option[1]] = true;
4516
                        }
4517
                    }
4518
4519
                    $options[$option[1]] = $option[1];
4520
                }
4521
                $form->addSelect($varName, $extraField['name'], $options);
4522
            }
4523
        }
4524
4525
        $defaults['search_type'] = intval($searchType);
4526
        $defaults['q'] = api_htmlentities(Security::remove_XSS($query));
4527
4528
        if (!empty($defaultParams)) {
4529
            $defaults = array_merge($defaults, $defaultParams);
4530
        }
4531
        $form->setDefaults($defaults);
4532
        $form->addButtonSearch(get_lang('Search'));
4533
4534
        $js = '<script>
4535
        extra_field_toogle();
4536
        function extra_field_toogle() {
4537
            if (jQuery("select[name=search_type]").val() != "1") { 
4538
                jQuery(".extra_field").hide(); 
4539
            } else { 
4540
                jQuery(".extra_field").show(); 
4541
            }
4542
        }
4543
        </script>';
4544
4545
        return $js.$form->returnForm();
4546
    }
4547
4548
    /**
4549
     * Shows the user menu.
4550
     */
4551
    public static function show_menu()
4552
    {
4553
        echo '<div class="actions">';
4554
        echo '<a href="/main/auth/profile.php">'.
4555
            Display::return_icon('profile.png').' '.get_lang('PersonalData').'</a>';
4556
        echo '<a href="/main/messages/inbox.php">'.
4557
            Display::return_icon('inbox.png').' '.get_lang('Inbox').'</a>';
4558
        echo '<a href="/main/messages/outbox.php">'.
4559
            Display::return_icon('outbox.png').' '.get_lang('Outbox').'</a>';
4560
        echo '<span style="float:right; padding-top:7px;">'.
4561
        '<a href="/main/auth/profile.php?show=1">'.
4562
            Display::return_icon('edit.gif').' '.get_lang('Configuration').'</a>';
4563
        echo '</span>';
4564
        echo '</div>';
4565
    }
4566
4567
    /**
4568
     * Allow to register contact to social network.
4569
     *
4570
     * @param int $friend_id     user friend id
4571
     * @param int $my_user_id    user id
4572
     * @param int $relation_type relation between users see constants definition
4573
     *
4574
     * @return bool
4575
     */
4576
    public static function relate_users($friend_id, $my_user_id, $relation_type)
4577
    {
4578
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4579
4580
        $friend_id = (int) $friend_id;
4581
        $my_user_id = (int) $my_user_id;
4582
        $relation_type = (int) $relation_type;
4583
4584
        $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
4585
                WHERE
4586
                    friend_user_id='.$friend_id.' AND
4587
                    user_id='.$my_user_id.' AND
4588
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
4589
        $result = Database::query($sql);
4590
        $row = Database::fetch_array($result, 'ASSOC');
4591
        $current_date = api_get_utc_datetime();
4592
4593
        if ($row['count'] == 0) {
4594
            $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
4595
                    VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
4596
            Database::query($sql);
4597
4598
            return true;
4599
        }
4600
4601
        $sql = 'SELECT COUNT(*) as count, relation_type  FROM '.$tbl_my_friend.'
4602
                WHERE
4603
                    friend_user_id='.$friend_id.' AND
4604
                    user_id='.$my_user_id.' AND
4605
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
4606
        $result = Database::query($sql);
4607
        $row = Database::fetch_array($result, 'ASSOC');
4608
4609
        if ($row['count'] == 1) {
4610
            //only for the case of a RRHH or a Student BOSS
4611
            if ($row['relation_type'] != $relation_type &&
4612
                ($relation_type == USER_RELATION_TYPE_RRHH || $relation_type == USER_RELATION_TYPE_BOSS)
4613
            ) {
4614
                $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
4615
                        VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
4616
            } else {
4617
                $sql = 'UPDATE '.$tbl_my_friend.' SET relation_type='.$relation_type.'
4618
                        WHERE friend_user_id='.$friend_id.' AND user_id='.$my_user_id;
4619
            }
4620
            Database::query($sql);
4621
4622
            return true;
4623
        }
4624
4625
        return false;
4626
    }
4627
4628
    /**
4629
     * Deletes a contact.
4630
     *
4631
     * @param int user friend id
4632
     * @param bool true will delete ALL friends relationship from $friend_id
4633
     * @param string                                              $with_status_condition
4634
     *
4635
     * @author isaac flores paz <[email protected]>
4636
     * @author Julio Montoya <[email protected]> Cleaning code
4637
     */
4638
    public static function remove_user_rel_user(
4639
        $friend_id,
4640
        $real_removed = false,
4641
        $with_status_condition = ''
4642
    ) {
4643
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4644
        $tbl_my_message = Database::get_main_table(TABLE_MESSAGE);
4645
        $friend_id = (int) $friend_id;
4646
        $user_id = api_get_user_id();
4647
4648
        if ($real_removed) {
4649
            $extra_condition = '';
4650
            if ($with_status_condition != '') {
4651
                $extra_condition = ' AND relation_type = '.intval($with_status_condition);
4652
            }
4653
            $sql = 'DELETE FROM '.$tbl_my_friend.'
4654
                    WHERE 
4655
                        relation_type <> '.USER_RELATION_TYPE_RRHH.' AND 
4656
                        friend_user_id='.$friend_id.' '.$extra_condition;
4657
            Database::query($sql);
4658
            $sql = 'DELETE FROM '.$tbl_my_friend.'
4659
                   WHERE 
4660
                    relation_type <> '.USER_RELATION_TYPE_RRHH.' AND 
4661
                    user_id='.$friend_id.' '.$extra_condition;
4662
            Database::query($sql);
4663
        } else {
4664
            $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
4665
                    WHERE
4666
                        user_id='.$user_id.' AND
4667
                        relation_type NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.') AND
4668
                        friend_user_id='.$friend_id;
4669
            $result = Database::query($sql);
4670
            $row = Database::fetch_array($result, 'ASSOC');
4671
            if ($row['count'] == 1) {
4672
                //Delete user rel user
4673
                $sql_i = 'UPDATE '.$tbl_my_friend.' SET relation_type='.USER_RELATION_TYPE_DELETED.'
4674
                          WHERE user_id='.$user_id.' AND friend_user_id='.$friend_id;
4675
4676
                $sql_j = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
4677
                          WHERE 
4678
                                user_receiver_id='.$user_id.' AND 
4679
                                user_sender_id='.$friend_id.' AND update_date="0000-00-00 00:00:00" ';
4680
                //Delete user
4681
                $sql_ij = 'UPDATE '.$tbl_my_friend.'  SET relation_type='.USER_RELATION_TYPE_DELETED.'
4682
                           WHERE user_id='.$friend_id.' AND friend_user_id='.$user_id;
4683
                $sql_ji = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
4684
                           WHERE 
4685
                                user_receiver_id='.$friend_id.' AND 
4686
                                user_sender_id='.$user_id.' AND 
4687
                                update_date="0000-00-00 00:00:00" ';
4688
                Database::query($sql_i);
4689
                Database::query($sql_j);
4690
                Database::query($sql_ij);
4691
                Database::query($sql_ji);
4692
            }
4693
        }
4694
4695
        // Delete accepted invitations
4696
        $sql = "DELETE FROM $tbl_my_message 
4697
                WHERE
4698
                    msg_status = ".MESSAGE_STATUS_INVITATION_ACCEPTED." AND
4699
                    (
4700
                        user_receiver_id = $user_id AND 
4701
                        user_sender_id = $friend_id
4702
                    ) OR 
4703
                    (
4704
                        user_sender_id = $user_id AND 
4705
                        user_receiver_id = $friend_id
4706
                    )
4707
        ";
4708
        Database::query($sql);
4709
    }
4710
4711
    /**
4712
     * @param int $userId
4713
     *
4714
     * @return array
4715
     */
4716
    public static function getDrhListFromUser($userId)
4717
    {
4718
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
4719
        $tblUserRelUser = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4720
        $tblUserRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4721
        $userId = (int) $userId;
4722
4723
        $orderBy = null;
4724
        if (api_is_western_name_order()) {
4725
            $orderBy .= ' ORDER BY firstname, lastname ';
4726
        } else {
4727
            $orderBy .= ' ORDER BY lastname, firstname ';
4728
        }
4729
4730
        $sql = "SELECT u.id, username, u.firstname, u.lastname
4731
                FROM $tblUser u
4732
                INNER JOIN $tblUserRelUser uru ON (uru.friend_user_id = u.id)
4733
                INNER JOIN $tblUserRelAccessUrl a ON (a.user_id = u.id)
4734
                WHERE
4735
                    access_url_id = ".api_get_current_access_url_id()." AND
4736
                    uru.user_id = '$userId' AND
4737
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
4738
                $orderBy
4739
                ";
4740
        $result = Database::query($sql);
4741
4742
        return Database::store_result($result);
4743
    }
4744
4745
    /**
4746
     * get users followed by human resource manager.
4747
     *
4748
     * @param int    $userId
4749
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
4750
     * @param bool   $getOnlyUserId
4751
     * @param bool   $getSql
4752
     * @param bool   $getCount
4753
     * @param int    $from
4754
     * @param int    $numberItems
4755
     * @param int    $column
4756
     * @param string $direction
4757
     * @param int    $active
4758
     * @param string $lastConnectionDate
4759
     *
4760
     * @return array users
4761
     */
4762
    public static function get_users_followed_by_drh(
4763
        $userId,
4764
        $userStatus = 0,
4765
        $getOnlyUserId = false,
4766
        $getSql = false,
4767
        $getCount = false,
4768
        $from = null,
4769
        $numberItems = null,
4770
        $column = null,
4771
        $direction = null,
4772
        $active = null,
4773
        $lastConnectionDate = null
4774
    ) {
4775
        return self::getUsersFollowedByUser(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getUsersFol...astConnectionDate, DRH) also could return the type string which is incompatible with the documented return type array.
Loading history...
4776
            $userId,
4777
            $userStatus,
4778
            $getOnlyUserId,
4779
            $getSql,
4780
            $getCount,
4781
            $from,
4782
            $numberItems,
4783
            $column,
4784
            $direction,
4785
            $active,
4786
            $lastConnectionDate,
4787
            DRH
4788
        );
4789
    }
4790
4791
    /**
4792
     * Get users followed by human resource manager.
4793
     *
4794
     * @param int    $userId
4795
     * @param int    $userStatus         Filter users by status (STUDENT, COURSEMANAGER, etc)
4796
     * @param bool   $getOnlyUserId
4797
     * @param bool   $getSql
4798
     * @param bool   $getCount
4799
     * @param int    $from
4800
     * @param int    $numberItems
4801
     * @param int    $column
4802
     * @param string $direction
4803
     * @param int    $active
4804
     * @param string $lastConnectionDate
4805
     * @param int    $status             the function is called by who? COURSEMANAGER, DRH?
4806
     * @param string $keyword
4807
     *
4808
     * @return mixed Users list (array) or the SQL query if $getSQL was set to true
4809
     */
4810
    public static function getUsersFollowedByUser(
4811
        $userId,
4812
        $userStatus = null,
4813
        $getOnlyUserId = false,
4814
        $getSql = false,
4815
        $getCount = false,
4816
        $from = null,
4817
        $numberItems = null,
4818
        $column = null,
4819
        $direction = null,
4820
        $active = null,
4821
        $lastConnectionDate = null,
4822
        $status = null,
4823
        $keyword = null
4824
    ) {
4825
        // Database Table Definitions
4826
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
4827
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
4828
        $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4829
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4830
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4831
        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4832
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4833
        $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
4834
4835
        $userId = (int) $userId;
4836
        $limitCondition = '';
4837
4838
        if (isset($from) && isset($numberItems)) {
4839
            $from = (int) $from;
4840
            $numberItems = (int) $numberItems;
4841
            $limitCondition = "LIMIT $from, $numberItems";
4842
        }
4843
4844
        $column = Database::escape_string($column);
4845
        $direction = in_array(strtolower($direction), ['asc', 'desc']) ? $direction : null;
4846
4847
        $userConditions = '';
4848
        if (!empty($userStatus)) {
4849
            $userConditions .= ' AND u.status = '.intval($userStatus);
4850
        }
4851
4852
        $select = " SELECT DISTINCT u.id user_id, u.username, u.lastname, u.firstname, u.email ";
4853
        if ($getOnlyUserId) {
4854
            $select = " SELECT DISTINCT u.id user_id";
4855
        }
4856
4857
        $masterSelect = "SELECT DISTINCT * FROM ";
4858
4859
        if ($getCount) {
4860
            $masterSelect = "SELECT COUNT(DISTINCT(user_id)) as count FROM ";
4861
            $select = " SELECT DISTINCT(u.id) user_id";
4862
        }
4863
4864
        if (!is_null($active)) {
4865
            $active = intval($active);
4866
            $userConditions .= " AND u.active = $active ";
4867
        }
4868
4869
        if (!empty($keyword)) {
4870
            $keyword = Database::escape_string($keyword);
4871
            $userConditions .= " AND (
4872
                u.username LIKE '%$keyword%' OR
4873
                u.firstname LIKE '%$keyword%' OR
4874
                u.lastname LIKE '%$keyword%' OR
4875
                u.official_code LIKE '%$keyword%' OR
4876
                u.email LIKE '%$keyword%'
4877
            )";
4878
        }
4879
4880
        if (!empty($lastConnectionDate)) {
4881
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
4882
            $userConditions .= " AND u.last_login <= '$lastConnectionDate' ";
4883
        }
4884
4885
        $courseConditions = null;
4886
        $sessionConditionsCoach = null;
4887
        $sessionConditionsTeacher = null;
4888
        $drhConditions = null;
4889
        $teacherSelect = null;
4890
4891
        switch ($status) {
4892
            case DRH:
4893
                $drhConditions .= " AND
4894
                    friend_user_id = '$userId' AND
4895
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
4896
                ";
4897
                break;
4898
            case COURSEMANAGER:
4899
                $drhConditions .= " AND
4900
                    friend_user_id = '$userId' AND
4901
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
4902
                ";
4903
4904
                $sessionConditionsCoach .= " AND
4905
                    (s.id_coach = '$userId')
4906
                ";
4907
4908
                $sessionConditionsTeacher .= " AND
4909
                    (scu.status = 2 AND scu.user_id = '$userId')
4910
                ";
4911
4912
                $teacherSelect =
4913
                "UNION ALL (
4914
                        $select
4915
                        FROM $tbl_user u
4916
                        INNER JOIN $tbl_session_rel_user sru ON (sru.user_id = u.id)
4917
                        WHERE
4918
                            (
4919
                                sru.session_id IN (
4920
                                    SELECT DISTINCT(s.id) FROM $tbl_session s INNER JOIN
4921
                                    $tbl_session_rel_access_url session_rel_access_rel_user
4922
                                    ON session_rel_access_rel_user.session_id = s.id
4923
                                    WHERE access_url_id = ".api_get_current_access_url_id()."
4924
                                    $sessionConditionsCoach                                  
4925
                                ) OR sru.session_id IN (
4926
                                    SELECT DISTINCT(s.id) FROM $tbl_session s
4927
                                    INNER JOIN $tbl_session_rel_access_url url
4928
                                    ON (url.session_id = s.id)
4929
                                    INNER JOIN $tbl_session_rel_course_rel_user scu
4930
                                    ON (scu.session_id = s.id)
4931
                                    WHERE access_url_id = ".api_get_current_access_url_id()."
4932
                                    $sessionConditionsTeacher
4933
                                )
4934
                            )                            
4935
                            $userConditions
4936
                    )
4937
                    UNION ALL(
4938
                        $select
4939
                        FROM $tbl_user u
4940
                        INNER JOIN $tbl_course_user cu ON (cu.user_id = u.id)
4941
                        WHERE cu.c_id IN (
4942
                            SELECT DISTINCT(c_id) FROM $tbl_course_user
4943
                            WHERE user_id = $userId AND status = ".COURSEMANAGER."
4944
                        )
4945
                        $userConditions
4946
                    )"
4947
                ;
4948
                break;
4949
            case STUDENT_BOSS:
4950
                $drhConditions = " AND friend_user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
4951
                break;
4952
            case HRM_REQUEST:
4953
                $drhConditions .= " AND
4954
                    friend_user_id = '$userId' AND
4955
                    relation_type = '".USER_RELATION_TYPE_HRM_REQUEST."'
4956
                ";
4957
                break;
4958
        }
4959
4960
        $join = null;
4961
        $sql = " $masterSelect
4962
                (
4963
                    (
4964
                        $select
4965
                        FROM $tbl_user u
4966
                        INNER JOIN $tbl_user_rel_user uru ON (uru.user_id = u.id)
4967
                        LEFT JOIN $tbl_user_rel_access_url a ON (a.user_id = u.id)
4968
                        $join
4969
                        WHERE
4970
                            access_url_id = ".api_get_current_access_url_id()."
4971
                            $drhConditions
4972
                            $userConditions
4973
                    )
4974
                    $teacherSelect
4975
4976
                ) as t1";
4977
4978
        if ($getSql) {
4979
            return $sql;
4980
        }
4981
        if ($getCount) {
4982
            $result = Database::query($sql);
4983
            $row = Database::fetch_array($result);
4984
4985
            return $row['count'];
4986
        }
4987
4988
        $orderBy = null;
4989
        if ($getOnlyUserId == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
4990
            if (api_is_western_name_order()) {
4991
                $orderBy .= " ORDER BY firstname, lastname ";
4992
            } else {
4993
                $orderBy .= " ORDER BY lastname, firstname ";
4994
            }
4995
4996
            if (!empty($column) && !empty($direction)) {
4997
                // Fixing order due the UNIONs
4998
                $column = str_replace('u.', '', $column);
4999
                $orderBy = " ORDER BY $column $direction ";
5000
            }
5001
        }
5002
5003
        $sql .= $orderBy;
5004
        $sql .= $limitCondition;
5005
5006
        $result = Database::query($sql);
5007
        $users = [];
5008
        if (Database::num_rows($result) > 0) {
5009
            while ($row = Database::fetch_array($result)) {
5010
                $users[$row['user_id']] = $row;
5011
            }
5012
        }
5013
5014
        return $users;
5015
    }
5016
5017
    /**
5018
     * Subscribes users to human resource manager (Dashboard feature).
5019
     *
5020
     * @param int   $hr_dept_id
5021
     * @param array $users_id
5022
     * @param bool  $deleteOtherAssignedUsers
5023
     *
5024
     * @return int
5025
     */
5026
    public static function subscribeUsersToHRManager(
5027
        $hr_dept_id,
5028
        $users_id,
5029
        $deleteOtherAssignedUsers = true
5030
    ) {
5031
        return self::subscribeUsersToUser(
5032
            $hr_dept_id,
5033
            $users_id,
5034
            USER_RELATION_TYPE_RRHH,
5035
            false,
5036
            $deleteOtherAssignedUsers
5037
        );
5038
    }
5039
5040
    /**
5041
     * Register request to assign users to HRM.
5042
     *
5043
     * @param int   $hrmId   The HRM ID
5044
     * @param array $usersId The users IDs
5045
     *
5046
     * @return int
5047
     */
5048
    public static function requestUsersToHRManager($hrmId, $usersId)
5049
    {
5050
        return self::subscribeUsersToUser(
5051
            $hrmId,
5052
            $usersId,
5053
            USER_RELATION_TYPE_HRM_REQUEST,
5054
            false,
5055
            false
5056
        );
5057
    }
5058
5059
    /**
5060
     * Remove the requests for assign a user to a HRM.
5061
     *
5062
     * @param User  $hrmId
5063
     * @param array $usersId List of user IDs from whom to remove all relations requests with HRM
5064
     */
5065
    public static function clearHrmRequestsForUser(User $hrmId, $usersId)
5066
    {
5067
        $users = implode(', ', $usersId);
5068
        Database::getManager()
5069
            ->createQuery('
5070
                DELETE FROM ChamiloCoreBundle:UserRelUser uru
5071
                WHERE uru.friendUserId = :hrm_id AND uru.relationType = :relation_type AND uru.userId IN (:users_ids)
5072
            ')
5073
            ->execute(['hrm_id' => $hrmId, 'relation_type' => USER_RELATION_TYPE_HRM_REQUEST, 'users_ids' => $users]);
5074
    }
5075
5076
    /**
5077
     * Add subscribed users to a user by relation type.
5078
     *
5079
     * @param int    $userId                   The user id
5080
     * @param array  $subscribedUsersId        The id of subscribed users
5081
     * @param string $relationType             The relation type
5082
     * @param bool   $deleteUsersBeforeInsert
5083
     * @param bool   $deleteOtherAssignedUsers
5084
     *
5085
     * @return int
5086
     */
5087
    public static function subscribeUsersToUser(
5088
        $userId,
5089
        $subscribedUsersId,
5090
        $relationType,
5091
        $deleteUsersBeforeInsert = false,
5092
        $deleteOtherAssignedUsers = true
5093
    ) {
5094
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5095
        $userRelAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5096
5097
        $userId = intval($userId);
5098
        $relationType = intval($relationType);
5099
        $affectedRows = 0;
5100
5101
        if ($deleteOtherAssignedUsers) {
5102
            if (api_get_multiple_access_url()) {
5103
                // Deleting assigned users to hrm_id
5104
                $sql = "SELECT s.user_id 
5105
                        FROM $userRelUserTable s 
5106
                        INNER JOIN $userRelAccessUrlTable a
5107
                        ON (a.user_id = s.user_id) 
5108
                        WHERE 
5109
                            friend_user_id = $userId AND 
5110
                            relation_type = $relationType AND 
5111
                            access_url_id = ".api_get_current_access_url_id();
5112
            } else {
5113
                $sql = "SELECT user_id 
5114
                        FROM $userRelUserTable 
5115
                        WHERE 
5116
                            friend_user_id = $userId AND 
5117
                            relation_type = $relationType";
5118
            }
5119
            $result = Database::query($sql);
5120
5121
            if (Database::num_rows($result) > 0) {
5122
                while ($row = Database::fetch_array($result)) {
5123
                    $sql = "DELETE FROM $userRelUserTable 
5124
                            WHERE
5125
                                user_id = {$row['user_id']} AND 
5126
                                friend_user_id = $userId AND 
5127
                                relation_type = $relationType";
5128
                    Database::query($sql);
5129
                }
5130
            }
5131
        }
5132
5133
        if ($deleteUsersBeforeInsert) {
5134
            $sql = "DELETE FROM $userRelUserTable 
5135
                    WHERE 
5136
                        user_id = $userId AND
5137
                        relation_type = $relationType";
5138
            Database::query($sql);
5139
        }
5140
5141
        // Inserting new user list
5142
        if (is_array($subscribedUsersId)) {
5143
            foreach ($subscribedUsersId as $subscribedUserId) {
5144
                $subscribedUserId = intval($subscribedUserId);
5145
                $sql = "SELECT id FROM $userRelUserTable
5146
                        WHERE user_id = $subscribedUserId
5147
                        AND friend_user_id = $userId
5148
                        AND relation_type = $relationType";
5149
                $result = Database::query($sql);
5150
                $num = Database::num_rows($result);
5151
                if ($num === 0) {
5152
                    $date = api_get_utc_datetime();
5153
                    $sql = "INSERT INTO $userRelUserTable (user_id, friend_user_id, relation_type, last_edit)
5154
                        VALUES ($subscribedUserId, $userId, $relationType, '$date')";
5155
                    $result = Database::query($sql);
5156
                    $affectedRows += Database::affected_rows($result);
5157
                }
5158
            }
5159
        }
5160
5161
        return $affectedRows;
5162
    }
5163
5164
    /**
5165
     * This function check if an user is followed by human resources manager.
5166
     *
5167
     * @param int $user_id
5168
     * @param int $hr_dept_id Human resources manager
5169
     *
5170
     * @return bool
5171
     */
5172
    public static function is_user_followed_by_drh($user_id, $hr_dept_id)
5173
    {
5174
        // Database table and variables Definitions
5175
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5176
        $user_id = intval($user_id);
5177
        $hr_dept_id = intval($hr_dept_id);
5178
        $result = false;
5179
5180
        $sql = "SELECT user_id FROM $tbl_user_rel_user
5181
                WHERE
5182
                    user_id = $user_id AND
5183
                    friend_user_id = $hr_dept_id AND
5184
                    relation_type = ".USER_RELATION_TYPE_RRHH;
5185
        $rs = Database::query($sql);
5186
        if (Database::num_rows($rs) > 0) {
5187
            $result = true;
5188
        }
5189
5190
        return $result;
5191
    }
5192
5193
    /**
5194
     * Return the user id of teacher or session administrator.
5195
     *
5196
     * @param array $courseInfo
5197
     *
5198
     * @return mixed The user id, or false if the session ID was negative
5199
     */
5200
    public static function get_user_id_of_course_admin_or_session_admin($courseInfo)
5201
    {
5202
        $session = api_get_session_id();
5203
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
5204
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
5205
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
5206
        $courseId = $courseInfo['real_id'];
5207
5208
        if ($session == 0 || is_null($session)) {
5209
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
5210
                    INNER JOIN '.$table_course_user.' ru
5211
                    ON ru.user_id = u.id
5212
                    WHERE
5213
                        ru.status = 1 AND
5214
                        ru.c_id = "'.$courseId.'" ';
5215
            $rs = Database::query($sql);
5216
            $num_rows = Database::num_rows($rs);
5217
            if ($num_rows == 1) {
5218
                $row = Database::fetch_array($rs);
5219
5220
                return $row['uid'];
5221
            } else {
5222
                $my_num_rows = $num_rows;
5223
                $my_user_id = Database::result($rs, $my_num_rows - 1, 'uid');
5224
5225
                return $my_user_id;
5226
            }
5227
        } elseif ($session > 0) {
5228
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
5229
                    INNER JOIN '.$table_session_course_user.' sru
5230
                    ON sru.user_id=u.id
5231
                    WHERE
5232
                        sru.c_id="'.$courseId.'" AND
5233
                        sru.status=2';
5234
            $rs = Database::query($sql);
5235
            $row = Database::fetch_array($rs);
5236
5237
            return $row['uid'];
5238
        }
5239
5240
        return false;
5241
    }
5242
5243
    /**
5244
     * Determines if a user is a gradebook certified.
5245
     *
5246
     * @param int $cat_id  The category id of gradebook
5247
     * @param int $user_id The user id
5248
     *
5249
     * @return bool
5250
     */
5251
    public static function is_user_certified($cat_id, $user_id)
5252
    {
5253
        $table_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
5254
        $sql = 'SELECT path_certificate FROM '.$table_certificate.'
5255
                WHERE
5256
                    cat_id="'.intval($cat_id).'" AND
5257
                    user_id="'.intval($user_id).'"';
5258
        $rs = Database::query($sql);
5259
        $row = Database::fetch_array($rs);
5260
        if ($row['path_certificate'] == '' || is_null($row['path_certificate'])) {
5261
            return false;
5262
        } else {
5263
            return true;
5264
        }
5265
    }
5266
5267
    /**
5268
     * Gets the info about a gradebook certificate for a user by course.
5269
     *
5270
     * @param string $course_code The course code
5271
     * @param int    $user_id     The user id
5272
     *
5273
     * @return array if there is not information return false
5274
     */
5275
    public static function get_info_gradebook_certificate($course_code, $user_id)
5276
    {
5277
        $tbl_grade_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
5278
        $tbl_grade_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
5279
        $session_id = api_get_session_id();
5280
5281
        if (empty($session_id)) {
5282
            $session_condition = ' AND (session_id = "" OR session_id = 0 OR session_id IS NULL )';
5283
        } else {
5284
            $session_condition = " AND session_id = $session_id";
5285
        }
5286
5287
        $sql = 'SELECT * FROM '.$tbl_grade_certificate.' 
5288
                WHERE cat_id = (
5289
                    SELECT id FROM '.$tbl_grade_category.'
5290
                    WHERE
5291
                        course_code = "'.Database::escape_string($course_code).'" '.$session_condition.' 
5292
                    LIMIT 1 
5293
                ) AND user_id='.intval($user_id);
5294
5295
        $rs = Database::query($sql);
5296
        if (Database::num_rows($rs) > 0) {
5297
            $row = Database::fetch_array($rs, 'ASSOC');
5298
            $score = $row['score_certificate'];
5299
            $category_id = $row['cat_id'];
5300
            $cat = Category::load($category_id);
5301
            $displayscore = ScoreDisplay::instance();
5302
            if (isset($cat) && $displayscore->is_custom()) {
5303
                $grade = $displayscore->display_score(
5304
                    [$score, $cat[0]->get_weight()],
5305
                    SCORE_DIV_PERCENT_WITH_CUSTOM
5306
                );
5307
            } else {
5308
                $grade = $displayscore->display_score(
5309
                    [$score, $cat[0]->get_weight()]
5310
                );
5311
            }
5312
            $row['grade'] = $grade;
5313
5314
            return $row;
5315
        }
5316
5317
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
5318
    }
5319
5320
    /**
5321
     * Gets the user path of user certificated.
5322
     *
5323
     * @param int The user id
5324
     *
5325
     * @return array containing path_certificate and cat_id
5326
     */
5327
    public static function get_user_path_certificate($user_id)
5328
    {
5329
        $my_certificate = [];
5330
        $table_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
5331
        $table_gradebook_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
5332
5333
        $session_id = api_get_session_id();
5334
        $user_id = intval($user_id);
5335
        if ($session_id == 0 || is_null($session_id)) {
5336
            $sql_session = 'AND (session_id='.intval($session_id).' OR isnull(session_id)) ';
5337
        } elseif ($session_id > 0) {
5338
            $sql_session = 'AND session_id='.intval($session_id);
5339
        } else {
5340
            $sql_session = '';
5341
        }
5342
        $sql = "SELECT tc.path_certificate,tc.cat_id,tgc.course_code,tgc.name
5343
                FROM $table_certificate tc, $table_gradebook_category tgc
5344
                WHERE tgc.id = tc.cat_id AND tc.user_id = $user_id
5345
                ORDER BY tc.date_certificate DESC 
5346
                LIMIT 5";
5347
5348
        $rs = Database::query($sql);
5349
        while ($row = Database::fetch_array($rs)) {
5350
            $my_certificate[] = $row;
5351
        }
5352
5353
        return $my_certificate;
5354
    }
5355
5356
    /**
5357
     * This function check if the user is a coach inside session course.
5358
     *
5359
     * @param int $user_id    User id
5360
     * @param int $courseId
5361
     * @param int $session_id
5362
     *
5363
     * @return bool True if the user is a coach
5364
     */
5365
    public static function is_session_course_coach($user_id, $courseId, $session_id)
5366
    {
5367
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
5368
        // Protect data
5369
        $user_id = intval($user_id);
5370
        $courseId = intval($courseId);
5371
        $session_id = intval($session_id);
5372
        $result = false;
5373
5374
        $sql = "SELECT session_id FROM $table
5375
                WHERE
5376
                  session_id = $session_id AND
5377
                  c_id = $courseId AND
5378
                  user_id = $user_id AND
5379
                  status = 2 ";
5380
        $res = Database::query($sql);
5381
5382
        if (Database::num_rows($res) > 0) {
5383
            $result = true;
5384
        }
5385
5386
        return $result;
5387
    }
5388
5389
    /**
5390
     * This function returns an icon path that represents the favicon of the website of which the url given.
5391
     * Defaults to the current Chamilo favicon.
5392
     *
5393
     * @param string $url1 URL of website where to look for favicon.ico
5394
     * @param string $url2 Optional second URL of website where to look for favicon.ico
5395
     *
5396
     * @return string Path of icon to load
5397
     */
5398
    public static function get_favicon_from_url($url1, $url2 = null)
5399
    {
5400
        $icon_link = '';
5401
        $url = $url1;
5402
        if (empty($url1)) {
5403
            $url = $url2;
5404
            if (empty($url)) {
5405
                $url = api_get_access_url(api_get_current_access_url_id());
5406
                $url = $url[0];
5407
            }
5408
        }
5409
        if (!empty($url)) {
5410
            $pieces = parse_url($url);
5411
            $icon_link = $pieces['scheme'].'://'.$pieces['host'].'/favicon.ico';
5412
        }
5413
5414
        return $icon_link;
5415
    }
5416
5417
    /**
5418
     * Returns an array of the different types of user extra fields [id => title translation].
5419
     *
5420
     * @return array
5421
     */
5422
    public static function get_user_field_types()
5423
    {
5424
        $types = [];
5425
        $types[self::USER_FIELD_TYPE_TEXT] = get_lang('FieldTypeText');
5426
        $types[self::USER_FIELD_TYPE_TEXTAREA] = get_lang('FieldTypeTextarea');
5427
        $types[self::USER_FIELD_TYPE_RADIO] = get_lang('FieldTypeRadio');
5428
        $types[self::USER_FIELD_TYPE_SELECT] = get_lang('FieldTypeSelect');
5429
        $types[self::USER_FIELD_TYPE_SELECT_MULTIPLE] = get_lang('FieldTypeSelectMultiple');
5430
        $types[self::USER_FIELD_TYPE_DATE] = get_lang('FieldTypeDate');
5431
        $types[self::USER_FIELD_TYPE_DATETIME] = get_lang('FieldTypeDatetime');
5432
        $types[self::USER_FIELD_TYPE_DOUBLE_SELECT] = get_lang('FieldTypeDoubleSelect');
5433
        $types[self::USER_FIELD_TYPE_DIVIDER] = get_lang('FieldTypeDivider');
5434
        $types[self::USER_FIELD_TYPE_TAG] = get_lang('FieldTypeTag');
5435
        $types[self::USER_FIELD_TYPE_TIMEZONE] = get_lang('FieldTypeTimezone');
5436
        $types[self::USER_FIELD_TYPE_SOCIAL_PROFILE] = get_lang('FieldTypeSocialProfile');
5437
        $types[self::USER_FIELD_TYPE_FILE] = get_lang('FieldTypeFile');
5438
        $types[self::USER_FIELD_TYPE_MOBILE_PHONE_NUMBER] = get_lang('FieldTypeMobilePhoneNumber');
5439
5440
        return $types;
5441
    }
5442
5443
    /**
5444
     * @param User $user
5445
     */
5446
    public static function add_user_as_admin(User $user)
5447
    {
5448
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
5449
        if ($user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\UserBundle\Entity\User, thus it always evaluated to true.
Loading history...
5450
            $userId = $user->getId();
5451
5452
            if (!self::is_admin($userId)) {
5453
                $sql = "INSERT INTO $table_admin SET user_id = $userId";
5454
                Database::query($sql);
5455
            }
5456
5457
            $group = Container::$container->get('fos_user.group_manager')->findGroupBy(['code' => 'ADMIN']);
5458
            if ($group) {
5459
                $user->addGroup($group);
5460
            }
5461
            self::getManager()->updateUser($user, true);
5462
        }
5463
    }
5464
5465
    /**
5466
     * @param int $userId
5467
     */
5468
    public static function remove_user_admin($userId)
5469
    {
5470
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
5471
        $userId = intval($userId);
5472
        if (self::is_admin($userId)) {
5473
            $sql = "DELETE FROM $table_admin WHERE user_id = $userId";
5474
            Database::query($sql);
5475
        }
5476
    }
5477
5478
    /**
5479
     * @param string $from
5480
     * @param string $to
5481
     */
5482
    public static function update_all_user_languages($from, $to)
5483
    {
5484
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
5485
        $from = Database::escape_string($from);
5486
        $to = Database::escape_string($to);
5487
5488
        if (!empty($to) && !empty($from)) {
5489
            $sql = "UPDATE $table_user SET language = '$to'
5490
                    WHERE language = '$from'";
5491
            Database::query($sql);
5492
        }
5493
    }
5494
5495
    /**
5496
     * Subscribe boss to students.
5497
     *
5498
     * @param int   $bossId  The boss id
5499
     * @param array $usersId The users array
5500
     *
5501
     * @return int Affected rows
5502
     */
5503
    public static function subscribeBossToUsers($bossId, $usersId)
5504
    {
5505
        return self::subscribeUsersToUser(
5506
            $bossId,
5507
            $usersId,
5508
            USER_RELATION_TYPE_BOSS
5509
        );
5510
    }
5511
5512
    /**
5513
     * @param int $userId
5514
     *
5515
     * @return bool
5516
     */
5517
    public static function removeAllBossFromStudent($userId)
5518
    {
5519
        $userId = (int) $userId;
5520
5521
        if (empty($userId)) {
5522
            return false;
5523
        }
5524
5525
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5526
        $sql = "DELETE FROM $userRelUserTable 
5527
                WHERE user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
5528
        Database::query($sql);
5529
5530
        return true;
5531
    }
5532
5533
    /**
5534
     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
5535
     *
5536
     * @param int   $studentId
5537
     * @param array $bossList
5538
     * @param bool  $sendNotification
5539
     *
5540
     * @return mixed Affected rows or false on failure
5541
     */
5542
    public static function subscribeUserToBossList(
5543
        $studentId,
5544
        $bossList,
5545
        $sendNotification = false
5546
    ) {
5547
        $inserted = 0;
5548
        if (!empty($bossList)) {
5549
            sort($bossList);
5550
            $studentId = (int) $studentId;
5551
            $studentInfo = api_get_user_info($studentId);
5552
5553
            if (empty($studentInfo)) {
5554
                return false;
5555
            }
5556
5557
            $previousBossList = self::getStudentBossList($studentId);
5558
            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
5559
            sort($previousBossList);
5560
5561
            // Boss list is the same, nothing changed.
5562
            if ($bossList == $previousBossList) {
5563
                return false;
5564
            }
5565
5566
            $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5567
            self::removeAllBossFromStudent($studentId);
5568
5569
            foreach ($bossList as $bossId) {
5570
                $bossId = (int) $bossId;
5571
                $sql = "INSERT IGNORE INTO $userRelUserTable (user_id, friend_user_id, relation_type)
5572
                        VALUES ($studentId, $bossId, ".USER_RELATION_TYPE_BOSS.")";
5573
                $insertId = Database::query($sql);
5574
5575
                if ($insertId) {
5576
                    if ($sendNotification) {
5577
                        $name = $studentInfo['complete_name'];
5578
                        $url = api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$studentId;
5579
                        $url = Display::url($url, $url);
5580
                        $subject = sprintf(get_lang('UserXHasBeenAssignedToBoss'), $name);
5581
                        $message = sprintf(get_lang('UserXHasBeenAssignedToBossWithUrlX'), $name, $url);
5582
                        MessageManager::send_message_simple(
5583
                            $bossId,
5584
                            $subject,
5585
                            $message
5586
                        );
5587
                    }
5588
                    $inserted++;
5589
                }
5590
            }
5591
        } else {
5592
            self::removeAllBossFromStudent($studentId);
5593
        }
5594
5595
        return $inserted;
5596
    }
5597
5598
    /**
5599
     * Get users followed by student boss.
5600
     *
5601
     * @param int    $userId
5602
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
5603
     * @param bool   $getOnlyUserId
5604
     * @param bool   $getSql
5605
     * @param bool   $getCount
5606
     * @param int    $from
5607
     * @param int    $numberItems
5608
     * @param int    $column
5609
     * @param string $direction
5610
     * @param int    $active
5611
     * @param string $lastConnectionDate
5612
     *
5613
     * @return array users
5614
     */
5615
    public static function getUsersFollowedByStudentBoss(
5616
        $userId,
5617
        $userStatus = 0,
5618
        $getOnlyUserId = false,
5619
        $getSql = false,
5620
        $getCount = false,
5621
        $from = null,
5622
        $numberItems = null,
5623
        $column = null,
5624
        $direction = null,
5625
        $active = null,
5626
        $lastConnectionDate = null
5627
    ) {
5628
        return self::getUsersFollowedByUser(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getUsersFol...tionDate, STUDENT_BOSS) also could return the type string which is incompatible with the documented return type array.
Loading history...
5629
            $userId,
5630
            $userStatus,
5631
            $getOnlyUserId,
5632
            $getSql,
5633
            $getCount,
5634
            $from,
5635
            $numberItems,
5636
            $column,
5637
            $direction,
5638
            $active,
5639
            $lastConnectionDate,
5640
            STUDENT_BOSS
5641
        );
5642
    }
5643
5644
    /**
5645
     * Get the teacher (users with COURSEMANGER status) list.
5646
     *
5647
     * @return array The list
5648
     */
5649
    public static function getTeachersList()
5650
    {
5651
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
5652
        $resultData = Database::select(
5653
            'user_id, lastname, firstname, username',
5654
            $userTable,
5655
            [
5656
            'where' => [
5657
                'status = ?' => COURSEMANAGER,
5658
            ],
5659
        ]
5660
        );
5661
5662
        foreach ($resultData as &$teacherData) {
5663
            $teacherData['completeName'] = api_get_person_name(
5664
                $teacherData['firstname'],
5665
                $teacherData['lastname']
5666
            );
5667
        }
5668
5669
        return $resultData;
5670
    }
5671
5672
    /**
5673
     * @return array
5674
     */
5675
    public static function getOfficialCodeGrouped()
5676
    {
5677
        $user = Database::get_main_table(TABLE_MAIN_USER);
5678
        $sql = "SELECT DISTINCT official_code
5679
                FROM $user
5680
                GROUP BY official_code";
5681
        $result = Database::query($sql);
5682
        $values = Database::store_result($result, 'ASSOC');
5683
        $result = [];
5684
        foreach ($values as $value) {
5685
            $result[$value['official_code']] = $value['official_code'];
5686
        }
5687
5688
        return $result;
5689
    }
5690
5691
    /**
5692
     * @param string $officialCode
5693
     *
5694
     * @return array
5695
     */
5696
    public static function getUsersByOfficialCode($officialCode)
5697
    {
5698
        $user = Database::get_main_table(TABLE_MAIN_USER);
5699
        $officialCode = Database::escape_string($officialCode);
5700
5701
        $sql = "SELECT DISTINCT id
5702
                FROM $user
5703
                WHERE official_code = '$officialCode'
5704
                ";
5705
        $result = Database::query($sql);
5706
5707
        $users = [];
5708
        while ($row = Database::fetch_array($result)) {
5709
            $users[] = $row['id'];
5710
        }
5711
5712
        return $users;
5713
    }
5714
5715
    /**
5716
     * Calc the expended time (in seconds) by a user in a course.
5717
     *
5718
     * @param int    $userId    The user id
5719
     * @param int    $courseId  The course id
5720
     * @param int    $sessionId Optional. The session id
5721
     * @param string $from      Optional. From date
5722
     * @param string $until     Optional. Until date
5723
     *
5724
     * @return int The time
5725
     */
5726
    public static function getTimeSpentInCourses(
5727
        $userId,
5728
        $courseId,
5729
        $sessionId = 0,
5730
        $from = '',
5731
        $until = ''
5732
    ) {
5733
        $userId = intval($userId);
5734
        $sessionId = intval($sessionId);
5735
        $trackCourseAccessTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5736
        $whereConditions = [
5737
            'user_id = ? ' => $userId,
5738
            'AND c_id = ? ' => $courseId,
5739
            'AND session_id = ? ' => $sessionId,
5740
        ];
5741
5742
        if (!empty($from) && !empty($until)) {
5743
            $whereConditions["AND (login_course_date >= '?' "] = $from;
5744
            $whereConditions["AND logout_course_date <= DATE_ADD('?', INTERVAL 1 DAY)) "] = $until;
5745
        }
5746
5747
        $trackResult = Database::select(
5748
            'SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as total_time',
5749
            $trackCourseAccessTable,
5750
            [
5751
                'where' => $whereConditions,
5752
            ],
5753
            'first'
5754
        );
5755
5756
        if ($trackResult != false) {
5757
            return $trackResult['total_time'] ? $trackResult['total_time'] : 0;
5758
        }
5759
5760
        return 0;
5761
    }
5762
5763
    /**
5764
     * Get the boss user ID from a followed user id.
5765
     *
5766
     * @param $userId
5767
     *
5768
     * @return bool
5769
     */
5770
    public static function getFirstStudentBoss($userId)
5771
    {
5772
        $userId = intval($userId);
5773
        if ($userId > 0) {
5774
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5775
            $row = Database::select(
5776
                'DISTINCT friend_user_id AS boss_id',
5777
                $userRelTable,
5778
                [
5779
                    'where' => [
5780
                        'user_id = ? AND relation_type = ? LIMIT 1' => [
5781
                            $userId,
5782
                            USER_RELATION_TYPE_BOSS,
5783
                        ],
5784
                    ],
5785
                ]
5786
            );
5787
            if (!empty($row)) {
5788
                return $row[0]['boss_id'];
5789
            }
5790
        }
5791
5792
        return false;
5793
    }
5794
5795
    /**
5796
     * Get the boss user ID from a followed user id.
5797
     *
5798
     * @param int $userId student id
5799
     *
5800
     * @return array
5801
     */
5802
    public static function getStudentBossList($userId)
5803
    {
5804
        $userId = (int) $userId;
5805
        if ($userId > 0) {
5806
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5807
            $result = Database::select(
5808
                'DISTINCT friend_user_id AS boss_id',
5809
                $userRelTable,
5810
                [
5811
                    'where' => [
5812
                        'user_id = ? AND relation_type = ? ' => [
5813
                            $userId,
5814
                            USER_RELATION_TYPE_BOSS,
5815
                        ],
5816
                    ],
5817
                ]
5818
            );
5819
5820
            return $result;
5821
        }
5822
5823
        return [];
5824
    }
5825
5826
    /**
5827
     * @param int $bossId
5828
     * @param int $studentId
5829
     *
5830
     * @return bool
5831
     */
5832
    public static function userIsBossOfStudent($bossId, $studentId)
5833
    {
5834
        $result = false;
5835
        $bossList = self::getStudentBossList($studentId);
5836
        if (!empty($bossList)) {
5837
            $bossList = array_column($bossList, 'boss_id');
5838
            if (in_array($bossId, $bossList)) {
5839
                $result = true;
5840
            }
5841
        }
5842
5843
        return $result;
5844
    }
5845
5846
    /**
5847
     * Displays the name of the user and makes the link to the user profile.
5848
     *
5849
     * @param array $userInfo
5850
     *
5851
     * @return string
5852
     */
5853
    public static function getUserProfileLink($userInfo)
5854
    {
5855
        if (isset($userInfo) && isset($userInfo['user_id'])) {
5856
            return Display::url(
5857
                $userInfo['complete_name_with_username'],
5858
                $userInfo['profile_url']
5859
            );
5860
        } else {
5861
            return get_lang('Anonymous');
5862
        }
5863
    }
5864
5865
    /**
5866
     * Displays the name of the user and makes the link to the user profile.
5867
     *
5868
     * @param $userInfo
5869
     *
5870
     * @return string
5871
     */
5872
    public static function getUserProfileLinkWithPicture($userInfo)
5873
    {
5874
        return Display::url(
5875
            Display::img($userInfo['avatar']),
5876
            $userInfo['profile_url']
5877
        );
5878
    }
5879
5880
    /**
5881
     * Get users whose name matches $firstname and $lastname.
5882
     *
5883
     * @param string $firstname Firstname to search
5884
     * @param string $lastname  Lastname to search
5885
     *
5886
     * @return array The user list
5887
     */
5888
    public static function getUsersByName($firstname, $lastname)
5889
    {
5890
        $firstname = Database::escape_string($firstname);
5891
        $lastname = Database::escape_string($lastname);
5892
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
5893
5894
        $sql = <<<SQL
5895
            SELECT id, username, lastname, firstname
5896
            FROM $userTable
5897
            WHERE 
5898
                firstname LIKE '$firstname%' AND
5899
                lastname LIKE '$lastname%'
5900
SQL;
5901
        $result = Database::query($sql);
5902
        $users = [];
5903
        while ($resultData = Database::fetch_object($result)) {
5904
            $users[] = $resultData;
5905
        }
5906
5907
        return $users;
5908
    }
5909
5910
    /**
5911
     * @param int $optionSelected
5912
     *
5913
     * @return string
5914
     */
5915
    public static function getUserSubscriptionTab($optionSelected = 1)
5916
    {
5917
        $allowAdmin = api_get_setting('allow_user_course_subscription_by_course_admin');
5918
        if (($allowAdmin === 'true' && api_is_allowed_to_edit()) ||
5919
            api_is_platform_admin()
5920
        ) {
5921
            $userPath = api_get_path(WEB_CODE_PATH).'user/';
5922
5923
            $headers = [
5924
                [
5925
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.STUDENT,
5926
                    'content' => get_lang('Students'),
5927
                ],
5928
                [
5929
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.COURSEMANAGER,
5930
                    'content' => get_lang('Teachers'),
5931
                ],
5932
                /*[
5933
                    'url' => $userPath.'subscribe_user.php?'.api_get_cidreq(),
5934
                    'content' => get_lang('Students'),
5935
                ],
5936
                [
5937
                    'url' => $userPath.'subscribe_user.php?type=teacher&'.api_get_cidreq(),
5938
                    'content' => get_lang('Teachers'),
5939
                ],*/
5940
                [
5941
                    'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
5942
                    'content' => get_lang('Groups'),
5943
                ],
5944
                [
5945
                    'url' => $userPath.'class.php?'.api_get_cidreq(),
5946
                    'content' => get_lang('Classes'),
5947
                ],
5948
            ];
5949
5950
            return Display::tabsOnlyLink($headers, $optionSelected);
5951
        }
5952
5953
        return '';
5954
    }
5955
5956
    /**
5957
     * Make sure this function is protected because it does NOT check password!
5958
     *
5959
     * This function defines globals.
5960
     *
5961
     * @param int  $userId
5962
     * @param bool $checkIfUserCanLoginAs
5963
     *
5964
     * @return bool
5965
     *
5966
     * @author Evie Embrechts
5967
     * @author Yannick Warnier <[email protected]>
5968
     */
5969
    public static function loginAsUser($userId, $checkIfUserCanLoginAs = true)
5970
    {
5971
        $userId = (int) $userId;
5972
        $userInfo = api_get_user_info($userId);
5973
5974
        // Check if the user is allowed to 'login_as'
5975
        $canLoginAs = true;
5976
        if ($checkIfUserCanLoginAs) {
5977
            $canLoginAs = api_can_login_as($userId);
5978
        }
5979
5980
        if (!$canLoginAs || empty($userInfo)) {
5981
            return false;
5982
        }
5983
5984
        if ($userId) {
5985
            $logInfo = [
5986
                'tool' => 'logout',
5987
                'tool_id' => 0,
5988
                'tool_id_detail' => 0,
5989
                'action' => '',
5990
                'info' => 'Change user (login as)',
5991
            ];
5992
            Event::registerLog($logInfo);
0 ignored issues
show
Bug introduced by
The method registerLog() does not exist on Event. ( Ignorable by Annotation )

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

5992
            Event::/** @scrutinizer ignore-call */ 
5993
                   registerLog($logInfo);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
5993
5994
            // Logout the current user
5995
            self::loginDelete(api_get_user_id());
5996
5997
            Session::erase('_user');
5998
            Session::erase('is_platformAdmin');
5999
            Session::erase('is_allowedCreateCourse');
6000
            Session::erase('_uid');
6001
6002
            // Cleaning session variables
6003
            $_user['firstName'] = $userInfo['firstname'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$_user was never initialized. Although not strictly required by PHP, it is generally a good practice to add $_user = array(); before regardless.
Loading history...
6004
            $_user['lastName'] = $userInfo['lastname'];
6005
            $_user['mail'] = $userInfo['email'];
6006
            $_user['official_code'] = $userInfo['official_code'];
6007
            $_user['picture_uri'] = $userInfo['picture_uri'];
6008
            $_user['user_id'] = $userId;
6009
            $_user['id'] = $userId;
6010
            $_user['status'] = $userInfo['status'];
6011
6012
            // Filling session variables with new data
6013
            Session::write('_uid', $userId);
6014
            Session::write('_user', $userInfo);
6015
            Session::write('is_platformAdmin', (bool) self::is_admin($userId));
6016
            Session::write('is_allowedCreateCourse', $userInfo['status'] == 1);
6017
            // will be useful later to know if the user is actually an admin or not (example reporting)
6018
            Session::write('login_as', true);
6019
6020
            $logInfo = [
6021
                'tool' => 'login',
6022
                'tool_id' => 0,
6023
                'tool_id_detail' => 0,
6024
                'action' => '',
6025
                'info' => $userId,
6026
            ];
6027
            Event::registerLog($logInfo);
6028
6029
            return true;
6030
        }
6031
6032
        return false;
6033
    }
6034
6035
    /**
6036
     * Remove all login records from the track_e_online stats table,
6037
     * for the given user ID.
6038
     *
6039
     * @param int $userId User ID
6040
     */
6041
    public static function loginDelete($userId)
6042
    {
6043
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
6044
        $userId = (int) $userId;
6045
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
6046
        Database::query($query);
6047
    }
6048
6049
    /**
6050
     * Login as first admin user registered in the platform.
6051
     *
6052
     * @return array
6053
     */
6054
    public static function logInAsFirstAdmin()
6055
    {
6056
        $adminList = self::get_all_administrators();
6057
6058
        if (!empty($adminList)) {
6059
            $userInfo = current($adminList);
6060
            if (!empty($userInfo)) {
6061
                $result = self::loginAsUser($userInfo['user_id'], false);
6062
                if ($result && api_is_platform_admin()) {
6063
                    return api_get_user_info();
6064
                }
6065
            }
6066
        }
6067
6068
        return [];
6069
    }
6070
6071
    /**
6072
     * Check if user is teacher of a student based in their courses.
6073
     *
6074
     * @param $teacherId
6075
     * @param $studentId
6076
     *
6077
     * @return array
6078
     */
6079
    public static function getCommonCoursesBetweenTeacherAndStudent($teacherId, $studentId)
6080
    {
6081
        $courses = CourseManager::getCoursesFollowedByUser(
6082
            $teacherId,
6083
            COURSEMANAGER
6084
        );
6085
        if (empty($courses)) {
6086
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
6087
        }
6088
6089
        $coursesFromUser = CourseManager::get_courses_list_by_user_id($studentId);
6090
        if (empty($coursesFromUser)) {
6091
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
6092
        }
6093
6094
        $coursesCodeList = array_column($courses, 'code');
6095
        $coursesCodeFromUserList = array_column($coursesFromUser, 'code');
6096
        $commonCourses = array_intersect($coursesCodeList, $coursesCodeFromUserList);
6097
        $commonCourses = array_filter($commonCourses);
6098
6099
        if (!empty($commonCourses)) {
6100
            return $commonCourses;
6101
        }
6102
6103
        return [];
6104
    }
6105
6106
    /**
6107
     * @param int $teacherId
6108
     * @param int $studentId
6109
     *
6110
     * @return bool
6111
     */
6112
    public static function isTeacherOfStudent($teacherId, $studentId)
6113
    {
6114
        $courses = self::getCommonCoursesBetweenTeacherAndStudent(
6115
            $teacherId,
6116
            $studentId
6117
        );
6118
6119
        if (!empty($courses)) {
6120
            return true;
6121
        }
6122
6123
        return false;
6124
    }
6125
6126
    /**
6127
     * Send user confirmation mail.
6128
     *
6129
     * @param User $user
6130
     *
6131
     * @throws Exception
6132
     */
6133
    public static function sendUserConfirmationMail(User $user)
6134
    {
6135
        $uniqueId = api_get_unique_id();
6136
        $user->setConfirmationToken($uniqueId);
6137
6138
        Database::getManager()->persist($user);
6139
        Database::getManager()->flush();
6140
6141
        $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
6142
6143
        // Check if the user was originally set for an automated subscription to a course or session
6144
        $courseCodeToRedirect = Session::read('course_redirect');
6145
        $sessionToRedirect = Session::read('session_redirect');
6146
        if (!empty($courseCodeToRedirect)) {
6147
            $url .= '&c='.$courseCodeToRedirect;
6148
        }
6149
        if (!empty($sessionToRedirect)) {
6150
            $url .= '&s='.$sessionToRedirect;
6151
        }
6152
        $mailSubject = get_lang('RegistrationConfirmation');
6153
        $mailBody = get_lang('RegistrationConfirmationEmailMessage')
6154
            .PHP_EOL
6155
            .Display::url($url, $url);
6156
6157
        api_mail_html(
6158
            UserManager::formatUserFullName($user),
6159
            $user->getEmail(),
6160
            $mailSubject,
6161
            $mailBody
6162
        );
6163
        Display::addFlash(Display::return_message(get_lang('CheckYourEmailAndFollowInstructions')));
6164
    }
6165
6166
    /**
6167
     * Anonymize a user. Replace personal info by anonymous info.
6168
     *
6169
     * @param int  $userId   User id
6170
     * @param bool $deleteIP Whether to replace the IP address in logs tables by 127.0.0.1 or to leave as is
6171
     *
6172
     * @throws \Exception
6173
     *
6174
     * @return bool
6175
     * @assert (0) === false
6176
     */
6177
    public static function anonymize($userId, $deleteIP = true)
6178
    {
6179
        global $debug;
6180
        if (empty($userId)) {
6181
            return false;
6182
        }
6183
        $em = Database::getManager();
6184
        $user = api_get_user_entity($userId);
6185
        $uniqueId = uniqid('anon', true);
6186
        $user
6187
            ->setFirstname($uniqueId)
6188
            ->setLastname($uniqueId)
6189
            ->setBiography('')
6190
            ->setAddress('')
6191
            ->setCurriculumItems(null)
6192
            ->setDateOfBirth(null)
6193
            ->setCompetences('')
6194
            ->setDiplomas('')
6195
            ->setOpenarea('')
6196
            ->setTeach('')
6197
            ->setProductions(null)
6198
            ->setOpenid('')
6199
            ->setEmailCanonical($uniqueId.'@example.com')
6200
            ->setEmail($uniqueId.'@example.com')
6201
            ->setUsername($uniqueId)
6202
            ->setUsernameCanonical($uniqueId)
6203
            ->setPhone('')
6204
            ->setOfficialCode('')
6205
        ;
6206
6207
        self::deleteUserPicture($userId);
6208
        self::cleanUserRequestsOfRemoval($userId);
6209
6210
        // The IP address is a border-case personal data, as it does
6211
        // not directly allow for personal identification (it is not
6212
        // a completely safe value in most countries - the IP could
6213
        // be used by neighbours and crackers)
6214
        if ($deleteIP) {
6215
            $substitute = '127.0.0.1';
6216
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
6217
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE access_user_id = $userId";
6218
            $res = Database::query($sql);
6219
            if ($res === false && $debug > 0) {
6220
                error_log("Could not anonymize IP address for user $userId ($sql)");
6221
            }
6222
6223
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
6224
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
6225
            $res = Database::query($sql);
6226
            if ($res === false && $debug > 0) {
6227
                error_log("Could not anonymize IP address for user $userId ($sql)");
6228
            }
6229
6230
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6231
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE exe_user_id = $userId";
6232
            $res = Database::query($sql);
6233
            if ($res === false && $debug > 0) {
6234
                error_log("Could not anonymize IP address for user $userId ($sql)");
6235
            }
6236
6237
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
6238
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
6239
            $res = Database::query($sql);
6240
            if ($res === false && $debug > 0) {
6241
                error_log("Could not anonymize IP address for user $userId ($sql)");
6242
            }
6243
6244
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
6245
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
6246
            $res = Database::query($sql);
6247
            if ($res === false && $debug > 0) {
6248
                error_log("Could not anonymize IP address for user $userId ($sql)");
6249
            }
6250
6251
            $table = Database::get_course_table(TABLE_WIKI);
6252
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
6253
            $res = Database::query($sql);
6254
            if ($res === false && $debug > 0) {
6255
                error_log("Could not anonymize IP address for user $userId ($sql)");
6256
            }
6257
6258
            $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
6259
            $sql = "UPDATE $table set ip_address = '$substitute' WHERE sys_insert_user_id = $userId";
6260
            $res = Database::query($sql);
6261
            if ($res === false && $debug > 0) {
6262
                error_log("Could not anonymize IP address for user $userId ($sql)");
6263
            }
6264
6265
            $table = Database::get_course_table(TABLE_WIKI);
6266
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
6267
            $res = Database::query($sql);
6268
            if ($res === false && $debug > 0) {
6269
                error_log("Could not anonymize IP address for user $userId ($sql)");
6270
            }
6271
        }
6272
        $em->persist($user);
6273
        $em->flush($user);
6274
        Event::addEvent(LOG_USER_ANONYMIZE, LOG_USER_ID, $userId);
6275
6276
        return true;
6277
    }
6278
6279
    /**
6280
     * @param int $userId
6281
     *
6282
     * @throws Exception
6283
     *
6284
     * @return string
6285
     */
6286
    public static function anonymizeUserWithVerification($userId)
6287
    {
6288
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
6289
6290
        $message = '';
6291
        if (api_is_platform_admin() ||
6292
            ($allowDelete && api_is_session_admin())
6293
        ) {
6294
            $userToUpdateInfo = api_get_user_info($userId);
6295
            $currentUserId = api_get_user_id();
6296
6297
            if ($userToUpdateInfo &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $userToUpdateInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
6298
                api_global_admin_can_edit_admin($userId, null, $allowDelete)
6299
            ) {
6300
                if ($userId != $currentUserId &&
6301
                    self::anonymize($userId)
6302
                ) {
6303
                    $message = Display::return_message(
6304
                        sprintf(get_lang('UserXAnonymized'), $userToUpdateInfo['complete_name_with_username']),
6305
                        'confirmation'
6306
                    );
6307
                } else {
6308
                    $message = Display::return_message(
6309
                        sprintf(get_lang('CannotAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
6310
                        'error'
6311
                    );
6312
                }
6313
            } else {
6314
                $message = Display::return_message(
6315
                    sprintf(get_lang('NoPermissionToAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
6316
                    'error'
6317
                );
6318
            }
6319
        }
6320
6321
        return $message;
6322
    }
6323
6324
    /**
6325
     * @param int $userId
6326
     *
6327
     * @throws Exception
6328
     *
6329
     * @return string
6330
     */
6331
    public static function deleteUserWithVerification($userId)
6332
    {
6333
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
6334
        $message = Display::return_message(get_lang('CannotDeleteUser'), 'error');
6335
        $userToUpdateInfo = api_get_user_info($userId);
6336
6337
        // User must exist.
6338
        if (empty($userToUpdateInfo)) {
6339
            return $message;
6340
        }
6341
6342
        $currentUserId = api_get_user_id();
6343
6344
        // Cannot delete myself.
6345
        if ($userId == $currentUserId) {
6346
            return $message;
6347
        }
6348
6349
        if (api_is_platform_admin() ||
6350
            ($allowDelete && api_is_session_admin())
6351
        ) {
6352
            if (api_global_admin_can_edit_admin($userId, null, $allowDelete)) {
6353
                if (self::delete_user($userId)) {
6354
                    $message = Display::return_message(
6355
                        get_lang('UserDeleted').': '.$userToUpdateInfo['complete_name_with_username'],
6356
                        'confirmation'
6357
                    );
6358
                } else {
6359
                    $message = Display::return_message(get_lang('CannotDeleteUserBecauseOwnsCourse'), 'error');
6360
                }
6361
            }
6362
        }
6363
6364
        return $message;
6365
    }
6366
6367
    /**
6368
     * @return array
6369
     */
6370
    public static function createDataPrivacyExtraFields()
6371
    {
6372
        self::create_extra_field(
6373
            'request_for_legal_agreement_consent_removal_justification',
6374
            1, //text
6375
            'Request for legal agreement consent removal justification	',
6376
            ''
6377
        );
6378
6379
        self::create_extra_field(
6380
            'request_for_delete_account_justification',
6381
            1, //text
6382
            'Request for delete account justification',
6383
            ''
6384
        );
6385
6386
        $extraFieldId = self::create_extra_field(
6387
            'request_for_legal_agreement_consent_removal',
6388
            1, //text
6389
            'Request for legal agreement consent removal',
6390
            ''
6391
        );
6392
6393
        $extraFieldIdDeleteAccount = self::create_extra_field(
6394
            'request_for_delete_account',
6395
            1, //text
6396
            'Request for delete user account',
6397
            ''
6398
        );
6399
6400
        return [
6401
            'delete_account_extra_field' => $extraFieldIdDeleteAccount,
6402
            'delete_legal' => $extraFieldId,
6403
        ];
6404
    }
6405
6406
    /**
6407
     * @param int $userId
6408
     */
6409
    public static function cleanUserRequestsOfRemoval($userId)
6410
    {
6411
        $userId = (int) $userId;
6412
6413
        $extraFieldValue = new ExtraFieldValue('user');
6414
        $extraFieldsToDelete = [
6415
            'legal_accept',
6416
            'request_for_legal_agreement_consent_removal',
6417
            'request_for_legal_agreement_consent_removal_justification',
6418
            'request_for_delete_account_justification', // just in case delete also this
6419
            'request_for_delete_account',
6420
        ];
6421
6422
        foreach ($extraFieldsToDelete as $variable) {
6423
            $value = $extraFieldValue->get_values_by_handler_and_field_variable(
6424
                $userId,
6425
                $variable
6426
            );
6427
            if ($value && isset($value['id'])) {
6428
                $extraFieldValue->delete($value['id']);
6429
            }
6430
        }
6431
    }
6432
6433
    /**
6434
     * @return int
6435
     */
6436
    public static function getCountActiveUsers()
6437
    {
6438
        $table = Database::get_main_table(TABLE_MAIN_USER);
6439
        $sql = "SELECT count(id) count FROM $table WHERE active = 1 AND status <> ".ANONYMOUS;
6440
        $result = Database::query($sql);
6441
        $row = Database::fetch_array($result);
6442
6443
        return (int) $row['count'];
6444
    }
6445
6446
    
6447
    /**
6448
     * @param array $userInfo
6449
     * @param int   $searchYear
6450
     *
6451
     * @throws Exception
6452
     *
6453
     * @return array
6454
     */
6455
    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
6456
    {
6457
        $timezone = new DateTimeZone(api_get_timezone());
6458
6459
        $sessions = [];
6460
6461
        if (DRH == $userInfo['status']) {
6462
            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
6463
        } elseif (api_is_platform_admin(true)) {
6464
            $sessions = SessionManager::getSessionsForAdmin($userInfo['id']);
6465
        } else {
6466
            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
6467
            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
6468
6469
            foreach ($sessionsByCategory as $sessionsInCategory) {
6470
                $sessions = array_merge($sessions, $sessionsInCategory);
6471
            }
6472
        }
6473
6474
        $sessions = array_map(
6475
            function ($sessionInfo) {
6476
                if (!isset($sessionInfo['session_id'])) {
6477
                    $sessionInfo['session_id'] = $sessionInfo['id'];
6478
                }
6479
                if (!isset($sessionInfo['session_name'])) {
6480
                    $sessionInfo['session_name'] = $sessionInfo['name'];
6481
                }
6482
6483
                return $sessionInfo;
6484
            },
6485
            $sessions
6486
        );
6487
6488
        $calendarSessions = [];
6489
6490
        foreach ($sessions as $sessionInfo) {
6491
            if (!empty($sessionInfo['duration'])) {
6492
                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
6493
                    $sessionInfo['session_id'],
6494
                    $userInfo['id']
6495
                );
6496
6497
                if (empty($courseAccess)) {
6498
                    continue;
6499
                }
6500
6501
                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
6502
                $lastAccessDate = clone $firstAcessDate;
6503
                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
6504
6505
                $firstAccessYear = (int) $firstAcessDate->format('Y');
6506
                $lastAccessYear = (int) $lastAccessDate->format('Y');
6507
6508
                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
6509
                    $calendarSessions[$sessionInfo['session_id']] = [
6510
                        'name' => $sessionInfo['session_name'],
6511
                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
6512
                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
6513
                    ];
6514
                }
6515
6516
                continue;
6517
            }
6518
6519
            $accessStartDate = !empty($sessionInfo['access_start_date'])
6520
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
6521
                : null;
6522
            $accessEndDate = !empty($sessionInfo['access_end_date'])
6523
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
6524
                : null;
6525
            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
6526
            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
6527
6528
            $isValid = false;
6529
6530
            if ($accessStartYear && $accessEndYear) {
6531
                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
6532
                    $isValid = true;
6533
                }
6534
            }
6535
6536
            if ($accessStartYear && !$accessEndYear) {
6537
                if ($accessStartYear == $searchYear) {
6538
                    $isValid = true;
6539
                }
6540
            }
6541
6542
            if (!$accessStartYear && $accessEndYear) {
6543
                if ($accessEndYear == $searchYear) {
6544
                    $isValid = true;
6545
                }
6546
            }
6547
6548
            if ($isValid) {
6549
                $calendarSessions[$sessionInfo['session_id']] = [
6550
                    'name' => $sessionInfo['session_name'],
6551
                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
6552
                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
6553
                ];
6554
            }
6555
        }
6556
6557
        return $calendarSessions;
6558
    }
6559
6560
    /**
6561
     * Get sessions info for planification calendar.
6562
     *
6563
     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
6564
     * @param int   $searchYear
6565
     *
6566
     * @throws Exception
6567
     *
6568
     * @return array
6569
     */
6570
    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
6571
    {
6572
        $timezone = new DateTimeZone(api_get_timezone());
6573
        $calendar = [];
6574
6575
        foreach ($sessionsList as $sessionId => $sessionInfo) {
6576
            $startDate = $sessionInfo['access_start_date']
6577
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
6578
                : null;
6579
            $endDate = $sessionInfo['access_end_date']
6580
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
6581
                : null;
6582
6583
            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
6584
            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
6585
            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
6586
            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
6587
            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
6588
            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
6589
6590
            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
6591
            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
6592
6593
            $calendar[] = [
6594
                'id' => $sessionId,
6595
                'name' => $sessionInfo['name'],
6596
                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
0 ignored issues
show
Bug introduced by
It seems like $startDate can also be of type DateTime; however, parameter $startDate of SessionManager::convertSessionDateToString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

6596
                'human_date' => SessionManager::convertSessionDateToString(/** @scrutinizer ignore-type */ $startDate, $endDate, false, true),
Loading history...
Bug introduced by
It seems like $endDate can also be of type DateTime; however, parameter $endDate of SessionManager::convertSessionDateToString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

6596
                'human_date' => SessionManager::convertSessionDateToString($startDate, /** @scrutinizer ignore-type */ $endDate, false, true),
Loading history...
6597
                'start_in_last_year' => $startYear < $searchYear,
6598
                'end_in_next_year' => $endYear > $searchYear,
6599
                'no_start' => !$startWeek,
6600
                'no_end' => !$endWeek,
6601
                'start' => $start,
6602
                'duration' => $duration > 0 ? $duration : 1,
6603
            ];
6604
        }
6605
6606
        usort(
6607
            $calendar,
6608
            function ($sA, $sB) {
6609
                if ($sA['start'] == $sB['start']) {
6610
                    return 0;
6611
                }
6612
6613
                if ($sA['start'] < $sB['start']) {
6614
                    return -1;
6615
                }
6616
6617
                return 1;
6618
            }
6619
        );
6620
6621
        return $calendar;
6622
    }
6623
6624
6625
    /**
6626
     * Return the user's full name. Optionally with the username.
6627
     *
6628
     * @param User $user
6629
     * @param bool $includeUsername Optional. By default username is not included.
6630
     *
6631
     * @return string
6632
     */
6633
    public static function formatUserFullName(User $user, $includeUsername = false): string
6634
    {
6635
        $fullName = api_get_person_name($user->getFirstname(), $user->getLastname());
6636
6637
        if ($includeUsername && api_get_setting('profile.hide_username_with_complete_name') === 'false') {
6638
            $username = $user->getUsername();
6639
6640
            return "$fullName ($username)";
6641
        }
6642
6643
        return $fullName;
6644
    }
6645
6646
    /**
6647
     * @return EncoderFactory
6648
     */
6649
    private static function getEncoderFactory()
6650
    {
6651
        $encryption = self::getPasswordEncryption();
6652
        $encoders = [
6653
            'Chamilo\\UserBundle\\Entity\\User' => new \Chamilo\UserBundle\Security\Encoder($encryption),
0 ignored issues
show
Bug introduced by
It seems like $encryption can also be of type boolean; however, parameter $passwordEncrypt of Chamilo\UserBundle\Security\Encoder::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

6653
            'Chamilo\\UserBundle\\Entity\\User' => new \Chamilo\UserBundle\Security\Encoder(/** @scrutinizer ignore-type */ $encryption),
Loading history...
6654
        ];
6655
6656
        $encoderFactory = new EncoderFactory($encoders);
6657
6658
        return $encoderFactory;
6659
    }
6660
6661
    /**
6662
     * @param User $user
6663
     *
6664
     * @return \Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface
6665
     */
6666
    private static function getEncoder(User $user)
6667
    {
6668
        $encoderFactory = self::getEncoderFactory();
6669
6670
        return $encoderFactory->getEncoder($user);
6671
    }
6672
6673
    /**
6674
     * Disables or enables a user.
6675
     *
6676
     * @param int $user_id
6677
     * @param int $active  Enable or disable
6678
     *
6679
     * @return bool True on success, false on failure
6680
     * @assert (-1,0) === false
6681
     * @assert (1,1) === true
6682
     */
6683
    private static function change_active_state($user_id, $active)
6684
    {
6685
        if (strval(intval($user_id)) != $user_id) {
6686
            return false;
6687
        }
6688
        if ($user_id < 1) {
6689
            return false;
6690
        }
6691
        $user_id = intval($user_id);
6692
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6693
        $sql = "UPDATE $table_user SET active = '$active' WHERE id = $user_id";
6694
        $r = Database::query($sql);
6695
        $ev = LOG_USER_DISABLE;
6696
        if ($active == 1) {
6697
            $ev = LOG_USER_ENABLE;
6698
        }
6699
        if ($r !== false) {
6700
            Event::addEvent($ev, LOG_USER_ID, $user_id);
6701
        }
6702
6703
        return $r;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $r returns the type Doctrine\DBAL\Driver\Statement which is incompatible with the documented return type boolean.
Loading history...
6704
    }
6705
6706
    /**
6707
     * Get either a Gravatar URL or complete image tag for a specified email address.
6708
     *
6709
     * @param string $email The email address
6710
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
6711
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
6712
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
6713
     * @param bool   $img   True to return a complete IMG tag False for just the URL
6714
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
6715
     *
6716
     * @return string containing either just a URL or a complete image tag
6717
     * @source http://gravatar.com/site/implement/images/php/
6718
     */
6719
    private static function getGravatar(
6720
        $email,
6721
        $s = 80,
6722
        $d = 'mm',
6723
        $r = 'g',
6724
        $img = false,
6725
        $atts = []
6726
    ) {
6727
        $url = 'http://www.gravatar.com/avatar/';
6728
        if (!empty($_SERVER['HTTPS'])) {
6729
            $url = 'https://secure.gravatar.com/avatar/';
6730
        }
6731
        $url .= md5(strtolower(trim($email)));
6732
        $url .= "?s=$s&d=$d&r=$r";
6733
        if ($img) {
6734
            $url = '<img src="'.$url.'"';
6735
            foreach ($atts as $key => $val) {
6736
                $url .= ' '.$key.'="'.$val.'"';
6737
            }
6738
            $url .= ' />';
6739
        }
6740
6741
        return $url;
6742
    }
6743
}
6744