UserManager::getSubscribedSessionsByYear()   F
last analyzed

Complexity

Conditions 28
Paths 6492

Size

Total Lines 102
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 62
nop 2
dl 0
loc 102
rs 0
c 0
b 0
f 0
nc 6492

How to fix   Long Method    Complexity   

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:

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_person_name(
337
                api_get_setting('administratorName'),
338
                api_get_setting('administratorSurname'),
339
                null,
340
                PERSON_NAME_EMAIL_ADDRESS
341
            );
342
            $email_admin = api_get_setting('emailAdministrator');
343
344
            $url = api_get_path(WEB_PATH);
345
            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

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

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

1207
            $emailsubject = '['./** @scrutinizer ignore-type */ api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
Loading history...
1208
            $sender_name = api_get_person_name(
1209
                api_get_setting('administratorName'),
1210
                api_get_setting('administratorSurname'),
1211
                null,
1212
                PERSON_NAME_EMAIL_ADDRESS
1213
            );
1214
            $email_admin = api_get_setting('emailAdministrator');
1215
            $url = api_get_path(WEB_PATH);
1216
            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

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

1613
        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...
1614
            if ($idCampus) {
1615
                $urlId = $idCampus;
1616
            } else {
1617
                $urlId = api_get_current_access_url_id();
1618
            }
1619
            $sql .= " INNER JOIN $userUrlTable url_user
1620
                      ON (user.id = url_user.user_id)
1621
                      WHERE url_user.access_url_id = $urlId";
1622
        } else {
1623
            $sql .= " WHERE 1=1 ";
1624
        }
1625
1626
        if (count($conditions) > 0) {
1627
            foreach ($conditions as $field => $value) {
1628
                $field = Database::escape_string($field);
1629
                $value = Database::escape_string($value);
1630
                $sql .= " AND $field = '$value'";
1631
            }
1632
        }
1633
1634
        if (count($order_by) > 0) {
1635
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
1636
        }
1637
1638
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
1639
            $limit_from = (int) $limit_from;
1640
            $limit_to = (int) $limit_to;
1641
            $sql .= " LIMIT $limit_from, $limit_to";
1642
        }
1643
        $sql_result = Database::query($sql);
1644
        while ($result = Database::fetch_array($sql_result)) {
1645
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1646
            $return_array[] = $result;
1647
        }
1648
1649
        return $return_array;
1650
    }
1651
1652
    public static function getUserListExtraConditions(
1653
        $conditions = [],
1654
        $order_by = [],
1655
        $limit_from = false,
1656
        $limit_to = false,
1657
        $idCampus = null,
1658
        $extraConditions = '',
1659
        $getCount = false
1660
    ) {
1661
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1662
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1663
        $return_array = [];
1664
        $sql = "SELECT user.*, user.id as user_id FROM $user_table user ";
1665
1666
        if ($getCount) {
1667
            $sql = "SELECT count(user.id) count FROM $user_table user ";
1668
        }
1669
1670
        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

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

2843
            $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...
2844
                $session_id,
2845
                null,
2846
                $ignore_visibility_for_admins
2847
            );
2848
2849
            if (SESSION_VISIBLE != $visibility) {
2850
                // Course Coach session visibility.
2851
                $blockedCourseCount = 0;
2852
                $closedVisibilityList = [
2853
                    COURSE_VISIBILITY_CLOSED,
2854
                    COURSE_VISIBILITY_HIDDEN,
2855
                ];
2856
2857
                foreach ($courseList as $course) {
2858
                    // Checking session visibility
2859
                    $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

2859
                    $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...
2860
                        $session_id,
2861
                        $course['real_id'],
2862
                        $ignore_visibility_for_admins
2863
                    );
2864
2865
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
2866
                    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

2866
                    if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
2867
                        $blockedCourseCount++;
2868
                    }
2869
                }
2870
2871
                // If all courses are blocked then no show in the list.
2872
                if ($blockedCourseCount === count($courseList)) {
2873
                    $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

2873
                    $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2874
                } else {
2875
                    $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...
2876
                }
2877
            }
2878
2879
            switch ($visibility) {
2880
                case SESSION_VISIBLE_READ_ONLY:
2881
                case SESSION_VISIBLE:
2882
                case SESSION_AVAILABLE:
2883
                    break;
2884
                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

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

3062
                $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...
3063
                $session = api_get_session_entity($session_id);
3064
3065
                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

3065
                if (/** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $session_visibility) {
Loading history...
3066
                    continue;
3067
                }
3068
3069
                $coursesAsGeneralCoach = $sessionRepo->getSessionCoursesByStatusInUserSubscription(
3070
                    $user,
3071
                    $session,
3072
                    SessionEntity::GENERAL_COACH,
3073
                    $url
3074
                );
3075
                $coursesAsCourseCoach = $sessionRepo->getSessionCoursesByStatusInCourseSubscription(
3076
                    $user,
3077
                    $session,
3078
                    SessionEntity::COURSE_COACH,
3079
                    $url
3080
                );
3081
3082
                // This query is horribly slow when more than a few thousand
3083
                // users and just a few sessions to which they are subscribed
3084
                $coursesInSession = array_map(
3085
                    function (SessionRelCourse $courseInSession) {
3086
                        $course = $courseInSession->getCourse();
3087
3088
                        return [
3089
                            'code' => $course->getCode(),
3090
                            'i' => $course->getTitle(),
3091
                            'l' => $course->getCourseLanguage(),
3092
                            'sort' => 1,
3093
                        ];
3094
                    },
3095
                    array_merge($coursesAsGeneralCoach, $coursesAsCourseCoach)
3096
                );
3097
3098
                foreach ($coursesInSession as $result_row) {
3099
                    $result_row['t'] = $formattedUserName;
3100
                    $result_row['email'] = $user->getEmail();
3101
                    $result_row['access_start_date'] = $session->getAccessStartDate()?->format('Y-m-d H:i:s');
3102
                    $result_row['access_end_date'] = $session->getAccessEndDate()?->format('Y-m-d H:i:s');
3103
                    $result_row['session_id'] = $session->getId();
3104
                    $result_row['session_name'] = $session->getTitle();
3105
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
3106
                    $key = $result_row['session_id'].' - '.$result_row['code'];
3107
                    $personal_course_list[$key] = $result_row;
3108
                }
3109
            }
3110
        }
3111
3112
        foreach ($sessions as $enreg) {
3113
            $session_id = $enreg['id'];
3114
            $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

3114
            $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...
3115
            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

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

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

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

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