UserManager::update_user()   F
last analyzed

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 211
Code Lines 124

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 124
nc 29978
nop 22
dl 0
loc 211
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

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

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

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

Loading history...
61
62
        return $hasher->isPasswordValid($user, $plainPassword);
63
    }
64
65
    /**
66
     * @param int    $userId
67
     * @param string $password
68
     */
69
    public static function updatePassword($userId, $password)
70
    {
71
        $user = api_get_user_entity($userId);
72
        $user->setPlainPassword($password);
73
        Container::getUserRepository()->updateUser($user, true);
74
    }
75
76
    /**
77
     * Creates a new user for the platform.
78
     *
79
     * @param string        $firstName
80
     * @param string        $lastName
81
     * @param int           $status                  (1 for course tutor, 5 for student, 6 for anonymous)
82
     * @param string        $email
83
     * @param string        $loginName
84
     * @param string        $password
85
     * @param string        $officialCode           Any official code (optional)
86
     * @param string        $language                User language    (optional)
87
     * @param string        $phone                   Phone number    (optional)
88
     * @param string        $pictureUri             Picture URI        (optional)
89
     * @param string        $authSources              Authentication source (defaults to 'platform', dependind on constant)
90
     * @param string $expirationDate          Account expiration date (optional, defaults to null)
91
     * @param int           $active                  Whether the account is enabled or disabled by default
92
     * @param int           $hrDeptId              The department of HR in which the user is registered (defaults to 0)
93
     * @param array         $extra                   Extra fields (labels must be prefixed by "extra_")
94
     * @param string        $encryptMethod          Used if password is given encrypted. Set to an empty string by default
95
     * @param bool          $sendMail
96
     * @param bool          $isAdmin
97
     * @param string        $address
98
     * @param bool          $sendEmailToAllAdmins
99
     * @param FormValidator $form
100
     * @param int           $creatorId
101
     * @param array         $emailTemplate
102
     * @param string        $redirectToURLAfterLogin
103
     *
104
     * @return mixed new user id - if the new user creation succeeds, false otherwise
105
     * @desc The function tries to retrieve user id from the session.
106
     * If it exists, the current user id is the creator id. If a problem arises,
107
     * @assert ('Sam','Gamegie',5,'[email protected]','jo','jo') > 1
108
     * @assert ('Pippin','Took',null,null,'jo','jo') === false
109
     *@author Hugues Peeters <[email protected]>,
110
     * @author Roan Embrechts <[email protected]>
111
     *
112
     */
113
    public static function create_user(
114
        $firstName,
115
        $lastName,
116
        $status,
117
        $email,
118
        $loginName,
119
        $password,
120
        $officialCode = '',
121
        $language = '',
122
        $phone = '',
123
        $pictureUri = '',
124
        ?array $authSources = [],
125
        $expirationDate = null,
126
        $active = 1,
127
        $hrDeptId = 0,
128
        $extra = [],
129
        $encryptMethod = '',
130
        $sendMail = false,
131
        $isAdmin = false,
132
        $address = '',
133
        $sendEmailToAllAdmins = false,
134
        $form = null,
135
        $creatorId = 0,
136
        $emailTemplate = [],
137
        $redirectToURLAfterLogin = ''
138
    ) {
139
        $authSources = !empty($authSources) ? $authSources : [UserAuthSource::PLATFORM];
140
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
141
142
        if (0 === $creatorId) {
143
            Display::addFlash(
144
                Display::return_message(get_lang('A user creator is required'))
145
            );
146
147
            return false;
148
        }
149
150
        $creatorInfo = api_get_user_info($creatorId);
151
        $creatorEmail = $creatorInfo['email'] ?? '';
152
153
        // First check if the login exists.
154
        if (!Container::getUserRepository()->isUsernameAvailable($loginName)) {
155
            Display::addFlash(
156
                Display::return_message(get_lang('This login is already taken !'))
157
            );
158
159
            return false;
160
        }
161
162
        Container::getEventDispatcher()
163
            ->dispatch(
164
                new UserCreatedEvent([], AbstractEvent::TYPE_PRE),
165
                Events::USER_CREATED
166
            )
167
        ;
168
169
        $original_password = $password;
170
171
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
172
        $access_url_id = $accessUrl->getId();
173
174
        $hostingLimitUsers = get_hosting_limit($access_url_id, 'users');
175
176
        if ($hostingLimitUsers !== null && $hostingLimitUsers > 0) {
177
            $num = self::get_number_of_users();
178
            if ($num >= $hostingLimitUsers) {
179
                api_warn_hosting_contact('users');
180
                Display::addFlash(
181
                    Display::return_message(
182
                        get_lang('Sorry, this installation has a users limit, which has now been reached. To increase the number of users allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.'),
183
                        'warning'
184
                    )
185
                );
186
187
                return false;
188
            }
189
        }
190
191
        if (1 === $status) {
192
            $hostingLimitTeachers = get_hosting_limit($access_url_id, 'teachers');
193
194
            if ($hostingLimitTeachers !== null && $hostingLimitTeachers > 0) {
195
                $num = self::get_number_of_users(1);
196
                if ($num >= $hostingLimitTeachers) {
197
                    Display::addFlash(
198
                        Display::return_message(
199
                            get_lang('Sorry, this installation has a teachers limit, which has now been reached. To increase the number of teachers allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.'),
200
                            'warning'
201
                        )
202
                    );
203
                    api_warn_hosting_contact('hosting_limit_teachers');
204
205
                    return false;
206
                }
207
            }
208
        }
209
210
        if (empty($password)) {
211
            if (in_array(UserAuthSource::PLATFORM, $authSources)) {
212
                Display::addFlash(
213
                    Display::return_message(
214
                        get_lang('Required field').': '.get_lang('Password'),
215
                        'warning'
216
                    )
217
                );
218
219
                return false;
220
            }
221
            // Use authSource as pseudo-password (validated by external auth).
222
            $password = $authSources;
223
        }
224
225
        // Checking the user language
226
        $languages = api_get_languages();
227
        if (!in_array($language, array_keys($languages), true)) {
228
            $language = 'en_US'; // default
229
        }
230
231
        $now = new DateTime();
232
        if (empty($expirationDate) || '0000-00-00 00:00:00' === $expirationDate) {
233
            $expirationDate = null;
234
        } else {
235
            $expirationDate = api_get_utc_datetime($expirationDate, true, true);
236
        }
237
238
        $repo = Container::getUserRepository();
239
        $user = $repo->createUser()
240
            ->setLastname($lastName)
241
            ->setFirstname($firstName)
242
            ->setUsername($loginName)
243
            ->setStatus($status)
244
            ->setPlainPassword($password)
245
            ->setEmail($email)
246
            ->setOfficialCode($officialCode)
247
            ->setCreatorId($creatorId)
248
            ->setPhone($phone)
249
            ->setAddress($address)
250
            ->setLocale($language)
251
            ->setHrDeptId($hrDeptId)
252
            ->setActive($active)
253
            ->setTimezone(api_get_timezone())
254
        ;
255
256
        foreach ($authSources as $authSource) {
257
            $user->addAuthSourceByAuthentication($authSource, $accessUrl);
258
        }
259
260
        if (null !== $expirationDate) {
261
            $user->setExpirationDate($expirationDate);
262
        }
263
264
        $user->setRoleFromStatus($status);
265
        $dobStr = $_POST['date_of_birth'] ?? null;
266
        if ($dobStr) {
267
            $dob = \DateTime::createFromFormat('Y-m-d', $dobStr)
268
                ?: \DateTime::createFromFormat('d/m/Y', $dobStr)
269
                    ?: \DateTime::createFromFormat('d-m-Y', $dobStr);
270
271
            if ($dob instanceof \DateTime) {
0 ignored issues
show
introduced by
$dob is always a sub-type of DateTime.
Loading history...
272
                $user->setDateOfBirth($dob);
273
            }
274
        }
275
276
        $repo->updateUser($user, true);
277
        $userId = $user->getId();
278
279
        if (empty($userId)) {
280
            Display::addFlash(
281
                Display::return_message(get_lang('There happened an unknown error. Please contact the platform administrator.'))
282
            );
283
            return false;
284
        }
285
286
        $userLocale = $user->getLocale();
287
        if ($isAdmin) {
288
            self::addUserAsAdmin($user);
289
        }
290
291
        if (api_get_multiple_access_url()) {
292
            UrlManager::add_user_to_url($userId, api_get_current_access_url_id());
293
        } else {
294
            UrlManager::add_user_to_url($userId, 1);
295
        }
296
297
        if (is_array($extra) && count($extra) > 0) {
298
            $extra['item_id'] = $userId;
299
            $userFieldValue = new ExtraFieldValue('user');
300
            /* Force saving of extra fields (otherwise fields not visible are ignored) */
301
            $userFieldValue->saveFieldValues(
302
                $extra,
303
                true,
304
                false,
305
                [],
306
                [],
307
                true
308
            );
309
        } else {
310
            // Create notify settings by default
311
            self::update_extra_field_value($userId, 'mail_notify_invitation', '1');
312
            self::update_extra_field_value($userId, 'mail_notify_message', '1');
313
            self::update_extra_field_value($userId, 'mail_notify_group_message', '1');
314
        }
315
316
        self::update_extra_field_value($userId, 'already_logged_in', 'false');
317
318
        if (!empty($redirectToURLAfterLogin) && ('true' === api_get_setting('workflows.plugin_redirection_enabled'))) {
319
            RedirectionPlugin::insert($userId, $redirectToURLAfterLogin);
320
        }
321
322
        if (!empty($email) && $sendMail) {
323
            $recipient_name = api_get_person_name(
324
                $firstName,
325
                $lastName,
326
                null,
327
                PERSON_NAME_EMAIL_ADDRESS
328
            );
329
            $tpl = Container::getTwig();
330
331
            $emailSubject = $tpl->render(
332
                '@ChamiloCore/Mailer/Legacy/subject_registration_platform.html.twig',
333
                ['locale' => $userLocale]
334
            );
335
336
            $sender_name = api_get_setting('mail.mailer_from_name');
337
            $email_admin = api_get_setting('mail.mailer_from_email');
338
339
            $url = api_get_path(WEB_PATH);
340
            if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

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

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

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

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

Loading history...
341
                $access_url_id = api_get_current_access_url_id();
342
                if (-1 != $access_url_id) {
343
                    $urlInfo = api_get_access_url($access_url_id);
344
                    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...
345
                        $url = $urlInfo['url'];
346
                    }
347
                }
348
            }
349
350
            // Variables for the default template
351
            $params = [
352
                'complete_name' => stripslashes(api_get_person_name($firstName, $lastName)),
353
                'login_name' => $loginName,
354
                'original_password' => stripslashes((string) $original_password),
355
                'mailWebPath' => $url,
356
                'new_user' => $user,
357
                'search_link' => $url,
358
                'locale' => $userLocale,
359
            ];
360
361
            if ('true' === api_get_setting('session.allow_search_diagnostic')) {
362
                $urlSearch = api_get_path(WEB_CODE_PATH).'search/search.php';
363
                $linkSearch = Display::url($urlSearch, $urlSearch);
364
                $params['search_link'] = $linkSearch;
365
            }
366
367
            // Default Twig bodies: one for email (with password) and one for inbox (without password)
368
            $emailBodyEmail = $tpl->render(
369
                '@ChamiloCore/Mailer/Legacy/content_registration_platform.html.twig',
370
                $params + ['show_password' => true]
371
            );
372
            $emailBodyInbox = $tpl->render(
373
                '@ChamiloCore/Mailer/Legacy/content_registration_platform.html.twig',
374
                $params + ['show_password' => false]
375
            );
376
377
            $userInfo = api_get_user_info($userId);
378
            $mailTemplateManager = new MailTemplateManager();
379
380
            $emailBodyTemplate = '';
381
            if (!empty($emailTemplate)) {
382
                if (isset($emailTemplate['content_registration_platform.tpl']) &&
383
                    !empty($emailTemplate['content_registration_platform.tpl'])
384
                ) {
385
                    $emailBodyTemplate = $mailTemplateManager->parseTemplate(
386
                        $emailTemplate['content_registration_platform.tpl'],
387
                        $userInfo
388
                    );
389
                }
390
            }
391
392
            // If a custom email template is provided, use it only for the email (inbox copy stays sanitized Twig)
393
            if (!empty($emailBodyTemplate)) {
394
                $emailBodyEmail = $emailBodyTemplate;
395
            }
396
397
            $twoEmail = ('true' === api_get_setting('mail.send_two_inscription_confirmation_mail'));
398
399
            if (true === $twoEmail) {
400
                // Keep existing 2-email behavior (no structural changes)
401
                $emailBody = $tpl->render('@ChamiloCore/Mailer/Legacy/new_user_first_email_confirmation.html.twig');
402
                if (!empty($emailBodyTemplate) &&
403
                    isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
404
                    !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
405
                ) {
406
                    $emailBody = $mailTemplateManager->parseTemplate(
407
                        $emailTemplate['new_user_first_email_confirmation.tpl'],
408
                        $userInfo
409
                    );
410
                }
411
412
                api_mail_html(
413
                    $recipient_name,
414
                    $email,
415
                    $emailSubject,
416
                    $emailBody,
417
                    $sender_name,
418
                    $email_admin,
419
                    [],
420
                    [],
421
                    false,
422
                    [],
423
                    $creatorEmail
424
                );
425
426
                $emailBody = $tpl->render('@ChamiloCore/Mailer/Legacy/new_user_second_email_confirmation.html.twig');
427
                if (!empty($emailBodyTemplate) &&
428
                    isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
429
                    !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
430
                ) {
431
                    $emailBody = $mailTemplateManager->parseTemplate(
432
                        $emailTemplate['new_user_second_email_confirmation.tpl'],
433
                        $userInfo
434
                    );
435
                }
436
437
                api_mail_html(
438
                    $recipient_name,
439
                    $email,
440
                    $emailSubject,
441
                    $emailBody,
442
                    $sender_name,
443
                    $email_admin,
444
                    [],
445
                    [],
446
                    false,
447
                    [],
448
                    $creatorEmail
449
                );
450
451
                // Optional inbox copy (sanitized, and no email notification)
452
                $sendToInbox = ('true' === api_get_setting('registration.send_inscription_msg_to_inbox'));
453
                if ($sendToInbox) {
454
                    $adminList = self::get_all_administrators();
455
                    $senderId = 1;
456
                    if (!empty($adminList)) {
457
                        $adminInfo = current($adminList);
458
                        $senderId = $adminInfo['user_id'];
459
                    }
460
461
                    MessageManager::send_message_simple(
462
                        $userId,
463
                        $emailSubject,
464
                        $emailBodyInbox,
465
                        $senderId,
466
                        false,
467
                        false,
468
                        false,
469
                        [],
470
                        false
471
                    );
472
                }
473
            } else {
474
                // 1) Always send the registration email
475
                api_mail_html(
476
                    $recipient_name,
477
                    $email,
478
                    $emailSubject,
479
                    $emailBodyEmail,
480
                    $sender_name,
481
                    $email_admin,
482
                    [],
483
                    [],
484
                    false,
485
                    [],
486
                    $creatorEmail
487
                );
488
489
                // 2) Optionally copy to Chamilo inbox (sanitized, no email notification)
490
                $sendToInbox = ('true' === api_get_setting('registration.send_inscription_msg_to_inbox'));
491
                if ($sendToInbox) {
492
                    $adminList = self::get_all_administrators();
493
                    $senderId = 1;
494
                    if (!empty($adminList)) {
495
                        $adminInfo = current($adminList);
496
                        $senderId = $adminInfo['user_id'];
497
                    }
498
499
                    MessageManager::send_message_simple(
500
                        $userId,
501
                        $emailSubject,
502
                        $emailBodyInbox,
503
                        $senderId,
504
                        false,
505
                        false,
506
                        false,
507
                        [],
508
                        false
509
                    );
510
                }
511
            }
512
513
            // Admin notifications (keep behavior; use the email version as the reference body)
514
            $notification = api_get_setting('profile.send_notification_when_user_added', true);
515
            if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {
516
                foreach ($notification['admins'] as $adminId) {
517
                    $emailSubjectToAdmin = get_lang('The user has been added').': '.
518
                        api_get_person_name($firstName, $lastName);
519
520
                    MessageManager::send_message_simple(
521
                        $adminId,
522
                        $emailSubjectToAdmin,
523
                        $emailBodyEmail,
524
                        $userId
525
                    );
526
                }
527
            }
528
529
            /** @var TranslatorInterface $translator */
530
            $translator   = Container::$container->get('translator');
531
            $currentLocale = $translator->getLocale();
532
533
            $visibleCoreFields = [];
534
            $visibleExtraVars  = [];
535
536
            if ($form) {
537
                $formNames = [];
538
                if (property_exists($form, '_elements') && is_array($form->_elements)) {
539
                    foreach ($form->_elements as $el) {
540
                        if (is_object($el) && method_exists($el, 'getName')) {
541
                            $formNames[] = (string) $el->getName();
542
                        }
543
                    }
544
                }
545
546
                $cfg = api_get_setting('registration.allow_fields_inscription', true);
547
                $cfgCore = (is_array($cfg) && isset($cfg['fields']) && is_array($cfg['fields']))
548
                    ? $cfg['fields']
549
                    : [];
550
551
                $visibleCoreFields = array_values(array_intersect($cfgCore, $formNames));
552
553
                foreach ($formNames as $n) {
554
                    if (strpos($n, 'extra_') === 0) {
555
                        $visibleExtraVars[] = substr($n, 6);
556
                    }
557
                }
558
            }
559
560
            if ($sendEmailToAllAdmins) {
561
                $adminList = self::get_all_administrators();
562
                $url = api_get_path(WEB_CODE_PATH).'admin/user_information.php?user_id='.$user->getId();
563
564
                foreach ($adminList as $adminId => $adminData) {
565
                    $adminLocale = $adminData['locale'] ?? 'en_US';
566
                    $translator->setLocale($adminLocale);
567
568
                    $profileHtml      = self::renderRegistrationProfileHtml(
569
                        $user,
570
                        $extra ?? [],
571
                        $adminLocale,
572
                        $visibleCoreFields,
573
                        $visibleExtraVars
574
                    );
575
                    $userLanguageName = self::resolveLanguageName($user->getLocale());
576
577
                    $paramsAdmin = [
578
                        'complete_name' => stripslashes(api_get_person_name($firstName, $lastName)),
579
                        'user_added'    => $user,
580
                        'link'          => Display::url($url, $url),
581
                        'form'          => $profileHtml,
582
                        'user_language' => $userLanguageName,
583
                    ];
584
585
                    $emailBodyAdmin = $tpl->render(
586
                        '@ChamiloCore/Mailer/Legacy/content_registration_platform_to_admin.html.twig',
587
                        $paramsAdmin
588
                    );
589
590
                    $subject = get_lang('The user has been added', $adminLocale);
591
                    MessageManager::send_message_simple(
592
                        $adminId,
593
                        $subject,
594
                        $emailBodyAdmin,
595
                        $userId
596
                    );
597
598
                    $translator->setLocale($currentLocale);
599
                }
600
            }
601
        }
602
603
        Container::getEventDispatcher()
604
            ->dispatch(
605
                new UserCreatedEvent(
606
                    ['return' => $user, 'originalPassword' => $original_password],
607
                    AbstractEvent::TYPE_POST
608
                ),
609
                Events::USER_CREATED
610
            )
611
        ;
612
613
        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

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

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

1202
            $emailsubject = '['./** @scrutinizer ignore-type */ api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
Loading history...
1203
            $sender_name = api_get_person_name(
1204
                api_get_setting('administratorName'),
1205
                api_get_setting('administratorSurname'),
1206
                null,
1207
                PERSON_NAME_EMAIL_ADDRESS
1208
            );
1209
            $email_admin = api_get_setting('emailAdministrator');
1210
            $url = api_get_path(WEB_PATH);
1211
            if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

Loading history...
1609
            if ($idCampus) {
1610
                $urlId = $idCampus;
1611
            } else {
1612
                $urlId = api_get_current_access_url_id();
1613
            }
1614
            $sql .= " INNER JOIN $userUrlTable url_user
1615
                      ON (user.id = url_user.user_id)
1616
                      WHERE url_user.access_url_id = $urlId";
1617
        } else {
1618
            $sql .= " WHERE 1=1 ";
1619
        }
1620
1621
        if (count($conditions) > 0) {
1622
            foreach ($conditions as $field => $value) {
1623
                $field = Database::escape_string($field);
1624
                $value = Database::escape_string($value);
1625
                $sql .= " AND $field = '$value'";
1626
            }
1627
        }
1628
1629
        if (count($order_by) > 0) {
1630
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1631
        }
1632
1633
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1634
            $limit_from = (int) $limit_from;
1635
            $limit_to = (int) $limit_to;
1636
            $sql .= " LIMIT $limit_from, $limit_to";
1637
        }
1638
        $sql_result = Database::query($sql);
1639
        while ($result = Database::fetch_array($sql_result)) {
1640
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1641
            $return_array[] = $result;
1642
        }
1643
1644
        return $return_array;
1645
    }
1646
1647
    public static function getUserListExtraConditions(
1648
        $conditions = [],
1649
        $order_by = [],
1650
        $limit_from = false,
1651
        $limit_to = false,
1652
        $idCampus = null,
1653
        $extraConditions = '',
1654
        $getCount = false
1655
    ) {
1656
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1657
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1658
        $return_array = [];
1659
        $sql = "SELECT user.*, user.id as user_id FROM $user_table user ";
1660
1661
        if ($getCount) {
1662
            $sql = "SELECT count(user.id) count FROM $user_table user ";
1663
        }
1664
1665
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

    return false;
}

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

Loading history...
2062
        $del_image = Display::returnIconPath('delete.png');
2063
        $add_image = Display::returnIconPath('archive.png');
2064
        $del_text = get_lang('Delete');
2065
        $production_list = '';
2066
        if (count($productions) > 0) {
2067
            $production_list = '<div class="files-production"><ul id="productions">';
2068
            foreach ($productions as $file) {
2069
                $production_list .= '<li>
2070
                    <img src="'.$add_image.'" />
2071
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2072
                        '.htmlentities($file).'
2073
                    </a>';
2074
                if ($showDelete) {
2075
                    $production_list .= '&nbsp;&nbsp;
2076
                        <input
2077
                            style="width:16px;"
2078
                            type="image"
2079
                            name="remove_production['.urlencode($file).']"
2080
                            src="'.$del_image.'"
2081
                            alt="'.$del_text.'"
2082
                            title="'.$del_text.' '.htmlentities($file).'"
2083
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2084
                }
2085
            }
2086
            $production_list .= '</ul></div>';
2087
        }
2088
2089
        return $production_list;
2090
    }
2091
2092
    /**
2093
     * Returns an array with the user's productions.
2094
     *
2095
     * @param int $user_id User id
2096
     *
2097
     * @return array An array containing the user's productions
2098
     */
2099
    public static function get_user_productions($user_id)
2100
    {
2101
        return [];
2102
2103
        $production_repository = self::getUserPathById($user_id, 'system');
0 ignored issues
show
Unused Code introduced by
$production_repository =...yId($user_id, 'system') is not reachable.

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

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

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

    return false;
}

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

Loading history...
2104
        $productions = [];
2105
2106
        if (is_dir($production_repository)) {
2107
            $handle = opendir($production_repository);
2108
            while ($file = readdir($handle)) {
2109
                if ('.' == $file ||
2110
                    '..' == $file ||
2111
                    '.htaccess' == $file ||
2112
                    is_dir($production_repository.$file)
2113
                ) {
2114
                    // skip current/parent directory and .htaccess
2115
                    continue;
2116
                }
2117
2118
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
2119
                    // User's photos should not be listed as productions.
2120
                    continue;
2121
                }
2122
                $productions[] = $file;
2123
            }
2124
        }
2125
2126
        return $productions;
2127
    }
2128
2129
    /**
2130
     * Remove a user production.
2131
     *
2132
     * @param int    $user_id    User id
2133
     * @param string $production The production to remove
2134
     *
2135
     * @return bool
2136
     */
2137
    public static function remove_user_production($user_id, $production)
2138
    {
2139
        throw new Exception('remove_user_production');
2140
        /*$production_path = self::get_user_picture_path_by_id($user_id, 'system');
2141
        $production_file = $production_path['dir'].$production;
2142
        if (is_file($production_file)) {
2143
            unlink($production_file);
2144
2145
            return true;
2146
        }
2147
2148
        return false;*/
2149
    }
2150
2151
    /**
2152
     * Update an extra field value for a given user.
2153
     *
2154
     * @param int    $userId   User ID
2155
     * @param string $variable Field variable name
2156
     * @param string $value    Field value
2157
     *
2158
     * @return bool true if field updated, false otherwise
2159
     */
2160
    public static function update_extra_field_value($userId, $variable, $value = '')
2161
    {
2162
        $extraFieldValue = new ExtraFieldValue('user');
2163
        $params = [
2164
            'item_id' => $userId,
2165
            'variable' => $variable,
2166
            'field_value' => $value,
2167
        ];
2168
2169
        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...
2170
    }
2171
2172
    /**
2173
     * Get an array of extra fields with field details (type, default value and options).
2174
     *
2175
     * @param    int    Offset (from which row)
2176
     * @param    int    Number of items
2177
     * @param    int    Column on which sorting is made
2178
     * @param    string    Sorting direction
2179
     * @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...
2180
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
2181
     *
2182
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
2183
     */
2184
    public static function get_extra_fields(
2185
        $from = 0,
2186
        $number_of_items = 0,
2187
        $column = 5,
2188
        $direction = 'ASC',
2189
        $all_visibility = true,
2190
        $field_filter = null
2191
    ) {
2192
        $fields = [];
2193
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2194
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
2195
        $columns = [
2196
            'id',
2197
            'variable',
2198
            'value_type',
2199
            'display_text',
2200
            'default_value',
2201
            'field_order',
2202
            'filter',
2203
        ];
2204
        $column = (int) $column;
2205
        $sort_direction = '';
2206
        if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
2207
            $sort_direction = strtoupper($direction);
2208
        }
2209
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
2210
        $sqlf = "SELECT * FROM $t_uf WHERE item_type = $extraFieldType ";
2211
        if (!$all_visibility) {
2212
            $sqlf .= " AND visible_to_self = 1 ";
2213
        }
2214
        if (!is_null($field_filter)) {
2215
            $field_filter = (int) $field_filter;
2216
            $sqlf .= " AND filter = $field_filter ";
2217
        }
2218
        $sqlf .= " ORDER BY `".$columns[$column]."` $sort_direction ";
2219
        if (0 != $number_of_items) {
2220
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
2221
        }
2222
        $resf = Database::query($sqlf);
2223
        if (Database::num_rows($resf) > 0) {
2224
            while ($rowf = Database::fetch_array($resf)) {
2225
                $fields[$rowf['id']] = [
2226
                    0 => $rowf['id'],
2227
                    1 => $rowf['variable'],
2228
                    2 => $rowf['value_type'],
2229
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
2230
                    4 => $rowf['default_value'],
2231
                    5 => $rowf['field_order'],
2232
                    6 => $rowf['visible_to_self'],
2233
                    7 => $rowf['changeable'],
2234
                    8 => $rowf['filter'],
2235
                    9 => [],
2236
                    10 => '<a name="'.$rowf['id'].'"></a>',
2237
                ];
2238
2239
                $sqlo = "SELECT * FROM $t_ufo
2240
                         WHERE field_id = ".$rowf['id']."
2241
                         ORDER BY option_order ASC";
2242
                $reso = Database::query($sqlo);
2243
                if (Database::num_rows($reso) > 0) {
2244
                    while ($rowo = Database::fetch_array($reso)) {
2245
                        $fields[$rowf['id']][9][$rowo['id']] = [
2246
                            0 => $rowo['id'],
2247
                            1 => $rowo['option_value'],
2248
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
2249
                            3 => $rowo['option_order'],
2250
                        ];
2251
                    }
2252
                }
2253
            }
2254
        }
2255
2256
        return $fields;
2257
    }
2258
2259
    /**
2260
     * Creates a new extra field.
2261
     *
2262
     * @param string $variable    Field's internal variable name
2263
     * @param int    $fieldType   Field's type
2264
     * @param string $displayText Field's language var name
2265
     * @param string $default     Field's default value
2266
     *
2267
     * @return int
2268
     */
2269
    public static function create_extra_field(
2270
        $variable,
2271
        $valueType,
2272
        $displayText,
2273
        $default
2274
    ) {
2275
        $extraField = new ExtraField('user');
2276
        $params = [
2277
            'variable' => $variable,
2278
            'value_type' => $valueType,
2279
            'display_text' => $displayText,
2280
            'default_value' => $default,
2281
        ];
2282
2283
        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...
2284
    }
2285
2286
    /**
2287
     * Check if a field is available.
2288
     *
2289
     * @param string $variable
2290
     *
2291
     * @return bool
2292
     */
2293
    public static function is_extra_field_available($variable)
2294
    {
2295
        $extraField = new ExtraField('user');
2296
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
2297
2298
        return !empty($data) ? true : false;
2299
    }
2300
2301
    /**
2302
     * Gets user extra fields data.
2303
     *
2304
     * @param    int    User ID
2305
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
2306
     * @param    bool    Whether to return invisible fields as well
2307
     * @param    bool    Whether to split multiple-selection fields or not
2308
     *
2309
     * @return array Array of fields => value for the given user
2310
     */
2311
    public static function get_extra_user_data(
2312
        $user_id,
2313
        $prefix = false,
2314
        $allVisibility = true,
2315
        $splitMultiple = false,
2316
        $fieldFilter = null
2317
    ) {
2318
        $user_id = (int) $user_id;
2319
2320
        if (empty($user_id)) {
2321
            return [];
2322
        }
2323
2324
        $extra_data = [];
2325
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2326
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2327
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2328
                FROM $t_uf f
2329
                WHERE
2330
                    item_type = ".EntityExtraField::USER_FIELD_TYPE."
2331
                ";
2332
        $filter_cond = '';
2333
2334
        if (!$allVisibility) {
2335
            if (isset($fieldFilter)) {
2336
                $fieldFilter = (int) $fieldFilter;
2337
                $filter_cond .= " AND filter = $fieldFilter ";
2338
            }
2339
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
2340
        } else {
2341
            if (isset($fieldFilter)) {
2342
                $fieldFilter = (int) $fieldFilter;
2343
                $sql .= " AND filter = $fieldFilter ";
2344
            }
2345
        }
2346
2347
        $sql .= ' ORDER BY f.field_order';
2348
2349
        $res = Database::query($sql);
2350
        if (Database::num_rows($res) > 0) {
2351
            while ($row = Database::fetch_array($res)) {
2352
                if (self::USER_FIELD_TYPE_TAG == $row['type']) {
2353
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
2354
                    $extra_data['extra_'.$row['fvar']] = $tags;
2355
                } else {
2356
                    $sqlu = "SELECT field_value as fval
2357
                            FROM $t_ufv
2358
                            WHERE field_id=".$row['id']." AND item_id = ".$user_id;
2359
                    $resu = Database::query($sqlu);
2360
                    // get default value
2361
                    $sql_df = "SELECT default_value as fval_df FROM $t_uf
2362
                               WHERE id=".$row['id'];
2363
                    $res_df = Database::query($sql_df);
2364
2365
                    if (Database::num_rows($resu) > 0) {
2366
                        $rowu = Database::fetch_array($resu);
2367
                        $fval = $rowu['fval'];
2368
                        if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2369
                            $fval = explode(';', $rowu['fval']);
2370
                        }
2371
                    } else {
2372
                        $row_df = Database::fetch_array($res_df);
2373
                        $fval = $row_df['fval_df'];
2374
                    }
2375
                    // We get here (and fill the $extra_data array) even if there
2376
                    // is no user with data (we fill it with default values)
2377
                    if ($prefix) {
2378
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2379
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2380
                        } else {
2381
                            $extra_data['extra_'.$row['fvar']] = $fval;
2382
                        }
2383
                    } else {
2384
                        if (self::USER_FIELD_TYPE_RADIO == $row['type']) {
2385
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
2386
                        } else {
2387
                            $extra_data[$row['fvar']] = $fval;
2388
                        }
2389
                    }
2390
                }
2391
            }
2392
        }
2393
2394
        return $extra_data;
2395
    }
2396
2397
    /** Get extra user data by field.
2398
     * @param int    user ID
2399
     * @param string the internal variable name of the field
2400
     *
2401
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2402
     */
2403
    public static function get_extra_user_data_by_field(
2404
        $user_id,
2405
        $field_variable,
2406
        $prefix = false,
2407
        $all_visibility = true,
2408
        $splitmultiple = false
2409
    ) {
2410
        $user_id = (int) $user_id;
2411
2412
        if (empty($user_id)) {
2413
            return [];
2414
        }
2415
2416
        $extra_data = [];
2417
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
2418
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
2419
2420
        $sql = "SELECT f.id as id, f.variable as fvar, f.value_type as type
2421
                FROM $t_uf f
2422
                WHERE f.variable = '$field_variable' ";
2423
2424
        if (!$all_visibility) {
2425
            $sql .= " AND f.visible_to_self = 1 ";
2426
        }
2427
2428
        $sql .= " AND item_type = ".EntityExtraField::USER_FIELD_TYPE;
2429
        $sql .= " ORDER BY f.field_order ";
2430
2431
        $res = Database::query($sql);
2432
        if (Database::num_rows($res) > 0) {
2433
            while ($row = Database::fetch_array($res)) {
2434
                $sqlu = "SELECT field_value as fval FROM $t_ufv v
2435
                         INNER JOIN $t_uf f
2436
                         ON (v.field_id = f.id)
2437
                         WHERE
2438
                            item_type = ".EntityExtraField::USER_FIELD_TYPE." AND
2439
                            field_id = ".$row['id']." AND
2440
                            item_id = ".$user_id;
2441
                $resu = Database::query($sqlu);
2442
                $fval = '';
2443
                if (Database::num_rows($resu) > 0) {
2444
                    $rowu = Database::fetch_array($resu);
2445
                    $fval = $rowu['fval'];
2446
                    if (self::USER_FIELD_TYPE_SELECT_MULTIPLE == $row['type']) {
2447
                        $fval = explode(';', $rowu['fval']);
2448
                    }
2449
                }
2450
                if ($prefix) {
2451
                    $extra_data['extra_'.$row['fvar']] = $fval;
2452
                } else {
2453
                    $extra_data[$row['fvar']] = $fval;
2454
                }
2455
            }
2456
        }
2457
2458
        return $extra_data;
2459
    }
2460
2461
    /**
2462
     * Get the extra field information for a certain field (the options as well).
2463
     *
2464
     * @param int $variable The name of the field we want to know everything about
2465
     *
2466
     * @return array Array containing all the information about the extra profile field
2467
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2468
     *               as returned by the database)
2469
     *
2470
     * @author Julio Montoya
2471
     *
2472
     * @since v1.8.6
2473
     */
2474
    public static function get_extra_field_information_by_name($variable)
2475
    {
2476
        $extraField = new ExtraField('user');
2477
2478
        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...
2479
    }
2480
2481
    /**
2482
     * Get the extra field information for user tag (the options as well).
2483
     *
2484
     * @param int $variable The name of the field we want to know everything about
2485
     *
2486
     * @return array Array containing all the information about the extra profile field
2487
     *               (first level of array contains field details, then 'options' sub-array contains options details,
2488
     *               as returned by the database)
2489
     *
2490
     * @author José Loguercio
2491
     *
2492
     * @since v1.11.0
2493
     */
2494
    public static function get_extra_field_tags_information_by_name($variable)
2495
    {
2496
        $extraField = new ExtraField('user');
2497
2498
        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...
2499
    }
2500
2501
    /**
2502
     * Get all the extra field information of a certain field (also the options).
2503
     *
2504
     * @param int $fieldId the ID of the field we want to know everything of
2505
     *
2506
     * @return array $return containing all th information about the extra profile field
2507
     *
2508
     * @author Julio Montoya
2509
     *
2510
     * @deprecated
2511
     * @since v1.8.6
2512
     */
2513
    public static function get_extra_field_information($fieldId)
2514
    {
2515
        $extraField = new ExtraField('user');
2516
2517
        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...
2518
    }
2519
2520
    /**
2521
     * Get extra user data by value.
2522
     *
2523
     * @param string $variable       the internal variable name of the field
2524
     * @param string $value          the internal value of the field
2525
     * @param bool   $all_visibility
2526
     *
2527
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
2528
     */
2529
    public static function get_extra_user_data_by_value($variable, $value, $all_visibility = true)
2530
    {
2531
        $extraFieldValue = new ExtraFieldValue('user');
2532
        $extraField = new ExtraField('user');
2533
2534
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
2535
2536
        if (false === $info) {
2537
            return [];
2538
        }
2539
2540
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
2541
            $variable,
2542
            $value,
2543
            false,
2544
            false,
2545
            true
2546
        );
2547
2548
        $result = [];
2549
        if (!empty($data)) {
2550
            foreach ($data as $item) {
2551
                $result[] = $item['item_id'];
2552
            }
2553
        }
2554
2555
        return $result;
2556
    }
2557
2558
    /**
2559
     * Get extra user data by tags value.
2560
     *
2561
     * @param int    $fieldId the ID of the field we want to know everything of
2562
     * @param string $tag     the tag name for search
2563
     *
2564
     * @return array with extra data info of a user
2565
     *
2566
     * @author José Loguercio
2567
     *
2568
     * @since v1.11.0
2569
     */
2570
    public static function get_extra_user_data_by_tags($fieldId, $tag)
2571
    {
2572
        $extraField = new ExtraField('user');
2573
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
2574
        $array = [];
2575
        foreach ($result as $index => $user) {
2576
            $array[] = $user['user_id'];
2577
        }
2578
2579
        return $array;
2580
    }
2581
2582
    /**
2583
     * Get extra user data by field variable.
2584
     *
2585
     * @param string $variable field variable
2586
     *
2587
     * @return array data
2588
     */
2589
    public static function get_extra_user_data_by_field_variable($variable)
2590
    {
2591
        $extraInfo = self::get_extra_field_information_by_name($variable);
2592
        $field_id = (int) $extraInfo['id'];
2593
2594
        $extraField = new ExtraFieldValue('user');
2595
        $data = $extraField->getValuesByFieldId($field_id);
2596
2597
        if (!empty($data)) {
2598
            foreach ($data as $row) {
2599
                $user_id = $row['item_id'];
2600
                $data[$user_id] = $row;
2601
            }
2602
        }
2603
2604
        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...
2605
    }
2606
2607
    /**
2608
     * Get extra user data tags by field variable.
2609
     *
2610
     * @param string $variable field variable
2611
     *
2612
     * @return array
2613
     */
2614
    public static function get_extra_user_data_for_tags($variable)
2615
    {
2616
        $data = self::get_extra_field_tags_information_by_name($variable);
2617
2618
        return $data;
2619
    }
2620
2621
    /**
2622
     * Gives a list of [session_category][session_id] for the current user.
2623
     *
2624
     * @param int  $user_id
2625
     * @param bool $is_time_over                 whether to fill the first element or not
2626
     *                                           (to give space for courses out of categories)
2627
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
2628
     * @param bool $ignoreTimeLimit              ignore time start/end
2629
     * @param bool $getCount
2630
     *
2631
     * @return array list of statuses [session_category][session_id]
2632
     *
2633
     * @todo ensure multiple access urls are managed correctly
2634
     */
2635
    public static function get_sessions_by_category(
2636
        $user_id,
2637
        $is_time_over = true,
2638
        $ignore_visibility_for_admins = false,
2639
        $ignoreTimeLimit = false,
2640
        $getCount = false
2641
    ) {
2642
        $user_id = (int) $user_id;
2643
2644
        if (empty($user_id)) {
2645
            return [];
2646
        }
2647
2648
        // Get the list of sessions per user
2649
        $now = new DateTime('now', new DateTimeZone('UTC'));
2650
2651
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
2652
        // join would not catch session-courses where the user is general
2653
        // session coach but which do not have students nor coaches registered
2654
        $dqlSelect = ' COUNT(DISTINCT s.id) ';
2655
2656
        if (!$getCount) {
2657
            $dqlSelect = " DISTINCT
2658
                s.id,
2659
                s.title,
2660
                s.accessStartDate AS access_start_date,
2661
                s.accessEndDate AS access_end_date,
2662
                s.duration,
2663
                sc.id AS session_category_id,
2664
                sc.title AS session_category_title,
2665
                sc.dateStart AS session_category_date_start,
2666
                sc.dateEnd AS session_category_date_end,
2667
                s.coachAccessStartDate AS coach_access_start_date,
2668
                s.coachAccessEndDate AS coach_access_end_date,
2669
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
2670
                , s.position AS position
2671
            ";
2672
        }
2673
2674
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
2675
        // is awfully inefficient for large sets of data (1m25s for 58K
2676
        // sessions, BT#14115) but executing a similar query twice and grouping
2677
        // the results afterwards in PHP takes about 1/1000th of the time
2678
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
2679
        $dqlStudent = "SELECT $dqlSelect
2680
            FROM ChamiloCoreBundle:Session AS s
2681
            LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
2682
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2683
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2684
            WHERE scu.user = :user AND url.url = :url ";
2685
        $dqlCoach = "SELECT $dqlSelect
2686
            FROM ChamiloCoreBundle:Session AS s
2687
            INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.session = s.id
2688
            LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc
2689
            INNER JOIN ChamiloCoreBundle:SessionRelUser AS su WITH su.session = s
2690
            WHERE (su.user = :user AND su.relationType = ".SessionEntity::GENERAL_COACH.") AND url.url = :url ";
2691
2692
        // Default order
2693
        $order = 'ORDER BY sc.title, s.title';
2694
2695
        // Order by date if showing all sessions
2696
        $showAllSessions = ('true' === api_get_setting('session.show_all_sessions_on_my_course_page'));
2697
        if ($showAllSessions) {
2698
            $order = 'ORDER BY s.accessStartDate';
2699
        }
2700
2701
        // Order by position
2702
        if ('true' === api_get_setting('session.session_list_order')) {
2703
            $order = 'ORDER BY s.position';
2704
        }
2705
2706
        // Order by dates according to settings
2707
        $orderBySettings = api_get_setting('session.my_courses_session_order', true);
2708
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
2709
            $field = $orderBySettings['field'];
2710
            $orderSetting = $orderBySettings['order'];
2711
            switch ($field) {
2712
                case 'start_date':
2713
                    $order = " ORDER BY s.accessStartDate $orderSetting";
2714
                    break;
2715
                case 'end_date':
2716
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
2717
                    if ('asc' == $orderSetting) {
2718
                        // Put null values at the end
2719
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
2720
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
2721
                    }
2722
                    break;
2723
                case 'name':
2724
                case 'title':
2725
                    $order = " ORDER BY s.title $orderSetting ";
2726
                    break;
2727
            }
2728
        }
2729
2730
        $dqlStudent .= $order;
2731
        $dqlCoach .= $order;
2732
2733
        $accessUrlId = api_get_current_access_url_id();
2734
        $dqlStudent = Database::getManager()
2735
            ->createQuery($dqlStudent)
2736
            ->setParameters(
2737
                ['user' => $user_id, 'url' => $accessUrlId]
2738
            )
2739
        ;
2740
        $dqlCoach = Database::getManager()
2741
            ->createQuery($dqlCoach)
2742
            ->setParameters(
2743
                ['user' => $user_id, 'url' => $accessUrlId]
2744
            )
2745
        ;
2746
2747
        if ($getCount) {
2748
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
2749
        }
2750
2751
        $sessionDataStudent = $dqlStudent->getResult();
2752
        $sessionDataCoach = $dqlCoach->getResult();
2753
2754
        $sessionData = [];
2755
        // First fill $sessionData with student sessions
2756
        if (!empty($sessionDataStudent)) {
2757
            foreach ($sessionDataStudent as $row) {
2758
                $sessionData[$row['id']] = $row;
2759
            }
2760
        }
2761
        // Overwrite session data of the user as a student with session data
2762
        // of the user as a coach.
2763
        // There shouldn't be such duplicate rows, but just in case...
2764
        if (!empty($sessionDataCoach)) {
2765
            foreach ($sessionDataCoach as $row) {
2766
                $sessionData[$row['id']] = $row;
2767
            }
2768
        }
2769
2770
        $collapsable = ('true' === api_get_setting('session.allow_user_session_collapsable'));
2771
2772
2773
2774
        $extraField = new ExtraFieldValue('session');
2775
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
2776
2777
        if (empty($sessionData)) {
2778
            return [];
2779
        }
2780
        $categories = [];
2781
        foreach ($sessionData as $row) {
2782
            $session_id = $row['id'];
2783
            $coachList = SessionManager::getCoachesBySession($session_id);
2784
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
2785
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
2786
            $courseList = self::get_courses_list_by_session($user_id, $session_id);
2787
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
2788
2789
            // User portal filters:
2790
            if (false === $ignoreTimeLimit) {
2791
                if ($is_time_over) {
2792
                    // History
2793
                    if ($row['duration']) {
2794
                        if ($daysLeft >= 0) {
2795
                            continue;
2796
                        }
2797
                    } else {
2798
                        if (empty($row['access_end_date'])) {
2799
                            continue;
2800
                        } else {
2801
                            if ($row['access_end_date'] > $now) {
2802
                                continue;
2803
                            }
2804
                        }
2805
                    }
2806
                } else {
2807
                    // Current user portal
2808
                    $isGeneralCoach = api_get_session_entity($row['id'])->hasUserAsGeneralCoach(api_get_user_entity($user_id));
2809
                    $isCoachOfCourse = in_array($user_id, $coachList);
2810
2811
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
2812
                        // Teachers can access the session depending in the access_coach date
2813
                    } else {
2814
                        if ($row['duration']) {
2815
                            if ($daysLeft <= 0) {
2816
                                continue;
2817
                            }
2818
                        } else {
2819
                            if (isset($row['access_end_date']) &&
2820
                                !empty($row['access_end_date'])
2821
                            ) {
2822
                                if ($row['access_end_date'] <= $now) {
2823
                                    continue;
2824
                                }
2825
                            }
2826
                        }
2827
                    }
2828
                }
2829
            }
2830
2831
            $categories[$row['session_category_id']]['session_category'] = [
2832
                'id' => $row['session_category_id'],
2833
                'name' => $row['session_category_title'],
2834
                'date_start' => $categoryStart,
2835
                'date_end' => $categoryEnd,
2836
            ];
2837
2838
            $visibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

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

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

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

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

Loading history...
2839
                $session_id,
2840
                null,
2841
                $ignore_visibility_for_admins
2842
            );
2843
2844
            if (SESSION_VISIBLE != $visibility) {
2845
                // Course Coach session visibility.
2846
                $blockedCourseCount = 0;
2847
                $closedVisibilityList = [
2848
                    COURSE_VISIBILITY_CLOSED,
2849
                    COURSE_VISIBILITY_HIDDEN,
2850
                ];
2851
2852
                foreach ($courseList as $course) {
2853
                    // Checking session visibility
2854
                    $sessionCourseVisibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

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

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

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

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

Loading history...
2855
                        $session_id,
2856
                        $course['real_id'],
2857
                        $ignore_visibility_for_admins
2858
                    );
2859
2860
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
2861
                    if (false === $courseIsVisible || SESSION_INVISIBLE == $sessionCourseVisibility) {
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

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

2861
                    if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
2862
                        $blockedCourseCount++;
2863
                    }
2864
                }
2865
2866
                // If all courses are blocked then no show in the list.
2867
                if ($blockedCourseCount === count($courseList)) {
2868
                    $visibility = SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

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

2868
                    $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2869
                } else {
2870
                    $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...
2871
                }
2872
            }
2873
2874
            switch ($visibility) {
2875
                case SESSION_VISIBLE_READ_ONLY:
2876
                case SESSION_VISIBLE:
2877
                case SESSION_AVAILABLE:
2878
                    break;
2879
                case SESSION_INVISIBLE:
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

3066
                if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3067
                    continue;
3068
                }
3069
3070
                $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3071
                    $user,
3072
                    $session,
3073
                    SessionEntity::GENERAL_COACH,
3074
                    $url
3075
                );
3076
                $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3077
                    $user,
3078
                    $session,
3079
                    SessionEntity::COURSE_COACH,
3080
                    $url
3081
                );
3082
3083
                $sessionRelCourses = array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach);
3084
3085
                // MultiURL: ensure courses are filtered by access_url_rel_course (same behavior as legacy SQL above)
3086
                if (api_get_multiple_access_url() && -1 != $access_url_id && !empty($sessionRelCourses)) {
3087
                    $courseIds = [];
3088
                    foreach ($sessionRelCourses as $src) {
3089
                        $courseIds[] = (int) $src->getCourse()->getId();
3090
                    }
3091
                    $courseIds = array_values(array_unique($courseIds));
3092
3093
                    if (!empty($courseIds)) {
3094
                        $ids = implode(',', $courseIds);
3095
                        $sqlAllowed = "SELECT c_id FROM $tbl_url_course WHERE access_url_id = $access_url_id AND c_id IN ($ids)";
3096
                        $resAllowed = Database::query($sqlAllowed);
3097
3098
                        $allowed = [];
3099
                        while ($r = Database::fetch_assoc($resAllowed)) {
3100
                            $allowed[(int) $r['c_id']] = true;
3101
                        }
3102
3103
                        $sessionRelCourses = array_values(array_filter(
3104
                            $sessionRelCourses,
3105
                            function (SessionRelCourse $src) use ($allowed) {
3106
                                return isset($allowed[(int) $src->getCourse()->getId()]);
3107
                            }
3108
                        ));
3109
                    } else {
3110
                        $sessionRelCourses = [];
3111
                    }
3112
                }
3113
3114
                $coursesInSession = array_map(
3115
                    function (SessionRelCourse $courseInSession) {
3116
                        $course = $courseInSession->getCourse();
3117
3118
                        return [
3119
                            'code' => $course->getCode(),
3120
                            'i' => $course->getTitle(),
3121
                            'l' => $course->getCourseLanguage(),
3122
                            'sort' => 1,
3123
                        ];
3124
                    },
3125
                    $sessionRelCourses
3126
                );
3127
3128
                foreach ($coursesInSession as $result_row) {
3129
                    $result_row['t'] = $formattedUserName;
3130
                    $result_row['email'] = $user->getEmail();
3131
                    $result_row['access_start_date'] = $session->getAccessStartDate()?->format('Y-m-d H:i:s');
3132
                    $result_row['access_end_date'] = $session->getAccessEndDate()?->format('Y-m-d H:i:s');
3133
                    $result_row['session_id'] = $session->getId();
3134
                    $result_row['session_name'] = $session->getTitle();
3135
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
3136
                    $key = $result_row['session_id'].' - '.$result_row['code'];
3137
                    $personal_course_list[$key] = $result_row;
3138
                }
3139
            }
3140
        }
3141
3142
        foreach ($sessions as $enreg) {
3143
            $session_id = $enreg['id'];
3144
            $session_visibility = api_get_session_visibility($session_id);
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

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

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

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

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

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

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

3145
            if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3146
                continue;
3147
            }
3148
3149
            // MultiURL filter for this legacy SQL too (otherwise it may return courses from other URLs)
3150
            $join_access_url_2 = '';
3151
            $where_access_url_2 = '';
3152
            if (api_get_multiple_access_url() && -1 != $access_url_id) {
3153
                $join_access_url_2 = " INNER JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id ";
3154
                $where_access_url_2 = " AND url_rel_course.access_url_id = $access_url_id ";
3155
            }
3156
3157
            $sql = "SELECT DISTINCT
3158
            course.code code,
3159
            course.title i, CONCAT(user.lastname,' ',user.firstname) t,
3160
            email,
3161
            course.course_language l,
3162
            1 sort,
3163
            access_start_date,
3164
            access_end_date,
3165
            session.id as session_id,
3166
            session.title as session_name,
3167
            IF((session_course_user.user_id = $user_id AND session_course_user.status = ".SessionEntity::COURSE_COACH."),'2', '5')
3168
        FROM $tbl_session_course_user as session_course_user
3169
        INNER JOIN $tbl_course AS course
3170
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
3171
        $join_access_url_2
3172
        INNER JOIN $tbl_session as session
3173
            ON session_course_user.session_id = session.id
3174
        LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
3175
        WHERE session_course_user.user_id = $user_id
3176
            $where_access_url_2
3177
        ORDER BY i";
3178
3179
            $course_list_sql_result = Database::query($sql);
3180
            while ($result_row = Database::fetch_assoc($course_list_sql_result)) {
3181
                $result_row['course_info'] = api_get_course_info($result_row['code']);
3182
                $key = $result_row['session_id'].' - '.$result_row['code'];
3183
                if (!isset($personal_course_list[$key])) {
3184
                    $personal_course_list[$key] = $result_row;
3185
                }
3186
            }
3187
        }
3188
3189
        return $personal_course_list;
3190
    }
3191
3192
    /**
3193
     * Gives a list of courses for the given user in the given session.
3194
     *
3195
     * @param int $user_id
3196
     * @param int $session_id
3197
     *
3198
     * @return array list of statuses (session_id-course_code => status)
3199
     */
3200
    public static function get_courses_list_by_session($user_id, $session_id)
3201
    {
3202
        // Database Table Definitions
3203
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
3204
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3205
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3206
3207
        $user_id = (int) $user_id;
3208
        $session_id = (int) $session_id;
3209
3210
        $sessionRepo = Container::getSessionRepository();
3211
3212
        $user = api_get_user_entity($user_id);
3213
        $session = api_get_session_entity($session_id);
3214
        $url = null;
3215
3216
        // We filter the courses from the URL
3217
        $join_access_url = $where_access_url = '';
3218
        if (api_get_multiple_access_url()) {
3219
            $urlId = api_get_current_access_url_id();
3220
            if (-1 != $urlId) {
3221
                $url = api_get_url_entity($urlId);
3222
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3223
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
3224
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
3225
            }
3226
        }
3227
3228
        /* This query is very similar to the query below, but it will check the
3229
        session_rel_course_user table if there are courses registered
3230
        to our user or not */
3231
        $sql = "SELECT DISTINCT
3232
                    c.title,
3233
                    c.visibility,
3234
                    c.id as real_id,
3235
                    c.code as course_code,
3236
                    sc.position,
3237
                    c.unsubscribe
3238
                FROM $tbl_session_course_user as scu
3239
                INNER JOIN $tbl_session_course sc
3240
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
3241
                INNER JOIN $tableCourse as c
3242
                ON (scu.c_id = c.id)
3243
                $join_access_url
3244
                WHERE
3245
                    scu.user_id = $user_id AND
3246
                    scu.session_id = $session_id
3247
                    $where_access_url
3248
                ORDER BY sc.position ASC";
3249
3250
        $myCourseList = [];
3251
        $courses = [];
3252
        $result = Database::query($sql);
3253
        if (Database::num_rows($result) > 0) {
3254
            while ($result_row = Database::fetch_assoc($result)) {
3255
                $result_row['status'] = 5;
3256
                if (!in_array($result_row['real_id'], $courses)) {
3257
                    $position = $result_row['position'];
3258
                    if (!isset($myCourseList[$position])) {
3259
                        $myCourseList[$position] = $result_row;
3260
                    } else {
3261
                        $myCourseList[] = $result_row;
3262
                    }
3263
                    $courses[] = $result_row['real_id'];
3264
                }
3265
            }
3266
        }
3267
3268
        if (api_is_allowed_to_create_course()) {
3269
            $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3270
                $user,
3271
                $session,
3272
                SessionEntity::GENERAL_COACH,
3273
                $url
3274
            );
3275
            $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3276
                $user,
3277
                $session,
3278
                SessionEntity::COURSE_COACH,
3279
                $url
3280
            );
3281
3282
            $coursesInSession = array_map(
3283
                function (SessionRelCourse $courseInSession) {
3284
                    $course = $courseInSession->getCourse();
3285
3286
                    return [
3287
                        'title' => $course->getTitle(),
3288
                        'visibility' => $course->getVisibility(),
3289
                        'real_id' => $course->getId(),
3290
                        'course_code' => $course->getCode(),
3291
                        'position' => $courseInSession->getPosition(),
3292
                        'unsubscribe' => $course->getUnsubscribe(),
3293
                    ];
3294
                },
3295
                array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3296
            );
3297
3298
            foreach ($coursesInSession as $result_row) {
3299
                $result_row['status'] = 2;
3300
                if (!in_array($result_row['real_id'], $courses)) {
3301
                    $position = $result_row['position'];
3302
                    if (!isset($myCourseList[$position])) {
3303
                        $myCourseList[$position] = $result_row;
3304
                    } else {
3305
                        $myCourseList[] = $result_row;
3306
                    }
3307
                    $courses[] = $result_row['real_id'];
3308
                }
3309
            }
3310
        }
3311
3312
        if (api_is_drh()) {
3313
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
3314
            $sessionList = array_keys($sessionList);
3315
            if (in_array($session_id, $sessionList)) {
3316
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3317
                if (!empty($courseList)) {
3318
                    foreach ($courseList as $course) {
3319
                        if (!in_array($course['id'], $courses)) {
3320
                            $position = $course['position'];
3321
                            if (!isset($myCourseList[$position])) {
3322
                                $myCourseList[$position] = $course;
3323
                            } else {
3324
                                $myCourseList[] = $course;
3325
                            }
3326
                        }
3327
                    }
3328
                }
3329
            }
3330
        } else {
3331
            //check if user is general coach for this session
3332
            if ($session && $session->hasUserAsGeneralCoach($user)) {
3333
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
3334
                if (!empty($courseList)) {
3335
                    foreach ($courseList as $course) {
3336
                        if (!in_array($course['id'], $courses)) {
3337
                            $position = $course['position'];
3338
                            if (!isset($myCourseList[$position])) {
3339
                                $myCourseList[$position] = $course;
3340
                            } else {
3341
                                $myCourseList[] = $course;
3342
                            }
3343
                        }
3344
                    }
3345
                }
3346
            }
3347
        }
3348
3349
        if (!empty($myCourseList)) {
3350
            ksort($myCourseList);
3351
            $checkPosition = array_filter(array_column($myCourseList, 'position'));
3352
            if (empty($checkPosition)) {
3353
                // The session course list doesn't have any position,
3354
                // then order the course list by course code
3355
                $list = array_column($myCourseList, 'course_code');
3356
                array_multisort($myCourseList, SORT_ASC, $list);
0 ignored issues
show
Bug introduced by
SORT_ASC cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

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

3356
                array_multisort($myCourseList, /** @scrutinizer ignore-type */ SORT_ASC, $list);
Loading history...
3357
            }
3358
        }
3359
3360
        return $myCourseList;
3361
    }
3362
3363
    /**
3364
     * Get user id from a username.
3365
     *
3366
     * @param string $username
3367
     *
3368
     * @return int User ID (or false if not found)
3369
     */
3370
    public static function get_user_id_from_username($username)
3371
    {
3372
        if (empty($username)) {
3373
            return false;
3374
        }
3375
        $username = trim($username);
3376
        $username = Database::escape_string($username);
3377
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
3378
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
3379
        $res = Database::query($sql);
3380
3381
        if (false === $res) {
3382
            return false;
3383
        }
3384
        if (1 !== Database::num_rows($res)) {
3385
            return false;
3386
        }
3387
        $row = Database::fetch_array($res);
3388
3389
        return $row['id'];
3390
    }
3391
3392
    /**
3393
     * Get the users files upload from his share_folder.
3394
     *
3395
     * @param string $user_id      User ID
3396
     * @param string $course       course directory
3397
     * @param string $resourceType resource type: images, all
3398
     *
3399
     * @return string
3400
     */
3401
    /*public static function get_user_upload_files_by_course(
3402
        $user_id,
3403
        $course,
3404
        $resourceType = 'all'
3405
    ) {
3406
        $return = '';
3407
        $user_id = (int) $user_id;
3408
3409
        if (!empty($user_id) && !empty($course)) {
3410
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3411
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
3412
            $file_list = [];
3413
3414
            if (is_dir($path)) {
3415
                $handle = opendir($path);
3416
                while ($file = readdir($handle)) {
3417
                    if ('.' == $file || '..' == $file || '.htaccess' == $file || is_dir($path.$file)) {
3418
                        continue; // skip current/parent directory and .htaccess
3419
                    }
3420
                    $file_list[] = $file;
3421
                }
3422
                if (count($file_list) > 0) {
3423
                    $return = "<h4>$course</h4>";
3424
                    $return .= '<ul class="thumbnails">';
3425
                }
3426
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
3427
                foreach ($file_list as $file) {
3428
                    if ('all' == $resourceType) {
3429
                        $return .= '<li>
3430
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
3431
                    } elseif ('images' == $resourceType) {
3432
                        //get extension
3433
                        $ext = explode('.', $file);
3434
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
3435
                            $return .= '<li class="span2">
3436
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
3437
                                                <img src="'.$web_path.urlencode($file).'" >
3438
                                            </a>
3439
                                        </li>';
3440
                        }
3441
                    }
3442
                }
3443
                if (count($file_list) > 0) {
3444
                    $return .= '</ul>';
3445
                }
3446
            }
3447
        }
3448
3449
        return $return;
3450
    }*/
3451
3452
    /**
3453
     * Gets the API key (or keys) and return them into an array.
3454
     *
3455
     * @param int     Optional user id (defaults to the result of api_get_user_id())
3456
     * @param string $api_service
3457
     *
3458
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
3459
     */
3460
    public static function get_api_keys($user_id = null, $api_service = 'default')
3461
    {
3462
        if ($user_id != strval(intval($user_id))) {
3463
            return false;
3464
        }
3465
        if (empty($user_id)) {
3466
            $user_id = api_get_user_id();
3467
        }
3468
        if (false === $user_id) {
3469
            return false;
3470
        }
3471
        $service_name = Database::escape_string($api_service);
3472
        if (false === is_string($service_name)) {
3473
            return false;
3474
        }
3475
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3476
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
3477
        $res = Database::query($sql);
3478
        if (false === $res) {
3479
            return false;
3480
        } //error during query
3481
        $num = Database::num_rows($res);
3482
        if (0 == $num) {
3483
            return false;
3484
        }
3485
        $list = [];
3486
        while ($row = Database::fetch_array($res)) {
3487
            $list[$row['id']] = $row['api_key'];
3488
        }
3489
3490
        return $list;
3491
    }
3492
3493
    /**
3494
     * Adds a new API key to the users' account.
3495
     *
3496
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
3497
     * @param string $api_service
3498
     *
3499
     * @return bool True on success, false on failure
3500
     */
3501
    public static function add_api_key($user_id = null, $api_service = 'default')
3502
    {
3503
        if ($user_id != strval(intval($user_id))) {
3504
            return false;
3505
        }
3506
        if (empty($user_id)) {
3507
            $user_id = api_get_user_id();
3508
        }
3509
        if (false === $user_id) {
3510
            return false;
3511
        }
3512
        $service_name = Database::escape_string($api_service);
3513
        if (false === is_string($service_name)) {
3514
            return false;
3515
        }
3516
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3517
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
3518
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
3519
        $res = Database::query($sql);
3520
        if (false === $res) {
3521
            return false;
3522
        } //error during query
3523
        $num = Database::insert_id();
3524
3525
        return 0 == $num ? false : $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 == $num ? false : $num also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
3526
    }
3527
3528
    /**
3529
     * Deletes an API key from the user's account.
3530
     *
3531
     * @param   int     API key's internal ID
3532
     *
3533
     * @return bool True on success, false on failure
3534
     */
3535
    public static function delete_api_key($key_id)
3536
    {
3537
        if ($key_id != strval(intval($key_id))) {
3538
            return false;
3539
        }
3540
        if (false === $key_id) {
3541
            return false;
3542
        }
3543
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3544
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
3545
        $res = Database::query($sql);
3546
        if (false === $res) {
3547
            return false;
3548
        } //error during query
3549
        $num = Database::num_rows($res);
3550
        if (1 !== $num) {
3551
            return false;
3552
        }
3553
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
3554
        $res = Database::query($sql);
3555
        if (false === $res) {
3556
            return false;
3557
        } //error during query
3558
3559
        return true;
3560
    }
3561
3562
    /**
3563
     * Regenerate an API key from the user's account.
3564
     *
3565
     * @param   int     user ID (defaults to the results of api_get_user_id())
3566
     * @param   string  API key's internal ID
3567
     *
3568
     * @return int num
3569
     */
3570
    public static function update_api_key($user_id, $api_service)
3571
    {
3572
        if ($user_id != strval(intval($user_id))) {
3573
            return false;
3574
        }
3575
        if (false === $user_id) {
3576
            return false;
3577
        }
3578
        $service_name = Database::escape_string($api_service);
3579
        if (false === is_string($service_name)) {
3580
            return false;
3581
        }
3582
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3583
        $sql = "SELECT id FROM $t_api
3584
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3585
        $res = Database::query($sql);
3586
        $num = Database::num_rows($res);
3587
        if (1 == $num) {
3588
            $id_key = Database::fetch_assoc($res);
3589
            self::delete_api_key($id_key['id']);
3590
            $num = self::add_api_key($user_id, $api_service);
3591
        } elseif (0 == $num) {
3592
            $num = self::add_api_key($user_id, $api_service);
3593
        }
3594
3595
        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...
3596
    }
3597
3598
    /**
3599
     * @param   int     user ID (defaults to the results of api_get_user_id())
3600
     * @param   string    API key's internal ID
3601
     *
3602
     * @return int row ID, or return false if not found
3603
     */
3604
    public static function get_api_key_id($user_id, $api_service)
3605
    {
3606
        if ($user_id != strval(intval($user_id))) {
3607
            return false;
3608
        }
3609
        if (false === $user_id) {
3610
            return false;
3611
        }
3612
        if (empty($api_service)) {
3613
            return false;
3614
        }
3615
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
3616
        $api_service = Database::escape_string($api_service);
3617
        $sql = "SELECT id FROM $t_api
3618
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
3619
        $res = Database::query($sql);
3620
        if (Database::num_rows($res) < 1) {
3621
            return false;
3622
        }
3623
        $row = Database::fetch_assoc($res);
3624
3625
        return $row['id'];
3626
    }
3627
3628
    /**
3629
     * Checks if a user_id is platform admin.
3630
     *
3631
     * @param   int user ID
3632
     *
3633
     * @return bool True if is admin, false otherwise
3634
     *
3635
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
3636
     */
3637
    public static function is_admin($user_id)
3638
    {
3639
        $user_id = (int) $user_id;
3640
        if (empty($user_id)) {
3641
            return false;
3642
        }
3643
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
3644
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
3645
        $res = Database::query($sql);
3646
3647
        return 1 === Database::num_rows($res);
3648
    }
3649
3650
    /**
3651
     * Get the total count of users.
3652
     *
3653
     * @param ?int $status Status of users to be counted
3654
     * @param ?int $access_url_id Access URL ID (optional)
3655
     * @param ?int $active
3656
     *
3657
     * @return mixed Number of users or false on error
3658
     * @throws \Doctrine\DBAL\Exception
3659
     */
3660
    public static function get_number_of_users(
3661
        ?int $status = 0,
3662
        ?int $access_url_id = 1,
3663
        ?int $active = null,
3664
        ?string $dateFrom = null,
3665
        ?string $dateUntil = null
3666
    ): mixed {
3667
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
3668
        $tableAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
3669
3670
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlUtil::isMultiple ( Ignorable by Annotation )

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

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

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

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

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

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

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

5535
            Event::/** @scrutinizer ignore-call */ 
5536
                   registerLog([

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...
5536
                'tool' => 'logout',
5537
                'tool_id' => 0,
5538
                'tool_id_detail' => 0,
5539
                'action' => '',
5540
                'info' => 'Change user (login as)',
5541
            ]);
5542
5543
            // Logout current user
5544
            self::loginDelete(api_get_user_id());
5545
5546
            return true;
5547
        }
5548
5549
        return false;
5550
    }
5551
5552
    /**
5553
     * Remove all login records from the track_e_online stats table,
5554
     * for the given user ID.
5555
     *
5556
     * @param int $userId User ID
5557
     */
5558
    public static function loginDelete($userId)
5559
    {
5560
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
5561
        $userId = (int) $userId;
5562
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
5563
        Database::query($query);
5564
    }
5565
5566
    /**
5567
     * Login as first admin user registered in the platform.
5568
     *
5569
     * @return array
5570
     */
5571
    public static function logInAsFirstAdmin()
5572
    {
5573
        $adminList = self::get_all_administrators();
5574
5575
        if (!empty($adminList)) {
5576
            $userInfo = current($adminList);
5577
            if (!empty($userInfo)) {
5578
                $result = self::loginAsUser($userInfo['user_id'], false);
5579
                if ($result && api_is_platform_admin()) {
5580
                    return api_get_user_info();
0 ignored issues
show
Bug Best Practice introduced by
The expression return api_get_user_info() could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

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

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

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

Loading history...
6303
        $email,
6304
        $s = 80,
6305
        $d = 'mm',
6306
        $r = 'g',
6307
        $img = false,
6308
        $atts = []
6309
    ) {
6310
        $url = 'http://www.gravatar.com/avatar/';
6311
        if (!empty($_SERVER['HTTPS'])) {
6312
            $url = 'https://secure.gravatar.com/avatar/';
6313
        }
6314
        $url .= md5(strtolower(trim($email)));
6315
        $url .= "?s=$s&d=$d&r=$r";
6316
        if ($img) {
6317
            $url = '<img src="'.$url.'"';
6318
            foreach ($atts as $key => $val) {
6319
                $url .= ' '.$key.'="'.$val.'"';
6320
            }
6321
            $url .= ' />';
6322
        }
6323
6324
        return $url;
6325
    }
6326
6327
    /**
6328
     * Count users in courses and if they have certificate.
6329
     * This function is resource intensive.
6330
     *
6331
     * @return array
6332
     * @throws Exception
6333
     * @throws \Doctrine\DBAL\Exception
6334
     */
6335
    public static function countUsersWhoFinishedCourses()
6336
    {
6337
        $courses = [];
6338
        $currentAccessUrlId = api_get_current_access_url_id();
6339
        $sql = "SELECT course.code, course.id as cid, cru.user_id
6340
                FROM course_rel_user cru
6341
                    JOIN course ON cru.c_id = course.id
6342
                    JOIN access_url_rel_user auru on cru.user_id = auru.user_id
6343
                    JOIN access_url_rel_course ON course.id = access_url_rel_course.c_id
6344
                WHERE access_url_rel_course.access_url_id = $currentAccessUrlId
6345
                ORDER BY course.code
6346
        ";
6347
        $res = Database::query($sql);
6348
        if (Database::num_rows($res) > 0) {
6349
            while ($row = Database::fetch_array($res)) {
6350
                if (!isset($courses[$row['code']])) {
6351
                    $courses[$row['code']] = [
6352
                        'subscribed' => 0,
6353
                        'finished' => 0,
6354
                    ];
6355
                }
6356
                $courses[$row['code']]['subscribed']++;
6357
                $entityManager = Database::getManager();
6358
                $repository = $entityManager->getRepository(GradebookCategory::class);
6359
                //todo check when have more than 1 gradebook
6360
                /** @var GradebookCategory $gradebook */
6361
                $gradebook = $repository->findOneBy(['course' => $row['cid']]);
6362
                if (!empty($gradebook)) {
6363
                    $finished = 0;
6364
                    Database::getManager()->persist($gradebook);
6365
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6366
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6367
                    if (!empty($finished)) {
6368
                        $courses[$row['code']]['finished']++;
6369
                    }
6370
                }
6371
            }
6372
        }
6373
        return $courses;
6374
    }
6375
6376
    /**
6377
     * Count users in sessions and if they have certificate.
6378
     * This function is resource intensive.
6379
     *
6380
     * @return array
6381
     * @throws Exception
6382
     * @throws \Doctrine\DBAL\Exception
6383
     */
6384
    public static function countUsersWhoFinishedCoursesInSessions()
6385
    {
6386
        $coursesInSessions = [];
6387
        $currentAccessUrlId = api_get_current_access_url_id();
6388
        $sql = "SELECT course.code, srcru.session_id, srcru.user_id, session.title
6389
                FROM session_rel_course_rel_user srcru
6390
                    JOIN course ON srcru.c_id = course.id
6391
                    JOIN access_url_rel_session aurs on srcru.session_id = aurs.session_id
6392
                    JOIN session ON srcru.session_id = session.id
6393
                WHERE aurs.access_url_id = $currentAccessUrlId
6394
                ORDER BY course.code, session.title
6395
        ";
6396
        $res = Database::query($sql);
6397
        if (Database::num_rows($res) > 0) {
6398
            while ($row = Database::fetch_array($res)) {
6399
                $index = $row['code'].' ('.$row['title'].')';
6400
                if (!isset($coursesInSessions[$index])) {
6401
                    $coursesInSessions[$index] = [
6402
                        'subscribed' => 0,
6403
                        'finished' => 0,
6404
                    ];
6405
                }
6406
                $coursesInSessions[$index]['subscribed']++;
6407
                $entityManager = Database::getManager();
6408
                $repository = $entityManager->getRepository(GradebookCategory::class);
6409
                /** @var GradebookCategory $gradebook */
6410
                $gradebook = $repository->findOneBy(
6411
                    [
6412
                        'course' => $row['cid'],
6413
                        'sessionId' => $row['session_id'],
6414
                    ]
6415
                );
6416
                if (!empty($gradebook)) {
6417
                    $finished = 0;
6418
                    Database::getManager()->persist($gradebook);
6419
                    $certificateRepo = $entityManager->getRepository(\Chamilo\CoreBundle\Entity\GradebookCertificate::class);
6420
                    $finished = $certificateRepo->getCertificateByUserId($gradebook->getId(), $row['user_id']);
6421
                    if (!empty($finished)) {
6422
                        $coursesInSessions[$index]['finished']++;
6423
                    }
6424
                }
6425
            }
6426
        }
6427
        return $coursesInSessions;
6428
    }
6429
6430
    public static function redirectToResetPassword($userId): void
6431
    {
6432
        if ('true' !== api_get_setting('security.force_renew_password_at_first_login')) {
6433
            return;
6434
        }
6435
        $askPassword = self::get_extra_user_data_by_field(
6436
            $userId,
6437
            'ask_new_password'
6438
        );
6439
        if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
6440
            1 === (int) $askPassword['ask_new_password']
6441
        ) {
6442
            $uniqueId = api_get_unique_id();
6443
            $userObj = api_get_user_entity($userId);
6444
            $userObj->setConfirmationToken($uniqueId);
6445
            $userObj->setPasswordRequestedAt(new \DateTime());
6446
            Database::getManager()->persist($userObj);
6447
            Database::getManager()->flush();
6448
            $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
6449
            api_location($url);
6450
        }
6451
    }
6452
6453
}
6454