Passed
Push — 1.11.x ( a1052b...79856c )
by Yannick
11:17
created

UserManager   F

Complexity

Total Complexity 1029

Size/Duplication

Total Lines 8166
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 1029
eloc 3881
c 9
b 0
f 0
dl 0
loc 8166
rs 0.8

161 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
B delete_users() 0 17 8
A update_openid() 0 18 4
A ensureCASUserExtraFieldExists() 0 26 6
A canDeleteUser() 0 30 5
A createCASAuthenticatedUserFromLDAP() 0 27 4
A updateExpirationDate() 0 9 1
B casUserLoginName() 0 42 8
A updateUserFromLDAP() 0 36 4
F delete_user() 0 219 20
A activate_users() 0 21 4
A deactivate_users() 0 21 4
A encryptPassword() 0 7 1
C updateCasUser() 0 61 17
A createCASAuthenticatedUserFromScratch() 0 27 3
A isPasswordValid() 0 5 1
A getRepository() 0 5 1
A getPasswordEncryption() 0 8 2
A setPasswordEncryption() 0 3 1
A getManager() 0 19 2
F create_user() 0 540 71
B detectPasswordEncryption() 0 24 10
A updatePassword() 0 9 1
A checkPassword() 0 21 4
A get_all_extra_field_by_type() 0 5 1
F getSubscribedSessionsByYear() 0 102 28
A is_user_followed_by_drh() 0 18 2
A requestUsersToHRManager() 0 8 1
A addUserCareer() 0 18 3
A get_extra_field_information_by_name() 0 5 1
B get_number_of_users() 0 53 9
A update_api_key() 0 26 6
A getFirstStudentBoss() 0 23 3
A getTimeSpentInCourses() 0 36 5
B get_all_user_tags() 0 73 8
B loginAsUser() 0 62 5
B getUserListLike() 0 57 11
B get_user_list_by_ids() 0 33 8
A get_extra_user_data_by_field_variable() 0 16 3
A updateUserCareer() 0 15 2
A is_user_id_valid() 0 16 2
A subscribeUsersToHRManager() 0 11 1
A get_extra_user_data_by_value() 0 28 4
A is_admin() 0 11 2
F updateCourseRelationTypeExLearner() 0 125 28
A isTeacherOfStudent() 0 12 2
A create_extra_field() 0 15 1
B getUserPathById() 0 36 8
C get_extra_user_data() 0 85 13
B get_api_keys() 0 31 8
A getCommonCoursesBetweenTeacherAndStudent() 0 25 4
B get_user_productions() 0 26 8
A removeAllBossFromStudent() 0 14 2
A is_extra_field_available() 0 6 2
A get_extra_field_information() 0 5 1
A get_user_tags() 0 24 3
A loginDelete() 0 6 1
A getUserCareers() 0 16 1
A get_all_administrators() 0 29 4
A remove_user_production() 0 11 2
A formatUserFullName() 0 11 3
F anonymize() 0 104 19
A get_tag_id() 0 15 2
A getUserProfileLink() 0 10 3
A getUsersByName() 0 20 2
A is_username_empty() 0 3 1
A clearHrmRequestsForUser() 0 9 1
A getUsersByOfficialCode() 0 17 2
C get_user_upload_files_by_course() 0 49 16
A getStudentBossList() 0 22 2
B add_api_key() 0 25 7
A delete_user_tags() 0 17 5
A process_tags() 0 12 3
D getUserPicture() 0 91 18
A get_extra_user_data_for_tags() 0 5 1
B get_user_tags_to_string() 0 38 7
A get_api_key_id() 0 22 5
A deleteUserPicture() 0 3 1
A is_username_valid() 0 3 2
B anonymizeUserWithVerification() 0 36 8
A remove_user_rel_user() 0 71 4
B relate_users() 0 50 6
A cleanUserRequestsOfRemoval() 0 20 4
A disable() 0 8 2
A update_all_user_languages() 0 10 3
A get_users_followed_by_drh() 0 26 1
A create_unique_username() 0 24 4
B get_extra_user_data_by_field() 0 56 8
B deleteUserWithVerification() 0 34 8
A removeUserAdmin() 0 9 2
C get_extra_fields() 0 75 12
A get_top_tags() 0 24 3
F getUsersFollowedByUser() 0 248 26
A deleteUserFiles() 0 6 1
A blockIfMaxLoginAttempts() 0 47 5
A addUserAsAdmin() 0 12 3
A sendUserConfirmationMail() 0 31 3
A subscribeBossToUsers() 0 8 1
A update_extra_field_value() 0 10 1
A is_username_too_long() 0 3 1
F update_user() 0 298 40
A userHasCareer() 0 10 1
A getUserCareer() 0 10 1
B getUserSubscriptionTab() 0 46 7
A enable() 0 8 2
A getExtraFilterableFields() 0 18 5
B subscribeUserToBossList() 0 69 9
F get_sessions_by_category() 0 279 51
A get_user_id_from_username() 0 20 4
A getOfficialCodeGrouped() 0 14 2
F get_user_list() 0 99 16
A is_session_course_coach() 0 22 2
A is_user_certified() 0 19 3
B build_user_extra_file_list() 0 44 7
A remove_user_extra_file() 0 16 3
A getDrhListFromUser() 0 27 2
B update_user_extra_file() 0 40 8
B get_user_picture_path_by_id() 0 51 8
A getUsersFollowedByStudentBoss() 0 26 1
A get_extra_field_tags_information_by_name() 0 5 1
A create_username() 0 27 5
A logInAsFirstAdmin() 0 15 5
B build_production_list() 0 41 7
A purify_username() 0 22 3
B get_info_gradebook_certificate() 0 59 7
A get_tags() 0 25 4
A get_extra_user_data_by_tags() 0 10 2
B getUserPicturePathById() 0 47 8
F update_user_picture() 0 108 21
F get_personal_session_course_list() 0 210 25
A get_user_id_from_original_id() 0 27 2
F getSessionsCalendarByYear() 0 52 15
A userIsBossOfStudent() 0 12 3
A show_menu() 0 14 1
B get_user_extra_files() 0 31 7
A get_user_id_of_course_admin_or_session_admin() 0 47 5
B subscribeUsersToUser() 0 78 9
A get_favicon_from_url() 0 17 4
C get_search_form_where_extra_fields() 0 43 14
C getUserListExtraConditions() 0 67 12
B add_tag() 0 57 6
A is_username_available() 0 11 2
A createDataPrivacyExtraFields() 0 33 1
A delete_api_key() 0 25 6
F get_courses_list_by_session() 0 172 29
B get_search_form() 0 81 10
A get_tag_id_from_id() 0 14 2
A getScriptFunctionForActiveFilter() 0 34 1
A getEncoderFactory() 0 8 1
A getAllowedRolesAsTeacher() 0 5 1
B getUserStatusList() 0 31 7
A getGravatar() 0 23 4
A getUsersByMail() 0 18 2
A getExpirationDateByRole() 0 20 4
A getEncoder() 0 5 1
C redirectToResetPassword() 0 71 12
B getActiveFilterForTable() 0 38 6
A change_active_state() 0 21 4
B countUsersWhoFinishedCoursesInSessions() 0 47 6
A isEmailingAllowed() 0 16 4
B countUsersWhoFinishedCourses() 0 42 6

How to fix   Complexity   

Complex Class

Complex classes like UserManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UserManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField;
6
use Chamilo\CoreBundle\Entity\Repository\AccessUrlRepository;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Entity\SkillRelUser;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SkillRelUser. Consider defining an alias.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
9
use Chamilo\CoreBundle\Entity\SkillRelUserComment;
10
use Chamilo\CoreBundle\Entity\TrackELoginAttempt;
11
use Chamilo\UserBundle\Entity\User;
12
use Chamilo\UserBundle\Repository\UserRepository;
13
use ChamiloSession as Session;
14
use Symfony\Component\Filesystem\Filesystem;
15
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
16
17
/**
18
 * Class UserManager.
19
 *
20
 * This library provides functions for user management.
21
 * Include/require it in your code to use its functionality.
22
 *
23
 * @author Julio Montoya <[email protected]> Social network groups added 2009/12
24
 */
25
class UserManager
26
{
27
    // This constants are deprecated use the constants located in ExtraField
28
    public const USER_FIELD_TYPE_TEXT = 1;
29
    public const USER_FIELD_TYPE_TEXTAREA = 2;
30
    public const USER_FIELD_TYPE_RADIO = 3;
31
    public const USER_FIELD_TYPE_SELECT = 4;
32
    public const USER_FIELD_TYPE_SELECT_MULTIPLE = 5;
33
    public const USER_FIELD_TYPE_DATE = 6;
34
    public const USER_FIELD_TYPE_DATETIME = 7;
35
    public const USER_FIELD_TYPE_DOUBLE_SELECT = 8;
36
    public const USER_FIELD_TYPE_DIVIDER = 9;
37
    public const USER_FIELD_TYPE_TAG = 10;
38
    public const USER_FIELD_TYPE_TIMEZONE = 11;
39
    public const USER_FIELD_TYPE_SOCIAL_PROFILE = 12;
40
    public const USER_FIELD_TYPE_FILE = 13;
41
    public const USER_FIELD_TYPE_MOBILE_PHONE_NUMBER = 14;
42
43
    private static $encryptionMethod;
44
45
    /**
46
     * Constructor.
47
     *
48
     * @assert () === null
49
     */
50
    public function __construct()
51
    {
52
    }
53
54
    /**
55
     * Repository is use to query the DB, selects, etc.
56
     *
57
     * @return UserRepository
58
     */
59
    public static function getRepository()
60
    {
61
        /** @var UserRepository $userRepository */
62
63
        return Database::getManager()->getRepository('ChamiloUserBundle:User');
64
    }
65
66
    /**
67
     * Create/update/delete methods are available in the UserManager
68
     * (based in the Sonata\UserBundle\Entity\UserManager).
69
     *
70
     * @return Chamilo\UserBundle\Entity\Manager\UserManager
71
     */
72
    public static function getManager()
73
    {
74
        static $userManager;
75
76
        if (!isset($userManager)) {
77
            $encoderFactory = self::getEncoderFactory();
78
            $passwordUpdater = new \FOS\UserBundle\Util\PasswordUpdater($encoderFactory);
79
            $canonicalFieldUpdater = new \FOS\UserBundle\Util\CanonicalFieldsUpdater(
80
                new \FOS\UserBundle\Util\Canonicalizer(), new \FOS\UserBundle\Util\Canonicalizer()
81
            );
82
            $userManager = new Chamilo\UserBundle\Entity\Manager\UserManager(
83
                $passwordUpdater,
84
                $canonicalFieldUpdater,
85
                Database::getManager(),
86
                'Chamilo\\UserBundle\\Entity\\User'
87
            );
88
        }
89
90
        return $userManager;
91
    }
92
93
    /**
94
     * @param string $encryptionMethod
95
     */
96
    public static function setPasswordEncryption($encryptionMethod)
97
    {
98
        self::$encryptionMethod = $encryptionMethod;
99
    }
100
101
    /**
102
     * @return bool|mixed
103
     */
104
    public static function getPasswordEncryption()
105
    {
106
        $encryptionMethod = self::$encryptionMethod;
107
        if (empty($encryptionMethod)) {
108
            $encryptionMethod = api_get_configuration_value('password_encryption');
109
        }
110
111
        return $encryptionMethod;
112
    }
113
114
    /**
115
     * Validates the password.
116
     *
117
     * @param $encoded
118
     * @param $raw
119
     * @param $salt
120
     *
121
     * @return bool
122
     */
123
    public static function isPasswordValid($encoded, $raw, $salt)
124
    {
125
        $encoder = new \Chamilo\UserBundle\Security\Encoder(self::getPasswordEncryption());
126
127
        return $encoder->isPasswordValid($encoded, $raw, $salt);
128
    }
129
130
    /**
131
     * Detects and returns the type of encryption of the given encrypted
132
     * password.
133
     *
134
     * @param string $encoded The encrypted password
135
     * @param string $salt    The user salt, if any
136
     */
137
    public static function detectPasswordEncryption(string $encoded, string $salt): string
138
    {
139
        $encryption = false;
140
141
        $length = strlen($encoded);
142
143
        $pattern = '/^\$2y\$04\$[A-Za-z0-9\.\/]{53}$/';
144
145
        if ($length == 60 && preg_match($pattern, $encoded)) {
146
            $encryption = 'bcrypt';
147
        } elseif ($length == 32 && ctype_xdigit($encoded)) {
148
            $encryption = 'md5';
149
        } elseif ($length == 40 && ctype_xdigit($encoded)) {
150
            $encryption = 'sha1';
151
        } else {
152
            $start = strpos($encoded, '{');
153
            if ($start !== false && substr($encoded, -1, 1) == '}') {
154
                if (substr($encoded, $start + 1, -1) == $salt) {
155
                    $encryption = 'none';
156
                }
157
            }
158
        }
159
160
        return $encryption;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $encryption could return the type false which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
161
    }
162
163
    /**
164
     * Checks if the password is correct for this user.
165
     * If the password_conversion setting is true, also update the password
166
     * in the database to a new encryption method.
167
     *
168
     * @param string $encoded Encrypted password
169
     * @param string $raw     Clear password given through login form
170
     * @param string $salt    User salt, if any
171
     * @param int    $userId  The user's internal ID
172
     */
173
    public static function checkPassword(string $encoded, string $raw, string $salt, int $userId): bool
174
    {
175
        $result = false;
176
177
        if (true === api_get_configuration_value('password_conversion')) {
178
            $detectedEncryption = self::detectPasswordEncryption($encoded, $salt);
179
            if (self::getPasswordEncryption() != $detectedEncryption) {
180
                $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption);
181
                $result = $encoder->isPasswordValid($encoded, $raw, $salt);
182
                if ($result) {
183
                    $raw = $encoder->encodePassword($encoded, $salt);
184
                    self::updatePassword($userId, $raw);
185
                }
186
            } else {
187
                $result = self::isPasswordValid($encoded, $raw, $salt);
188
            }
189
        } else {
190
            $result = self::isPasswordValid($encoded, $raw, $salt);
191
        }
192
193
        return $result;
194
    }
195
196
    /**
197
     * Encrypt the password using the current encoder.
198
     *
199
     * @param string $raw The clear password
200
     */
201
    public static function encryptPassword(string $raw, User $user): string
202
    {
203
        $encoder = self::getEncoder($user);
204
205
        return $encoder->encodePassword(
206
            $raw,
207
            $user->getSalt()
208
        );
209
    }
210
211
    /**
212
     * Update the password of the given user to the given (in-clear) password.
213
     *
214
     * @param int    $userId   Internal user ID
215
     * @param string $password Password in clear
216
     */
217
    public static function updatePassword(int $userId, string $password): void
218
    {
219
        $repository = self::getRepository();
220
        /** @var User $user */
221
        $user = $repository->find($userId);
222
        $userManager = self::getManager();
223
        $user->setPlainPassword($password);
224
        $userManager->updateUser($user, true);
225
        Event::addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $userId);
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

225
        Event::/** @scrutinizer ignore-call */ 
226
               addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $userId);

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...
226
    }
227
228
    /**
229
     * Updates user expiration date.
230
     *
231
     * @param int    $userId
232
     * @param string $expirationDate
233
     */
234
    public static function updateExpirationDate($userId, $expirationDate)
235
    {
236
        $repository = self::getRepository();
237
        /** @var User $user */
238
        $user = $repository->find($userId);
239
        $userManager = self::getManager();
240
        $expirationDate = api_get_utc_datetime($expirationDate, false, true);
241
        $user->setExpirationDate($expirationDate);
242
        $userManager->updateUser($user, true);
243
    }
244
245
    /**
246
     * Creates a new user for the platform.
247
     *
248
     * @author Hugues Peeters <[email protected]>,
249
     * @author Roan Embrechts <[email protected]>
250
     *
251
     * @param string        $firstName
252
     * @param string        $lastName
253
     * @param int           $status                  (1 for course tutor, 5 for student, 6 for anonymous)
254
     * @param string        $email
255
     * @param string        $loginName
256
     * @param string        $password
257
     * @param string        $official_code           Any official code (optional)
258
     * @param string        $language                User language    (optional)
259
     * @param string        $phone                   Phone number    (optional)
260
     * @param string        $picture_uri             Picture URI        (optional)
261
     * @param string        $authSource              Authentication source (defaults to 'platform', dependind on constant)
262
     * @param string        $expirationDate          Account expiration date (optional, defaults to null)
263
     * @param int           $active                  Whether the account is enabled or disabled by default
264
     * @param int           $hr_dept_id              The department of HR in which the user is registered (defaults to 0)
265
     * @param array         $extra                   Extra fields (prefix labels with "extra_")
266
     * @param string        $encrypt_method          Used if password is given encrypted. Set to an empty string by default
267
     * @param bool          $send_mail
268
     * @param bool          $isAdmin
269
     * @param string        $address
270
     * @param bool          $sendEmailToAllAdmins
271
     * @param FormValidator $form
272
     * @param int           $creatorId
273
     * @param array         $emailTemplate
274
     * @param string        $redirectToURLAfterLogin
275
     *
276
     * @return mixed new user id - if the new user creation succeeds, false otherwise
277
     * @desc The function tries to retrieve user id from the session.
278
     * If it exists, the current user id is the creator id. If a problem arises,
279
     * @assert ('Sam','Gamegie',5,'[email protected]','jo','jo') > 1
280
     * @assert ('Pippin','Took',null,null,'jo','jo') === false
281
     */
282
    public static function create_user(
283
        $firstName,
284
        $lastName,
285
        $status,
286
        $email,
287
        $loginName,
288
        $password,
289
        $official_code = '',
290
        $language = '',
291
        $phone = '',
292
        $picture_uri = '',
293
        $authSource = PLATFORM_AUTH_SOURCE,
294
        $expirationDate = null,
295
        $active = 1,
296
        $hr_dept_id = 0,
297
        $extra = [],
298
        $encrypt_method = '',
299
        $send_mail = false,
300
        $isAdmin = false,
301
        $address = '',
302
        $sendEmailToAllAdmins = false,
303
        $form = null,
304
        $creatorId = 0,
305
        $emailTemplate = [],
306
        $redirectToURLAfterLogin = ''
307
    ) {
308
        $creatorId = empty($creatorId) ? api_get_user_id() : 0;
309
        $creatorInfo = api_get_user_info($creatorId);
310
        $creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
311
312
        $hook = HookCreateUser::create();
313
        if (!empty($hook)) {
314
            $hook->notifyCreateUser(HOOK_EVENT_TYPE_PRE);
315
        }
316
317
        if ('true' === api_get_setting('registration', 'email')) {
318
            // Force email validation.
319
            if (false === api_valid_email($email)) {
320
                Display::addFlash(
321
                   Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$email, 'warning')
322
               );
323
324
                return false;
325
            }
326
        } else {
327
            // Allow empty email. If email is set, check if is valid.
328
            if (!empty($email) && false === api_valid_email($email)) {
329
                Display::addFlash(
330
                    Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$email, 'warning')
331
                );
332
333
                return false;
334
            }
335
        }
336
337
        if ('true' === api_get_setting('login_is_email')) {
338
            if (false === api_valid_email($loginName)) {
339
                Display::addFlash(
340
                    Display::return_message(get_lang('PleaseEnterValidEmail').' - '.$loginName, 'warning')
341
                );
342
343
                return false;
344
            }
345
        } else {
346
            if (false === self::is_username_valid($loginName)) {
347
                Display::addFlash(
348
                    Display::return_message(get_lang('UsernameWrong').' - '.$loginName, 'warning')
349
                );
350
351
                return false;
352
            }
353
        }
354
355
        // First check wether the login already exists
356
        if (!self::is_username_available($loginName)) {
357
            Display::addFlash(
358
                Display::return_message(get_lang('LoginAlreadyTaken').' - '.$loginName, 'warning')
359
            );
360
361
            return false;
362
        }
363
364
        global $_configuration;
365
        $original_password = $password;
366
367
        $access_url_id = 1;
368
        if (api_get_multiple_access_url()) {
369
            $access_url_id = api_get_current_access_url_id();
370
        } else {
371
            // In some cases, the first access_url ID might be different from 1
372
            // for example when using a DB cluster or hacking the DB manually.
373
            // In this case, we want the first row, not necessarily "1".
374
            $dbm = Database::getManager();
375
            /** @var AccessUrlRepository $accessUrlRepository */
376
            $accessUrlRepository = $dbm->getRepository('ChamiloCoreBundle:AccessUrl');
377
            $accessUrl = $accessUrlRepository->getFirstId();
378
            if (!empty($accessUrl[0]) && !empty($accessUrl[0][1])) {
379
                $access_url_id = $accessUrl[0][1];
380
            }
381
        }
382
383
        if (isset($_configuration[$access_url_id]) &&
384
            is_array($_configuration[$access_url_id]) &&
385
            isset($_configuration[$access_url_id]['hosting_limit_users']) &&
386
            $_configuration[$access_url_id]['hosting_limit_users'] > 0) {
387
            $num = self::get_number_of_users(null, $access_url_id);
388
            if ($num >= $_configuration[$access_url_id]['hosting_limit_users']) {
389
                api_warn_hosting_contact('hosting_limit_users');
390
                Display::addFlash(
391
                    Display::return_message(
392
                        get_lang('PortalUsersLimitReached'),
393
                        'warning'
394
                    )
395
                );
396
397
                return false;
398
            }
399
        }
400
401
        if ($status === 1 &&
402
            isset($_configuration[$access_url_id]) &&
403
            is_array($_configuration[$access_url_id]) &&
404
            isset($_configuration[$access_url_id]['hosting_limit_teachers']) &&
405
            $_configuration[$access_url_id]['hosting_limit_teachers'] > 0
406
        ) {
407
            $num = self::get_number_of_users(1, $access_url_id);
408
            if ($num >= $_configuration[$access_url_id]['hosting_limit_teachers']) {
409
                Display::addFlash(
410
                    Display::return_message(
411
                        get_lang('PortalTeachersLimitReached'),
412
                        'warning'
413
                    )
414
                );
415
                api_warn_hosting_contact('hosting_limit_teachers');
416
417
                return false;
418
            }
419
        }
420
421
        if (empty($password)) {
422
            if ($authSource === PLATFORM_AUTH_SOURCE) {
423
                Display::addFlash(
424
                    Display::return_message(
425
                        get_lang('ThisFieldIsRequired').': '.get_lang(
426
                            'Password'
427
                        ),
428
                        'warning'
429
                    )
430
                );
431
432
                return false;
433
            }
434
435
            // We use the authSource as password.
436
            // The real validation will be by processed by the auth
437
            // source not Chamilo
438
            $password = $authSource;
439
        }
440
441
        // database table definition
442
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
443
444
        // Checking the user language
445
        $languages = api_get_languages();
446
        $language = strtolower($language);
447
448
        if (isset($languages['folder'])) {
449
            if (!in_array($language, $languages['folder'])) {
450
                $language = api_get_setting('platformLanguage');
451
            }
452
        }
453
454
        $currentDate = api_get_utc_datetime();
455
        $now = new DateTime();
456
457
        if (empty($expirationDate) || $expirationDate == '0000-00-00 00:00:00') {
458
            // Default expiration date
459
            // if there is a default duration of a valid account then
460
            // we have to change the expiration_date accordingly
461
            // Accept 0000-00-00 00:00:00 as a null value to avoid issues with
462
            // third party code using this method with the previous (pre-1.10)
463
            // value of 0000...
464
            if (api_get_setting('account_valid_duration') != '') {
465
                $expirationDate = new DateTime($currentDate);
466
                $days = (int) api_get_setting('account_valid_duration');
467
                $expirationDate->modify('+'.$days.' day');
468
            }
469
        } else {
470
            $expirationDate = api_get_utc_datetime($expirationDate);
471
            $expirationDate = new \DateTime($expirationDate, new DateTimeZone('UTC'));
472
        }
473
474
        $userManager = self::getManager();
475
476
        /** @var User $user */
477
        $user = $userManager->createUser();
478
479
        $user
480
            ->setLastname($lastName)
481
            ->setFirstname($firstName)
482
            ->setUsername($loginName)
483
            ->setStatus($status)
484
            ->setPlainPassword($password)
485
            ->setEmail($email)
486
            ->setOfficialCode($official_code)
487
            ->setPictureUri($picture_uri)
488
            ->setCreatorId($creatorId)
489
            ->setAuthSource($authSource)
490
            ->setPhone($phone)
491
            ->setAddress($address)
492
            ->setLanguage($language)
493
            ->setRegistrationDate($now)
494
            ->setHrDeptId($hr_dept_id)
495
            ->setActive($active)
496
            ->setEnabled($active)
497
        ;
498
499
        if (!empty($expirationDate)) {
500
            $user->setExpirationDate($expirationDate);
501
        }
502
503
        $userManager->updateUser($user);
504
        $userId = $user->getId();
505
506
        if (!empty($userId)) {
507
            $return = $userId;
508
            $sql = "UPDATE $table_user SET user_id = $return WHERE id = $return";
509
            Database::query($sql);
510
511
            if ($isAdmin) {
512
                self::addUserAsAdmin($user);
513
            }
514
515
            if (api_get_multiple_access_url()) {
516
                UrlManager::add_user_to_url($userId, api_get_current_access_url_id());
517
            } else {
518
                //we are adding by default the access_url_user table with access_url_id = 1
519
                UrlManager::add_user_to_url($userId, 1);
520
            }
521
522
            $extra['item_id'] = $userId;
523
524
            if (is_array($extra) && count($extra) > 0) {
525
                $userFieldValue = new ExtraFieldValue('user');
526
                // Force saving of extra fields (otherwise, if the current
527
                // user is not admin, fields not visible to the user - most
528
                // of them - are just ignored)
529
                $userFieldValue->saveFieldValues(
530
                    $extra,
531
                    true,
532
                    null,
533
                    null,
534
                    null,
535
                    true
536
                );
537
            } else {
538
                // Create notify settings by default
539
                self::update_extra_field_value(
540
                    $userId,
541
                    'mail_notify_invitation',
542
                    '1'
543
                );
544
                self::update_extra_field_value(
545
                    $userId,
546
                    'mail_notify_message',
547
                    '1'
548
                );
549
                self::update_extra_field_value(
550
                    $userId,
551
                    'mail_notify_group_message',
552
                    '1'
553
                );
554
            }
555
556
            self::update_extra_field_value(
557
                $userId,
558
                'already_logged_in',
559
                'false'
560
            );
561
562
            if (!empty($redirectToURLAfterLogin) && api_get_configuration_value('plugin_redirection_enabled')) {
563
                RedirectionPlugin::insert($userId, $redirectToURLAfterLogin);
564
            }
565
566
            if (!empty($email) && $send_mail) {
567
                $recipient_name = api_get_person_name(
568
                    $firstName,
569
                    $lastName,
570
                    null,
571
                    PERSON_NAME_EMAIL_ADDRESS
572
                );
573
                $tplSubject = new Template(
574
                    null,
575
                    false,
576
                    false,
577
                    false,
578
                    false,
579
                    false
580
                );
581
                $layoutSubject = $tplSubject->get_template('mail/subject_registration_platform.tpl');
582
                $emailSubject = $tplSubject->fetch($layoutSubject);
583
                $sender_name = api_get_person_name(
584
                    api_get_setting('administratorName'),
585
                    api_get_setting('administratorSurname'),
586
                    null,
587
                    PERSON_NAME_EMAIL_ADDRESS
588
                );
589
                $email_admin = api_get_setting('emailAdministrator');
590
591
                $url = api_get_path(WEB_PATH);
592
                if (api_is_multiple_url_enabled()) {
593
                    $access_url_id = api_get_current_access_url_id();
594
                    if ($access_url_id != -1) {
595
                        $urlInfo = api_get_access_url($access_url_id);
596
                        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...
597
                            $url = $urlInfo['url'];
598
                        }
599
                    }
600
                }
601
602
                $tplContent = new Template(
603
                    null,
604
                    false,
605
                    false,
606
                    false,
607
                    false,
608
                    false
609
                );
610
                // variables for the default template
611
                $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
612
                $tplContent->assign('login_name', $loginName);
613
                $tplContent->assign('original_password', stripslashes($original_password));
614
                $tplContent->assign('mailWebPath', $url);
615
                $tplContent->assign('new_user', $user);
616
                // Adding this variable but not used in default template, used for task BT19518 with a customized template
617
                $tplContent->assign('status_type', $status);
618
619
                $layoutContent = $tplContent->get_template('mail/content_registration_platform.tpl');
620
                $emailBody = $tplContent->fetch($layoutContent);
621
622
                $userInfo = api_get_user_info($userId);
623
                $mailTemplateManager = new MailTemplateManager();
624
625
                /* MANAGE EVENT WITH MAIL */
626
                if (EventsMail::check_if_using_class('user_registration')) {
627
                    $values["about_user"] = $return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$values was never initialized. Although not strictly required by PHP, it is generally a good practice to add $values = array(); before regardless.
Loading history...
628
                    $values["password"] = $original_password;
629
                    $values["send_to"] = [$return];
630
                    $values["prior_lang"] = null;
631
                    EventsDispatcher::events('user_registration', $values);
632
                } else {
633
                    $phoneNumber = isset($extra['mobile_phone_number']) ? $extra['mobile_phone_number'] : null;
634
                    $additionalParameters = [
635
                        'smsType' => SmsPlugin::WELCOME_LOGIN_PASSWORD,
636
                        'userId' => $return,
637
                        'mobilePhoneNumber' => $phoneNumber,
638
                        'password' => $original_password,
639
                    ];
640
641
                    $emailBodyTemplate = '';
642
                    if (!empty($emailTemplate)) {
643
                        if (isset($emailTemplate['content_registration_platform.tpl']) &&
644
                            !empty($emailTemplate['content_registration_platform.tpl'])
645
                        ) {
646
                            $emailBodyTemplate = $mailTemplateManager->parseTemplate(
647
                                $emailTemplate['content_registration_platform.tpl'],
648
                                $userInfo
649
                            );
650
                        }
651
                    }
652
653
                    $twoEmail = api_get_configuration_value('send_two_inscription_confirmation_mail');
654
                    if ($twoEmail === true) {
655
                        $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
656
                        $emailBody = $tplContent->fetch($layoutContent);
657
658
                        if (!empty($emailTemplate) &&
659
                            isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
660
                            !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
661
                        ) {
662
                            $emailBody = $mailTemplateManager->parseTemplate(
663
                                $emailTemplate['new_user_first_email_confirmation.tpl'],
664
                                $userInfo
665
                            );
666
                        }
667
668
                        api_mail_html(
669
                            $recipient_name,
670
                            $email,
671
                            $emailSubject,
672
                            $emailBody,
673
                            $sender_name,
674
                            $email_admin,
675
                            null,
676
                            null,
677
                            null,
678
                            $additionalParameters,
679
                            $creatorEmail
680
                        );
681
682
                        $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
683
                        $emailBody = $tplContent->fetch($layoutContent);
684
685
                        if (!empty($emailTemplate) &&
686
                            isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
687
                            !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
688
                        ) {
689
                            $emailBody = $mailTemplateManager->parseTemplate(
690
                                $emailTemplate['new_user_second_email_confirmation.tpl'],
691
                                $userInfo
692
                            );
693
                        }
694
695
                        api_mail_html(
696
                            $recipient_name,
697
                            $email,
698
                            $emailSubject,
699
                            $emailBody,
700
                            $sender_name,
701
                            $email_admin,
702
                            null,
703
                            null,
704
                            null,
705
                            $additionalParameters,
706
                            $creatorEmail
707
                        );
708
                    } else {
709
                        if (!empty($emailBodyTemplate)) {
710
                            $emailBody = $emailBodyTemplate;
711
                        }
712
                        $sendToInbox = api_get_configuration_value('send_inscription_msg_to_inbox');
713
                        if ($sendToInbox) {
714
                            $adminList = self::get_all_administrators();
715
                            $senderId = 1;
716
                            if (!empty($adminList)) {
717
                                $adminInfo = current($adminList);
718
                                $senderId = $adminInfo['user_id'];
719
                            }
720
721
                            MessageManager::send_message_simple(
722
                                $userId,
723
                                $emailSubject,
724
                                $emailBody,
725
                                $senderId
726
                            );
727
                        } else {
728
                            api_mail_html(
729
                                $recipient_name,
730
                                $email,
731
                                $emailSubject,
732
                                $emailBody,
733
                                $sender_name,
734
                                $email_admin,
735
                                null,
736
                                null,
737
                                null,
738
                                $additionalParameters,
739
                                $creatorEmail
740
                            );
741
                        }
742
                    }
743
744
                    $notification = api_get_configuration_value('send_notification_when_user_added');
745
                    if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {
746
                        foreach ($notification['admins'] as $adminId) {
747
                            $emailSubjectToAdmin = get_lang('UserAdded').': '.api_get_person_name($firstName, $lastName);
748
                            MessageManager::send_message_simple($adminId, $emailSubjectToAdmin, $emailBody, $userId);
749
                        }
750
                    }
751
                }
752
753
                if ($sendEmailToAllAdmins) {
754
                    $adminList = self::get_all_administrators();
755
756
                    $tplContent = new Template(
757
                        null,
758
                        false,
759
                        false,
760
                        false,
761
                        false,
762
                        false
763
                    );
764
                    // variables for the default template
765
                    $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstName, $lastName)));
766
                    $tplContent->assign('user_added', $user);
767
                    $renderer = FormValidator::getDefaultRenderer();
768
                    // Form template
769
                    $elementTemplate = ' {label}: {element} <br />';
770
                    $renderer->setElementTemplate($elementTemplate);
771
                    /** @var FormValidator $form */
772
                    $form->freeze(null, $elementTemplate);
773
                    $form->removeElement('submit');
774
                    $formData = $form->returnForm();
775
                    $url = api_get_path(WEB_CODE_PATH).'admin/user_information.php?user_id='.$user->getId();
776
                    $tplContent->assign('link', Display::url($url, $url));
777
                    $tplContent->assign('form', $formData);
778
779
                    $layoutContent = $tplContent->get_template('mail/content_registration_platform_to_admin.tpl');
780
                    $emailBody = $tplContent->fetch($layoutContent);
781
782
                    if (!empty($emailTemplate) &&
783
                        isset($emailTemplate['content_registration_platform_to_admin.tpl']) &&
784
                        !empty($emailTemplate['content_registration_platform_to_admin.tpl'])
785
                    ) {
786
                        $emailBody = $mailTemplateManager->parseTemplate(
787
                            $emailTemplate['content_registration_platform_to_admin.tpl'],
788
                            $userInfo
789
                        );
790
                    }
791
792
                    $subject = get_lang('UserAdded');
793
                    foreach ($adminList as $adminId => $data) {
794
                        MessageManager::send_message_simple(
795
                            $adminId,
796
                            $subject,
797
                            $emailBody,
798
                            $userId
799
                        );
800
                    }
801
                }
802
                /* ENDS MANAGE EVENT WITH MAIL */
803
            }
804
805
            if (!empty($hook)) {
806
                $hook->setEventData([
807
                    'return' => $userId,
808
                    'originalPassword' => $original_password,
809
                ]);
810
                $hook->notifyCreateUser(HOOK_EVENT_TYPE_POST);
811
            }
812
            Event::addEvent(LOG_USER_CREATE, LOG_USER_ID, $userId);
813
        } else {
814
            Display::addFlash(
815
                Display::return_message(get_lang('ErrorContactPlatformAdmin'))
816
            );
817
818
            return false;
819
        }
820
821
        return $return;
822
    }
823
824
    /**
825
     * Ensure the CAS-authenticated user exists in the database.
826
     *
827
     * @param $casUser string the CAS user identifier
828
     *
829
     * @throws Exception if more than one user share the same CAS user identifier
830
     *
831
     * @return string|bool the recognised user login name or false if not found
832
     */
833
    public static function casUserLoginName($casUser)
834
    {
835
        $loginName = false;
836
837
        // look inside the casUser extra field
838
        if (UserManager::is_extra_field_available('cas_user')) {
839
            $valueModel = new ExtraFieldValue('user');
840
            $itemList = $valueModel->get_item_id_from_field_variable_and_field_value(
841
                'cas_user',
842
                $casUser,
843
                false,
844
                false,
845
                true
846
            );
847
            if (false !== $itemList) {
848
                // at least one user has $casUser in the 'cas_user' extra field
849
                // we attempt to load each candidate user because there might be deleted ones
850
                // (extra field values of a deleted user might remain)
851
                foreach ($itemList as $item) {
852
                    $userId = intval($item['item_id']);
853
                    $user = UserManager::getRepository()->find($userId);
854
                    if (!is_null($user)) {
855
                        if (false === $loginName) {
856
                            $loginName = $user->getUsername();
857
                        } else {
858
                            throw new Exception(get_lang('MoreThanOneUserMatched'));
859
                        }
860
                    }
861
                }
862
            }
863
        }
864
865
        if (false === $loginName) {
866
            // no matching 'cas_user' extra field value, or no such extra field
867
            // falling back to the old behaviour: $casUser must be the login name
868
            $userId = UserManager::get_user_id_from_username($casUser);
869
            if (false !== $userId) {
870
                $loginName = $casUser;
871
            }
872
        }
873
874
        return $loginName;
875
    }
876
877
    /**
878
     * Checks the availability of extra field 'cas_user'
879
     * and creates it if missing.
880
     *
881
     * @throws Exception on failure
882
     */
883
    public static function ensureCASUserExtraFieldExists()
884
    {
885
        if (!self::is_extra_field_available('cas_user')) {
886
            $extraField = new ExtraField('user');
887
            if (false === $extraField->save(
888
                    [
889
                        'variable' => 'cas_user',
890
                        'field_type' => ExtraField::FIELD_TYPE_TEXT,
891
                        'display_text' => get_lang('CAS User Identifier'),
892
                        'visible_to_self' => true,
893
                        'filter' => true,
894
                    ]
895
                )) {
896
                throw new Exception(get_lang('FailedToCreateExtraFieldCasUser'));
897
            }
898
899
            $rules = api_get_configuration_value('cas_user_map');
900
            if (!empty($rules) && isset($rules['extra'])) {
901
                foreach ($rules['extra'] as $extra) {
902
                    $extraField->save(
903
                        [
904
                            'variable' => $extra,
905
                            'field_type' => ExtraField::FIELD_TYPE_TEXT,
906
                            'display_text' => $extra,
907
                            'visible_to_self' => false,
908
                            'filter' => false,
909
                        ]
910
                    );
911
                }
912
            }
913
        }
914
    }
915
916
    /**
917
     * Create a CAS-authenticated user from scratch, from its CAS user identifier, with temporary default values.
918
     *
919
     * @param string $casUser the CAS user identifier
920
     *
921
     * @throws Exception on error
922
     *
923
     * @return string the login name of the new user
924
     */
925
    public static function createCASAuthenticatedUserFromScratch($casUser)
926
    {
927
        self::ensureCASUserExtraFieldExists();
928
929
        $loginName = 'cas_user_'.$casUser;
930
        $defaultValue = get_lang('EditInProfile');
931
        $defaultEmailValue = get_lang('EditInProfile');
932
        require_once __DIR__.'/../../auth/external_login/functions.inc.php';
933
        if ('true' === api_get_setting('login_is_email')) {
934
            $defaultEmailValue = $casUser;
935
        }
936
        $userId = external_add_user(
937
            [
938
                'username' => $loginName,
939
                'auth_source' => CAS_AUTH_SOURCE,
940
                'firstname' => $defaultValue,
941
                'lastname' => $defaultValue,
942
                'email' => $defaultEmailValue,
943
            ]
944
        );
945
        if (false === $userId) {
946
            throw new Exception(get_lang('FailedUserCreation'));
947
        }
948
        // Not checking function update_extra_field_value return value because not reliable
949
        self::update_extra_field_value($userId, 'cas_user', $casUser);
950
951
        return $loginName;
952
    }
953
954
    public static function updateCasUser($_user)
955
    {
956
        $rules = api_get_configuration_value('cas_user_map');
957
958
        if (empty($_user)) {
959
            return false;
960
        }
961
962
        if (!empty($rules)) {
963
            $userEntity = api_get_user_entity($_user['id']);
964
            $attributes = phpCAS::getAttributes();
965
            if (isset($rules['fields'])) {
966
                $isAdmin = false;
967
                foreach ($rules['fields'] as $field => $attributeName) {
968
                    if (!isset($attributes[$attributeName])) {
969
                        continue;
970
                    }
971
                    $value = $attributes[$attributeName];
972
                    // Check replace.
973
                    if (isset($rules['replace'][$attributeName])) {
974
                        $value = $rules['replace'][$attributeName][$value];
975
                    }
976
977
                    switch ($field) {
978
                        case 'email':
979
                            $userEntity->setEmail($value);
980
                            break;
981
                        case 'firstname':
982
                            $userEntity->setFirstname($value);
983
                            break;
984
                        case 'lastname':
985
                            $userEntity->setLastname($value);
986
                            break;
987
                        case 'active':
988
                            $userEntity->setActive('false' === $value);
989
                            break;
990
                        case 'status':
991
                            if (PLATFORM_ADMIN === (int) $value) {
992
                                $value = COURSEMANAGER;
993
                                $isAdmin = true;
994
                            }
995
                            $userEntity->setStatus($value);
996
                            break;
997
                    }
998
999
                    Database::getManager()->persist($userEntity);
1000
                    Database::getManager()->flush();
1001
1002
                    if ($isAdmin) {
1003
                        self::addUserAsAdmin($userEntity);
1004
                    }
1005
                }
1006
            }
1007
1008
            if (isset($rules['extra'])) {
1009
                foreach ($rules['extra'] as $variable) {
1010
                    if (isset($attributes[$variable])) {
1011
                        self::update_extra_field_value(
1012
                            $_user['id'],
1013
                            $variable,
1014
                            $attributes[$variable]
1015
                        );
1016
                    }
1017
                }
1018
            }
1019
        }
1020
    }
1021
1022
    /**
1023
     * Create a CAS-authenticated user from LDAP, from its CAS user identifier.
1024
     *
1025
     * @param $casUser
1026
     *
1027
     * @throws Exception
1028
     *
1029
     * @return string login name of the new user
1030
     */
1031
    public static function createCASAuthenticatedUserFromLDAP($casUser)
1032
    {
1033
        self::ensureCASUserExtraFieldExists();
1034
1035
        require_once __DIR__.'/../../auth/external_login/ldap.inc.php';
1036
        $login = extldapCasUserLogin($casUser);
1037
        if (false !== $login) {
1038
            $ldapUser = extldap_authenticate($login, 'nopass', true);
1039
            if (false !== $ldapUser) {
1040
                require_once __DIR__.'/../../auth/external_login/functions.inc.php';
1041
                $user = extldap_get_chamilo_user($ldapUser);
1042
                $user['username'] = $login;
1043
                $user['auth_source'] = CAS_AUTH_SOURCE;
1044
                $userId = external_add_user($user);
1045
                if (false !== $userId) {
1046
                    // Not checking function update_extra_field_value return value because not reliable
1047
                    self::update_extra_field_value($userId, 'cas_user', $casUser);
1048
1049
                    return $login;
1050
                } else {
1051
                    throw new Exception('Could not create the new user '.$login);
1052
                }
1053
            } else {
1054
                throw new Exception('Could not load the new user from LDAP using its login '.$login);
0 ignored issues
show
Bug introduced by
Are you sure $login of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

1054
                throw new Exception('Could not load the new user from LDAP using its login './** @scrutinizer ignore-type */ $login);
Loading history...
1055
            }
1056
        } else {
1057
            throw new Exception('Could not find the new user from LDAP using its cas user identifier '.$casUser);
1058
        }
1059
    }
1060
1061
    /**
1062
     * updates user record in database from its LDAP record
1063
     * copies relevant LDAP attribute values : firstname, lastname and email.
1064
     *
1065
     * @param $login string the user login name
1066
     *
1067
     * @throws Exception when the user login name is not found in the LDAP or in the database
1068
     */
1069
    public static function updateUserFromLDAP($login)
1070
    {
1071
        require_once __DIR__.'/../../auth/external_login/ldap.inc.php';
1072
1073
        $ldapUser = extldap_authenticate($login, 'nopass', true);
1074
        if (false === $ldapUser) {
1075
            throw new Exception(get_lang('NoSuchUserInLDAP'));
1076
        }
1077
1078
        $user = extldap_get_chamilo_user($ldapUser);
1079
        $userInfo = api_get_user_info_from_username($login);
1080
        if (false === $userInfo) {
1081
            throw new Exception(get_lang('NoSuchUserInInternalDatabase'));
1082
        }
1083
1084
        $userId = UserManager::update_user(
1085
            $userInfo['user_id'],
1086
            $user["firstname"],
1087
            $user["lastname"],
1088
            $login,
1089
            null,
1090
            $userInfo['auth_source'],
1091
            $user["email"],
1092
            $userInfo['status'],
1093
            $userInfo['official_code'],
1094
            $userInfo['phone'],
1095
            $userInfo['picture_uri'],
1096
            $userInfo['expiration_date'],
1097
            $userInfo['active'],
1098
            $userInfo['creator_id'],
1099
            $userInfo['hr_dept_id'],
1100
            null,
1101
            $userInfo['language']
1102
        );
1103
        if (false === $userId) {
1104
            throw new Exception(get_lang('CouldNotUpdateUser'));
1105
        }
1106
    }
1107
1108
    /**
1109
     * Can user be deleted? This function checks whether there's a course
1110
     * in which the given user is the
1111
     * only course administrator. If that is the case, the user can't be
1112
     * deleted because the course would remain without a course admin.
1113
     *
1114
     * @param int $user_id The user id
1115
     *
1116
     * @return bool true if user can be deleted
1117
     *
1118
     * @assert (null) === false
1119
     * @assert (-1) === false
1120
     * @assert ('abc') === false
1121
     */
1122
    public static function canDeleteUser($user_id)
1123
    {
1124
        $deny = api_get_configuration_value('deny_delete_users');
1125
1126
        if ($deny) {
1127
            return false;
1128
        }
1129
1130
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1131
        $user_id = (int) $user_id;
1132
1133
        if (empty($user_id)) {
1134
            return false;
1135
        }
1136
1137
        $res = Database::query(
1138
            "SELECT c_id FROM $table_course_user WHERE status = 1 AND user_id = $user_id"
1139
        );
1140
        while ($course = Database::fetch_assoc($res)) {
1141
            $sql = Database::query(
1142
                "SELECT COUNT(id) number FROM $table_course_user WHERE status = 1 AND c_id = {$course['c_id']}"
1143
            );
1144
            $res2 = Database::fetch_assoc($sql);
1145
1146
            if ($res2['number'] == 1) {
1147
                return false;
1148
            }
1149
        }
1150
1151
        return true;
1152
    }
1153
1154
    /**
1155
     * Delete a user from the platform, and all its belongings. This is a
1156
     * very dangerous function that should only be accessible by
1157
     * super-admins. Other roles should only be able to disable a user,
1158
     * which removes access to the platform but doesn't delete anything.
1159
     *
1160
     * @param int The ID of th user to be deleted
1161
     *
1162
     * @throws Exception
1163
     *
1164
     * @return bool true if user is successfully deleted, false otherwise
1165
     * @assert (null) === false
1166
     * @assert ('abc') === false
1167
     */
1168
    public static function delete_user($user_id)
1169
    {
1170
        $user_id = (int) $user_id;
1171
1172
        if (empty($user_id)) {
1173
            return false;
1174
        }
1175
1176
        if (!self::canDeleteUser($user_id)) {
1177
            return false;
1178
        }
1179
1180
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1181
        $usergroup_rel_user = Database::get_main_table(TABLE_USERGROUP_REL_USER);
1182
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1183
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
1184
        $table_session = Database::get_main_table(TABLE_MAIN_SESSION);
1185
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
1186
        $table_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
1187
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1188
        $table_group = Database::get_course_table(TABLE_GROUP_USER);
1189
        $table_work = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
1190
1191
        // Unsubscribe the user from all groups in all his courses
1192
        $sql = "SELECT c.id
1193
                FROM $table_course c
1194
                INNER JOIN $table_course_user cu
1195
                ON (c.id = cu.c_id)
1196
                WHERE
1197
                    cu.user_id = '".$user_id."' AND
1198
                    relation_type<>".COURSE_RELATION_TYPE_RRHH."
1199
                ";
1200
1201
        $res = Database::query($sql);
1202
        while ($course = Database::fetch_object($res)) {
1203
            $sql = "DELETE FROM $table_group
1204
                    WHERE c_id = {$course->id} AND user_id = $user_id";
1205
            Database::query($sql);
1206
        }
1207
1208
        // Unsubscribe user from usergroup_rel_user
1209
        $sql = "DELETE FROM $usergroup_rel_user WHERE user_id = '".$user_id."'";
1210
        Database::query($sql);
1211
1212
        // Unsubscribe user from all courses
1213
        $sql = "DELETE FROM $table_course_user WHERE user_id = '".$user_id."'";
1214
        Database::query($sql);
1215
1216
        // Unsubscribe user from all courses in sessions
1217
        $sql = "DELETE FROM $table_session_course_user WHERE user_id = '".$user_id."'";
1218
        Database::query($sql);
1219
1220
        // If the user was added as a id_coach then set the current admin as coach see BT#
1221
        $currentUserId = api_get_user_id();
1222
        $sql = "UPDATE $table_session SET id_coach = $currentUserId
1223
                WHERE id_coach = '".$user_id."'";
1224
        Database::query($sql);
1225
1226
        $sql = "UPDATE $table_session SET session_admin_id = $currentUserId
1227
                WHERE session_admin_id = '".$user_id."'";
1228
        Database::query($sql);
1229
1230
        // Unsubscribe user from all sessions
1231
        $sql = "DELETE FROM $table_session_user
1232
                WHERE user_id = '".$user_id."'";
1233
        Database::query($sql);
1234
1235
        if (api_get_configuration_value('plugin_redirection_enabled')) {
1236
            RedirectionPlugin::deleteUserRedirection($user_id);
1237
        }
1238
1239
        $user_info = api_get_user_info($user_id);
1240
1241
        try {
1242
            self::deleteUserFiles($user_id);
1243
        } catch (Exception $exception) {
1244
            error_log('Delete user exception: '.$exception->getMessage());
1245
        }
1246
1247
        // Delete the personal course categories
1248
        $course_cat_table = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
1249
        $sql = "DELETE FROM $course_cat_table WHERE user_id = '".$user_id."'";
1250
        Database::query($sql);
1251
1252
        // Delete user from the admin table
1253
        $sql = "DELETE FROM $table_admin WHERE user_id = '".$user_id."'";
1254
        Database::query($sql);
1255
1256
        // Delete the personal agenda-items from this user
1257
        $agenda_table = Database::get_main_table(TABLE_PERSONAL_AGENDA);
1258
        $sql = "DELETE FROM $agenda_table WHERE user = '".$user_id."'";
1259
        Database::query($sql);
1260
1261
        $gradebook_results_table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
1262
        $sql = 'DELETE FROM '.$gradebook_results_table.' WHERE user_id = '.$user_id;
1263
        Database::query($sql);
1264
1265
        $extraFieldValue = new ExtraFieldValue('user');
1266
        $extraFieldValue->deleteValuesByItem($user_id);
1267
1268
        UrlManager::deleteUserFromAllUrls($user_id);
1269
1270
        if (api_get_setting('allow_social_tool') === 'true') {
1271
            $userGroup = new UserGroup();
1272
            //Delete user from portal groups
1273
            $group_list = $userGroup->get_groups_by_user($user_id);
1274
            if (!empty($group_list)) {
1275
                foreach ($group_list as $group_id => $data) {
1276
                    $userGroup->delete_user_rel_group($user_id, $group_id);
1277
                }
1278
            }
1279
1280
            // Delete user from friend lists
1281
            SocialManager::remove_user_rel_user($user_id, true);
1282
        }
1283
1284
        // Removing survey invitation
1285
        SurveyManager::delete_all_survey_invitations_by_user($user_id);
1286
1287
        // Delete students works
1288
        $sql = "DELETE FROM $table_work WHERE user_id = $user_id AND c_id <> 0";
1289
        Database::query($sql);
1290
1291
        $sql = "UPDATE c_item_property SET to_user_id = NULL
1292
                WHERE to_user_id = '".$user_id."'";
1293
        Database::query($sql);
1294
1295
        $sql = "UPDATE c_item_property SET insert_user_id = NULL
1296
                WHERE insert_user_id = '".$user_id."'";
1297
        Database::query($sql);
1298
1299
        $sql = "UPDATE c_item_property SET lastedit_user_id = NULL
1300
                WHERE lastedit_user_id = '".$user_id."'";
1301
        Database::query($sql);
1302
1303
        // Skills
1304
        $em = Database::getManager();
1305
1306
        $criteria = ['user' => $user_id];
1307
        $skills = $em->getRepository('ChamiloCoreBundle:SkillRelUser')->findBy($criteria);
1308
        if ($skills) {
1309
            /** @var SkillRelUser $skill */
1310
            foreach ($skills as $skill) {
1311
                $comments = $skill->getComments();
1312
                if ($comments) {
1313
                    /** @var SkillRelUserComment $comment */
1314
                    foreach ($comments as $comment) {
1315
                        $em->remove($comment);
1316
                    }
1317
                }
1318
                $em->remove($skill);
1319
            }
1320
            $em->flush();
1321
        }
1322
1323
        // ExtraFieldSavedSearch
1324
        $criteria = ['user' => $user_id];
1325
        $searchList = $em->getRepository('ChamiloCoreBundle:ExtraFieldSavedSearch')->findBy($criteria);
1326
        if ($searchList) {
1327
            foreach ($searchList as $search) {
1328
                $em->remove($search);
1329
            }
1330
            $em->flush();
1331
        }
1332
1333
        $connection = Database::getManager()->getConnection();
1334
        $tableExists = $connection->getSchemaManager()->tablesExist(['plugin_bbb_room']);
1335
        if ($tableExists) {
1336
            // Delete user from database
1337
            $sql = "DELETE FROM plugin_bbb_room WHERE participant_id = $user_id";
1338
            Database::query($sql);
1339
        }
1340
1341
        // Delete user/ticket relationships :(
1342
        $tableExists = $connection->getSchemaManager()->tablesExist(['ticket_ticket']);
1343
        if ($tableExists) {
1344
            TicketManager::deleteUserFromTicketSystem($user_id);
1345
        }
1346
1347
        $tableExists = $connection->getSchemaManager()->tablesExist(['c_lp_category_user']);
1348
        if ($tableExists) {
1349
            $sql = "DELETE FROM c_lp_category_user WHERE user_id = $user_id";
1350
            Database::query($sql);
1351
        }
1352
1353
        $app_plugin = new AppPlugin();
1354
        $app_plugin->performActionsWhenDeletingItem('user', $user_id);
1355
1356
        // Delete user from database
1357
        $sql = "DELETE FROM $table_user WHERE id = '".$user_id."'";
1358
        Database::query($sql);
1359
1360
        // Add event to system log
1361
        $user_id_manager = api_get_user_id();
1362
1363
        Event::addEvent(
1364
            LOG_USER_DELETE,
1365
            LOG_USER_ID,
1366
            $user_id,
1367
            api_get_utc_datetime(),
1368
            $user_id_manager
1369
        );
1370
1371
        Event::addEvent(
1372
            LOG_USER_DELETE,
1373
            LOG_USER_OBJECT,
1374
            $user_info,
1375
            api_get_utc_datetime(),
1376
            $user_id_manager
1377
        );
1378
        $cacheAvailable = api_get_configuration_value('apc');
1379
        if ($cacheAvailable === true) {
1380
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
1381
            if (apcu_exists($apcVar)) {
1382
                apcu_delete($apcVar);
1383
            }
1384
        }
1385
1386
        return true;
1387
    }
1388
1389
    /**
1390
     * Deletes users completely. Can be called either as:
1391
     * - UserManager::delete_users(1, 2, 3); or
1392
     * - UserManager::delete_users(array(1, 2, 3));.
1393
     *
1394
     * @param array|int $ids
1395
     *
1396
     * @return bool True if at least one user was successfuly deleted. False otherwise.
1397
     *
1398
     * @author Laurent Opprecht
1399
     *
1400
     * @uses \UserManager::delete_user() to actually delete each user
1401
     * @assert (null) === false
1402
     * @assert (-1) === false
1403
     * @assert (array(-1)) === false
1404
     */
1405
    public static function delete_users($ids = [])
1406
    {
1407
        $result = false;
1408
        $ids = is_array($ids) ? $ids : func_get_args();
1409
        if (!is_array($ids) || count($ids) == 0) {
1410
            return false;
1411
        }
1412
        $ids = array_map('intval', $ids);
1413
        foreach ($ids as $id) {
1414
            if (empty($id) || $id < 1) {
1415
                continue;
1416
            }
1417
            $deleted = self::delete_user($id);
1418
            $result = $deleted || $result;
1419
        }
1420
1421
        return $result;
1422
    }
1423
1424
    /**
1425
     * Disable users. Can be called either as:
1426
     * - UserManager::deactivate_users(1, 2, 3);
1427
     * - UserManager::deactivate_users(array(1, 2, 3));.
1428
     *
1429
     * @param array|int $ids
1430
     *
1431
     * @return bool
1432
     *
1433
     * @author Laurent Opprecht
1434
     * @assert (null) === false
1435
     * @assert (array(-1)) === false
1436
     */
1437
    public static function deactivate_users($ids = [])
1438
    {
1439
        if (empty($ids)) {
1440
            return false;
1441
        }
1442
1443
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1444
1445
        $ids = is_array($ids) ? $ids : func_get_args();
1446
        $ids = array_map('intval', $ids);
1447
        $ids = implode(',', $ids);
1448
1449
        $sql = "UPDATE $table_user SET active = 0 WHERE id IN ($ids)";
1450
        $r = Database::query($sql);
1451
        if ($r !== false) {
1452
            Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $ids);
1453
1454
            return true;
1455
        }
1456
1457
        return false;
1458
    }
1459
1460
    /**
1461
     * Enable users. Can be called either as:
1462
     * - UserManager::activate_users(1, 2, 3);
1463
     * - UserManager::activate_users(array(1, 2, 3));.
1464
     *
1465
     * @param array|int IDs of the users to enable
1466
     *
1467
     * @return bool
1468
     *
1469
     * @author Laurent Opprecht
1470
     * @assert (null) === false
1471
     * @assert (array(-1)) === false
1472
     */
1473
    public static function activate_users($ids = [])
1474
    {
1475
        if (empty($ids)) {
1476
            return false;
1477
        }
1478
1479
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1480
1481
        $ids = is_array($ids) ? $ids : func_get_args();
1482
        $ids = array_map('intval', $ids);
1483
        $ids = implode(',', $ids);
1484
1485
        $sql = "UPDATE $table_user SET active = 1 WHERE id IN ($ids)";
1486
        $r = Database::query($sql);
1487
        if ($r !== false) {
1488
            Event::addEvent(LOG_USER_ENABLE, LOG_USER_ID, $ids);
1489
1490
            return true;
1491
        }
1492
1493
        return false;
1494
    }
1495
1496
    /**
1497
     * Update user information with new openid.
1498
     *
1499
     * @param int    $user_id
1500
     * @param string $openid
1501
     *
1502
     * @return bool true if the user information was updated
1503
     * @assert (false,'') === false
1504
     * @assert (-1,'') === false
1505
     */
1506
    public static function update_openid($user_id, $openid)
1507
    {
1508
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1509
        if ($user_id != strval(intval($user_id))) {
1510
            return false;
1511
        }
1512
        if ($user_id === false) {
1513
            return false;
1514
        }
1515
        $sql = "UPDATE $table_user SET
1516
                openid='".Database::escape_string($openid)."'";
1517
        $sql .= " WHERE id= $user_id";
1518
1519
        if (Database::query($sql) !== false) {
1520
            return true;
1521
        }
1522
1523
        return false;
1524
    }
1525
1526
    /**
1527
     * Update user information with all the parameters passed to this function.
1528
     *
1529
     * @param int    $user_id         The ID of the user to be updated
1530
     * @param string $firstname       The user's firstname
1531
     * @param string $lastname        The user's lastname
1532
     * @param string $username        The user's username (login)
1533
     * @param string $password        The user's password
1534
     * @param string $auth_source     The authentication source (default: "platform")
1535
     * @param string $email           The user's e-mail address
1536
     * @param int    $status          The user's status
1537
     * @param string $official_code   The user's official code (usually just an internal institutional code)
1538
     * @param string $phone           The user's phone number
1539
     * @param string $picture_uri     The user's picture URL (internal to the Chamilo directory)
1540
     * @param string $expiration_date The date at which this user will be automatically disabled
1541
     * @param int    $active          Whether this account needs to be enabled (1) or disabled (0)
1542
     * @param int    $creator_id      The user ID of the person who registered this user (optional, defaults to null)
1543
     * @param int    $hr_dept_id      The department of HR in which the user is registered (optional, defaults to 0)
1544
     * @param array  $extra           Additional fields to add to this user as extra fields (defaults to null)
1545
     * @param string $language        The language to which the user account will be set
1546
     * @param string $encrypt_method  The cipher method. This parameter is deprecated. It will use the system's default
1547
     * @param bool   $send_email      Whether to send an e-mail to the user after the update is complete
1548
     * @param int    $reset_password  Method used to reset password (0, 1, 2 or 3 - see usage examples for details)
1549
     * @param string $address
1550
     * @param array  $emailTemplate
1551
     *
1552
     * @return bool|int False on error, or the user ID if the user information was updated
1553
     * @assert (false, false, false, false, false, false, false, false, false, false, false, false, false) === false
1554
     */
1555
    public static function update_user(
1556
        $user_id,
1557
        $firstname,
1558
        $lastname,
1559
        $username,
1560
        $password,
1561
        $auth_source,
1562
        $email,
1563
        $status,
1564
        $official_code,
1565
        $phone,
1566
        $picture_uri,
1567
        $expiration_date,
1568
        $active,
1569
        $creator_id = null,
1570
        $hr_dept_id = 0,
1571
        $extra = null,
1572
        $language = 'english',
1573
        $encrypt_method = '',
1574
        $send_email = false,
1575
        $reset_password = 0,
1576
        $address = null,
1577
        $emailTemplate = []
1578
    ) {
1579
        $hook = HookUpdateUser::create();
1580
        if (!empty($hook)) {
1581
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_PRE);
1582
        }
1583
        $original_password = $password;
1584
        $user_id = (int) $user_id;
1585
        $creator_id = (int) $creator_id;
1586
1587
        if (empty($user_id)) {
1588
            return false;
1589
        }
1590
1591
        $userManager = self::getManager();
1592
        /** @var User $user */
1593
        $user = self::getRepository()->find($user_id);
1594
1595
        if (empty($user)) {
1596
            return false;
1597
        }
1598
1599
        if ($reset_password == 0) {
1600
            $password = null;
1601
            $auth_source = $user->getAuthSource();
1602
        } elseif ($reset_password == 1) {
1603
            $original_password = $password = api_generate_password();
1604
            $auth_source = PLATFORM_AUTH_SOURCE;
1605
        } elseif ($reset_password == 2) {
1606
            //$password = $password;
1607
            $auth_source = PLATFORM_AUTH_SOURCE;
1608
        } elseif ($reset_password == 3) {
1609
            //$password = $password;
1610
            //$auth_source = $auth_source;
1611
        }
1612
1613
        // Checking the user language
1614
        $languages = api_get_languages();
1615
        if (!in_array($language, $languages['folder'])) {
1616
            $language = api_get_setting('platformLanguage');
1617
        }
1618
1619
        $change_active = 0;
1620
        $isUserActive = $user->getActive();
1621
        if ($isUserActive != $active) {
1622
            $change_active = 1;
1623
        }
1624
1625
        $originalUsername = $user->getUsername();
1626
1627
        // If username is different from original then check if it exists.
1628
        if ($originalUsername !== $username) {
1629
            $available = self::is_username_available($username);
1630
            if ($available === false) {
1631
                return false;
1632
            }
1633
        }
1634
1635
        if (!empty($expiration_date)) {
1636
            $expiration_date = api_get_utc_datetime($expiration_date);
1637
            $expiration_date = new \DateTime(
1638
                $expiration_date,
1639
                new DateTimeZone('UTC')
1640
            );
1641
        }
1642
1643
        $user
1644
            ->setLastname($lastname)
1645
            ->setFirstname($firstname)
1646
            ->setUsername($username)
1647
            ->setStatus($status)
1648
            ->setAuthSource($auth_source)
1649
            ->setLanguage($language)
1650
            ->setEmail($email)
1651
            ->setOfficialCode($official_code)
1652
            ->setPhone($phone)
1653
            ->setAddress($address)
1654
            ->setPictureUri($picture_uri)
1655
            ->setExpirationDate($expiration_date)
1656
            ->setActive($active)
1657
            ->setEnabled($active)
1658
            ->setHrDeptId($hr_dept_id)
1659
        ;
1660
1661
        if (!is_null($password)) {
1662
            $user->setPlainPassword($password);
1663
            Event::addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $user_id);
1664
            $date = api_get_local_time(
1665
                null,
1666
                null,
1667
                null,
1668
                null,
1669
                null,
1670
                null,
1671
                'Y-m-d'
1672
            );
1673
            $extraFieldValue = new ExtraFieldValue('user');
1674
            $extraFieldValue->save(
1675
                [
1676
                    'item_id' => $user->getId(),
1677
                    'variable' => 'password_updated_at',
1678
                    'value' => $date
1679
                ]
1680
            );
1681
        }
1682
1683
        $userManager->updateUser($user, true);
1684
        Event::addEvent(LOG_USER_UPDATE, LOG_USER_ID, $user_id);
1685
1686
        if ($change_active == 1) {
1687
            if ($active == 1) {
1688
                $event_title = LOG_USER_ENABLE;
1689
            } else {
1690
                $event_title = LOG_USER_DISABLE;
1691
            }
1692
            Event::addEvent($event_title, LOG_USER_ID, $user_id);
1693
        }
1694
1695
        if (is_array($extra) && count($extra) > 0) {
1696
            $res = true;
1697
            foreach ($extra as $fname => $fvalue) {
1698
                $res = $res && self::update_extra_field_value(
1699
                    $user_id,
1700
                    $fname,
1701
                    $fvalue
1702
                );
1703
            }
1704
        }
1705
1706
        if (!empty($email) && $send_email) {
1707
            $recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1708
            $sender_name = api_get_person_name(
1709
                api_get_setting('administratorName'),
1710
                api_get_setting('administratorSurname'),
1711
                null,
1712
                PERSON_NAME_EMAIL_ADDRESS
1713
            );
1714
            $email_admin = api_get_setting('emailAdministrator');
1715
            $url = api_get_path(WEB_PATH);
1716
            if (api_is_multiple_url_enabled()) {
1717
                $access_url_id = api_get_current_access_url_id();
1718
                if ($access_url_id != -1) {
1719
                    $url = api_get_access_url($access_url_id);
1720
                    $url = $url['url'];
1721
                }
1722
            }
1723
1724
            $tplContent = new Template(
1725
                null,
1726
                false,
1727
                false,
1728
                false,
1729
                false,
1730
                false
1731
            );
1732
            $tplContent->assign('complete_name', stripslashes(api_get_person_name($firstname, $lastname)));
1733
            $tplContent->assign('login_name', $username);
1734
            $originalPassword = '';
1735
            if ($reset_password > 0) {
1736
                $originalPassword = stripslashes($original_password);
1737
            }
1738
            $tplContent->assign('original_password', $originalPassword);
1739
            // variables for the default template
1740
            $tplContent->assign('portal_url', $url);
1741
            // Adding this variable but not used in default template, used for task BT19518 with a customized template
1742
            $tplContent->assign('status_type', $status);
1743
            $creatorInfo = api_get_user_info($creator_id);
1744
            $creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
1745
            $emailsubject = '['.api_get_setting('siteName').'] '.get_lang('YourReg').' '.api_get_setting('siteName');
1746
1747
            if (!is_null($password) && api_get_configuration_value('send_two_inscription_confirmation_mail')) {
1748
                // The user has a new password *and* we need to tell him so,
1749
                // but the configuration is set to send 2 separate e-mails
1750
                // (one for username, one for password) when sending pass
1751
                $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
1752
                $emailBody = $tplContent->fetch($layoutContent);
1753
                $mailTemplateManager = new MailTemplateManager();
1754
                if (!empty($emailTemplate) &&
1755
                    isset($emailTemplate['new_user_first_email_confirmation.tpl']) &&
1756
                    !empty($emailTemplate['new_user_first_email_confirmation.tpl'])
1757
                ) {
1758
                    $userInfo = api_get_user_info($user_id);
1759
                    $emailBody = $mailTemplateManager->parseTemplate(
1760
                        $emailTemplate['new_user_first_email_confirmation.tpl'],
1761
                        $userInfo
1762
                    );
1763
                }
1764
1765
                api_mail_html(
1766
                    $recipient_name,
1767
                    $email,
1768
                    $emailsubject,
1769
                    $emailBody,
1770
                    $sender_name,
1771
                    $email_admin,
1772
                    null,
1773
                    null,
1774
                    null,
1775
                    null,
1776
                    $creatorEmail
1777
                );
1778
1779
                $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
1780
                $emailBody = $tplContent->fetch($layoutContent);
1781
                $mailTemplateManager = new MailTemplateManager();
1782
                if (!empty($emailTemplate) &&
1783
                    isset($emailTemplate['new_user_second_email_confirmation.tpl']) &&
1784
                    !empty($emailTemplate['new_user_second_email_confirmation.tpl'])
1785
                ) {
1786
                    $userInfo = api_get_user_info($user_id);
1787
                    $emailBody = $mailTemplateManager->parseTemplate(
1788
                        $emailTemplate['new_user_second_email_confirmation.tpl'],
1789
                        $userInfo
1790
                    );
1791
                }
1792
1793
                api_mail_html(
1794
                    $recipient_name,
1795
                    $email,
1796
                    $emailsubject,
1797
                    $emailBody,
1798
                    $sender_name,
1799
                    $email_admin,
1800
                    null,
1801
                    null,
1802
                    null,
1803
                    null,
1804
                    $creatorEmail
1805
                );
1806
1807
1808
            } else {
1809
                $layoutContent = $tplContent->get_template('mail/user_edit_content.tpl');
1810
                $emailBody = $tplContent->fetch($layoutContent);
1811
                $mailTemplateManager = new MailTemplateManager();
1812
                if (!empty($emailTemplate) &&
1813
                    isset($emailTemplate['user_edit_content.tpl']) &&
1814
                    !empty($emailTemplate['user_edit_content.tpl'])
1815
                ) {
1816
                    $userInfo = api_get_user_info($user_id);
1817
                    $emailBody = $mailTemplateManager->parseTemplate(
1818
                        $emailTemplate['user_edit_content.tpl'],
1819
                        $userInfo
1820
                    );
1821
                }
1822
1823
                api_mail_html(
1824
                    $recipient_name,
1825
                    $email,
1826
                    $emailsubject,
1827
                    $emailBody,
1828
                    $sender_name,
1829
                    $email_admin,
1830
                    null,
1831
                    null,
1832
                    null,
1833
                    null,
1834
                    $creatorEmail
1835
                );
1836
            }
1837
        }
1838
1839
        if (!empty($hook)) {
1840
            $hook->setEventData(['user' => $user]);
1841
            $hook->notifyUpdateUser(HOOK_EVENT_TYPE_POST);
1842
        }
1843
1844
        $cacheAvailable = api_get_configuration_value('apc');
1845
        if ($cacheAvailable === true) {
1846
            $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$user_id;
1847
            if (apcu_exists($apcVar)) {
1848
                apcu_delete($apcVar);
1849
            }
1850
        }
1851
1852
        return $user->getId();
1853
    }
1854
1855
    /**
1856
     * Disables a user.
1857
     *
1858
     * @param int User id
1859
     *
1860
     * @return bool
1861
     *
1862
     * @uses \UserManager::change_active_state() to actually disable the user
1863
     * @assert (0) === false
1864
     */
1865
    public static function disable($user_id)
1866
    {
1867
        if (empty($user_id)) {
1868
            return false;
1869
        }
1870
        self::change_active_state($user_id, 0);
1871
1872
        return true;
1873
    }
1874
1875
    /**
1876
     * Enable a user.
1877
     *
1878
     * @param int User id
1879
     *
1880
     * @return bool
1881
     *
1882
     * @uses \UserManager::change_active_state() to actually disable the user
1883
     * @assert (0) === false
1884
     */
1885
    public static function enable($user_id)
1886
    {
1887
        if (empty($user_id)) {
1888
            return false;
1889
        }
1890
        self::change_active_state($user_id, 1);
1891
1892
        return true;
1893
    }
1894
1895
    /**
1896
     * Returns the user's id based on the original id and field name in
1897
     * the extra fields. Returns 0 if no user was found. This function is
1898
     * mostly useful in the context of a web services-based sinchronization.
1899
     *
1900
     * @param string Original user id
1901
     * @param string Original field name
1902
     *
1903
     * @return int User id
1904
     * @assert ('0','---') === 0
1905
     */
1906
    public static function get_user_id_from_original_id(
1907
        $original_user_id_value,
1908
        $original_user_id_name
1909
    ) {
1910
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
1911
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1912
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
1913
1914
        $original_user_id_name = Database::escape_string($original_user_id_name);
1915
        $original_user_id_value = Database::escape_string($original_user_id_value);
1916
1917
        $sql = "SELECT item_id as user_id
1918
                FROM $t_uf uf
1919
                INNER JOIN $t_ufv ufv
1920
                ON ufv.field_id = uf.id
1921
                WHERE
1922
                    variable = '$original_user_id_name' AND
1923
                    value = '$original_user_id_value' AND
1924
                    extra_field_type = $extraFieldType
1925
                ";
1926
        $res = Database::query($sql);
1927
        $row = Database::fetch_object($res);
1928
        if ($row) {
1929
            return $row->user_id;
1930
        }
1931
1932
        return 0;
1933
    }
1934
1935
    /**
1936
     * Check if a username is available.
1937
     *
1938
     * @param string $username the wanted username
1939
     *
1940
     * @return bool true if the wanted username is available
1941
     * @assert ('') === false
1942
     * @assert ('xyzxyzxyz') === true
1943
     */
1944
    public static function is_username_available($username)
1945
    {
1946
        if (empty($username)) {
1947
            return false;
1948
        }
1949
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
1950
        $sql = "SELECT username FROM $table_user
1951
                WHERE username = '".Database::escape_string($username)."'";
1952
        $res = Database::query($sql);
1953
1954
        return Database::num_rows($res) == 0;
1955
    }
1956
1957
    /**
1958
     * Creates a username using person's names, i.e. creates jmontoya from Julio Montoya.
1959
     *
1960
     * @param string $firstname the first name of the user
1961
     * @param string $lastname  the last name of the user
1962
     *
1963
     * @return string suggests a username that contains only ASCII-letters and digits,
1964
     *                without check for uniqueness within the system
1965
     *
1966
     * @author Julio Montoya Armas
1967
     * @author Ivan Tcholakov, 2009 - rework about internationalization.
1968
     * @assert ('','') === false
1969
     * @assert ('a','b') === 'ab'
1970
     */
1971
    public static function create_username($firstname, $lastname)
1972
    {
1973
        if (empty($firstname) && empty($lastname)) {
1974
            return false;
1975
        }
1976
1977
        // The first letter only.
1978
        $firstname = api_substr(
1979
            preg_replace(USERNAME_PURIFIER, '', $firstname),
1980
            0,
1981
            1
1982
        );
1983
        //Looking for a space in the lastname
1984
        $pos = api_strpos($lastname, ' ');
1985
        if ($pos !== false) {
1986
            $lastname = api_substr($lastname, 0, $pos);
1987
        }
1988
1989
        $lastname = preg_replace(USERNAME_PURIFIER, '', $lastname);
1990
        $username = $firstname.$lastname;
1991
        if (empty($username)) {
1992
            $username = 'user';
1993
        }
1994
1995
        $username = URLify::transliterate($username);
1996
1997
        return strtolower(substr($username, 0, USERNAME_MAX_LENGTH - 3));
1998
    }
1999
2000
    /**
2001
     * Creates a unique username, using:
2002
     * 1. the first name and the last name of a user;
2003
     * 2. an already created username but not checked for uniqueness yet.
2004
     *
2005
     * @param string $firstname The first name of a given user. If the second parameter $lastname is NULL, then this
2006
     *                          parameter is treated as username which is to be checked f
2007
     *                          or uniqueness and to be modified when it is necessary.
2008
     * @param string $lastname  the last name of the user
2009
     *
2010
     * @return string Returns a username that contains only ASCII-letters and digits and that is unique in the system.
2011
     *                Note: When the method is called several times with same parameters,
2012
     *                its results look like the following sequence: ivan, ivan2, ivan3, ivan4, ...
2013
     *
2014
     * @author Ivan Tcholakov, 2009
2015
     */
2016
    public static function create_unique_username($firstname, $lastname = null)
2017
    {
2018
        if (is_null($lastname)) {
2019
            // In this case the actual input parameter $firstname should contain ASCII-letters and digits only.
2020
            // For making this method tolerant of mistakes,
2021
            // let us transliterate and purify the suggested input username anyway.
2022
            // So, instead of the sentence $username = $firstname; we place the following:
2023
            $username = strtolower(preg_replace(USERNAME_PURIFIER, '', $firstname));
2024
        } else {
2025
            $username = self::create_username($firstname, $lastname);
2026
        }
2027
        if (!self::is_username_available($username)) {
2028
            $i = 2;
2029
            $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
2030
            while (!self::is_username_available($temp_username)) {
2031
                $i++;
2032
                $temp_username = substr($username, 0, USERNAME_MAX_LENGTH - strlen((string) $i)).$i;
2033
            }
2034
            $username = $temp_username;
2035
        }
2036
2037
        $username = URLify::transliterate($username);
2038
2039
        return $username;
2040
    }
2041
2042
    /**
2043
     * Modifies a given username accordingly to the specification for valid characters and length.
2044
     *
2045
     * @param $username string          The input username
2046
     * @param bool $strict (optional)   When this flag is TRUE, the result is guaranteed for full compliance,
2047
     *                     otherwise compliance may be partial. The default value is FALSE.
2048
     *
2049
     * @return string the resulting purified username
2050
     */
2051
    public static function purify_username($username, $strict = false)
2052
    {
2053
        if ($strict) {
2054
            // 1. Conversion of unacceptable letters (latinian letters with accents for example)
2055
            // into ASCII letters in order they not to be totally removed.
2056
            // 2. Applying the strict purifier.
2057
            // 3. Length limitation.
2058
            if ('true' === api_get_setting('login_is_email')) {
2059
                $return = substr(preg_replace(USERNAME_PURIFIER_MAIL, '', $username), 0, USERNAME_MAX_LENGTH);
2060
            } else {
2061
                $return = substr(preg_replace(USERNAME_PURIFIER, '', $username), 0, USERNAME_MAX_LENGTH);
2062
            }
2063
2064
            return URLify::transliterate($return);
2065
        }
2066
2067
        // 1. Applying the shallow purifier.
2068
        // 2. Length limitation.
2069
        return substr(
2070
            preg_replace(USERNAME_PURIFIER_SHALLOW, '', $username),
2071
            0,
2072
            USERNAME_MAX_LENGTH
2073
        );
2074
    }
2075
2076
    /**
2077
     * Checks whether the user id exists in the database.
2078
     *
2079
     * @param int $userId User id
2080
     *
2081
     * @return bool True if user id was found, false otherwise
2082
     */
2083
    public static function is_user_id_valid($userId)
2084
    {
2085
        $resultData = Database::select(
2086
            'COUNT(1) AS count',
2087
            Database::get_main_table(TABLE_MAIN_USER),
2088
            [
2089
                'where' => ['id = ?' => (int) $userId],
2090
            ],
2091
            'first'
2092
        );
2093
2094
        if ($resultData === false) {
2095
            return false;
2096
        }
2097
2098
        return $resultData['count'] > 0;
2099
    }
2100
2101
    /**
2102
     * Checks whether a given username matches to the specification strictly.
2103
     * The empty username is assumed here as invalid.
2104
     * Mostly this function is to be used in the user interface built-in validation routines
2105
     * for providing feedback while usernames are enterd manually.
2106
     *
2107
     * @param string $username the input username
2108
     *
2109
     * @return bool returns TRUE if the username is valid, FALSE otherwise
2110
     */
2111
    public static function is_username_valid($username)
2112
    {
2113
        return !empty($username) && $username == self::purify_username($username, true);
2114
    }
2115
2116
    /**
2117
     * Checks whether a username is empty. If the username contains whitespace characters,
2118
     * such as spaces, tabulators, newlines, etc.,
2119
     * it is assumed as empty too. This function is safe for validation unpurified data (during importing).
2120
     *
2121
     * @param string $username the given username
2122
     *
2123
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
2124
     */
2125
    public static function is_username_empty($username)
2126
    {
2127
        return strlen(self::purify_username($username, false)) == 0;
2128
    }
2129
2130
    /**
2131
     * Checks whether a username is too long or not.
2132
     *
2133
     * @param string $username the given username, it should contain only ASCII-letters and digits
2134
     *
2135
     * @return bool returns TRUE if length of the username exceeds the limit, FALSE otherwise
2136
     */
2137
    public static function is_username_too_long($username)
2138
    {
2139
        return strlen($username) > USERNAME_MAX_LENGTH;
2140
    }
2141
2142
    /**
2143
     * Get the users by ID.
2144
     *
2145
     * @param array  $ids    student ids
2146
     * @param string $active
2147
     * @param string $order
2148
     * @param string $limit
2149
     *
2150
     * @return array $result student information
2151
     */
2152
    public static function get_user_list_by_ids($ids = [], $active = null, $order = null, $limit = null)
2153
    {
2154
        if (empty($ids)) {
2155
            return [];
2156
        }
2157
2158
        $ids = is_array($ids) ? $ids : [$ids];
2159
        $ids = array_map('intval', $ids);
2160
        $ids = implode(',', $ids);
2161
2162
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
2163
        $sql = "SELECT * FROM $tbl_user WHERE id IN ($ids)";
2164
        if (!is_null($active)) {
2165
            $sql .= ' AND active='.($active ? '1' : '0');
2166
        }
2167
2168
        if (!is_null($order)) {
2169
            $order = Database::escape_string($order);
2170
            $sql .= ' ORDER BY '.$order;
2171
        }
2172
2173
        if (!is_null($limit)) {
2174
            $limit = Database::escape_string($limit);
2175
            $sql .= ' LIMIT '.$limit;
2176
        }
2177
2178
        $rs = Database::query($sql);
2179
        $result = [];
2180
        while ($row = Database::fetch_array($rs)) {
2181
            $result[] = $row;
2182
        }
2183
2184
        return $result;
2185
    }
2186
2187
    /**
2188
     * Get a list of users of which the given conditions match with an = 'cond'.
2189
     *
2190
     * @param array $conditions a list of condition (example : status=>STUDENT)
2191
     * @param array $order_by   a list of fields on which sort
2192
     *
2193
     * @return array an array with all users of the platform
2194
     *
2195
     * @todo security filter order by
2196
     */
2197
    public static function get_user_list(
2198
        $conditions = [],
2199
        $order_by = [],
2200
        $limit_from = false,
2201
        $limit_to = false,
2202
        $idCampus = null,
2203
        $keyword = null,
2204
        $lastConnectionDate = null,
2205
        $getCount = false,
2206
        $filterUsers = null
2207
    ) {
2208
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2209
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2210
        $return_array = [];
2211
2212
        if ($getCount) {
2213
            $sql = "SELECT count(user.id) as nbUsers FROM $user_table user ";
2214
        } else {
2215
            $sql = "SELECT user.* FROM $user_table user ";
2216
        }
2217
2218
        if (api_is_multiple_url_enabled()) {
2219
            if ($idCampus) {
2220
                $urlId = $idCampus;
2221
            } else {
2222
                $urlId = api_get_current_access_url_id();
2223
            }
2224
            $sql .= " INNER JOIN $userUrlTable url_user
2225
                      ON (user.user_id = url_user.user_id)
2226
                      WHERE url_user.access_url_id = $urlId";
2227
        } else {
2228
            $sql .= " WHERE 1=1 ";
2229
        }
2230
2231
        if (!empty($keyword)) {
2232
            $keyword = trim(Database::escape_string($keyword));
2233
            $keywordParts = array_filter(explode(' ', $keyword));
2234
            $extraKeyword = '';
2235
            if (!empty($keywordParts)) {
2236
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
2237
                if (!empty($keywordPartsFixed)) {
2238
                    $extraKeyword .= " OR
2239
                        CONCAT(user.firstname, ' ', user.lastname) LIKE '%$keywordPartsFixed%' OR
2240
                        CONCAT(user.lastname, ' ', user.firstname) LIKE '%$keywordPartsFixed%' ";
2241
                }
2242
            }
2243
2244
            $sql .= " AND (
2245
                user.username LIKE '%$keyword%' OR
2246
                user.firstname LIKE '%$keyword%' OR
2247
                user.lastname LIKE '%$keyword%' OR
2248
                user.official_code LIKE '%$keyword%' OR
2249
                user.email LIKE '%$keyword%' OR
2250
                CONCAT(user.firstname, ' ', user.lastname) LIKE '%$keyword%' OR
2251
                CONCAT(user.lastname, ' ', user.firstname) LIKE '%$keyword%'
2252
                $extraKeyword
2253
            )";
2254
        }
2255
2256
        if (!empty($lastConnectionDate)) {
2257
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
2258
            $sql .= " AND user.last_login <= '$lastConnectionDate' ";
2259
        }
2260
2261
        if (count($conditions) > 0) {
2262
            foreach ($conditions as $field => $value) {
2263
                $field = Database::escape_string($field);
2264
                $value = Database::escape_string($value);
2265
                $sql .= " AND $field = '$value'";
2266
            }
2267
        }
2268
2269
        if (!empty($filterUsers)) {
2270
            $sql .= " AND user.id IN(".implode(',', $filterUsers).")";
2271
        }
2272
2273
        if (count($order_by) > 0) {
2274
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2275
        }
2276
2277
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
2278
            $limit_from = (int) $limit_from;
2279
            $limit_to = (int) $limit_to;
2280
            $sql .= " LIMIT $limit_from, $limit_to";
2281
        }
2282
        $sql_result = Database::query($sql);
2283
2284
        if ($getCount) {
2285
            $result = Database::fetch_array($sql_result);
2286
2287
            return $result['nbUsers'];
2288
        }
2289
2290
        while ($result = Database::fetch_array($sql_result)) {
2291
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
2292
            $return_array[] = $result;
2293
        }
2294
2295
        return $return_array;
2296
    }
2297
2298
    public static function getUserListExtraConditions(
2299
        $conditions = [],
2300
        $order_by = [],
2301
        $limit_from = false,
2302
        $limit_to = false,
2303
        $idCampus = null,
2304
        $extraConditions = '',
2305
        $getCount = false
2306
    ) {
2307
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2308
        $userUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2309
        $return_array = [];
2310
        $sql = "SELECT user.* FROM $user_table user ";
2311
2312
        if ($getCount) {
2313
            $sql = "SELECT count(user.id) count FROM $user_table user ";
2314
        }
2315
2316
        if (api_is_multiple_url_enabled()) {
2317
            if ($idCampus) {
2318
                $urlId = $idCampus;
2319
            } else {
2320
                $urlId = api_get_current_access_url_id();
2321
            }
2322
            $sql .= " INNER JOIN $userUrlTable url_user
2323
                      ON (user.user_id = url_user.user_id)
2324
                      WHERE url_user.access_url_id = $urlId";
2325
        } else {
2326
            $sql .= " WHERE 1=1 ";
2327
        }
2328
2329
        $sql .= " AND status <> ".ANONYMOUS." ";
2330
2331
        if (count($conditions) > 0) {
2332
            foreach ($conditions as $field => $value) {
2333
                $field = Database::escape_string($field);
2334
                $value = Database::escape_string($value);
2335
                $sql .= " AND $field = '$value'";
2336
            }
2337
        }
2338
2339
        $sql .= str_replace("\'", "'", Database::escape_string($extraConditions));
2340
2341
        if (!empty($order_by) && count($order_by) > 0) {
2342
            $sql .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2343
        }
2344
2345
        if (is_numeric($limit_from) && is_numeric($limit_from)) {
2346
            $limit_from = (int) $limit_from;
2347
            $limit_to = (int) $limit_to;
2348
            $sql .= " LIMIT $limit_from, $limit_to";
2349
        }
2350
2351
        $sql_result = Database::query($sql);
2352
2353
        if ($getCount) {
2354
            $result = Database::fetch_array($sql_result);
2355
2356
            return $result['count'];
2357
        }
2358
2359
        while ($result = Database::fetch_array($sql_result)) {
2360
            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
2361
            $return_array[] = $result;
2362
        }
2363
2364
        return $return_array;
2365
    }
2366
2367
    /**
2368
     * Get a list of users of which the given conditions match with a LIKE '%cond%'.
2369
     *
2370
     * @param array  $conditions       a list of condition (exemple : status=>STUDENT)
2371
     * @param array  $order_by         a list of fields on which sort
2372
     * @param bool   $simple_like      Whether we want a simple LIKE 'abc' or a LIKE '%abc%'
2373
     * @param string $condition        Whether we want the filters to be combined by AND or OR
2374
     * @param array  $onlyThisUserList
2375
     *
2376
     * @return array an array with all users of the platform
2377
     *
2378
     * @todo optional course code parameter, optional sorting parameters...
2379
     * @todo security filter order_by
2380
     */
2381
    public static function getUserListLike(
2382
        $conditions = [],
2383
        $order_by = [],
2384
        $simple_like = false,
2385
        $condition = 'AND',
2386
        $onlyThisUserList = []
2387
    ) {
2388
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
2389
        $tblAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2390
        $return_array = [];
2391
        $sql_query = "SELECT user.id FROM $user_table user ";
2392
2393
        if (api_is_multiple_url_enabled()) {
2394
            $sql_query .= " INNER JOIN $tblAccessUrlRelUser auru ON auru.user_id = user.id ";
2395
        }
2396
2397
        $sql_query .= ' WHERE 1 = 1 ';
2398
        if (count($conditions) > 0) {
2399
            $temp_conditions = [];
2400
            foreach ($conditions as $field => $value) {
2401
                $field = Database::escape_string($field);
2402
                $value = Database::escape_string($value);
2403
                if ($simple_like) {
2404
                    $temp_conditions[] = $field." LIKE '$value%'";
2405
                } else {
2406
                    $temp_conditions[] = $field.' LIKE \'%'.$value.'%\'';
2407
                }
2408
            }
2409
            if (!empty($temp_conditions)) {
2410
                $sql_query .= ' AND '.implode(' '.$condition.' ', $temp_conditions);
2411
            }
2412
2413
            if (api_is_multiple_url_enabled()) {
2414
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
2415
            }
2416
        } else {
2417
            if (api_is_multiple_url_enabled()) {
2418
                $sql_query .= ' AND auru.access_url_id = '.api_get_current_access_url_id();
2419
            }
2420
        }
2421
2422
        if (!empty($onlyThisUserList)) {
2423
            $onlyThisUserListToString = implode("','", $onlyThisUserList);
2424
            $sql_query .= " AND user.id IN ('$onlyThisUserListToString') ";
2425
        }
2426
2427
        if (count($order_by) > 0) {
2428
            $sql_query .= ' ORDER BY '.Database::escape_string(implode(',', $order_by));
2429
        }
2430
2431
        $sql_result = Database::query($sql_query);
2432
        while ($result = Database::fetch_array($sql_result)) {
2433
            $userInfo = api_get_user_info($result['id']);
2434
            $return_array[] = $userInfo;
2435
        }
2436
2437
        return $return_array;
2438
    }
2439
2440
    /**
2441
     * Get user picture URL or path from user ID (returns an array).
2442
     * The return format is a complete path, enabling recovery of the directory
2443
     * with dirname() or the file with basename(). This also works for the
2444
     * functions dealing with the user's productions, as they are located in
2445
     * the same directory.
2446
     *
2447
     * @param int    $id       User ID
2448
     * @param string $type     Type of path to return (can be 'system', 'web')
2449
     * @param array  $userInfo user information to avoid query the DB
2450
     *                         returns the /main/img/unknown.jpg image set it at true
2451
     *
2452
     * @return array Array of 2 elements: 'dir' and 'file' which contain
2453
     *               the dir and file as the name implies if image does not exist it will
2454
     *               return the unknow image if anonymous parameter is true if not it returns an empty array
2455
     */
2456
    public static function get_user_picture_path_by_id(
2457
        $id,
2458
        $type = 'web',
2459
        $userInfo = []
2460
    ) {
2461
        switch ($type) {
2462
            case 'system': // Base: absolute system path.
2463
                $base = api_get_path(SYS_CODE_PATH);
2464
                break;
2465
            case 'web': // Base: absolute web path.
2466
            default:
2467
                $base = api_get_path(WEB_CODE_PATH);
2468
                break;
2469
        }
2470
2471
        $anonymousPath = [
2472
            'dir' => $base.'img/',
2473
            'file' => 'unknown.jpg',
2474
            'email' => '',
2475
        ];
2476
2477
        if (empty($id) || empty($type)) {
2478
            return $anonymousPath;
2479
        }
2480
2481
        $id = (int) $id;
2482
        if (empty($userInfo)) {
2483
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
2484
            $sql = "SELECT email, picture_uri FROM $user_table
2485
                    WHERE id = ".$id;
2486
            $res = Database::query($sql);
2487
2488
            if (!Database::num_rows($res)) {
2489
                return $anonymousPath;
2490
            }
2491
            $user = Database::fetch_array($res);
2492
            if (empty($user['picture_uri'])) {
2493
                return $anonymousPath;
2494
            }
2495
        } else {
2496
            $user = $userInfo;
2497
        }
2498
2499
        $pictureFilename = trim($user['picture_uri']);
2500
2501
        $dir = self::getUserPathById($id, $type);
2502
2503
        return [
2504
            'dir' => $dir,
2505
            'file' => $pictureFilename,
2506
            'email' => $user['email'],
2507
        ];
2508
    }
2509
2510
    /**
2511
     * *** READ BEFORE REVIEW THIS FUNCTION ***
2512
     * This function is a exact copy from get_user_picture_path_by_id() and it was create it to avoid
2513
     * a recursive calls for get_user_picture_path_by_id() in another functions when you update a user picture
2514
     * in same script, so you can find this function usage in update_user_picture() function.
2515
     *
2516
     * @param int    $id       User ID
2517
     * @param string $type     Type of path to return (can be 'system', 'web')
2518
     * @param array  $userInfo user information to avoid query the DB
2519
     *                         returns the /main/img/unknown.jpg image set it at true
2520
     *
2521
     * @return array Array of 2 elements: 'dir' and 'file' which contain
2522
     *               the dir and file as the name implies if image does not exist it will
2523
     *               return the unknown image if anonymous parameter is true if not it returns an empty array
2524
     */
2525
    public static function getUserPicturePathById($id, $type = 'web', $userInfo = [])
2526
    {
2527
        switch ($type) {
2528
            case 'system': // Base: absolute system path.
2529
                $base = api_get_path(SYS_CODE_PATH);
2530
                break;
2531
            case 'web': // Base: absolute web path.
2532
            default:
2533
                $base = api_get_path(WEB_CODE_PATH);
2534
                break;
2535
        }
2536
2537
        $anonymousPath = [
2538
            'dir' => $base.'img/',
2539
            'file' => 'unknown.jpg',
2540
            'email' => '',
2541
        ];
2542
2543
        if (empty($id) || empty($type)) {
2544
            return $anonymousPath;
2545
        }
2546
2547
        $id = (int) $id;
2548
        if (empty($userInfo)) {
2549
            $user_table = Database::get_main_table(TABLE_MAIN_USER);
2550
            $sql = "SELECT email, picture_uri FROM $user_table WHERE id = $id";
2551
            $res = Database::query($sql);
2552
2553
            if (!Database::num_rows($res)) {
2554
                return $anonymousPath;
2555
            }
2556
            $user = Database::fetch_array($res);
2557
2558
            if (empty($user['picture_uri'])) {
2559
                return $anonymousPath;
2560
            }
2561
        } else {
2562
            $user = $userInfo;
2563
        }
2564
2565
        $pictureFilename = trim($user['picture_uri']);
2566
        $dir = self::getUserPathById($id, $type);
2567
2568
        return [
2569
            'dir' => $dir,
2570
            'file' => $pictureFilename,
2571
            'email' => $user['email'],
2572
        ];
2573
    }
2574
2575
    /**
2576
     * Get user path from user ID (returns an array).
2577
     * The return format is a complete path to a folder ending with "/"
2578
     * In case the first level of subdirectory of users/ does not exist, the
2579
     * function will attempt to create it. Probably not the right place to do it
2580
     * but at least it avoids headaches in many other places.
2581
     *
2582
     * @param int    $id   User ID
2583
     * @param string $type Type of path to return (can be 'system', 'web', 'last')
2584
     *
2585
     * @return string User folder path (i.e. /var/www/chamilo/app/upload/users/1/1/)
2586
     */
2587
    public static function getUserPathById($id, $type)
2588
    {
2589
        $id = (int) $id;
2590
        if (!$id) {
2591
            return null;
2592
        }
2593
2594
        $userPath = "users/$id/";
2595
        if (api_get_setting('split_users_upload_directory') === 'true') {
2596
            $userPath = 'users/'.substr((string) $id, 0, 1).'/'.$id.'/';
2597
            // In exceptional cases, on some portals, the intermediate base user
2598
            // directory might not have been created. Make sure it is before
2599
            // going further.
2600
2601
            $rootPath = api_get_path(SYS_UPLOAD_PATH).'users/'.substr((string) $id, 0, 1);
2602
            if (!is_dir($rootPath)) {
2603
                $perm = api_get_permissions_for_new_directories();
2604
                try {
2605
                    mkdir($rootPath, $perm);
2606
                } catch (Exception $e) {
2607
                    error_log($e->getMessage());
2608
                }
2609
            }
2610
        }
2611
        switch ($type) {
2612
            case 'system': // Base: absolute system path.
2613
                $userPath = api_get_path(SYS_UPLOAD_PATH).$userPath;
2614
                break;
2615
            case 'web': // Base: absolute web path.
2616
                $userPath = api_get_path(WEB_UPLOAD_PATH).$userPath;
2617
                break;
2618
            case 'last': // Only the last part starting with users/
2619
                break;
2620
        }
2621
2622
        return $userPath;
2623
    }
2624
2625
    /**
2626
     * Gets the current user image.
2627
     *
2628
     * @param string $user_id
2629
     * @param int    $size        it can be USER_IMAGE_SIZE_SMALL,
2630
     *                            USER_IMAGE_SIZE_MEDIUM, USER_IMAGE_SIZE_BIG or  USER_IMAGE_SIZE_ORIGINAL
2631
     * @param bool   $addRandomId
2632
     * @param array  $userInfo    to avoid query the DB
2633
     *
2634
     * @return string
2635
     */
2636
    public static function getUserPicture(
2637
        $user_id,
2638
        $size = USER_IMAGE_SIZE_MEDIUM,
2639
        $addRandomId = true,
2640
        $userInfo = []
2641
    ) {
2642
        // Make sure userInfo is defined. Otherwise, define it!
2643
        if (empty($userInfo) || !is_array($userInfo) || count($userInfo) == 0) {
2644
            if (empty($user_id)) {
2645
                return '';
2646
            } else {
2647
                $userInfo = api_get_user_info($user_id);
2648
            }
2649
        }
2650
2651
        $imageWebPath = self::get_user_picture_path_by_id(
2652
            $user_id,
2653
            'web',
2654
            $userInfo
2655
        );
2656
        $pictureWebFile = $imageWebPath['file'];
2657
        $pictureWebDir = $imageWebPath['dir'];
2658
2659
        $pictureAnonymousSize = '128';
2660
        $gravatarSize = 22;
2661
        $realSizeName = 'small_';
2662
2663
        switch ($size) {
2664
            case USER_IMAGE_SIZE_SMALL:
2665
                $pictureAnonymousSize = '32';
2666
                $realSizeName = 'small_';
2667
                $gravatarSize = 32;
2668
                break;
2669
            case USER_IMAGE_SIZE_MEDIUM:
2670
                $pictureAnonymousSize = '64';
2671
                $realSizeName = 'medium_';
2672
                $gravatarSize = 64;
2673
                break;
2674
            case USER_IMAGE_SIZE_ORIGINAL:
2675
                $pictureAnonymousSize = '128';
2676
                $realSizeName = '';
2677
                $gravatarSize = 128;
2678
                break;
2679
            case USER_IMAGE_SIZE_BIG:
2680
                $pictureAnonymousSize = '128';
2681
                $realSizeName = 'big_';
2682
                $gravatarSize = 128;
2683
                break;
2684
        }
2685
2686
        $gravatarEnabled = api_get_setting('gravatar_enabled');
2687
        $anonymousPath = Display::returnIconPath('unknown.png', $pictureAnonymousSize);
2688
        if ($pictureWebFile == 'unknown.jpg' || empty($pictureWebFile)) {
2689
            if ($gravatarEnabled === 'true') {
2690
                $file = self::getGravatar(
2691
                    $imageWebPath['email'],
2692
                    $gravatarSize,
2693
                    api_get_setting('gravatar_type')
2694
                );
2695
2696
                if ($addRandomId) {
2697
                    $file .= '&rand='.uniqid();
2698
                }
2699
2700
                return $file;
2701
            }
2702
2703
            return $anonymousPath;
2704
        }
2705
2706
        $pictureSysPath = self::get_user_picture_path_by_id($user_id, 'system');
2707
        $file = $pictureSysPath['dir'].$realSizeName.$pictureWebFile;
2708
        $picture = '';
2709
        if (file_exists($file)) {
2710
            $picture = $pictureWebDir.$realSizeName.$pictureWebFile;
2711
        } else {
2712
            $file = $pictureSysPath['dir'].$pictureWebFile;
2713
            if (file_exists($file) && !is_dir($file)) {
2714
                $picture = $pictureWebFile['dir'].$pictureWebFile;
2715
            }
2716
        }
2717
2718
        if (empty($picture)) {
2719
            return $anonymousPath;
2720
        }
2721
2722
        if ($addRandomId) {
2723
            $picture .= '?rand='.uniqid();
2724
        }
2725
2726
        return $picture;
2727
    }
2728
2729
    /**
2730
     * Creates new user photos in various sizes of a user, or deletes user photos.
2731
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2732
     *
2733
     * @param int    $user_id the user internal identification number
2734
     * @param string $file    The common file name for the newly created photos.
2735
     *                        It will be checked and modified for compatibility with the file system.
2736
     *                        If full name is provided, path component is ignored.
2737
     *                        If an empty name is provided, then old user photos are deleted only,
2738
     *
2739
     * @see     UserManager::delete_user_picture() as the prefered way for deletion.
2740
     *
2741
     * @param string $source_file    the full system name of the image from which user photos will be created
2742
     * @param string $cropParameters Optional string that contents "x,y,width,height" of a cropped image format
2743
     *
2744
     * @return bool Returns the resulting common file name of created images which usually should be stored in database.
2745
     *              When deletion is requested returns empty string.
2746
     *              In case of internal error or negative validation returns FALSE.
2747
     */
2748
    public static function update_user_picture(
2749
        $user_id,
2750
        $file = null,
2751
        $source_file = null,
2752
        $cropParameters = ''
2753
    ) {
2754
        if (empty($user_id)) {
2755
            return false;
2756
        }
2757
        $delete = empty($file);
2758
        if (empty($source_file)) {
2759
            $source_file = $file;
2760
        }
2761
2762
        // User-reserved directory where photos have to be placed.
2763
        $path_info = self::getUserPicturePathById($user_id, 'system');
2764
        $path = $path_info['dir'];
2765
2766
        // If this directory does not exist - we create it.
2767
        if (!file_exists($path)) {
2768
            mkdir($path, api_get_permissions_for_new_directories(), true);
2769
        }
2770
2771
        // The old photos (if any).
2772
        $old_file = $path_info['file'];
2773
2774
        // Let us delete them.
2775
        if ($old_file != 'unknown.jpg') {
2776
            if (KEEP_THE_OLD_IMAGE_AFTER_CHANGE) {
0 ignored issues
show
Bug introduced by
The constant KEEP_THE_OLD_IMAGE_AFTER_CHANGE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2777
                $prefix = 'saved_'.date('Y_m_d_H_i_s').'_'.uniqid('').'_';
2778
                @rename($path.'small_'.$old_file, $path.$prefix.'small_'.$old_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2784
                @unlink($path.'medium_'.$old_file);
2785
                @unlink($path.'big_'.$old_file);
2786
                @unlink($path.$old_file);
2787
            }
2788
        }
2789
2790
        // Exit if only deletion has been requested. Return an empty picture name.
2791
        if ($delete) {
2792
            return '';
2793
        }
2794
2795
        // Validation 2.
2796
        $allowed_types = api_get_supported_image_extensions();
2797
        $file = str_replace('\\', '/', $file);
2798
        $filename = (($pos = strrpos($file, '/')) !== false) ? substr($file, $pos + 1) : $file;
2799
        $extension = strtolower(substr(strrchr($filename, '.'), 1));
2800
        if (!in_array($extension, $allowed_types)) {
2801
            return false;
2802
        }
2803
2804
        // This is the common name for the new photos.
2805
        if (KEEP_THE_NAME_WHEN_CHANGE_IMAGE && $old_file != 'unknown.jpg') {
0 ignored issues
show
Bug introduced by
The constant KEEP_THE_NAME_WHEN_CHANGE_IMAGE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2806
            $old_extension = strtolower(substr(strrchr($old_file, '.'), 1));
2807
            $filename = in_array($old_extension, $allowed_types) ? substr($old_file, 0, -strlen($old_extension)) : $old_file;
2808
            $filename = (substr($filename, -1) == '.') ? $filename.$extension : $filename.'.'.$extension;
2809
        } else {
2810
            $filename = api_replace_dangerous_char($filename);
2811
            if (PREFIX_IMAGE_FILENAME_WITH_UID) {
0 ignored issues
show
Bug introduced by
The constant PREFIX_IMAGE_FILENAME_WITH_UID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2812
                $filename = uniqid('').'_'.$filename;
2813
            }
2814
            // We always prefix user photos with user ids, so on setting
2815
            // api_get_setting('split_users_upload_directory') === 'true'
2816
            // the correspondent directories to be found successfully.
2817
            $filename = $user_id.'_'.$filename;
2818
        }
2819
2820
        if (!file_exists($source_file)) {
2821
            return false;
2822
        }
2823
2824
        $mimeContentType = mime_content_type($source_file);
2825
        if (false === strpos($mimeContentType, 'image')) {
2826
            return false;
2827
        }
2828
2829
        //Crop the image to adjust 1:1 ratio
2830
        $image = new Image($source_file);
2831
        $image->crop($cropParameters);
2832
2833
        // Storing the new photos in 4 versions with various sizes.
2834
        $userPath = self::getUserPathById($user_id, 'system');
2835
2836
        // If this path does not exist - we create it.
2837
        if (!file_exists($userPath)) {
2838
            mkdir($userPath, api_get_permissions_for_new_directories(), true);
2839
        }
2840
        $small = new Image($source_file);
2841
        $small->resize(32);
2842
        $small->send_image($userPath.'small_'.$filename);
2843
        $medium = new Image($source_file);
2844
        $medium->resize(85);
2845
        $medium->send_image($userPath.'medium_'.$filename);
2846
        $normal = new Image($source_file);
2847
        $normal->resize(200);
2848
        $normal->send_image($userPath.$filename);
2849
2850
        $big = new Image($source_file); // This is the original picture.
2851
        $big->send_image($userPath.'big_'.$filename);
2852
2853
        $result = $small && $medium && $normal && $big;
0 ignored issues
show
introduced by
$medium is of type Image, thus it always evaluated to true.
Loading history...
introduced by
$normal is of type Image, thus it always evaluated to true.
Loading history...
introduced by
$big is of type Image, thus it always evaluated to true.
Loading history...
2854
2855
        return $result ? $filename : false;
2856
    }
2857
2858
    /**
2859
     * Update User extra field file type into {user_folder}/{$extra_field}.
2860
     *
2861
     * @param int    $user_id     The user internal identification number
2862
     * @param string $extra_field The $extra_field The extra field name
2863
     * @param null   $file        The filename
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $file is correct as it would always require null to be passed?
Loading history...
2864
     * @param null   $source_file The temporal filename
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $source_file is correct as it would always require null to be passed?
Loading history...
2865
     *
2866
     * @return bool|null return filename if success, but false
2867
     */
2868
    public static function update_user_extra_file(
2869
        $user_id,
2870
        $extra_field = '',
2871
        $file = null,
2872
        $source_file = null
2873
    ) {
2874
        // Add Filter
2875
        $source_file = Security::filter_filename($source_file);
2876
        $file = Security::filter_filename($file);
2877
2878
        if (empty($user_id)) {
2879
            return false;
2880
        }
2881
2882
        if (empty($source_file)) {
2883
            $source_file = $file;
2884
        }
2885
2886
        // User-reserved directory where extra file have to be placed.
2887
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
2888
        $path = $path_info['dir'];
2889
        if (!empty($extra_field)) {
2890
            $path .= $extra_field.'/';
2891
        }
2892
        // If this directory does not exist - we create it.
2893
        if (!file_exists($path)) {
2894
            @mkdir($path, api_get_permissions_for_new_directories(), true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2895
        }
2896
2897
        if (filter_extension($file)) {
2898
            if (@move_uploaded_file($source_file, $path.$file)) {
2899
                if ($extra_field) {
2900
                    return $extra_field.'/'.$file;
2901
                } else {
2902
                    return $file;
2903
                }
2904
            }
2905
        }
2906
2907
        return false; // this should be returned if anything went wrong with the upload
2908
    }
2909
2910
    /**
2911
     * Deletes user photos.
2912
     * Note: This method relies on configuration setting from main/inc/conf/profile.conf.php.
2913
     *
2914
     * @param int $userId the user internal identification number
2915
     *
2916
     * @return mixed returns empty string on success, FALSE on error
2917
     */
2918
    public static function deleteUserPicture($userId)
2919
    {
2920
        return self::update_user_picture($userId);
2921
    }
2922
2923
    /**
2924
     * Returns an XHTML formatted list of productions for a user, or FALSE if he
2925
     * doesn't have any.
2926
     *
2927
     * If there has been a request to remove a production, the function will return
2928
     * without building the list unless forced to do so by the optional second
2929
     * parameter. This increases performance by avoiding to read through the
2930
     * productions on the filesystem before the removal request has been carried
2931
     * out because they'll have to be re-read afterwards anyway.
2932
     *
2933
     * @param int  $user_id    User id
2934
     * @param bool $force      Optional parameter to force building after a removal request
2935
     * @param bool $showDelete
2936
     *
2937
     * @return string A string containing the XHTML code to display the production list, or FALSE
2938
     */
2939
    public static function build_production_list($user_id, $force = false, $showDelete = false)
2940
    {
2941
        if (!$force && !empty($_POST['remove_production'])) {
2942
            return true; // postpone reading from the filesystem
2943
        }
2944
2945
        $productions = self::get_user_productions($user_id);
2946
2947
        if (empty($productions)) {
2948
            return false;
2949
        }
2950
2951
        $production_dir = self::getUserPathById($user_id, 'web');
2952
        $del_image = Display::returnIconPath('delete.png');
2953
        $add_image = Display::returnIconPath('archive.png');
2954
        $del_text = get_lang('Delete');
2955
        $production_list = '';
2956
        if (count($productions) > 0) {
2957
            $production_list = '<div class="files-production"><ul id="productions">';
2958
            foreach ($productions as $file) {
2959
                $production_list .= '<li>
2960
                    <img src="'.$add_image.'" />
2961
                    <a href="'.$production_dir.urlencode($file).'" target="_blank">
2962
                        '.htmlentities($file).'
2963
                    </a>';
2964
                if ($showDelete) {
2965
                    $production_list .= '&nbsp;&nbsp;
2966
                        <input
2967
                            style="width:16px;"
2968
                            type="image"
2969
                            name="remove_production['.urlencode($file).']"
2970
                            src="'.$del_image.'"
2971
                            alt="'.$del_text.'"
2972
                            title="'.$del_text.' '.htmlentities($file).'"
2973
                            onclick="javascript: return confirmation(\''.htmlentities($file).'\');" /></li>';
2974
                }
2975
            }
2976
            $production_list .= '</ul></div>';
2977
        }
2978
2979
        return $production_list;
2980
    }
2981
2982
    /**
2983
     * Returns an array with the user's productions.
2984
     *
2985
     * @param int $user_id User id
2986
     *
2987
     * @return array An array containing the user's productions
2988
     */
2989
    public static function get_user_productions($user_id)
2990
    {
2991
        $production_repository = self::getUserPathById($user_id, 'system');
2992
        $productions = [];
2993
2994
        if (is_dir($production_repository)) {
2995
            $handle = opendir($production_repository);
2996
            while ($file = readdir($handle)) {
2997
                if ($file == '.' ||
2998
                    $file == '..' ||
2999
                    $file == '.htaccess' ||
3000
                    is_dir($production_repository.$file)
3001
                ) {
3002
                    // skip current/parent directory and .htaccess
3003
                    continue;
3004
                }
3005
3006
                if (preg_match('/('.$user_id.'|[0-9a-f]{13}|saved)_.+\.(png|jpg|jpeg|gif)$/i', $file)) {
3007
                    // User's photos should not be listed as productions.
3008
                    continue;
3009
                }
3010
                $productions[] = $file;
3011
            }
3012
        }
3013
3014
        return $productions;
3015
    }
3016
3017
    /**
3018
     * Remove a user production.
3019
     *
3020
     * @param int    $user_id    User id
3021
     * @param string $production The production to remove
3022
     *
3023
     * @return bool
3024
     */
3025
    public static function remove_user_production($user_id, $production)
3026
    {
3027
        $production_path = self::get_user_picture_path_by_id($user_id, 'system');
3028
        $production_file = $production_path['dir'].$production;
3029
        if (is_file($production_file)) {
3030
            unlink($production_file);
3031
3032
            return true;
3033
        }
3034
3035
        return false;
3036
    }
3037
3038
    /**
3039
     * Update an extra field value for a given user.
3040
     *
3041
     * @param int    $userId   User ID
3042
     * @param string $variable Field variable name
3043
     * @param string $value    Field value
3044
     *
3045
     * @return bool true if field updated, false otherwise
3046
     */
3047
    public static function update_extra_field_value($userId, $variable, $value = '')
3048
    {
3049
        $extraFieldValue = new ExtraFieldValue('user');
3050
        $params = [
3051
            'item_id' => $userId,
3052
            'variable' => $variable,
3053
            'value' => $value,
3054
        ];
3055
3056
        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...
3057
    }
3058
3059
    /**
3060
     * Get an array of extra fields with field details (type, default value and options).
3061
     *
3062
     * @param    int    Offset (from which row)
3063
     * @param    int    Number of items
3064
     * @param    int    Column on which sorting is made
3065
     * @param    string    Sorting direction
3066
     * @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...
3067
     * @param    int        Optional. Whether we get all the fields with field_filter 1 or 0 or everything
3068
     *
3069
     * @return array Extra fields details (e.g. $list[2]['type'], $list[4]['options'][2]['title']
3070
     */
3071
    public static function get_extra_fields(
3072
        $from = 0,
3073
        $number_of_items = 0,
3074
        $column = 5,
3075
        $direction = 'ASC',
3076
        $all_visibility = true,
3077
        $field_filter = null
3078
    ) {
3079
        $fields = [];
3080
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3081
        $t_ufo = Database::get_main_table(TABLE_EXTRA_FIELD_OPTIONS);
3082
        $columns = [
3083
            'id',
3084
            'variable',
3085
            'field_type',
3086
            'display_text',
3087
            'default_value',
3088
            'field_order',
3089
            'filter',
3090
        ];
3091
        $column = (int) $column;
3092
        $sort_direction = '';
3093
        if (!empty($direction)) {
3094
            if (in_array(strtoupper($direction), ['ASC', 'DESC'])) {
3095
                $sort_direction = strtoupper($direction);
3096
            }
3097
        }
3098
        $extraFieldType = EntityExtraField::USER_FIELD_TYPE;
3099
        $sqlf = "SELECT * FROM $t_uf WHERE extra_field_type = $extraFieldType ";
3100
        if (!$all_visibility) {
3101
            $sqlf .= " AND visible_to_self = 1 ";
3102
        }
3103
        if (!is_null($field_filter)) {
3104
            $field_filter = (int) $field_filter;
3105
            $sqlf .= " AND filter = $field_filter ";
3106
        }
3107
        $sqlf .= " ORDER BY `".$columns[$column]."` $sort_direction ";
3108
        if ($number_of_items != 0) {
3109
            $sqlf .= " LIMIT ".intval($from).','.intval($number_of_items);
3110
        }
3111
        $resf = Database::query($sqlf);
3112
        if (Database::num_rows($resf) > 0) {
3113
            while ($rowf = Database::fetch_array($resf)) {
3114
                $fields[$rowf['id']] = [
3115
                    0 => $rowf['id'],
3116
                    1 => $rowf['variable'],
3117
                    2 => $rowf['field_type'],
3118
                    3 => empty($rowf['display_text']) ? '' : $rowf['display_text'],
3119
                    4 => $rowf['default_value'],
3120
                    5 => $rowf['field_order'],
3121
                    6 => $rowf['visible_to_self'],
3122
                    7 => $rowf['changeable'],
3123
                    8 => $rowf['filter'],
3124
                    9 => [],
3125
                    10 => '<a name="'.$rowf['id'].'"></a>',
3126
                ];
3127
3128
                $sqlo = "SELECT * FROM $t_ufo
3129
                         WHERE field_id = ".$rowf['id']."
3130
                         ORDER BY option_order ASC";
3131
                $reso = Database::query($sqlo);
3132
                if (Database::num_rows($reso) > 0) {
3133
                    while ($rowo = Database::fetch_array($reso)) {
3134
                        $fields[$rowf['id']][9][$rowo['id']] = [
3135
                            0 => $rowo['id'],
3136
                            1 => $rowo['option_value'],
3137
                            2 => empty($rowo['display_text']) ? '' : $rowo['display_text'],
3138
                            3 => $rowo['option_order'],
3139
                        ];
3140
                    }
3141
                }
3142
            }
3143
        }
3144
3145
        return $fields;
3146
    }
3147
3148
    /**
3149
     * Build a list of extra file already uploaded in $user_folder/{$extra_field}/.
3150
     *
3151
     * @param $user_id
3152
     * @param $extra_field
3153
     * @param bool $force
3154
     * @param bool $showDelete
3155
     *
3156
     * @return bool|string
3157
     */
3158
    public static function build_user_extra_file_list(
3159
        $user_id,
3160
        $extra_field,
3161
        $force = false,
3162
        $showDelete = false
3163
    ) {
3164
        if (!$force && !empty($_POST['remove_'.$extra_field])) {
3165
            return true; // postpone reading from the filesystem
3166
        }
3167
3168
        $extra_files = self::get_user_extra_files($user_id, $extra_field);
3169
        if (empty($extra_files)) {
3170
            return false;
3171
        }
3172
3173
        $path_info = self::get_user_picture_path_by_id($user_id, 'web');
3174
        $path = $path_info['dir'];
3175
        $del_image = Display::returnIconPath('delete.png');
3176
3177
        $del_text = get_lang('Delete');
3178
        $extra_file_list = '';
3179
        if (count($extra_files) > 0) {
3180
            $extra_file_list = '<div class="files-production"><ul id="productions">';
3181
            foreach ($extra_files as $file) {
3182
                $filename = substr($file, strlen($extra_field) + 1);
3183
                $extra_file_list .= '<li>'.Display::return_icon('archive.png').
3184
                    '<a href="'.$path.$extra_field.'/'.urlencode($filename).'" target="_blank">
3185
                        '.htmlentities($filename).
3186
                    '</a> ';
3187
                if ($showDelete) {
3188
                    $extra_file_list .= '<input
3189
                        style="width:16px;"
3190
                        type="image"
3191
                        name="remove_extra_'.$extra_field.'['.urlencode($file).']"
3192
                        src="'.$del_image.'"
3193
                        alt="'.$del_text.'"
3194
                        title="'.$del_text.' '.htmlentities($filename).'"
3195
                        onclick="javascript: return confirmation(\''.htmlentities($filename).'\');" /></li>';
3196
                }
3197
            }
3198
            $extra_file_list .= '</ul></div>';
3199
        }
3200
3201
        return $extra_file_list;
3202
    }
3203
3204
    /**
3205
     * Get valid filenames in $user_folder/{$extra_field}/.
3206
     *
3207
     * @param $user_id
3208
     * @param $extra_field
3209
     * @param bool $full_path
3210
     *
3211
     * @return array
3212
     */
3213
    public static function get_user_extra_files($user_id, $extra_field, $full_path = false)
3214
    {
3215
        if (!$full_path) {
3216
            // Nothing to do
3217
        } else {
3218
            $path_info = self::get_user_picture_path_by_id($user_id, 'system');
3219
            $path = $path_info['dir'];
3220
        }
3221
        $extra_data = self::get_extra_user_data_by_field($user_id, $extra_field);
3222
        $extra_files = $extra_data[$extra_field];
3223
3224
        $files = [];
3225
        if (is_array($extra_files)) {
3226
            foreach ($extra_files as $key => $value) {
3227
                if (!$full_path) {
3228
                    // Relative path from user folder
3229
                    $files[] = $value;
3230
                } else {
3231
                    $files[] = $path.$value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $path does not seem to be defined for all execution paths leading up to this point.
Loading history...
3232
                }
3233
            }
3234
        } elseif (!empty($extra_files)) {
3235
            if (!$full_path) {
3236
                // Relative path from user folder
3237
                $files[] = $extra_files;
3238
            } else {
3239
                $files[] = $path.$extra_files;
3240
            }
3241
        }
3242
3243
        return $files; // can be an empty array
3244
    }
3245
3246
    /**
3247
     * Remove an {$extra_file} from the user folder $user_folder/{$extra_field}/.
3248
     *
3249
     * @param int    $user_id
3250
     * @param string $extra_field
3251
     * @param string $extra_file
3252
     *
3253
     * @return bool
3254
     */
3255
    public static function remove_user_extra_file($user_id, $extra_field, $extra_file)
3256
    {
3257
        $extra_file = Security::filter_filename($extra_file);
3258
        $path_info = self::get_user_picture_path_by_id($user_id, 'system');
3259
        if (strpos($extra_file, $extra_field) !== false) {
3260
            $path_extra_file = $path_info['dir'].$extra_file;
3261
        } else {
3262
            $path_extra_file = $path_info['dir'].$extra_field.'/'.$extra_file;
3263
        }
3264
        if (is_file($path_extra_file)) {
3265
            unlink($path_extra_file);
3266
3267
            return true;
3268
        }
3269
3270
        return false;
3271
    }
3272
3273
    /**
3274
     * Creates a new extra field.
3275
     *
3276
     * @param string $variable    Field's internal variable name
3277
     * @param int    $fieldType   Field's type
3278
     * @param string $displayText Field's language var name
3279
     * @param string $default     Field's default value
3280
     *
3281
     * @return int
3282
     */
3283
    public static function create_extra_field(
3284
        $variable,
3285
        $fieldType,
3286
        $displayText,
3287
        $default
3288
    ) {
3289
        $extraField = new ExtraField('user');
3290
        $params = [
3291
            'variable' => $variable,
3292
            'field_type' => $fieldType,
3293
            'display_text' => $displayText,
3294
            'default_value' => $default,
3295
        ];
3296
3297
        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...
3298
    }
3299
3300
    /**
3301
     * Check if a field is available.
3302
     *
3303
     * @param string $variable
3304
     *
3305
     * @return bool
3306
     */
3307
    public static function is_extra_field_available($variable)
3308
    {
3309
        $extraField = new ExtraField('user');
3310
        $data = $extraField->get_handler_field_info_by_field_variable($variable);
3311
3312
        return !empty($data) ? true : false;
3313
    }
3314
3315
    /**
3316
     * Gets user extra fields data.
3317
     *
3318
     * @param    int    User ID
3319
     * @param    bool    Whether to prefix the fields indexes with "extra_" (might be used by formvalidator)
3320
     * @param    bool    Whether to return invisible fields as well
3321
     * @param    bool    Whether to split multiple-selection fields or not
3322
     * @param    mixed   Whether to filter on the value of filter
3323
     *
3324
     * @return array Array of fields => value for the given user
3325
     */
3326
    public static function get_extra_user_data(
3327
        $user_id,
3328
        $prefix = false,
3329
        $allVisibility = true,
3330
        $splitMultiple = false,
3331
        $fieldFilter = null
3332
    ) {
3333
        $user_id = (int) $user_id;
3334
3335
        if (empty($user_id)) {
3336
            return [];
3337
        }
3338
3339
        $extra_data = [];
3340
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3341
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3342
        $user_id = (int) $user_id;
3343
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
3344
                FROM $t_uf f
3345
                WHERE
3346
                    extra_field_type = ".EntityExtraField::USER_FIELD_TYPE."
3347
                ";
3348
        $filter_cond = '';
3349
3350
        if (!$allVisibility) {
3351
            if (isset($fieldFilter)) {
3352
                $fieldFilter = (int) $fieldFilter;
3353
                $filter_cond .= " AND filter = $fieldFilter ";
3354
            }
3355
            $sql .= " AND f.visible_to_self = 1 $filter_cond ";
3356
        } else {
3357
            if (isset($fieldFilter)) {
3358
                $fieldFilter = (int) $fieldFilter;
3359
                $sql .= " AND filter = $fieldFilter ";
3360
            }
3361
        }
3362
3363
        $sql .= ' ORDER BY f.field_order';
3364
3365
        $res = Database::query($sql);
3366
        if (Database::num_rows($res) > 0) {
3367
            while ($row = Database::fetch_array($res)) {
3368
                if ($row['type'] == self::USER_FIELD_TYPE_TAG) {
3369
                    $tags = self::get_user_tags_to_string($user_id, $row['id'], false);
3370
                    $extra_data['extra_'.$row['fvar']] = $tags;
3371
                } else {
3372
                    $sqlu = "SELECT value as fval
3373
                            FROM $t_ufv
3374
                            WHERE field_id = ".$row['id']." AND item_id = ".$user_id;
3375
                    $resu = Database::query($sqlu);
3376
3377
                    if (Database::num_rows($resu) > 0) {
3378
                        $rowu = Database::fetch_array($resu);
3379
                        $fval = $rowu['fval'];
3380
                        if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
3381
                            $fval = explode(';', $rowu['fval']);
3382
                        }
3383
                    } else {
3384
                        // get default value
3385
                        $sql_df = "SELECT default_value as fval_df FROM $t_uf
3386
                               WHERE id = ".$row['id'];
3387
                        $res_df = Database::query($sql_df);
3388
                        $row_df = Database::fetch_array($res_df);
3389
                        $fval = $row_df['fval_df'];
3390
                    }
3391
                    // We get here (and fill the $extra_data array) even if there
3392
                    // is no user with data (we fill it with default values)
3393
                    if ($prefix) {
3394
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
3395
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
3396
                        } else {
3397
                            $extra_data['extra_'.$row['fvar']] = $fval;
3398
                        }
3399
                    } else {
3400
                        if ($row['type'] == self::USER_FIELD_TYPE_RADIO) {
3401
                            $extra_data['extra_'.$row['fvar']]['extra_'.$row['fvar']] = $fval;
3402
                        } else {
3403
                            $extra_data[$row['fvar']] = $fval;
3404
                        }
3405
                    }
3406
                }
3407
            }
3408
        }
3409
3410
        return $extra_data;
3411
    }
3412
3413
    /**
3414
     * Get extra user data by field.
3415
     *
3416
     * @param int    user ID
3417
     * @param string the internal variable name of the field
3418
     *
3419
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
3420
     */
3421
    public static function get_extra_user_data_by_field(
3422
        $user_id,
3423
        $field_variable,
3424
        $prefix = false,
3425
        $all_visibility = true,
3426
        $splitmultiple = false
3427
    ) {
3428
        $user_id = (int) $user_id;
3429
3430
        if (empty($user_id)) {
3431
            return [];
3432
        }
3433
3434
        $extra_data = [];
3435
        $t_uf = Database::get_main_table(TABLE_EXTRA_FIELD);
3436
        $t_ufv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
3437
3438
        $sql = "SELECT f.id as id, f.variable as fvar, f.field_type as type
3439
                FROM $t_uf f
3440
                WHERE f.variable = '$field_variable' ";
3441
3442
        if (!$all_visibility) {
3443
            $sql .= " AND f.visible_to_self = 1 ";
3444
        }
3445
3446
        $sql .= " AND extra_field_type = ".EntityExtraField::USER_FIELD_TYPE;
3447
        $sql .= " ORDER BY f.field_order ";
3448
3449
        $res = Database::query($sql);
3450
        if (Database::num_rows($res) > 0) {
3451
            while ($row = Database::fetch_array($res)) {
3452
                $sqlu = "SELECT value as fval FROM $t_ufv v
3453
                         INNER JOIN $t_uf f
3454
                         ON (v.field_id = f.id)
3455
                         WHERE
3456
                            extra_field_type = ".EntityExtraField::USER_FIELD_TYPE." AND
3457
                            field_id = ".$row['id']." AND
3458
                            item_id = ".$user_id;
3459
                $resu = Database::query($sqlu);
3460
                $fval = '';
3461
                if (Database::num_rows($resu) > 0) {
3462
                    $rowu = Database::fetch_array($resu);
3463
                    $fval = $rowu['fval'];
3464
                    if ($row['type'] == self::USER_FIELD_TYPE_SELECT_MULTIPLE) {
3465
                        $fval = explode(';', $rowu['fval']);
3466
                    }
3467
                }
3468
                if ($prefix) {
3469
                    $extra_data['extra_'.$row['fvar']] = $fval;
3470
                } else {
3471
                    $extra_data[$row['fvar']] = $fval;
3472
                }
3473
            }
3474
        }
3475
3476
        return $extra_data;
3477
    }
3478
3479
    /**
3480
     * Get the extra field information for a certain field (the options as well).
3481
     *
3482
     * @param string $variable The name of the field we want to know everything about
3483
     *
3484
     * @return array Array containing all the information about the extra profile field
3485
     *               (first level of array contains field details, then 'options' sub-array contains options details,
3486
     *               as returned by the database)
3487
     *
3488
     * @author Julio Montoya
3489
     *
3490
     * @since v1.8.6
3491
     */
3492
    public static function get_extra_field_information_by_name($variable)
3493
    {
3494
        $extraField = new ExtraField('user');
3495
3496
        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...
3497
    }
3498
3499
    /**
3500
     * Get the extra field information for user tag (the options as well).
3501
     *
3502
     * @param int $variable The name of the field we want to know everything about
3503
     *
3504
     * @return array Array containing all the information about the extra profile field
3505
     *               (first level of array contains field details, then 'options' sub-array contains options details,
3506
     *               as returned by the database)
3507
     *
3508
     * @author José Loguercio
3509
     *
3510
     * @since v1.11.0
3511
     */
3512
    public static function get_extra_field_tags_information_by_name($variable)
3513
    {
3514
        $extraField = new ExtraField('user');
3515
3516
        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...
3517
    }
3518
3519
    /**
3520
     * @param string $type
3521
     *
3522
     * @return array
3523
     */
3524
    public static function get_all_extra_field_by_type($type)
3525
    {
3526
        $extraField = new ExtraField('user');
3527
3528
        return $extraField->get_all_extra_field_by_type($type);
3529
    }
3530
3531
    /**
3532
     * Get all the extra field information of a certain field (also the options).
3533
     *
3534
     * @param int $fieldId the ID of the field we want to know everything of
3535
     *
3536
     * @return array $return containing all th information about the extra profile field
3537
     *
3538
     * @author Julio Montoya
3539
     *
3540
     * @deprecated
3541
     * @since v1.8.6
3542
     */
3543
    public static function get_extra_field_information($fieldId)
3544
    {
3545
        $extraField = new ExtraField('user');
3546
3547
        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...
3548
    }
3549
3550
    /**
3551
     * Get extra user data by value.
3552
     *
3553
     * @param string $variable the internal variable name of the field
3554
     * @param string $value    the internal value of the field
3555
     * @param bool   $useLike
3556
     *
3557
     * @return array with extra data info of a user i.e array('field_variable'=>'value');
3558
     */
3559
    public static function get_extra_user_data_by_value($variable, $value, $useLike = false)
3560
    {
3561
        $extraFieldValue = new ExtraFieldValue('user');
3562
        $extraField = new ExtraField('user');
3563
3564
        $info = $extraField->get_handler_field_info_by_field_variable($variable);
3565
3566
        if (false === $info) {
3567
            return [];
3568
        }
3569
3570
        $data = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
3571
            $variable,
3572
            $value,
3573
            false,
3574
            false,
3575
            true,
3576
            $useLike
3577
        );
3578
3579
        $result = [];
3580
        if (!empty($data)) {
3581
            foreach ($data as $item) {
3582
                $result[] = $item['item_id'];
3583
            }
3584
        }
3585
3586
        return $result;
3587
    }
3588
3589
    /**
3590
     * Get extra user data by tags value.
3591
     *
3592
     * @param int    $fieldId the ID of the field we want to know everything of
3593
     * @param string $tag     the tag name for search
3594
     *
3595
     * @return array with extra data info of a user
3596
     *
3597
     * @author José Loguercio
3598
     *
3599
     * @since v1.11.0
3600
     */
3601
    public static function get_extra_user_data_by_tags($fieldId, $tag)
3602
    {
3603
        $extraField = new ExtraField('user');
3604
        $result = $extraField->getAllUserPerTag($fieldId, $tag);
3605
        $array = [];
3606
        foreach ($result as $index => $user) {
3607
            $array[] = $user['user_id'];
3608
        }
3609
3610
        return $array;
3611
    }
3612
3613
    /**
3614
     * Get extra user data by field variable.
3615
     *
3616
     * @param string $variable field variable
3617
     *
3618
     * @return array data
3619
     */
3620
    public static function get_extra_user_data_by_field_variable($variable)
3621
    {
3622
        $extraInfo = self::get_extra_field_information_by_name($variable);
3623
        $field_id = (int) $extraInfo['id'];
3624
3625
        $extraField = new ExtraFieldValue('user');
3626
        $data = $extraField->getValuesByFieldId($field_id);
3627
3628
        if (!empty($data)) {
3629
            foreach ($data as $row) {
3630
                $user_id = $row['item_id'];
3631
                $data[$user_id] = $row;
3632
            }
3633
        }
3634
3635
        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...
3636
    }
3637
3638
    /**
3639
     * Get extra user data tags by field variable.
3640
     *
3641
     * @param string $variable field variable
3642
     *
3643
     * @return array
3644
     */
3645
    public static function get_extra_user_data_for_tags($variable)
3646
    {
3647
        $data = self::get_extra_field_tags_information_by_name($variable);
3648
3649
        return $data;
3650
    }
3651
3652
    /**
3653
     * Gives a list of [session_category][session_id] for the current user.
3654
     *
3655
     * @param int  $user_id
3656
     * @param bool $is_time_over                 whether to fill the first element or not
3657
     *                                           (to give space for courses out of categories)
3658
     * @param bool $ignore_visibility_for_admins optional true if limit time from session is over, false otherwise
3659
     * @param bool $ignoreTimeLimit              ignore time start/end
3660
     * @param bool $getCount
3661
     *
3662
     * @return array list of statuses [session_category][session_id]
3663
     *
3664
     * @todo ensure multiple access urls are managed correctly
3665
     */
3666
    public static function get_sessions_by_category(
3667
        $user_id,
3668
        $is_time_over = true,
3669
        $ignore_visibility_for_admins = false,
3670
        $ignoreTimeLimit = false,
3671
        $getCount = false
3672
    ) {
3673
        $user_id = (int) $user_id;
3674
3675
        if (empty($user_id)) {
3676
            return [];
3677
        }
3678
3679
        $allowOrder = api_get_configuration_value('session_list_order');
3680
        $position = '';
3681
        if ($allowOrder) {
3682
            $position = ', s.position AS position ';
3683
        }
3684
3685
        // Get the list of sessions per user
3686
        $now = new DateTime('now', new DateTimeZone('UTC'));
3687
3688
        // LEFT JOIN is used for session_rel_course_rel_user because an inner
3689
        // join would not catch session-courses where the user is general
3690
        // session coach but which do not have students nor coaches registered
3691
        $dqlSelect = ' COUNT(DISTINCT s.id) ';
3692
3693
        if (!$getCount) {
3694
            $dqlSelect = " DISTINCT
3695
                s.id,
3696
                s.name,
3697
                s.accessStartDate AS access_start_date,
3698
                s.accessEndDate AS access_end_date,
3699
                s.duration,
3700
                sc.id AS session_category_id,
3701
                sc.name AS session_category_name,
3702
                sc.dateStart AS session_category_date_start,
3703
                sc.dateEnd AS session_category_date_end,
3704
                s.coachAccessStartDate AS coach_access_start_date,
3705
                s.coachAccessEndDate AS coach_access_end_date,
3706
                CASE WHEN s.accessEndDate IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
3707
                $position
3708
            ";
3709
        }
3710
3711
        $dql = "SELECT $dqlSelect
3712
                FROM ChamiloCoreBundle:Session AS s
3713
                LEFT JOIN ChamiloCoreBundle:SessionRelCourseRelUser AS scu WITH scu.session = s
3714
                INNER JOIN ChamiloCoreBundle:AccessUrlRelSession AS url WITH url.sessionId = s.id
3715
                LEFT JOIN ChamiloCoreBundle:SessionCategory AS sc WITH s.category = sc ";
3716
3717
        // A single OR operation on scu.user = :user OR s.generalCoach = :user
3718
        // is awfully inefficient for large sets of data (1m25s for 58K
3719
        // sessions, BT#14115) but executing a similar query twice and grouping
3720
        // the results afterwards in PHP takes about 1/1000th of the time
3721
        // (0.1s + 0.0s) for the same set of data, so we do it this way...
3722
        $dqlStudent = $dql.' WHERE scu.user = :user AND url.accessUrlId = :url ';
3723
        $dqlCoach = $dql.' WHERE s.generalCoach = :user AND url.accessUrlId = :url ';
3724
3725
        // Default order
3726
        $order = 'ORDER BY sc.name, s.name';
3727
3728
        // Order by date if showing all sessions
3729
        $showAllSessions = api_get_configuration_value('show_all_sessions_on_my_course_page') === true;
3730
        if ($showAllSessions) {
3731
            $order = 'ORDER BY s.accessStartDate';
3732
        }
3733
3734
        // Order by position
3735
        if ($allowOrder) {
3736
            $order = 'ORDER BY s.position';
3737
        }
3738
3739
        // Order by dates according to settings
3740
        $orderBySettings = api_get_configuration_value('my_courses_session_order');
3741
        if (!empty($orderBySettings) && isset($orderBySettings['field']) && isset($orderBySettings['order'])) {
3742
            $field = $orderBySettings['field'];
3743
            $orderSetting = $orderBySettings['order'];
3744
            switch ($field) {
3745
                case 'start_date':
3746
                    $order = " ORDER BY s.accessStartDate $orderSetting";
3747
                    break;
3748
                case 'end_date':
3749
                    $order = " ORDER BY s.accessEndDate $orderSetting ";
3750
                    if ($orderSetting === 'asc') {
3751
                        // Put null values at the end
3752
                        // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
3753
                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
3754
                    }
3755
                    break;
3756
                case 'name':
3757
                    $order = " ORDER BY s.name $orderSetting ";
3758
                    break;
3759
            }
3760
        }
3761
3762
        $dqlStudent .= $order;
3763
        $dqlCoach .= $order;
3764
3765
        $accessUrlId = api_get_current_access_url_id();
3766
        $dqlStudent = Database::getManager()
3767
            ->createQuery($dqlStudent)
3768
            ->setParameters(
3769
                ['user' => $user_id, 'url' => $accessUrlId]
3770
            )
3771
        ;
3772
        $dqlCoach = Database::getManager()
3773
            ->createQuery($dqlCoach)
3774
            ->setParameters(
3775
                ['user' => $user_id, 'url' => $accessUrlId]
3776
            )
3777
        ;
3778
3779
        if ($getCount) {
3780
            return $dqlStudent->getSingleScalarResult() + $dqlCoach->getSingleScalarResult();
3781
        }
3782
3783
        $sessionDataStudent = $dqlStudent->getResult();
3784
        $sessionDataCoach = $dqlCoach->getResult();
3785
3786
        $sessionData = [];
3787
        // First fill $sessionData with student sessions
3788
        if (!empty($sessionDataStudent)) {
3789
            foreach ($sessionDataStudent as $row) {
3790
                $sessionData[$row['id']] = $row;
3791
            }
3792
        }
3793
3794
        // Overwrite session data of the user as a student with session data
3795
        // of the user as a coach.
3796
        // There shouldn't be such duplicate rows, but just in case...
3797
        if (!empty($sessionDataCoach)) {
3798
            foreach ($sessionDataCoach as $row) {
3799
                $sessionData[$row['id']] = $row;
3800
            }
3801
        }
3802
3803
        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
3804
        $extraField = new ExtraFieldValue('session');
3805
        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
3806
3807
        if (empty($sessionData)) {
3808
            return [];
3809
        }
3810
3811
        $categories = [];
3812
        foreach ($sessionData as $row) {
3813
            $session_id = $row['id'];
3814
            $coachList = SessionManager::getCoachesBySession($session_id);
3815
            $categoryStart = $row['session_category_date_start'] ? $row['session_category_date_start']->format('Y-m-d') : '';
3816
            $categoryEnd = $row['session_category_date_end'] ? $row['session_category_date_end']->format('Y-m-d') : '';
3817
            $courseList = self::get_courses_list_by_session($user_id, $session_id);
3818
3819
            $daysLeft = SessionManager::getDayLeftInSession($row, $user_id);
3820
3821
            // User portal filters:
3822
            if (false === $ignoreTimeLimit) {
3823
                if ($is_time_over) {
3824
                    // History
3825
                    if ($row['duration']) {
3826
                        if ($daysLeft >= 0) {
3827
                            continue;
3828
                        }
3829
                    } else {
3830
                        if (empty($row['access_end_date'])) {
3831
                            continue;
3832
                        } else {
3833
                            if ($row['access_end_date'] > $now) {
3834
                                continue;
3835
                            }
3836
                        }
3837
                    }
3838
                } else {
3839
                    // Current user portal
3840
                    $isGeneralCoach = SessionManager::user_is_general_coach($user_id, $row['id']);
3841
                    $isCoachOfCourse = in_array($user_id, $coachList);
3842
3843
                    if (api_is_platform_admin() || $isGeneralCoach || $isCoachOfCourse) {
3844
                        // Teachers can access the session depending in the access_coach date
3845
                    } else {
3846
                        if ($row['duration']) {
3847
                            if ($daysLeft <= 0) {
3848
                                continue;
3849
                            }
3850
                        } else {
3851
                            if (isset($row['access_end_date']) &&
3852
                                !empty($row['access_end_date'])
3853
                            ) {
3854
                                if ($row['access_end_date'] <= $now) {
3855
                                    continue;
3856
                                }
3857
                            }
3858
                        }
3859
                    }
3860
                }
3861
            }
3862
3863
            $categories[$row['session_category_id']]['session_category'] = [
3864
                'id' => $row['session_category_id'],
3865
                'name' => $row['session_category_name'],
3866
                'date_start' => $categoryStart,
3867
                'date_end' => $categoryEnd,
3868
            ];
3869
3870
            $visibility = api_get_session_visibility(
3871
                $session_id,
3872
                null,
3873
                $ignore_visibility_for_admins
3874
            );
3875
3876
            if ($visibility != SESSION_VISIBLE) {
3877
                // Course Coach session visibility.
3878
                $blockedCourseCount = 0;
3879
                $closedVisibilityList = [
3880
                    COURSE_VISIBILITY_CLOSED,
3881
                    COURSE_VISIBILITY_HIDDEN,
3882
                ];
3883
3884
                foreach ($courseList as $course) {
3885
                    // Checking session visibility
3886
                    $sessionCourseVisibility = api_get_session_visibility(
3887
                        $session_id,
3888
                        $course['real_id'],
3889
                        $ignore_visibility_for_admins
3890
                    );
3891
3892
                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
3893
                    if ($courseIsVisible === false || $sessionCourseVisibility == SESSION_INVISIBLE) {
3894
                        $blockedCourseCount++;
3895
                    }
3896
                }
3897
3898
                // If all courses are blocked then no show in the list.
3899
                if ($blockedCourseCount === count($courseList)) {
3900
                    $visibility = SESSION_INVISIBLE;
3901
                } else {
3902
                    $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...
3903
                }
3904
            }
3905
3906
            switch ($visibility) {
3907
                case SESSION_VISIBLE_READ_ONLY:
3908
                case SESSION_VISIBLE:
3909
                case SESSION_AVAILABLE:
3910
                    break;
3911
                case SESSION_INVISIBLE:
3912
                    if ($ignore_visibility_for_admins === false) {
3913
                        continue 2;
3914
                    }
3915
            }
3916
3917
            $collapsed = '';
3918
            $collapsedAction = '';
3919
            if ($collapsable) {
3920
                $collapsableData = SessionManager::getCollapsableData(
3921
                    $user_id,
3922
                    $session_id,
3923
                    $extraField,
3924
                    $collapsableLink
3925
                );
3926
                $collapsed = $collapsableData['collapsed'];
3927
                $collapsedAction = $collapsableData['collapsable_link'];
3928
            }
3929
3930
            $categories[$row['session_category_id']]['sessions'][] = [
3931
                'session_name' => $row['name'],
3932
                'session_id' => $row['id'],
3933
                'access_start_date' => $row['access_start_date'] ? $row['access_start_date']->format('Y-m-d H:i:s') : null,
3934
                'access_end_date' => $row['access_end_date'] ? $row['access_end_date']->format('Y-m-d H:i:s') : null,
3935
                'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
3936
                'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
3937
                'courses' => $courseList,
3938
                'collapsed' => $collapsed,
3939
                'collapsable_link' => $collapsedAction,
3940
                'duration' => $row['duration'],
3941
            ];
3942
        }
3943
3944
        return $categories;
3945
    }
3946
3947
    /**
3948
     * Gives a list of [session_id-course_code] => [status] for the current user.
3949
     *
3950
     * @param int $user_id
3951
     * @param int $sessionLimit
3952
     *
3953
     * @return array list of statuses (session_id-course_code => status)
3954
     */
3955
    public static function get_personal_session_course_list($user_id, $sessionLimit = null)
3956
    {
3957
        // Database Table Definitions
3958
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3959
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
3960
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3961
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3962
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3963
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3964
3965
        $user_id = (int) $user_id;
3966
3967
        if (empty($user_id)) {
3968
            return [];
3969
        }
3970
3971
        // We filter the courses from the URL
3972
        $join_access_url = $where_access_url = '';
3973
        if (api_get_multiple_access_url()) {
3974
            $access_url_id = api_get_current_access_url_id();
3975
            if ($access_url_id != -1) {
3976
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3977
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course ON url_rel_course.c_id = course.id";
3978
                $where_access_url = " AND access_url_id = $access_url_id ";
3979
            }
3980
        }
3981
3982
        // Courses in which we subscribed out of any session
3983
        $tbl_user_course_category = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
3984
3985
        $sql = "SELECT
3986
                    course.code,
3987
                    course_rel_user.status course_rel_status,
3988
                    course_rel_user.sort sort,
3989
                    course_rel_user.user_course_cat user_course_cat
3990
                 FROM $tbl_course_user course_rel_user
3991
                 LEFT JOIN $tbl_course course
3992
                 ON course.id = course_rel_user.c_id
3993
                 LEFT JOIN $tbl_user_course_category user_course_category
3994
                 ON course_rel_user.user_course_cat = user_course_category.id
3995
                 $join_access_url
3996
                 WHERE
3997
                    course_rel_user.user_id = '".$user_id."' AND
3998
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
3999
                    $where_access_url
4000
                 ORDER BY user_course_category.sort, course_rel_user.sort, course.title ASC";
4001
4002
        $course_list_sql_result = Database::query($sql);
4003
4004
        $personal_course_list = [];
4005
        if (Database::num_rows($course_list_sql_result) > 0) {
4006
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4007
                $course_info = api_get_course_info($result_row['code']);
4008
                $result_row['course_info'] = $course_info;
4009
                $personal_course_list[] = $result_row;
4010
            }
4011
        }
4012
4013
        $coachCourseConditions = '';
4014
        // Getting sessions that are related to a coach in the session_rel_course_rel_user table
4015
        if (api_is_allowed_to_create_course()) {
4016
            $sessionListFromCourseCoach = [];
4017
            $sql = " SELECT DISTINCT session_id
4018
                    FROM $tbl_session_course_user
4019
                    WHERE user_id = $user_id AND status = 2 ";
4020
4021
            $result = Database::query($sql);
4022
            if (Database::num_rows($result)) {
4023
                $result = Database::store_result($result);
4024
                foreach ($result as $session) {
4025
                    $sessionListFromCourseCoach[] = $session['session_id'];
4026
                }
4027
            }
4028
            if (!empty($sessionListFromCourseCoach)) {
4029
                $condition = implode("','", $sessionListFromCourseCoach);
4030
                $coachCourseConditions = " OR ( s.id IN ('$condition'))";
4031
            }
4032
        }
4033
4034
        // Get the list of sessions where the user is subscribed
4035
        // This is divided into two different queries
4036
        $sessions = [];
4037
        $sessionLimitRestriction = '';
4038
        if (!empty($sessionLimit)) {
4039
            $sessionLimit = (int) $sessionLimit;
4040
            $sessionLimitRestriction = "LIMIT $sessionLimit";
4041
        }
4042
4043
        $sql = "SELECT DISTINCT s.id, name, access_start_date, access_end_date
4044
                FROM $tbl_session_user su INNER JOIN $tbl_session s
4045
                ON (s.id = su.session_id)
4046
                WHERE (
4047
                    su.user_id = $user_id AND
4048
                    su.relation_type <> ".SESSION_RELATION_TYPE_RRHH."
4049
                )
4050
                $coachCourseConditions
4051
                ORDER BY access_start_date, access_end_date, name
4052
                $sessionLimitRestriction
4053
        ";
4054
4055
        $result = Database::query($sql);
4056
        if (Database::num_rows($result) > 0) {
4057
            while ($row = Database::fetch_assoc($result)) {
4058
                $sessions[$row['id']] = $row;
4059
            }
4060
        }
4061
4062
        $sql = "SELECT DISTINCT
4063
                id, name, access_start_date, access_end_date
4064
                FROM $tbl_session s
4065
                WHERE (
4066
                    id_coach = $user_id
4067
                )
4068
                $coachCourseConditions
4069
                ORDER BY access_start_date, access_end_date, name";
4070
4071
        $result = Database::query($sql);
4072
        if (Database::num_rows($result) > 0) {
4073
            while ($row = Database::fetch_assoc($result)) {
4074
                if (empty($sessions[$row['id']])) {
4075
                    $sessions[$row['id']] = $row;
4076
                }
4077
            }
4078
        }
4079
4080
        if (api_is_allowed_to_create_course()) {
4081
            foreach ($sessions as $enreg) {
4082
                $session_id = $enreg['id'];
4083
                $session_visibility = api_get_session_visibility($session_id);
4084
4085
                if ($session_visibility == SESSION_INVISIBLE) {
4086
                    continue;
4087
                }
4088
4089
                // This query is horribly slow when more than a few thousand
4090
                // users and just a few sessions to which they are subscribed
4091
                $sql = "SELECT DISTINCT
4092
                        course.code code,
4093
                        course.title i,
4094
                        ".(api_is_western_name_order() ? "CONCAT(user.firstname,' ',user.lastname)" : "CONCAT(user.lastname,' ',user.firstname)")." t,
4095
                        email, course.course_language l,
4096
                        1 sort,
4097
                        category_code user_course_cat,
4098
                        access_start_date,
4099
                        access_end_date,
4100
                        session.id as session_id,
4101
                        session.name as session_name
4102
                    FROM $tbl_session_course_user as session_course_user
4103
                    INNER JOIN $tbl_course AS course
4104
                        ON course.id = session_course_user.c_id
4105
                    INNER JOIN $tbl_session as session
4106
                        ON session.id = session_course_user.session_id
4107
                    LEFT JOIN $tbl_user as user
4108
                        ON user.id = session_course_user.user_id OR session.id_coach = user.id
4109
                    WHERE
4110
                        session_course_user.session_id = $session_id AND (
4111
                            (session_course_user.user_id = $user_id AND session_course_user.status = 2)
4112
                            OR session.id_coach = $user_id
4113
                        )
4114
                    ORDER BY i";
4115
                $course_list_sql_result = Database::query($sql);
4116
                while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4117
                    $result_row['course_info'] = api_get_course_info($result_row['code']);
4118
                    $key = $result_row['session_id'].' - '.$result_row['code'];
4119
                    $personal_course_list[$key] = $result_row;
4120
                }
4121
            }
4122
        }
4123
4124
        foreach ($sessions as $enreg) {
4125
            $session_id = $enreg['id'];
4126
            $session_visibility = api_get_session_visibility($session_id);
4127
            if ($session_visibility == SESSION_INVISIBLE) {
4128
                continue;
4129
            }
4130
4131
            /* This query is very similar to the above query,
4132
               but it will check the session_rel_course_user table if there are courses registered to our user or not */
4133
            $sql = "SELECT DISTINCT
4134
                course.code code,
4135
                course.title i, CONCAT(user.lastname,' ',user.firstname) t,
4136
                email,
4137
                course.course_language l,
4138
                1 sort,
4139
                category_code user_course_cat,
4140
                access_start_date,
4141
                access_end_date,
4142
                session.id as session_id,
4143
                session.name as session_name,
4144
                IF((session_course_user.user_id = 3 AND session_course_user.status=2),'2', '5')
4145
            FROM $tbl_session_course_user as session_course_user
4146
            INNER JOIN $tbl_course AS course
4147
            ON course.id = session_course_user.c_id AND session_course_user.session_id = $session_id
4148
            INNER JOIN $tbl_session as session
4149
            ON session_course_user.session_id = session.id
4150
            LEFT JOIN $tbl_user as user ON user.id = session_course_user.user_id
4151
            WHERE session_course_user.user_id = $user_id
4152
            ORDER BY i";
4153
4154
            $course_list_sql_result = Database::query($sql);
4155
            while ($result_row = Database::fetch_array($course_list_sql_result, 'ASSOC')) {
4156
                $result_row['course_info'] = api_get_course_info($result_row['code']);
4157
                $key = $result_row['session_id'].' - '.$result_row['code'];
4158
                if (!isset($personal_course_list[$key])) {
4159
                    $personal_course_list[$key] = $result_row;
4160
                }
4161
            }
4162
        }
4163
4164
        return $personal_course_list;
4165
    }
4166
4167
    /**
4168
     * Gives a list of courses for the given user in the given session.
4169
     *
4170
     * @param int $user_id
4171
     * @param int $session_id
4172
     *
4173
     * @return array list of statuses (session_id-course_code => status)
4174
     */
4175
    public static function get_courses_list_by_session($user_id, $session_id)
4176
    {
4177
        // Database Table Definitions
4178
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4179
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
4180
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4181
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
4182
4183
        $user_id = (int) $user_id;
4184
        $session_id = (int) $session_id;
4185
        // We filter the courses from the URL
4186
        $join_access_url = $where_access_url = '';
4187
        if (api_get_multiple_access_url()) {
4188
            $urlId = api_get_current_access_url_id();
4189
            if (-1 != $urlId) {
4190
                $tbl_url_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4191
                $join_access_url = " ,  $tbl_url_session url_rel_session ";
4192
                $where_access_url = " AND access_url_id = $urlId AND url_rel_session.session_id = $session_id ";
4193
            }
4194
        }
4195
4196
        $exlearnerCondition = "";
4197
        if (false !== api_get_configuration_value('user_edition_extra_field_to_check')) {
4198
            $exlearnerCondition = " AND scu.status NOT IN(".COURSE_EXLEARNER.")";
4199
        }
4200
4201
        /* This query is very similar to the query below, but it will check the
4202
        session_rel_course_user table if there are courses registered
4203
        to our user or not */
4204
        $sql = "SELECT DISTINCT
4205
                    c.title,
4206
                    c.visibility,
4207
                    c.id as real_id,
4208
                    c.code as course_code,
4209
                    c.course_language,
4210
                    sc.position,
4211
                    c.unsubscribe
4212
                FROM $tbl_session_course_user as scu
4213
                INNER JOIN $tbl_session_course sc
4214
                ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
4215
                INNER JOIN $tableCourse as c
4216
                ON (scu.c_id = c.id)
4217
                $join_access_url
4218
                WHERE
4219
                    scu.user_id = $user_id AND
4220
                    scu.session_id = $session_id
4221
                    $where_access_url
4222
                    $exlearnerCondition
4223
                ORDER BY sc.position ASC";
4224
4225
        $myCourseList = [];
4226
        $courses = [];
4227
        $result = Database::query($sql);
4228
        if (Database::num_rows($result) > 0) {
4229
            while ($result_row = Database::fetch_array($result, 'ASSOC')) {
4230
                $result_row['status'] = 5;
4231
                if (!in_array($result_row['real_id'], $courses)) {
4232
                    $position = $result_row['position'];
4233
                    if (!isset($myCourseList[$position])) {
4234
                        $myCourseList[$position] = $result_row;
4235
                    } else {
4236
                        $myCourseList[] = $result_row;
4237
                    }
4238
                    $courses[] = $result_row['real_id'];
4239
                }
4240
            }
4241
        }
4242
4243
        if (api_is_allowed_to_create_course()) {
4244
            $sql = "SELECT DISTINCT
4245
                        c.title,
4246
                        c.visibility,
4247
                        c.id as real_id,
4248
                        c.code as course_code,
4249
                        c.course_language,
4250
                        sc.position,
4251
                        c.unsubscribe
4252
                    FROM $tbl_session_course_user as scu
4253
                    INNER JOIN $tbl_session as s
4254
                    ON (scu.session_id = s.id)
4255
                    INNER JOIN $tbl_session_course sc
4256
                    ON (scu.session_id = sc.session_id AND scu.c_id = sc.c_id)
4257
                    INNER JOIN $tableCourse as c
4258
                    ON (scu.c_id = c.id)
4259
                    $join_access_url
4260
                    WHERE
4261
                      s.id = $session_id AND
4262
                      (
4263
                        (scu.user_id = $user_id AND scu.status = 2) OR
4264
                        s.id_coach = $user_id
4265
                      )
4266
                    $where_access_url
4267
                    $exlearnerCondition
4268
                    ORDER BY sc.position ASC";
4269
            $result = Database::query($sql);
4270
4271
            if (Database::num_rows($result) > 0) {
4272
                while ($result_row = Database::fetch_array($result, 'ASSOC')) {
4273
                    $result_row['status'] = 2;
4274
                    if (!in_array($result_row['real_id'], $courses)) {
4275
                        $position = $result_row['position'];
4276
                        if (!isset($myCourseList[$position])) {
4277
                            $myCourseList[$position] = $result_row;
4278
                        } else {
4279
                            $myCourseList[] = $result_row;
4280
                        }
4281
                        $courses[] = $result_row['real_id'];
4282
                    }
4283
                }
4284
            }
4285
        }
4286
4287
        if (api_is_drh()) {
4288
            $sessionList = SessionManager::get_sessions_followed_by_drh($user_id);
4289
            $sessionList = array_keys($sessionList);
4290
            if (in_array($session_id, $sessionList)) {
4291
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
4292
                if (!empty($courseList)) {
4293
                    foreach ($courseList as $course) {
4294
                        if (!in_array($course['id'], $courses)) {
4295
                            $position = $course['position'];
4296
                            if (!isset($myCourseList[$position])) {
4297
                                $myCourseList[$position] = $course;
4298
                            } else {
4299
                                $myCourseList[] = $course;
4300
                            }
4301
                        }
4302
                    }
4303
                }
4304
            }
4305
        } else {
4306
            //check if user is general coach for this session
4307
            $sessionInfo = api_get_session_info($session_id);
4308
            if ($sessionInfo['id_coach'] == $user_id) {
4309
                $courseList = SessionManager::get_course_list_by_session_id($session_id);
4310
                if (!empty($courseList)) {
4311
                    foreach ($courseList as $course) {
4312
                        if (!in_array($course['id'], $courses)) {
4313
                            $position = $course['position'];
4314
                            if (!isset($myCourseList[$position])) {
4315
                                $myCourseList[$position] = $course;
4316
                            } else {
4317
                                $myCourseList[] = $course;
4318
                            }
4319
                        }
4320
                    }
4321
                }
4322
            }
4323
        }
4324
4325
        if (!empty($myCourseList)) {
4326
            ksort($myCourseList);
4327
            $checkPosition = array_filter(array_column($myCourseList, 'position'));
4328
            if (empty($checkPosition)) {
4329
                // The session course list doesn't have any position,
4330
                // then order the course list by course code.
4331
                $orderByCode = array_column($myCourseList, 'course_code');
4332
                sort($orderByCode, SORT_NATURAL);
4333
                $newCourseList = [];
4334
                foreach ($orderByCode as $code) {
4335
                    foreach ($myCourseList as $course) {
4336
                        if ($code === $course['course_code']) {
4337
                            $newCourseList[] = $course;
4338
                            break;
4339
                        }
4340
                    }
4341
                }
4342
                $myCourseList = $newCourseList;
4343
            }
4344
        }
4345
4346
        return $myCourseList;
4347
    }
4348
4349
    /**
4350
     * Get user id from a username.
4351
     *
4352
     * @param string $username
4353
     *
4354
     * @return int User ID (or false if not found)
4355
     */
4356
    public static function get_user_id_from_username($username)
4357
    {
4358
        if (empty($username)) {
4359
            return false;
4360
        }
4361
        $username = trim($username);
4362
        $username = Database::escape_string($username);
4363
        $t_user = Database::get_main_table(TABLE_MAIN_USER);
4364
        $sql = "SELECT id FROM $t_user WHERE username = '$username'";
4365
        $res = Database::query($sql);
4366
4367
        if ($res === false) {
4368
            return false;
4369
        }
4370
        if (Database::num_rows($res) !== 1) {
4371
            return false;
4372
        }
4373
        $row = Database::fetch_array($res);
4374
4375
        return $row['id'];
4376
    }
4377
4378
    /**
4379
     * Get the users files upload from his share_folder.
4380
     *
4381
     * @param string $user_id      User ID
4382
     * @param string $course       course directory
4383
     * @param string $resourceType resource type: images, all
4384
     *
4385
     * @return string
4386
     */
4387
    public static function get_user_upload_files_by_course(
4388
        $user_id,
4389
        $course,
4390
        $resourceType = 'all'
4391
    ) {
4392
        $return = '';
4393
        $user_id = (int) $user_id;
4394
4395
        if (!empty($user_id) && !empty($course)) {
4396
            $path = api_get_path(SYS_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
4397
            $web_path = api_get_path(WEB_COURSE_PATH).$course.'/document/shared_folder/sf_user_'.$user_id.'/';
4398
            $file_list = [];
4399
4400
            if (is_dir($path)) {
4401
                $handle = opendir($path);
4402
                while ($file = readdir($handle)) {
4403
                    if ($file == '.' || $file == '..' || $file == '.htaccess' || is_dir($path.$file)) {
4404
                        continue; // skip current/parent directory and .htaccess
4405
                    }
4406
                    $file_list[] = $file;
4407
                }
4408
                if (count($file_list) > 0) {
4409
                    $return = "<h4>$course</h4>";
4410
                    $return .= '<ul class="thumbnails">';
4411
                }
4412
                $extensionList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif'];
4413
                foreach ($file_list as $file) {
4414
                    if ($resourceType == 'all') {
4415
                        $return .= '<li>
4416
                            <a href="'.$web_path.urlencode($file).'" target="_blank">'.htmlentities($file).'</a></li>';
4417
                    } elseif ($resourceType == 'images') {
4418
                        //get extension
4419
                        $ext = explode('.', $file);
4420
                        if (isset($ext[1]) && in_array($ext[1], $extensionList)) {
4421
                            $return .= '<li class="span2">
4422
                                            <a class="thumbnail" href="'.$web_path.urlencode($file).'" target="_blank">
4423
                                                <img src="'.$web_path.urlencode($file).'" >
4424
                                            </a>
4425
                                        </li>';
4426
                        }
4427
                    }
4428
                }
4429
                if (count($file_list) > 0) {
4430
                    $return .= '</ul>';
4431
                }
4432
            }
4433
        }
4434
4435
        return $return;
4436
    }
4437
4438
    /**
4439
     * Gets the API key (or keys) and return them into an array.
4440
     *
4441
     * @param int     Optional user id (defaults to the result of api_get_user_id())
4442
     * @param string $api_service
4443
     *
4444
     * @return mixed Non-indexed array containing the list of API keys for this user, or FALSE on error
4445
     */
4446
    public static function get_api_keys($user_id = null, $api_service = 'dokeos')
4447
    {
4448
        if ($user_id != strval(intval($user_id))) {
4449
            return false;
4450
        }
4451
        if (empty($user_id)) {
4452
            $user_id = api_get_user_id();
4453
        }
4454
        if ($user_id === false) {
4455
            return false;
4456
        }
4457
        $service_name = Database::escape_string($api_service);
4458
        if (is_string($service_name) === false) {
4459
            return false;
4460
        }
4461
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4462
        $sql = "SELECT * FROM $t_api WHERE user_id = $user_id AND api_service='$api_service';";
4463
        $res = Database::query($sql);
4464
        if ($res === false) {
4465
            return false;
4466
        } //error during query
4467
        $num = Database::num_rows($res);
4468
        if ($num == 0) {
4469
            return false;
4470
        }
4471
        $list = [];
4472
        while ($row = Database::fetch_array($res)) {
4473
            $list[$row['id']] = $row['api_key'];
4474
        }
4475
4476
        return $list;
4477
    }
4478
4479
    /**
4480
     * Adds a new API key to the users' account.
4481
     *
4482
     * @param   int     Optional user ID (defaults to the results of api_get_user_id())
4483
     * @param string $api_service
4484
     *
4485
     * @return bool True on success, false on failure
4486
     */
4487
    public static function add_api_key($user_id = null, $api_service = 'dokeos')
4488
    {
4489
        if ($user_id != strval(intval($user_id))) {
4490
            return false;
4491
        }
4492
        if (empty($user_id)) {
4493
            $user_id = api_get_user_id();
4494
        }
4495
        if ($user_id === false) {
4496
            return false;
4497
        }
4498
        $service_name = Database::escape_string($api_service);
4499
        if (is_string($service_name) === false) {
4500
            return false;
4501
        }
4502
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4503
        $md5 = md5((time() + ($user_id * 5)) - rand(10000, 10000)); //generate some kind of random key
4504
        $sql = "INSERT INTO $t_api (user_id, api_key,api_service) VALUES ($user_id,'$md5','$service_name')";
4505
        $res = Database::query($sql);
4506
        if ($res === false) {
4507
            return false;
4508
        } //error during query
4509
        $num = Database::insert_id();
4510
4511
        return $num == 0 ? false : $num;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $num == 0 ? false : $num also could return the type string which is incompatible with the documented return type boolean.
Loading history...
4512
    }
4513
4514
    /**
4515
     * Deletes an API key from the user's account.
4516
     *
4517
     * @param   int     API key's internal ID
4518
     *
4519
     * @return bool True on success, false on failure
4520
     */
4521
    public static function delete_api_key($key_id)
4522
    {
4523
        if ($key_id != strval(intval($key_id))) {
4524
            return false;
4525
        }
4526
        if ($key_id === false) {
4527
            return false;
4528
        }
4529
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4530
        $sql = "SELECT * FROM $t_api WHERE id = ".$key_id;
4531
        $res = Database::query($sql);
4532
        if ($res === false) {
4533
            return false;
4534
        } //error during query
4535
        $num = Database::num_rows($res);
4536
        if ($num !== 1) {
4537
            return false;
4538
        }
4539
        $sql = "DELETE FROM $t_api WHERE id = ".$key_id;
4540
        $res = Database::query($sql);
4541
        if ($res === false) {
4542
            return false;
4543
        } //error during query
4544
4545
        return true;
4546
    }
4547
4548
    /**
4549
     * Regenerate an API key from the user's account.
4550
     *
4551
     * @param   int     user ID (defaults to the results of api_get_user_id())
4552
     * @param   string  API key's internal ID
4553
     *
4554
     * @return int num
4555
     */
4556
    public static function update_api_key($user_id, $api_service)
4557
    {
4558
        if ($user_id != strval(intval($user_id))) {
4559
            return false;
4560
        }
4561
        if ($user_id === false) {
4562
            return false;
4563
        }
4564
        $service_name = Database::escape_string($api_service);
4565
        if (is_string($service_name) === false) {
4566
            return false;
4567
        }
4568
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4569
        $sql = "SELECT id FROM $t_api
4570
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
4571
        $res = Database::query($sql);
4572
        $num = Database::num_rows($res);
4573
        if ($num == 1) {
4574
            $id_key = Database::fetch_array($res, 'ASSOC');
4575
            self::delete_api_key($id_key['id']);
4576
            $num = self::add_api_key($user_id, $api_service);
4577
        } elseif ($num == 0) {
4578
            $num = self::add_api_key($user_id, $api_service);
4579
        }
4580
4581
        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...
4582
    }
4583
4584
    /**
4585
     * @param   int     user ID (defaults to the results of api_get_user_id())
4586
     * @param   string    API key's internal ID
4587
     *
4588
     * @return int row ID, or return false if not found
4589
     */
4590
    public static function get_api_key_id($user_id, $api_service)
4591
    {
4592
        if ($user_id != strval(intval($user_id))) {
4593
            return false;
4594
        }
4595
        if ($user_id === false) {
4596
            return false;
4597
        }
4598
        if (empty($api_service)) {
4599
            return false;
4600
        }
4601
        $t_api = Database::get_main_table(TABLE_MAIN_USER_API_KEY);
4602
        $api_service = Database::escape_string($api_service);
4603
        $sql = "SELECT id FROM $t_api
4604
                WHERE user_id=".$user_id." AND api_service='".$api_service."'";
4605
        $res = Database::query($sql);
4606
        if (Database::num_rows($res) < 1) {
4607
            return false;
4608
        }
4609
        $row = Database::fetch_array($res, 'ASSOC');
4610
4611
        return $row['id'];
4612
    }
4613
4614
    /**
4615
     * Checks if a user_id is platform admin.
4616
     *
4617
     * @param   int user ID
4618
     *
4619
     * @return bool True if is admin, false otherwise
4620
     *
4621
     * @see main_api.lib.php::api_is_platform_admin() for a context-based check
4622
     */
4623
    public static function is_admin($user_id)
4624
    {
4625
        $user_id = (int) $user_id;
4626
        if (empty($user_id)) {
4627
            return false;
4628
        }
4629
        $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
4630
        $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
4631
        $res = Database::query($sql);
4632
4633
        return Database::num_rows($res) === 1;
4634
    }
4635
4636
    /**
4637
     * Get the total count of users.
4638
     *
4639
     * @param int|null $status        Status of users to be counted
4640
     * @param int|null $access_url_id Access URL ID (optional)
4641
     *
4642
     * @return mixed Number of users or false on error
4643
     */
4644
    public static function get_number_of_users(
4645
        int $status = null,
4646
        int $access_url_id = null,
4647
        int $active = null,
4648
        string $dateFrom = null,
4649
        string $dateUntil = null
4650
    ) {
4651
        $tableUser = Database::get_main_table(TABLE_MAIN_USER);
4652
        $tableAccessUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
4653
4654
        if (empty($access_url_id)) {
4655
            $access_url_id = api_get_current_access_url_id();
4656
        }
4657
4658
        if (api_is_multiple_url_enabled()) {
4659
            $sql = "SELECT count(u.id)
4660
                    FROM $tableUser u
4661
                    INNER JOIN $tableAccessUrlRelUser url_user
4662
                    ON (u.id = url_user.user_id)
4663
                    WHERE url_user.access_url_id = $access_url_id
4664
            ";
4665
        } else {
4666
            $sql = "SELECT count(u.id)
4667
                    FROM $tableUser u
4668
                    WHERE 1 = 1 ";
4669
        }
4670
4671
        if (is_int($status) && $status > 0) {
4672
            $status = (int) $status;
4673
            $sql .= " AND u.status = $status ";
4674
        }
4675
4676
        if (isset($active)) {
4677
            $active = (int) $active;
4678
            $sql .= " AND u.active = $active ";
4679
        }
4680
4681
        if (!empty($dateFrom)) {
4682
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
4683
            $sql .= " AND u.registration_date >= '$dateFrom' ";
4684
        }
4685
4686
        if (!empty($dateUntil)) {
4687
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
4688
            $sql .= " AND u.registration_date <= '$dateUntil' ";
4689
        }
4690
4691
        $res = Database::query($sql);
4692
        if (Database::num_rows($res) === 1) {
4693
            return (int) Database::result($res, 0, 0);
4694
        }
4695
4696
        return false;
4697
    }
4698
4699
    /**
4700
     * Gets the tags of a specific field_id
4701
     * USER TAGS.
4702
     *
4703
     * Instructions to create a new user tag by Julio Montoya <[email protected]>
4704
     *
4705
     * 1. Create a new extra field in main/admin/user_fields.php with the "TAG" field type make it available and visible.
4706
     *    Called it "books" for example.
4707
     * 2. Go to profile main/auth/profile.php There you will see a special input (facebook style) that will show suggestions of tags.
4708
     * 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
4709
     * 4. Tags are independent this means that tags can't be shared between tags + book + hobbies.
4710
     * 5. Test and enjoy.
4711
     *
4712
     * @param string $tag
4713
     * @param int    $field_id      field_id
4714
     * @param string $return_format how we are going to result value in array or in a string (json)
4715
     * @param $limit
4716
     *
4717
     * @return mixed
4718
     */
4719
    public static function get_tags($tag, $field_id, $return_format = 'json', $limit = 10)
4720
    {
4721
        // database table definition
4722
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4723
        $field_id = (int) $field_id;
4724
        $limit = (int) $limit;
4725
        $tag = trim(Database::escape_string($tag));
4726
4727
        // all the information of the field
4728
        $sql = "SELECT DISTINCT id, tag from $table_user_tag
4729
                WHERE field_id = $field_id AND tag LIKE '$tag%'
4730
                ORDER BY tag
4731
                LIMIT $limit";
4732
        $result = Database::query($sql);
4733
        $return = [];
4734
        if (Database::num_rows($result) > 0) {
4735
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4736
                $return[] = ['id' => $row['tag'], 'text' => $row['tag']];
4737
            }
4738
        }
4739
        if ($return_format === 'json') {
4740
            $return = json_encode($return);
4741
        }
4742
4743
        return $return;
4744
    }
4745
4746
    /**
4747
     * @param int $field_id
4748
     * @param int $limit
4749
     *
4750
     * @return array
4751
     */
4752
    public static function get_top_tags($field_id, $limit = 100)
4753
    {
4754
        // database table definition
4755
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4756
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4757
        $field_id = (int) $field_id;
4758
        $limit = (int) $limit;
4759
        // all the information of the field
4760
        $sql = "SELECT count(*) count, tag FROM $table_user_tag_values  uv
4761
                INNER JOIN $table_user_tag ut
4762
                ON (ut.id = uv.tag_id)
4763
                WHERE field_id = $field_id
4764
                GROUP BY tag_id
4765
                ORDER BY count DESC
4766
                LIMIT $limit";
4767
        $result = Database::query($sql);
4768
        $return = [];
4769
        if (Database::num_rows($result) > 0) {
4770
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4771
                $return[] = $row;
4772
            }
4773
        }
4774
4775
        return $return;
4776
    }
4777
4778
    /**
4779
     * Get user's tags.
4780
     *
4781
     * @param int $user_id
4782
     * @param int $field_id
4783
     *
4784
     * @return array
4785
     */
4786
    public static function get_user_tags($user_id, $field_id)
4787
    {
4788
        // database table definition
4789
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4790
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4791
        $field_id = (int) $field_id;
4792
        $user_id = (int) $user_id;
4793
4794
        // all the information of the field
4795
        $sql = "SELECT ut.id, tag, count
4796
                FROM $table_user_tag ut
4797
                INNER JOIN $table_user_tag_values uv
4798
                ON (uv.tag_id=ut.ID)
4799
                WHERE field_id = $field_id AND user_id = $user_id
4800
                ORDER BY tag";
4801
        $result = Database::query($sql);
4802
        $return = [];
4803
        if (Database::num_rows($result) > 0) {
4804
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4805
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4806
            }
4807
        }
4808
4809
        return $return;
4810
    }
4811
4812
    /**
4813
     * Get user's tags.
4814
     *
4815
     * @param int  $user_id
4816
     * @param int  $field_id
4817
     * @param bool $show_links show links or not
4818
     *
4819
     * @return string
4820
     */
4821
    public static function get_user_tags_to_string($user_id, $field_id, $show_links = true)
4822
    {
4823
        // database table definition
4824
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4825
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4826
        $field_id = (int) $field_id;
4827
        $user_id = (int) $user_id;
4828
4829
        // all the information of the field
4830
        $sql = "SELECT ut.id, tag,count FROM $table_user_tag ut
4831
                INNER JOIN $table_user_tag_values uv
4832
                ON (uv.tag_id = ut.id)
4833
                WHERE field_id = $field_id AND user_id = $user_id
4834
                ORDER BY tag";
4835
4836
        $result = Database::query($sql);
4837
        $return = [];
4838
        if (Database::num_rows($result) > 0) {
4839
            while ($row = Database::fetch_array($result, 'ASSOC')) {
4840
                $return[$row['id']] = ['tag' => $row['tag'], 'count' => $row['count']];
4841
            }
4842
        }
4843
        $user_tags = $return;
4844
        $tag_tmp = [];
4845
        foreach ($user_tags as $tag) {
4846
            if ($show_links) {
4847
                $tag_tmp[] = '<a href="'.api_get_path(WEB_PATH).'main/search/index.php?q='.$tag['tag'].'">'.
4848
                    $tag['tag'].
4849
                '</a>';
4850
            } else {
4851
                $tag_tmp[] = $tag['tag'];
4852
            }
4853
        }
4854
4855
        if (is_array($user_tags) && count($user_tags) > 0) {
4856
            return implode(', ', $tag_tmp);
4857
        } else {
4858
            return '';
4859
        }
4860
    }
4861
4862
    /**
4863
     * Get the tag id.
4864
     *
4865
     * @param int $tag
4866
     * @param int $field_id
4867
     *
4868
     * @return int returns 0 if fails otherwise the tag id
4869
     */
4870
    public static function get_tag_id($tag, $field_id)
4871
    {
4872
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4873
        $tag = Database::escape_string($tag);
4874
        $field_id = (int) $field_id;
4875
        //with COLLATE latin1_bin to select query in a case sensitive mode
4876
        $sql = "SELECT id FROM $table_user_tag
4877
                WHERE tag LIKE '$tag' AND field_id = $field_id";
4878
        $result = Database::query($sql);
4879
        if (Database::num_rows($result) > 0) {
4880
            $row = Database::fetch_array($result, 'ASSOC');
4881
4882
            return $row['id'];
4883
        } else {
4884
            return 0;
4885
        }
4886
    }
4887
4888
    /**
4889
     * Get the tag id.
4890
     *
4891
     * @param int $tag_id
4892
     * @param int $field_id
4893
     *
4894
     * @return int 0 if fails otherwise the tag id
4895
     */
4896
    public static function get_tag_id_from_id($tag_id, $field_id)
4897
    {
4898
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4899
        $tag_id = (int) $tag_id;
4900
        $field_id = (int) $field_id;
4901
        $sql = "SELECT id FROM $table_user_tag
4902
                WHERE id = '$tag_id' AND field_id = $field_id";
4903
        $result = Database::query($sql);
4904
        if (Database::num_rows($result) > 0) {
4905
            $row = Database::fetch_array($result, 'ASSOC');
4906
4907
            return $row['id'];
4908
        } else {
4909
            return false;
4910
        }
4911
    }
4912
4913
    /**
4914
     * Adds a user-tag value.
4915
     *
4916
     * @param mixed $tag
4917
     * @param int   $user_id
4918
     * @param int   $field_id field id of the tag
4919
     *
4920
     * @return bool True if the tag was inserted or updated. False otherwise.
4921
     *              The return value doesn't take into account *values* added to the tag.
4922
     *              Only the creation/update of the tag field itself.
4923
     */
4924
    public static function add_tag($tag, $user_id, $field_id)
4925
    {
4926
        // database table definition
4927
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4928
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4929
        $tag = trim(Database::escape_string($tag));
4930
        $user_id = (int) $user_id;
4931
        $field_id = (int) $field_id;
4932
4933
        $tag_id = self::get_tag_id($tag, $field_id);
4934
4935
        /* IMPORTANT
4936
         *  @todo we don't create tags with numbers
4937
         *
4938
         */
4939
        if (is_numeric($tag)) {
4940
            //the form is sending an id this means that the user select it from the list so it MUST exists
4941
            /* $new_tag_id = self::get_tag_id_from_id($tag,$field_id);
4942
              if ($new_tag_id !== false) {
4943
              $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $new_tag_id";
4944
              $result = Database::query($sql);
4945
              $last_insert_id = $new_tag_id;
4946
              } else {
4947
              $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4948
              $result = Database::query($sql);
4949
              $last_insert_id = Database::insert_id();
4950
              } */
4951
        }
4952
4953
        //this is a new tag
4954
        if ($tag_id == 0) {
4955
            //the tag doesn't exist
4956
            $sql = "INSERT INTO $table_user_tag (tag, field_id,count) VALUES ('$tag','$field_id', count + 1)";
4957
            Database::query($sql);
4958
            $last_insert_id = Database::insert_id();
4959
        } else {
4960
            //the tag exists we update it
4961
            $sql = "UPDATE $table_user_tag SET count = count + 1 WHERE id  = $tag_id";
4962
            Database::query($sql);
4963
            $last_insert_id = $tag_id;
4964
        }
4965
4966
        if (!empty($last_insert_id) && ($last_insert_id != 0)) {
4967
            //we insert the relationship user-tag
4968
            $sql = "SELECT tag_id FROM $table_user_tag_values
4969
                    WHERE user_id = $user_id AND tag_id = $last_insert_id ";
4970
            $result = Database::query($sql);
4971
            //if the relationship does not exist we create it
4972
            if (Database::num_rows($result) == 0) {
4973
                $sql = "INSERT INTO $table_user_tag_values SET user_id = $user_id, tag_id = $last_insert_id";
4974
                Database::query($sql);
4975
            }
4976
4977
            return true;
4978
        }
4979
4980
        return false;
4981
    }
4982
4983
    /**
4984
     * Deletes an user tag.
4985
     *
4986
     * @param int $user_id
4987
     * @param int $field_id
4988
     */
4989
    public static function delete_user_tags($user_id, $field_id)
4990
    {
4991
        // database table definition
4992
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
4993
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
4994
        $user_id = (int) $user_id;
4995
4996
        $tags = self::get_user_tags($user_id, $field_id);
4997
        if (is_array($tags) && count($tags) > 0) {
4998
            foreach ($tags as $key => $tag) {
4999
                if ($tag['count'] > '0') {
5000
                    $sql = "UPDATE $table_user_tag SET count = count - 1  WHERE id = $key ";
5001
                    Database::query($sql);
5002
                }
5003
                $sql = "DELETE FROM $table_user_tag_values
5004
                        WHERE user_id = $user_id AND tag_id = $key";
5005
                Database::query($sql);
5006
            }
5007
        }
5008
    }
5009
5010
    /**
5011
     * Process the tag list comes from the UserManager::update_extra_field_value() function.
5012
     *
5013
     * @param array $tags     the tag list that will be added
5014
     * @param int   $user_id
5015
     * @param int   $field_id
5016
     *
5017
     * @return bool
5018
     */
5019
    public static function process_tags($tags, $user_id, $field_id)
5020
    {
5021
        // We loop the tags and add it to the DB
5022
        if (is_array($tags)) {
5023
            foreach ($tags as $tag) {
5024
                self::add_tag($tag, $user_id, $field_id);
5025
            }
5026
        } else {
5027
            self::add_tag($tags, $user_id, $field_id);
5028
        }
5029
5030
        return true;
5031
    }
5032
5033
    /**
5034
     * Returns a list of all administrators.
5035
     *
5036
     * @return array
5037
     */
5038
    public static function get_all_administrators()
5039
    {
5040
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
5041
        $table_admin = Database::get_main_table(TABLE_MAIN_ADMIN);
5042
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5043
        $access_url_id = api_get_current_access_url_id();
5044
        if (api_get_multiple_access_url()) {
5045
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
5046
                    FROM $tbl_url_rel_user as url
5047
                    INNER JOIN $table_admin as admin
5048
                    ON (admin.user_id=url.user_id)
5049
                    INNER JOIN $table_user u
5050
                    ON (u.id=admin.user_id)
5051
                    WHERE access_url_id ='".$access_url_id."'";
5052
        } else {
5053
            $sql = "SELECT admin.user_id, username, firstname, lastname, email, active
5054
                    FROM $table_admin as admin
5055
                    INNER JOIN $table_user u
5056
                    ON (u.id=admin.user_id)";
5057
        }
5058
        $result = Database::query($sql);
5059
        $return = [];
5060
        if (Database::num_rows($result) > 0) {
5061
            while ($row = Database::fetch_array($result, 'ASSOC')) {
5062
                $return[$row['user_id']] = $row;
5063
            }
5064
        }
5065
5066
        return $return;
5067
    }
5068
5069
    /**
5070
     * Search an user (tags, first name, last name and email ).
5071
     *
5072
     * @param string $tag
5073
     * @param int    $field_id        field id of the tag
5074
     * @param int    $from            where to start in the query
5075
     * @param int    $number_of_items
5076
     * @param bool   $getCount        get count or not
5077
     *
5078
     * @return array
5079
     */
5080
    public static function get_all_user_tags(
5081
        $tag,
5082
        $field_id = 0,
5083
        $from = 0,
5084
        $number_of_items = 10,
5085
        $getCount = false
5086
    ) {
5087
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
5088
        $table_user_tag = Database::get_main_table(TABLE_MAIN_TAG);
5089
        $table_user_tag_values = Database::get_main_table(TABLE_MAIN_USER_REL_TAG);
5090
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5091
5092
        $field_id = intval($field_id);
5093
        $from = intval($from);
5094
        $number_of_items = intval($number_of_items);
5095
5096
        $where_field = "";
5097
        $where_extra_fields = self::get_search_form_where_extra_fields();
5098
        if ($field_id != 0) {
5099
            $where_field = " field_id = $field_id AND ";
5100
        }
5101
5102
        // all the information of the field
5103
        if ($getCount) {
5104
            $select = "SELECT count(DISTINCT u.id) count";
5105
        } else {
5106
            $select = "SELECT DISTINCT u.id, u.username, firstname, lastname, email, picture_uri";
5107
        }
5108
5109
        $sql = " $select
5110
                FROM $user_table u
5111
                INNER JOIN $access_url_rel_user_table url_rel_user
5112
                ON (u.id = url_rel_user.user_id)
5113
                LEFT JOIN $table_user_tag_values uv
5114
                ON (u.id AND uv.user_id AND uv.user_id = url_rel_user.user_id)
5115
                LEFT JOIN $table_user_tag ut ON (uv.tag_id = ut.id)
5116
                WHERE
5117
                    ($where_field tag LIKE '".Database::escape_string($tag."%")."') OR
5118
                    (
5119
                        u.firstname LIKE '".Database::escape_string("%".$tag."%")."' OR
5120
                        u.lastname LIKE '".Database::escape_string("%".$tag."%")."' OR
5121
                        u.username LIKE '".Database::escape_string("%".$tag."%")."' OR
5122
                        concat(u.firstname, ' ', u.lastname) LIKE '".Database::escape_string("%".$tag."%")."' OR
5123
                        concat(u.lastname, ' ', u.firstname) LIKE '".Database::escape_string("%".$tag."%")."'
5124
                     )
5125
                     ".(!empty($where_extra_fields) ? $where_extra_fields : '')."
5126
                     AND url_rel_user.access_url_id=".api_get_current_access_url_id();
5127
5128
        $keyword_active = true;
5129
        // only active users
5130
        if ($keyword_active) {
5131
            $sql .= " AND u.active='1'";
5132
        }
5133
        // avoid anonymous
5134
        $sql .= " AND u.status <> 6 ";
5135
        $sql .= " ORDER BY username";
5136
        $sql .= " LIMIT $from , $number_of_items";
5137
5138
        $result = Database::query($sql);
5139
        $return = [];
5140
5141
        if (Database::num_rows($result) > 0) {
5142
            if ($getCount) {
5143
                $row = Database::fetch_array($result, 'ASSOC');
5144
5145
                return $row['count'];
5146
            }
5147
            while ($row = Database::fetch_array($result, 'ASSOC')) {
5148
                $return[$row['id']] = $row;
5149
            }
5150
        }
5151
5152
        return $return;
5153
    }
5154
5155
    /**
5156
     * Get extra filterable user fields (only type select).
5157
     *
5158
     * @return array Array of extra fields as [int => ['name' => ..., 'variable' => ..., 'data' => ...]] (
5159
     *               or empty array if no extra field)
5160
     */
5161
    public static function getExtraFilterableFields()
5162
    {
5163
        $extraFieldList = self::get_extra_fields();
5164
        $fields = [];
5165
        if (is_array($extraFieldList)) {
5166
            foreach ($extraFieldList as $extraField) {
5167
                // If is enabled to filter and is a "<select>" field type
5168
                if ($extraField[8] == 1 && $extraField[2] == 4) {
5169
                    $fields[] = [
5170
                        'name' => $extraField[3],
5171
                        'variable' => $extraField[1],
5172
                        'data' => $extraField[9],
5173
                    ];
5174
                }
5175
            }
5176
        }
5177
5178
        return $fields;
5179
    }
5180
5181
    /**
5182
     * Get extra where clauses for finding users based on extra filterable user fields (type select).
5183
     *
5184
     * @return string With AND clauses based on user's ID which have the values to search in extra user fields
5185
     *                (or empty if no extra field exists)
5186
     */
5187
    public static function get_search_form_where_extra_fields()
5188
    {
5189
        $useExtraFields = false;
5190
        $extraFields = self::getExtraFilterableFields();
5191
        $extraFieldResult = [];
5192
        if (is_array($extraFields) && count($extraFields) > 0) {
5193
            foreach ($extraFields as $extraField) {
5194
                $varName = 'field_'.$extraField['variable'];
5195
                if (self::is_extra_field_available($extraField['variable'])) {
5196
                    if (isset($_GET[$varName]) && $_GET[$varName] != '0') {
5197
                        $useExtraFields = true;
5198
                        $extraFieldResult[] = self::get_extra_user_data_by_value(
5199
                            $extraField['variable'],
5200
                            $_GET[$varName]
5201
                        );
5202
                    }
5203
                }
5204
            }
5205
        }
5206
5207
        if ($useExtraFields) {
5208
            $finalResult = [];
5209
            if (count($extraFieldResult) > 1) {
5210
                for ($i = 0; $i < count($extraFieldResult) - 1; $i++) {
5211
                    if (is_array($extraFieldResult[$i]) && is_array($extraFieldResult[$i + 1])) {
5212
                        $finalResult = array_intersect($extraFieldResult[$i], $extraFieldResult[$i + 1]);
5213
                    }
5214
                }
5215
            } else {
5216
                $finalResult = $extraFieldResult[0];
5217
            }
5218
5219
            if (is_array($finalResult) && count($finalResult) > 0) {
5220
                $whereFilter = " AND u.id IN  ('".implode("','", $finalResult)."') ";
5221
            } else {
5222
                //no results
5223
                $whereFilter = " AND u.id  = -1 ";
5224
            }
5225
5226
            return $whereFilter;
5227
        }
5228
5229
        return '';
5230
    }
5231
5232
    /**
5233
     * Show the search form.
5234
     *
5235
     * @param string $query the value of the search box
5236
     *
5237
     * @throws Exception
5238
     *
5239
     * @return string HTML form
5240
     */
5241
    public static function get_search_form($query, $defaultParams = [])
5242
    {
5243
        $searchType = isset($_GET['search_type']) ? $_GET['search_type'] : null;
5244
        $form = new FormValidator(
5245
            'search_user',
5246
            'get',
5247
            api_get_path(WEB_PATH).'main/social/search.php',
5248
            '',
5249
            [],
5250
            FormValidator::LAYOUT_HORIZONTAL
5251
        );
5252
5253
        $query = Security::remove_XSS($query);
5254
5255
        if (!empty($query)) {
5256
            $form->addHeader(get_lang('Results').' "'.$query.'"');
5257
        }
5258
5259
        $form->addText(
5260
            'q',
5261
            get_lang('UsersGroups'),
5262
            false,
5263
            [
5264
                'id' => 'q',
5265
            ]
5266
        );
5267
        $options = [
5268
            0 => get_lang('Select'),
5269
            1 => get_lang('User'),
5270
            2 => get_lang('Group'),
5271
        ];
5272
        $form->addSelect(
5273
            'search_type',
5274
            get_lang('Type'),
5275
            $options,
5276
            ['onchange' => 'javascript: extra_field_toogle();', 'id' => 'search_type']
5277
        );
5278
5279
        // Extra fields
5280
        $extraFields = self::getExtraFilterableFields();
5281
        $defaults = [];
5282
        if (is_array($extraFields) && count($extraFields) > 0) {
5283
            foreach ($extraFields as $extraField) {
5284
                $varName = 'field_'.$extraField['variable'];
5285
                $options = [
5286
                    0 => get_lang('Select'),
5287
                ];
5288
                foreach ($extraField['data'] as $option) {
5289
                    if (isset($_GET[$varName])) {
5290
                        if ($_GET[$varName] == $option[1]) {
5291
                            $defaults[$option[1]] = true;
5292
                        }
5293
                    }
5294
5295
                    $options[$option[1]] = $option[1];
5296
                }
5297
                $form->addSelect($varName, $extraField['name'], $options);
5298
            }
5299
        }
5300
5301
        $defaults['search_type'] = (int) $searchType;
5302
        $defaults['q'] = $query;
5303
5304
        if (!empty($defaultParams)) {
5305
            $defaults = array_merge($defaults, $defaultParams);
5306
        }
5307
        $form->setDefaults($defaults);
5308
        $form->addButtonSearch(get_lang('Search'));
5309
5310
        $js = '<script>
5311
        extra_field_toogle();
5312
        function extra_field_toogle() {
5313
            if (jQuery("select[name=search_type]").val() != "1") {
5314
                jQuery(".extra_field").hide();
5315
            } else {
5316
                jQuery(".extra_field").show();
5317
            }
5318
        }
5319
        </script>';
5320
5321
        return $js.$form->returnForm();
5322
    }
5323
5324
    /**
5325
     * Shows the user menu.
5326
     */
5327
    public static function show_menu()
5328
    {
5329
        echo '<div class="actions">';
5330
        echo '<a href="/main/auth/profile.php">'.
5331
            Display::return_icon('profile.png').' '.get_lang('PersonalData').'</a>';
5332
        echo '<a href="/main/messages/inbox.php">'.
5333
            Display::return_icon('inbox.png').' '.get_lang('Inbox').'</a>';
5334
        echo '<a href="/main/messages/outbox.php">'.
5335
            Display::return_icon('outbox.png').' '.get_lang('Outbox').'</a>';
5336
        echo '<span style="float:right; padding-top:7px;">'.
5337
        '<a href="/main/auth/profile.php?show=1">'.
5338
            Display::return_icon('edit.gif').' '.get_lang('Configuration').'</a>';
5339
        echo '</span>';
5340
        echo '</div>';
5341
    }
5342
5343
    /**
5344
     * Allow to register contact to social network.
5345
     *
5346
     * @param int $friend_id     user friend id
5347
     * @param int $my_user_id    user id
5348
     * @param int $relation_type relation between users see constants definition
5349
     *
5350
     * @return bool
5351
     */
5352
    public static function relate_users($friend_id, $my_user_id, $relation_type)
5353
    {
5354
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5355
5356
        $friend_id = (int) $friend_id;
5357
        $my_user_id = (int) $my_user_id;
5358
        $relation_type = (int) $relation_type;
5359
5360
        $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
5361
                WHERE
5362
                    friend_user_id='.$friend_id.' AND
5363
                    user_id='.$my_user_id.' AND
5364
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
5365
        $result = Database::query($sql);
5366
        $row = Database::fetch_array($result, 'ASSOC');
5367
        $current_date = api_get_utc_datetime();
5368
5369
        if ($row['count'] == 0) {
5370
            $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
5371
                    VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
5372
            Database::query($sql);
5373
5374
            return true;
5375
        }
5376
5377
        $sql = 'SELECT COUNT(*) as count, relation_type  FROM '.$tbl_my_friend.'
5378
                WHERE
5379
                    friend_user_id='.$friend_id.' AND
5380
                    user_id='.$my_user_id.' AND
5381
                    relation_type NOT IN('.USER_RELATION_TYPE_RRHH.', '.USER_RELATION_TYPE_BOSS.') ';
5382
        $result = Database::query($sql);
5383
        $row = Database::fetch_array($result, 'ASSOC');
5384
5385
        if ($row['count'] == 1) {
5386
            //only for the case of a RRHH or a Student BOSS
5387
            if ($row['relation_type'] != $relation_type &&
5388
                ($relation_type == USER_RELATION_TYPE_RRHH || $relation_type == USER_RELATION_TYPE_BOSS)
5389
            ) {
5390
                $sql = 'INSERT INTO '.$tbl_my_friend.'(friend_user_id,user_id,relation_type,last_edit)
5391
                        VALUES ('.$friend_id.','.$my_user_id.','.$relation_type.',"'.$current_date.'")';
5392
            } else {
5393
                $sql = 'UPDATE '.$tbl_my_friend.' SET relation_type='.$relation_type.'
5394
                        WHERE friend_user_id='.$friend_id.' AND user_id='.$my_user_id;
5395
            }
5396
            Database::query($sql);
5397
5398
            return true;
5399
        }
5400
5401
        return false;
5402
    }
5403
5404
    /**
5405
     * Deletes a contact.
5406
     *
5407
     * @param bool   $friend_id
5408
     * @param bool   $real_removed          true will delete ALL friends relationship
5409
     * @param string $with_status_condition
5410
     *
5411
     * @author isaac flores paz <[email protected]>
5412
     * @author Julio Montoya <[email protected]> Cleaning code
5413
     */
5414
    public static function remove_user_rel_user(
5415
        $friend_id,
5416
        $real_removed = false,
5417
        $with_status_condition = ''
5418
    ) {
5419
        $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5420
        $tbl_my_message = Database::get_main_table(TABLE_MESSAGE);
5421
        $friend_id = (int) $friend_id;
5422
        $user_id = api_get_user_id();
5423
5424
        if ($real_removed) {
5425
            $extra_condition = '';
5426
            if ($with_status_condition != '') {
5427
                $extra_condition = ' AND relation_type = '.intval($with_status_condition);
5428
            }
5429
            $sql = 'DELETE FROM '.$tbl_my_friend.'
5430
                    WHERE
5431
                        relation_type <> '.USER_RELATION_TYPE_RRHH.' AND
5432
                        friend_user_id='.$friend_id.' '.$extra_condition;
5433
            Database::query($sql);
5434
            $sql = 'DELETE FROM '.$tbl_my_friend.'
5435
                   WHERE
5436
                    relation_type <> '.USER_RELATION_TYPE_RRHH.' AND
5437
                    user_id='.$friend_id.' '.$extra_condition;
5438
            Database::query($sql);
5439
        } else {
5440
            $sql = 'SELECT COUNT(*) as count FROM '.$tbl_my_friend.'
5441
                    WHERE
5442
                        user_id='.$user_id.' AND
5443
                        relation_type NOT IN('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.') AND
5444
                        friend_user_id='.$friend_id;
5445
            $result = Database::query($sql);
5446
            $row = Database::fetch_array($result, 'ASSOC');
5447
            if ($row['count'] == 1) {
5448
                //Delete user rel user
5449
                $sql_i = 'UPDATE '.$tbl_my_friend.' SET relation_type='.USER_RELATION_TYPE_DELETED.'
5450
                          WHERE user_id='.$user_id.' AND friend_user_id='.$friend_id;
5451
5452
                $sql_j = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
5453
                          WHERE
5454
                                user_receiver_id='.$user_id.' AND
5455
                                user_sender_id='.$friend_id.' AND update_date="0000-00-00 00:00:00" ';
5456
                // Delete user
5457
                $sql_ij = 'UPDATE '.$tbl_my_friend.'  SET relation_type='.USER_RELATION_TYPE_DELETED.'
5458
                           WHERE user_id='.$friend_id.' AND friend_user_id='.$user_id;
5459
                $sql_ji = 'UPDATE '.$tbl_my_message.' SET msg_status='.MESSAGE_STATUS_INVITATION_DENIED.'
5460
                           WHERE
5461
                                user_receiver_id='.$friend_id.' AND
5462
                                user_sender_id='.$user_id.' AND
5463
                                update_date="0000-00-00 00:00:00" ';
5464
                Database::query($sql_i);
5465
                Database::query($sql_j);
5466
                Database::query($sql_ij);
5467
                Database::query($sql_ji);
5468
            }
5469
        }
5470
5471
        // Delete accepted invitations
5472
        $sql = "DELETE FROM $tbl_my_message
5473
                WHERE
5474
                    msg_status = ".MESSAGE_STATUS_INVITATION_ACCEPTED." AND
5475
                    (
5476
                        user_receiver_id = $user_id AND
5477
                        user_sender_id = $friend_id
5478
                    ) OR
5479
                    (
5480
                        user_sender_id = $user_id AND
5481
                        user_receiver_id = $friend_id
5482
                    )
5483
        ";
5484
        Database::query($sql);
5485
    }
5486
5487
    /**
5488
     * @param int $userId
5489
     *
5490
     * @return array
5491
     */
5492
    public static function getDrhListFromUser($userId)
5493
    {
5494
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
5495
        $tblUserRelUser = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5496
        $tblUserRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5497
        $userId = (int) $userId;
5498
5499
        $orderBy = null;
5500
        if (api_is_western_name_order()) {
5501
            $orderBy .= ' ORDER BY firstname, lastname ';
5502
        } else {
5503
            $orderBy .= ' ORDER BY lastname, firstname ';
5504
        }
5505
5506
        $sql = "SELECT u.id, username, u.firstname, u.lastname
5507
                FROM $tblUser u
5508
                INNER JOIN $tblUserRelUser uru ON (uru.friend_user_id = u.id)
5509
                INNER JOIN $tblUserRelAccessUrl a ON (a.user_id = u.id)
5510
                WHERE
5511
                    access_url_id = ".api_get_current_access_url_id()." AND
5512
                    uru.user_id = '$userId' AND
5513
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5514
                $orderBy
5515
                ";
5516
        $result = Database::query($sql);
5517
5518
        return Database::store_result($result);
5519
    }
5520
5521
    /**
5522
     * get users followed by human resource manager.
5523
     *
5524
     * @param int    $userId
5525
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
5526
     * @param bool   $getOnlyUserId
5527
     * @param bool   $getSql
5528
     * @param bool   $getCount
5529
     * @param int    $from
5530
     * @param int    $numberItems
5531
     * @param int    $column
5532
     * @param string $direction
5533
     * @param int    $active
5534
     * @param string $lastConnectionDate
5535
     *
5536
     * @return array users
5537
     */
5538
    public static function get_users_followed_by_drh(
5539
        $userId,
5540
        $userStatus = 0,
5541
        $getOnlyUserId = false,
5542
        $getSql = false,
5543
        $getCount = false,
5544
        $from = null,
5545
        $numberItems = null,
5546
        $column = null,
5547
        $direction = null,
5548
        $active = null,
5549
        $lastConnectionDate = null
5550
    ) {
5551
        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...
5552
            $userId,
5553
            $userStatus,
5554
            $getOnlyUserId,
5555
            $getSql,
5556
            $getCount,
5557
            $from,
5558
            $numberItems,
5559
            $column,
5560
            $direction,
5561
            $active,
5562
            $lastConnectionDate,
5563
            DRH
5564
        );
5565
    }
5566
5567
    /**
5568
     * Get users followed by human resource manager.
5569
     *
5570
     * @param int    $userId
5571
     * @param int    $userStatus             Filter users by status (STUDENT, COURSEMANAGER, etc)
5572
     * @param bool   $getOnlyUserId
5573
     * @param bool   $getSql
5574
     * @param bool   $getCount
5575
     * @param int    $from
5576
     * @param int    $numberItems
5577
     * @param int    $column
5578
     * @param string $direction
5579
     * @param int    $active
5580
     * @param string $lastConnectionDate
5581
     * @param int    $status                 the function is called by who? COURSEMANAGER, DRH?
5582
     * @param string $keyword
5583
     * @param bool   $checkSessionVisibility
5584
     *
5585
     * @return mixed Users list (array) or the SQL query if $getSQL was set to true
5586
     */
5587
    public static function getUsersFollowedByUser(
5588
        $userId,
5589
        $userStatus = null,
5590
        $getOnlyUserId = false,
5591
        $getSql = false,
5592
        $getCount = false,
5593
        $from = null,
5594
        $numberItems = null,
5595
        $column = null,
5596
        $direction = null,
5597
        $active = null,
5598
        $lastConnectionDate = null,
5599
        $status = null,
5600
        $keyword = null,
5601
        $checkSessionVisibility = false,
5602
        $filterUsers = null
5603
    ) {
5604
        // Database Table Definitions
5605
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
5606
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5607
        $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5608
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
5609
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
5610
        $tbl_session_rel_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
5611
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
5612
        $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
5613
5614
        $userId = (int) $userId;
5615
        $limitCondition = '';
5616
5617
        if (isset($from) && isset($numberItems)) {
5618
            $from = (int) $from;
5619
            $numberItems = (int) $numberItems;
5620
            $limitCondition = "LIMIT $from, $numberItems";
5621
        }
5622
5623
        $column = Database::escape_string($column);
5624
        $direction = in_array(strtolower($direction), ['asc', 'desc']) ? $direction : null;
5625
5626
        $userConditions = '';
5627
        if (!empty($userStatus)) {
5628
            $userConditions .= ' AND u.status = '.intval($userStatus);
5629
        }
5630
5631
        $select = " SELECT DISTINCT u.id user_id, u.username, u.lastname, u.firstname, u.email ";
5632
        if ($getOnlyUserId) {
5633
            $select = " SELECT DISTINCT u.id user_id";
5634
        }
5635
5636
        $masterSelect = "SELECT DISTINCT * FROM ";
5637
5638
        if ($getCount) {
5639
            $masterSelect = "SELECT COUNT(DISTINCT(user_id)) as count FROM ";
5640
            $select = " SELECT DISTINCT(u.id) user_id";
5641
        }
5642
5643
        if (!is_null($active)) {
5644
            $active = intval($active);
5645
            $userConditions .= " AND u.active = $active ";
5646
        }
5647
5648
        if (!empty($keyword)) {
5649
            $keyword = trim(Database::escape_string($keyword));
5650
            $keywordParts = array_filter(explode(' ', $keyword));
5651
            $extraKeyword = '';
5652
            if (!empty($keywordParts)) {
5653
                $keywordPartsFixed = Database::escape_string(implode('%', $keywordParts));
5654
                if (!empty($keywordPartsFixed)) {
5655
                    $extraKeyword .= " OR
5656
                        CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keywordPartsFixed%' OR
5657
                        CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keywordPartsFixed%' ";
5658
                }
5659
            }
5660
5661
            $userConditions .= " AND (
5662
                u.username LIKE '%$keyword%' OR
5663
                u.firstname LIKE '%$keyword%' OR
5664
                u.lastname LIKE '%$keyword%' OR
5665
                u.official_code LIKE '%$keyword%' OR
5666
                u.email LIKE '%$keyword%' OR
5667
                CONCAT(u.firstname, ' ', u.lastname) LIKE '%$keyword%' OR
5668
                CONCAT(u.lastname, ' ', u.firstname) LIKE '%$keyword%'
5669
                $extraKeyword
5670
            )";
5671
        }
5672
5673
        if (!empty($lastConnectionDate)) {
5674
            $lastConnectionDate = Database::escape_string($lastConnectionDate);
5675
            $userConditions .= " AND u.last_login <= '$lastConnectionDate' ";
5676
        }
5677
5678
        if (!empty($filterUsers)) {
5679
            $userConditions .= " AND u.id IN(".implode(',', $filterUsers).")";
5680
        }
5681
5682
        $sessionConditionsCoach = null;
5683
        $dateCondition = '';
5684
        $drhConditions = null;
5685
        $teacherSelect = null;
5686
5687
        $urlId = api_get_current_access_url_id();
5688
5689
        switch ($status) {
5690
            case DRH:
5691
                $drhConditions .= " AND
5692
                    friend_user_id = '$userId' AND
5693
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5694
                ";
5695
                break;
5696
            case COURSEMANAGER:
5697
                $drhConditions .= " AND
5698
                    friend_user_id = '$userId' AND
5699
                    relation_type = '".USER_RELATION_TYPE_RRHH."'
5700
                ";
5701
5702
                $sessionConditionsCoach .= " AND
5703
                    (s.id_coach = '$userId')
5704
                ";
5705
5706
                $sessionConditionsTeacher = " AND
5707
                    (scu.status = 2 AND scu.user_id = '$userId')
5708
                ";
5709
5710
                if ($checkSessionVisibility) {
5711
                    $today = api_strtotime('now', 'UTC');
5712
                    $today = date('Y-m-d', $today);
5713
                    $dateCondition = "
5714
                        AND
5715
                        (
5716
                            (s.access_start_date <= '$today' AND '$today' <= s.access_end_date) OR
5717
                            (s.access_start_date IS NULL AND s.access_end_date IS NULL) OR
5718
                            (s.access_start_date <= '$today' AND s.access_end_date IS NULL) OR
5719
                            ('$today' <= s.access_end_date AND s.access_start_date IS NULL)
5720
                        )
5721
					";
5722
                }
5723
5724
                // Use $tbl_session_rel_course_rel_user instead of $tbl_session_rel_user
5725
                /*
5726
                INNER JOIN $tbl_session_rel_user sru
5727
                ON (sru.user_id = u.id)
5728
                INNER JOIN $tbl_session_rel_course_rel_user scu
5729
                ON (scu.user_id = u.id AND scu.c_id IS NOT NULL AND visibility = 1)*/
5730
                $teacherSelect =
5731
                "UNION ALL (
5732
                        $select
5733
                        FROM $tbl_user u
5734
                        INNER JOIN $tbl_session_rel_user sru ON (sru.user_id = u.id)
5735
                        WHERE
5736
                            (
5737
                                sru.session_id IN (
5738
                                    SELECT DISTINCT(s.id) FROM $tbl_session s INNER JOIN
5739
                                    $tbl_session_rel_access_url session_rel_access_rel_user
5740
                                    ON session_rel_access_rel_user.session_id = s.id
5741
                                    WHERE access_url_id = ".$urlId."
5742
                                    $sessionConditionsCoach
5743
                                ) OR sru.session_id IN (
5744
                                    SELECT DISTINCT(s.id) FROM $tbl_session s
5745
                                    INNER JOIN $tbl_session_rel_access_url url
5746
                                    ON (url.session_id = s.id)
5747
                                    INNER JOIN $tbl_session_rel_course_rel_user scu
5748
                                    ON (scu.session_id = s.id)
5749
                                    WHERE access_url_id = ".$urlId."
5750
                                    $sessionConditionsTeacher
5751
                                    $dateCondition
5752
                                )
5753
                            )
5754
                            $userConditions
5755
                    )
5756
                    UNION ALL(
5757
                        $select
5758
                        FROM $tbl_user u
5759
                        INNER JOIN $tbl_course_user cu ON (cu.user_id = u.id)
5760
                        WHERE cu.c_id IN (
5761
                            SELECT DISTINCT(c_id) FROM $tbl_course_user
5762
                            WHERE user_id = $userId AND status = ".COURSEMANAGER."
5763
                        )
5764
                        $userConditions
5765
                    )"
5766
                ;
5767
                break;
5768
            case STUDENT_BOSS:
5769
                $drhConditions = " AND friend_user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
5770
                break;
5771
            case HRM_REQUEST:
5772
                $drhConditions .= " AND
5773
                    friend_user_id = '$userId' AND
5774
                    relation_type = '".USER_RELATION_TYPE_HRM_REQUEST."'
5775
                ";
5776
                break;
5777
        }
5778
5779
        $join = null;
5780
        $sql = " $masterSelect
5781
                (
5782
                    (
5783
                        $select
5784
                        FROM $tbl_user u
5785
                        INNER JOIN $tbl_user_rel_user uru ON (uru.user_id = u.id)
5786
                        LEFT JOIN $tbl_user_rel_access_url a ON (a.user_id = u.id)
5787
                        $join
5788
                        WHERE
5789
                            access_url_id = ".$urlId."
5790
                            $drhConditions
5791
                            $userConditions
5792
                    )
5793
                    $teacherSelect
5794
                ) as t1";
5795
5796
        if ($getSql) {
5797
            return $sql;
5798
        }
5799
5800
        if ($getCount) {
5801
            $result = Database::query($sql);
5802
            $row = Database::fetch_array($result);
5803
5804
            return $row['count'];
5805
        }
5806
5807
        $orderBy = null;
5808
        if ($getOnlyUserId == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
5809
            if (api_is_western_name_order()) {
5810
                $orderBy .= " ORDER BY firstname, lastname ";
5811
            } else {
5812
                $orderBy .= " ORDER BY lastname, firstname ";
5813
            }
5814
5815
            if (!empty($column) && !empty($direction)) {
5816
                // Fixing order due the UNIONs
5817
                $column = str_replace('u.', '', $column);
5818
                $column = trim($column);
5819
                $orderBy = " ORDER BY `$column` $direction ";
5820
            }
5821
        }
5822
5823
        $sql .= $orderBy;
5824
        $sql .= $limitCondition;
5825
5826
        $result = Database::query($sql);
5827
        $users = [];
5828
        if (Database::num_rows($result) > 0) {
5829
            while ($row = Database::fetch_array($result)) {
5830
                $users[$row['user_id']] = $row;
5831
            }
5832
        }
5833
5834
        return $users;
5835
    }
5836
5837
    /**
5838
     * Subscribes users to human resource manager (Dashboard feature).
5839
     *
5840
     * @param int   $hr_dept_id
5841
     * @param array $users_id
5842
     * @param bool  $deleteOtherAssignedUsers
5843
     *
5844
     * @return int
5845
     */
5846
    public static function subscribeUsersToHRManager(
5847
        $hr_dept_id,
5848
        $users_id,
5849
        $deleteOtherAssignedUsers = true
5850
    ) {
5851
        return self::subscribeUsersToUser(
5852
            $hr_dept_id,
5853
            $users_id,
5854
            USER_RELATION_TYPE_RRHH,
5855
            false,
5856
            $deleteOtherAssignedUsers
5857
        );
5858
    }
5859
5860
    /**
5861
     * Register request to assign users to HRM.
5862
     *
5863
     * @param int   $hrmId   The HRM ID
5864
     * @param array $usersId The users IDs
5865
     *
5866
     * @return int
5867
     */
5868
    public static function requestUsersToHRManager($hrmId, $usersId)
5869
    {
5870
        return self::subscribeUsersToUser(
5871
            $hrmId,
5872
            $usersId,
5873
            USER_RELATION_TYPE_HRM_REQUEST,
5874
            false,
5875
            false
5876
        );
5877
    }
5878
5879
    /**
5880
     * Remove the requests for assign a user to a HRM.
5881
     *
5882
     * @param array $usersId List of user IDs from whom to remove all relations requests with HRM
5883
     */
5884
    public static function clearHrmRequestsForUser(User $hrmId, $usersId)
5885
    {
5886
        $users = implode(', ', $usersId);
5887
        Database::getManager()
5888
            ->createQuery('
5889
                DELETE FROM ChamiloCoreBundle:UserRelUser uru
5890
                WHERE uru.friendUserId = :hrm_id AND uru.relationType = :relation_type AND uru.userId IN (:users_ids)
5891
            ')
5892
            ->execute(['hrm_id' => $hrmId, 'relation_type' => USER_RELATION_TYPE_HRM_REQUEST, 'users_ids' => $users]);
5893
    }
5894
5895
    /**
5896
     * Add subscribed users to a user by relation type.
5897
     *
5898
     * @param int    $userId                   The user id
5899
     * @param array  $subscribedUsersId        The id of subscribed users
5900
     * @param string $relationType             The relation type
5901
     * @param bool   $deleteUsersBeforeInsert
5902
     * @param bool   $deleteOtherAssignedUsers
5903
     *
5904
     * @return int
5905
     */
5906
    public static function subscribeUsersToUser(
5907
        $userId,
5908
        $subscribedUsersId,
5909
        $relationType,
5910
        $deleteUsersBeforeInsert = false,
5911
        $deleteOtherAssignedUsers = true
5912
    ) {
5913
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5914
        $userRelAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5915
5916
        $userId = (int) $userId;
5917
        $relationType = (int) $relationType;
5918
        $affectedRows = 0;
5919
5920
        if ($deleteOtherAssignedUsers) {
5921
            if (api_get_multiple_access_url()) {
5922
                // Deleting assigned users to hrm_id
5923
                $sql = "SELECT s.user_id
5924
                        FROM $userRelUserTable s
5925
                        INNER JOIN $userRelAccessUrlTable a
5926
                        ON (a.user_id = s.user_id)
5927
                        WHERE
5928
                            friend_user_id = $userId AND
5929
                            relation_type = $relationType AND
5930
                            access_url_id = ".api_get_current_access_url_id();
5931
            } else {
5932
                $sql = "SELECT user_id
5933
                        FROM $userRelUserTable
5934
                        WHERE
5935
                            friend_user_id = $userId AND
5936
                            relation_type = $relationType";
5937
            }
5938
            $result = Database::query($sql);
5939
5940
            if (Database::num_rows($result) > 0) {
5941
                while ($row = Database::fetch_array($result)) {
5942
                    $sql = "DELETE FROM $userRelUserTable
5943
                            WHERE
5944
                                user_id = {$row['user_id']} AND
5945
                                friend_user_id = $userId AND
5946
                                relation_type = $relationType";
5947
                    Database::query($sql);
5948
                }
5949
            }
5950
        }
5951
5952
        if ($deleteUsersBeforeInsert) {
5953
            $sql = "DELETE FROM $userRelUserTable
5954
                    WHERE
5955
                        user_id = $userId AND
5956
                        relation_type = $relationType";
5957
            Database::query($sql);
5958
        }
5959
5960
        // Inserting new user list
5961
        if (is_array($subscribedUsersId)) {
5962
            foreach ($subscribedUsersId as $subscribedUserId) {
5963
                $subscribedUserId = (int) $subscribedUserId;
5964
                $sql = "SELECT id
5965
                        FROM $userRelUserTable
5966
                        WHERE
5967
                            user_id = $subscribedUserId AND
5968
                            friend_user_id = $userId AND
5969
                            relation_type = $relationType";
5970
5971
                $result = Database::query($sql);
5972
                $num = Database::num_rows($result);
5973
                if ($num === 0) {
5974
                    $date = api_get_utc_datetime();
5975
                    $sql = "INSERT INTO $userRelUserTable (user_id, friend_user_id, relation_type, last_edit)
5976
                            VALUES ($subscribedUserId, $userId, $relationType, '$date')";
5977
                    $result = Database::query($sql);
5978
                    $affectedRows += Database::affected_rows($result);
5979
                }
5980
            }
5981
        }
5982
5983
        return $affectedRows;
5984
    }
5985
5986
    /**
5987
     * This function check if an user is followed by human resources manager.
5988
     *
5989
     * @param int $user_id
5990
     * @param int $hr_dept_id Human resources manager
5991
     *
5992
     * @return bool
5993
     */
5994
    public static function is_user_followed_by_drh($user_id, $hr_dept_id)
5995
    {
5996
        $tbl_user_rel_user = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
5997
        $user_id = (int) $user_id;
5998
        $hr_dept_id = (int) $hr_dept_id;
5999
        $result = false;
6000
6001
        $sql = "SELECT user_id FROM $tbl_user_rel_user
6002
                WHERE
6003
                    user_id = $user_id AND
6004
                    friend_user_id = $hr_dept_id AND
6005
                    relation_type = ".USER_RELATION_TYPE_RRHH;
6006
        $rs = Database::query($sql);
6007
        if (Database::num_rows($rs) > 0) {
6008
            $result = true;
6009
        }
6010
6011
        return $result;
6012
    }
6013
6014
    /**
6015
     * Return the user id of teacher or session administrator.
6016
     *
6017
     * @return int|bool The user id, or false if the session ID was negative
6018
     */
6019
    public static function get_user_id_of_course_admin_or_session_admin(array $courseInfo)
6020
    {
6021
        $session = api_get_session_id();
6022
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6023
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6024
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6025
6026
        if (empty($courseInfo)) {
6027
            return false;
6028
        }
6029
6030
        $courseId = $courseInfo['real_id'];
6031
6032
        if (empty($session)) {
6033
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
6034
                    INNER JOIN '.$table_course_user.' ru
6035
                    ON ru.user_id = u.id
6036
                    WHERE
6037
                        ru.status = '.COURSEMANAGER.' AND
6038
                        ru.c_id = "'.$courseId.'" ';
6039
        } else {
6040
            $sql = 'SELECT u.id uid FROM '.$table_user.' u
6041
                    INNER JOIN '.$table_session_course_user.' sru
6042
                    ON sru.user_id=u.id
6043
                    WHERE
6044
                        sru.c_id="'.$courseId.'" AND
6045
                        sru.session_id="'.$session.'" AND
6046
                        sru.status = '.SessionEntity::COACH;
6047
        }
6048
6049
        $rs = Database::query($sql);
6050
        $num_rows = Database::num_rows($rs);
6051
6052
        if (0 === $num_rows) {
6053
            return false;
6054
        }
6055
6056
        if (1 === $num_rows) {
6057
            $row = Database::fetch_array($rs);
6058
6059
            return (int) $row['uid'];
6060
        }
6061
6062
        $my_num_rows = $num_rows;
6063
        $my_user_id = Database::result($rs, $my_num_rows - 1, 'uid');
6064
6065
        return (int) $my_user_id;
6066
    }
6067
6068
    /**
6069
     * Determines if a user is a gradebook certified.
6070
     *
6071
     * @param int $cat_id  The category id of gradebook
6072
     * @param int $user_id The user id
6073
     *
6074
     * @return bool
6075
     */
6076
    public static function is_user_certified($cat_id, $user_id)
6077
    {
6078
        $cat_id = (int) $cat_id;
6079
        $user_id = (int) $user_id;
6080
6081
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
6082
        $sql = 'SELECT path_certificate
6083
                FROM '.$table.'
6084
                WHERE
6085
                    cat_id = "'.$cat_id.'" AND
6086
                    user_id = "'.$user_id.'"';
6087
        $rs = Database::query($sql);
6088
        $row = Database::fetch_array($rs);
6089
6090
        if ($row['path_certificate'] == '' || is_null($row['path_certificate'])) {
6091
            return false;
6092
        }
6093
6094
        return true;
6095
    }
6096
6097
    /**
6098
     * Gets the info about a gradebook certificate for a user by course.
6099
     *
6100
     * @param string $course_code The course code
6101
     * @param int    $session_id
6102
     * @param int    $user_id     The user id
6103
     * @param string $startDate   date string
6104
     * @param string $endDate     date string
6105
     *
6106
     * @return array if there is not information return false
6107
     */
6108
    public static function get_info_gradebook_certificate(
6109
        $course_code,
6110
        $session_id,
6111
        $user_id,
6112
        $startDate = null,
6113
        $endDate = null
6114
    ) {
6115
        $tbl_grade_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
6116
        $tbl_grade_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
6117
        $session_id = (int) $session_id;
6118
        $user_id = (int) $user_id;
6119
6120
        if (empty($session_id)) {
6121
            $session_condition = ' AND (session_id = "" OR session_id = 0 OR session_id IS NULL )';
6122
        } else {
6123
            $session_condition = " AND session_id = $session_id";
6124
        }
6125
6126
        $dateConditions = "";
6127
        if (!empty($startDate)) {
6128
            $startDate = api_get_utc_datetime($startDate, false, true);
6129
            $dateConditions .= " AND created_at >= '".$startDate->format('Y-m-d 00:00:00')."' ";
6130
        }
6131
        if (!empty($endDate)) {
6132
            $endDate = api_get_utc_datetime($endDate, false, true);
6133
            $dateConditions .= " AND created_at <= '".$endDate->format('Y-m-d 23:59:59')."' ";
6134
        }
6135
6136
        $sql = 'SELECT * FROM '.$tbl_grade_certificate.'
6137
                WHERE cat_id = (
6138
                    SELECT id FROM '.$tbl_grade_category.'
6139
                    WHERE
6140
                        course_code = "'.Database::escape_string($course_code).'" '.$session_condition.' '.$dateConditions.'
6141
                    LIMIT 1
6142
                ) AND user_id='.$user_id;
6143
6144
        $rs = Database::query($sql);
6145
        if (Database::num_rows($rs) > 0) {
6146
            $row = Database::fetch_array($rs, 'ASSOC');
6147
            $score = $row['score_certificate'];
6148
            $category_id = $row['cat_id'];
6149
            $cat = Category::load($category_id);
6150
            $displayscore = ScoreDisplay::instance();
6151
            if (isset($cat) && $displayscore->is_custom()) {
6152
                $grade = $displayscore->display_score(
6153
                    [$score, $cat[0]->get_weight()],
6154
                    SCORE_DIV_PERCENT_WITH_CUSTOM
6155
                );
6156
            } else {
6157
                $grade = $displayscore->display_score(
6158
                    [$score, $cat[0]->get_weight()]
6159
                );
6160
            }
6161
            $row['grade'] = $grade;
6162
6163
            return $row;
6164
        }
6165
6166
        return false;
6167
    }
6168
6169
    /**
6170
     * This function check if the user is a coach inside session course.
6171
     *
6172
     * @param int $user_id    User id
6173
     * @param int $courseId
6174
     * @param int $session_id
6175
     *
6176
     * @return bool True if the user is a coach
6177
     */
6178
    public static function is_session_course_coach($user_id, $courseId, $session_id)
6179
    {
6180
        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6181
        // Protect data
6182
        $user_id = intval($user_id);
6183
        $courseId = intval($courseId);
6184
        $session_id = intval($session_id);
6185
        $result = false;
6186
6187
        $sql = "SELECT session_id FROM $table
6188
                WHERE
6189
                  session_id = $session_id AND
6190
                  c_id = $courseId AND
6191
                  user_id = $user_id AND
6192
                  status = 2 ";
6193
        $res = Database::query($sql);
6194
6195
        if (Database::num_rows($res) > 0) {
6196
            $result = true;
6197
        }
6198
6199
        return $result;
6200
    }
6201
6202
    /**
6203
     * This function returns an icon path that represents the favicon of the website of which the url given.
6204
     * Defaults to the current Chamilo favicon.
6205
     *
6206
     * @param string $url1 URL of website where to look for favicon.ico
6207
     * @param string $url2 Optional second URL of website where to look for favicon.ico
6208
     *
6209
     * @return string Path of icon to load
6210
     */
6211
    public static function get_favicon_from_url($url1, $url2 = null)
6212
    {
6213
        $icon_link = '';
6214
        $url = $url1;
6215
        if (empty($url1)) {
6216
            $url = $url2;
6217
            if (empty($url)) {
6218
                $url = api_get_access_url(api_get_current_access_url_id());
6219
                $url = $url[0];
6220
            }
6221
        }
6222
        if (!empty($url)) {
6223
            $pieces = parse_url($url);
6224
            $icon_link = $pieces['scheme'].'://'.$pieces['host'].'/favicon.ico';
6225
        }
6226
6227
        return $icon_link;
6228
    }
6229
6230
    public static function addUserAsAdmin(User $user)
6231
    {
6232
        if ($user) {
0 ignored issues
show
introduced by
$user is of type Chamilo\UserBundle\Entity\User, thus it always evaluated to true.
Loading history...
6233
            $userId = $user->getId();
6234
            if (!self::is_admin($userId)) {
6235
                $table = Database::get_main_table(TABLE_MAIN_ADMIN);
6236
                $sql = "INSERT INTO $table SET user_id = $userId";
6237
                Database::query($sql);
6238
            }
6239
6240
            $user->addRole('ROLE_SUPER_ADMIN');
6241
            self::getManager()->updateUser($user, true);
6242
        }
6243
    }
6244
6245
    public static function removeUserAdmin(User $user)
6246
    {
6247
        $userId = (int) $user->getId();
6248
        if (self::is_admin($userId)) {
6249
            $table = Database::get_main_table(TABLE_MAIN_ADMIN);
6250
            $sql = "DELETE FROM $table WHERE user_id = $userId";
6251
            Database::query($sql);
6252
            $user->removeRole('ROLE_SUPER_ADMIN');
6253
            self::getManager()->updateUser($user, true);
6254
        }
6255
    }
6256
6257
    /**
6258
     * @param string $from
6259
     * @param string $to
6260
     */
6261
    public static function update_all_user_languages($from, $to)
6262
    {
6263
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
6264
        $from = Database::escape_string($from);
6265
        $to = Database::escape_string($to);
6266
6267
        if (!empty($to) && !empty($from)) {
6268
            $sql = "UPDATE $table_user SET language = '$to'
6269
                    WHERE language = '$from'";
6270
            Database::query($sql);
6271
        }
6272
    }
6273
6274
    /**
6275
     * Subscribe boss to students.
6276
     *
6277
     * @param int   $bossId                   The boss id
6278
     * @param array $usersId                  The users array
6279
     * @param bool  $deleteOtherAssignedUsers
6280
     *
6281
     * @return int Affected rows
6282
     */
6283
    public static function subscribeBossToUsers($bossId, $usersId, $deleteOtherAssignedUsers = true)
6284
    {
6285
        return self::subscribeUsersToUser(
6286
            $bossId,
6287
            $usersId,
6288
            USER_RELATION_TYPE_BOSS,
6289
            false,
6290
            $deleteOtherAssignedUsers
6291
        );
6292
    }
6293
6294
    /**
6295
     * @param int $userId
6296
     *
6297
     * @return bool
6298
     */
6299
    public static function removeAllBossFromStudent($userId)
6300
    {
6301
        $userId = (int) $userId;
6302
6303
        if (empty($userId)) {
6304
            return false;
6305
        }
6306
6307
        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6308
        $sql = "DELETE FROM $userRelUserTable
6309
                WHERE user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
6310
        Database::query($sql);
6311
6312
        return true;
6313
    }
6314
6315
    /**
6316
     * It updates course relation type as EX-LEARNER if project name (extra field from user_edition_extra_field_to_check) is changed.
6317
     *
6318
     * @param $userId
6319
     * @param $extraValue
6320
     *
6321
     * @throws \Doctrine\DBAL\Exception
6322
     */
6323
    public static function updateCourseRelationTypeExLearner($userId, $extraValue)
6324
    {
6325
        if (false !== api_get_configuration_value('user_edition_extra_field_to_check')) {
6326
            $extraToCheck = api_get_configuration_value('user_edition_extra_field_to_check');
6327
6328
            // Get the old user extra value to check
6329
            $userExtra = UserManager::get_extra_user_data_by_field($userId, $extraToCheck);
6330
            if (isset($userExtra[$extraToCheck]) && $userExtra[$extraToCheck] != $extraValue) {
6331
                // it searchs the courses with the user old extravalue
6332
                $extraFieldValues = new ExtraFieldValue('course');
6333
                $extraItems = $extraFieldValues->get_item_id_from_field_variable_and_field_value($extraToCheck, $userExtra[$extraToCheck], false, false, true);
6334
                $coursesTocheck = [];
6335
                if (!empty($extraItems)) {
6336
                    foreach ($extraItems as $items) {
6337
                        $coursesTocheck[] = $items['item_id'];
6338
                    }
6339
                }
6340
6341
                $tblUserGroupRelUser = Database::get_main_table(TABLE_USERGROUP_REL_USER);
6342
                $tblUserGroupRelCourse = Database::get_main_table(TABLE_USERGROUP_REL_COURSE);
6343
                $tblUserGroupRelSession = Database::get_main_table(TABLE_USERGROUP_REL_SESSION);
6344
                $tblSessionUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
6345
                $tblCourseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6346
6347
                // To check in main course
6348
                if (!empty($coursesTocheck)) {
6349
                    foreach ($coursesTocheck as $courseId) {
6350
                        $sql = "SELECT id FROM $tblCourseUser
6351
                                WHERE user_id = $userId AND c_id = $courseId";
6352
                        $rs = Database::query($sql);
6353
                        if (Database::num_rows($rs) > 0) {
6354
                            $id = Database::result($rs, 0, 0);
6355
                            $sql = "UPDATE $tblCourseUser SET relation_type = ".COURSE_EXLEARNER."
6356
                                    WHERE id = $id";
6357
                            Database::query($sql);
6358
                        }
6359
                    }
6360
                }
6361
6362
                // To check in sessions
6363
                if (!empty($coursesTocheck)) {
6364
                    $tblSessionCourseUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
6365
                    $sessionsToCheck = [];
6366
                    foreach ($coursesTocheck as $courseId) {
6367
                        $sql = "SELECT id, session_id FROM $tblSessionCourseUser
6368
                                WHERE user_id = $userId AND c_id = $courseId";
6369
                        $rs = Database::query($sql);
6370
                        if (Database::num_rows($rs) > 0) {
6371
                            $row = Database::fetch_array($rs);
6372
                            $id = $row['id'];
6373
                            $sessionId = $row['session_id'];
6374
                            $sql = "UPDATE $tblSessionCourseUser SET status = ".COURSE_EXLEARNER."
6375
                                    WHERE id = $id";
6376
                            Database::query($sql);
6377
                            $sessionsToCheck[] = $sessionId;
6378
                        }
6379
                    }
6380
                    // It checks if user is ex-learner in all courses in the session to update the session relation type
6381
                    if (!empty($sessionsToCheck)) {
6382
                        $sessionsToCheck = array_unique($sessionsToCheck);
6383
                        foreach ($sessionsToCheck as $sessionId) {
6384
                            $checkAll = Database::query("SELECT count(id) FROM $tblSessionCourseUser WHERE user_id = $userId AND session_id = $sessionId");
6385
                            $countAll = Database::result($checkAll, 0, 0);
6386
                            $checkExLearner = Database::query("SELECT count(id) FROM $tblSessionCourseUser WHERE status = ".COURSE_EXLEARNER." AND user_id = $userId AND session_id = $sessionId");
6387
                            $countExLearner = Database::result($checkExLearner, 0, 0);
6388
                            if ($countAll > 0 && $countAll == $countExLearner) {
6389
                                $sql = "UPDATE $tblSessionUser SET relation_type = ".COURSE_EXLEARNER."
6390
                                    WHERE user_id = $userId AND session_id = $sessionId";
6391
                                Database::query($sql);
6392
                            }
6393
                        }
6394
                    }
6395
                }
6396
                // To check users inside a class
6397
                $rsUser = Database::query("SELECT usergroup_id FROM $tblUserGroupRelUser WHERE user_id = $userId");
6398
                if (Database::num_rows($rsUser) > 0) {
6399
                    while ($rowUser = Database::fetch_array($rsUser)) {
6400
                        $usergroupId = $rowUser['usergroup_id'];
6401
6402
                        // Count courses with exlearners
6403
                        $sqlC1 = "SELECT count(id) FROM $tblUserGroupRelCourse WHERE usergroup_id = $usergroupId";
6404
                        $rsCourses = Database::query($sqlC1);
6405
                        $countGroupCourses = Database::result($rsCourses, 0, 0);
6406
6407
                        $sqlC2 = "SELECT count(cu.id)
6408
                                FROM $tblCourseUser cu
6409
                                INNER JOIN $tblUserGroupRelCourse gc
6410
                                    ON gc.course_id = cu.c_id
6411
                                WHERE
6412
                                    cu.user_id = $userId AND
6413
                                    usergroup_id = $usergroupId AND
6414
                                    relation_type = ".COURSE_EXLEARNER;
6415
                        $rsExCourses = Database::query($sqlC2);
6416
                        $countExCourses = Database::result($rsExCourses, 0, 0);
6417
                        $checkedExCourses = $countGroupCourses > 0 && ($countExCourses == $countGroupCourses);
6418
6419
                        // Count sessions with exlearners
6420
                        $sqlS1 = "SELECT count(id) FROM $tblUserGroupRelSession WHERE usergroup_id = $usergroupId";
6421
                        $rsSessions = Database::query($sqlS1);
6422
                        $countGroupSessions = Database::result($rsSessions, 0, 0);
6423
6424
                        $sqlS2 = "SELECT count(su.id)
6425
                                FROM $tblSessionUser su
6426
                                INNER JOIN $tblUserGroupRelSession gs
6427
                                    ON gs.session_id = su.session_id
6428
                                WHERE
6429
                                    su.user_id = $userId AND
6430
                                    usergroup_id = $usergroupId AND
6431
                                    relation_type = ".COURSE_EXLEARNER;
6432
                        $rsExSessions = Database::query($sqlS2);
6433
                        $countExSessions = Database::result($rsExSessions, 0, 0);
6434
                        $checkedExSessions = $countGroupSessions > 0 && ($countExSessions == $countGroupSessions);
6435
6436
                        // it checks if usergroup user should be set to EXLEARNER
6437
                        $checkedExClassLearner = false;
6438
                        if ($countGroupCourses > 0 && $countGroupSessions == 0) {
6439
                            $checkedExClassLearner = $checkedExCourses;
6440
                        } elseif ($countGroupCourses == 0 && $countGroupSessions > 0) {
6441
                            $checkedExClassLearner = $checkedExSessions;
6442
                        } elseif ($countGroupCourses > 0 && $countGroupSessions > 0) {
6443
                            $checkedExClassLearner = ($checkedExCourses && $checkedExSessions);
6444
                        }
6445
6446
                        if ($checkedExClassLearner) {
6447
                            Database::query("UPDATE $tblUserGroupRelUser SET relation_type = ".COURSE_EXLEARNER." WHERE user_id = $userId AND usergroup_id = $usergroupId");
6448
                        }
6449
                    }
6450
                }
6451
            }
6452
        }
6453
    }
6454
6455
    /**
6456
     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
6457
     *
6458
     * @param int   $studentId
6459
     * @param array $bossList
6460
     * @param bool  $sendNotification
6461
     *
6462
     * @return mixed Affected rows or false on failure
6463
     */
6464
    public static function subscribeUserToBossList(
6465
        $studentId,
6466
        $bossList,
6467
        $sendNotification = false
6468
    ) {
6469
        $inserted = 0;
6470
        if (!empty($bossList)) {
6471
            sort($bossList);
6472
            $studentId = (int) $studentId;
6473
            $studentInfo = api_get_user_info($studentId);
6474
6475
            if (empty($studentInfo)) {
6476
                return false;
6477
            }
6478
6479
            $previousBossList = self::getStudentBossList($studentId);
6480
            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
6481
            sort($previousBossList);
6482
6483
            // Boss list is the same, nothing changed.
6484
            if ($bossList == $previousBossList) {
6485
                return false;
6486
            }
6487
6488
            $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6489
            self::removeAllBossFromStudent($studentId);
6490
6491
            foreach ($bossList as $bossId) {
6492
                $bossId = (int) $bossId;
6493
                $bossInfo = api_get_user_info($bossId);
6494
6495
                if (empty($bossInfo)) {
6496
                    continue;
6497
                }
6498
6499
                $bossLanguage = $bossInfo['language'];
6500
6501
                $sql = "INSERT IGNORE INTO $userRelUserTable (user_id, friend_user_id, relation_type)
6502
                        VALUES ($studentId, $bossId, ".USER_RELATION_TYPE_BOSS.")";
6503
                $insertId = Database::query($sql);
6504
6505
                if ($insertId) {
6506
                    if ($sendNotification) {
6507
                        $name = $studentInfo['complete_name'];
6508
                        $url = api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$studentId;
6509
                        $url = Display::url($url, $url);
6510
                        $subject = sprintf(
6511
                            get_lang('UserXHasBeenAssignedToBoss', false, $bossLanguage),
6512
                            $name
6513
                        );
6514
                        $message = sprintf(
6515
                            get_lang('UserXHasBeenAssignedToBossWithUrlX', false, $bossLanguage),
6516
                            $name,
6517
                            $url
6518
                        );
6519
                        MessageManager::send_message_simple(
6520
                            $bossId,
6521
                            $subject,
6522
                            $message
6523
                        );
6524
                    }
6525
                    $inserted++;
6526
                }
6527
            }
6528
        } else {
6529
            self::removeAllBossFromStudent($studentId);
6530
        }
6531
6532
        return $inserted;
6533
    }
6534
6535
    /**
6536
     * Get users followed by student boss.
6537
     *
6538
     * @param int    $userId
6539
     * @param int    $userStatus         (STUDENT, COURSEMANAGER, etc)
6540
     * @param bool   $getOnlyUserId
6541
     * @param bool   $getSql
6542
     * @param bool   $getCount
6543
     * @param int    $from
6544
     * @param int    $numberItems
6545
     * @param int    $column
6546
     * @param string $direction
6547
     * @param int    $active
6548
     * @param string $lastConnectionDate
6549
     *
6550
     * @return array users
6551
     */
6552
    public static function getUsersFollowedByStudentBoss(
6553
        $userId,
6554
        $userStatus = 0,
6555
        $getOnlyUserId = false,
6556
        $getSql = false,
6557
        $getCount = false,
6558
        $from = null,
6559
        $numberItems = null,
6560
        $column = null,
6561
        $direction = null,
6562
        $active = null,
6563
        $lastConnectionDate = null
6564
    ) {
6565
        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...
6566
            $userId,
6567
            $userStatus,
6568
            $getOnlyUserId,
6569
            $getSql,
6570
            $getCount,
6571
            $from,
6572
            $numberItems,
6573
            $column,
6574
            $direction,
6575
            $active,
6576
            $lastConnectionDate,
6577
            STUDENT_BOSS
6578
        );
6579
    }
6580
6581
    /**
6582
     * @return array
6583
     */
6584
    public static function getOfficialCodeGrouped()
6585
    {
6586
        $user = Database::get_main_table(TABLE_MAIN_USER);
6587
        $sql = "SELECT DISTINCT official_code
6588
                FROM $user
6589
                GROUP BY official_code";
6590
        $result = Database::query($sql);
6591
        $values = Database::store_result($result, 'ASSOC');
6592
        $result = [];
6593
        foreach ($values as $value) {
6594
            $result[$value['official_code']] = $value['official_code'];
6595
        }
6596
6597
        return $result;
6598
    }
6599
6600
    /**
6601
     * @param string $officialCode
6602
     *
6603
     * @return array
6604
     */
6605
    public static function getUsersByOfficialCode($officialCode)
6606
    {
6607
        $user = Database::get_main_table(TABLE_MAIN_USER);
6608
        $officialCode = Database::escape_string($officialCode);
6609
6610
        $sql = "SELECT DISTINCT id
6611
                FROM $user
6612
                WHERE official_code = '$officialCode'
6613
                ";
6614
        $result = Database::query($sql);
6615
6616
        $users = [];
6617
        while ($row = Database::fetch_array($result)) {
6618
            $users[] = $row['id'];
6619
        }
6620
6621
        return $users;
6622
    }
6623
6624
    /**
6625
     * Calc the expended time (in seconds) by a user in a course.
6626
     *
6627
     * @param int    $userId    The user id
6628
     * @param int    $courseId  The course id
6629
     * @param int    $sessionId Optional. The session id
6630
     * @param string $from      Optional. From date
6631
     * @param string $until     Optional. Until date
6632
     *
6633
     * @return int The time
6634
     */
6635
    public static function getTimeSpentInCourses(
6636
        $userId,
6637
        $courseId,
6638
        $sessionId = 0,
6639
        $from = '',
6640
        $until = ''
6641
    ) {
6642
        $userId = (int) $userId;
6643
        $sessionId = (int) $sessionId;
6644
6645
        $trackCourseAccessTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
6646
        $whereConditions = [
6647
            'user_id = ? ' => $userId,
6648
            'AND c_id = ? ' => $courseId,
6649
            'AND session_id = ? ' => $sessionId,
6650
        ];
6651
6652
        if (!empty($from) && !empty($until)) {
6653
            $whereConditions["AND (login_course_date >= '?' "] = $from;
6654
            $whereConditions["AND logout_course_date <= DATE_ADD('?', INTERVAL 1 DAY)) "] = $until;
6655
        }
6656
6657
        $trackResult = Database::select(
6658
            'SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as total_time',
6659
            $trackCourseAccessTable,
6660
            [
6661
                'where' => $whereConditions,
6662
            ],
6663
            'first'
6664
        );
6665
6666
        if ($trackResult != false) {
6667
            return $trackResult['total_time'] ? $trackResult['total_time'] : 0;
6668
        }
6669
6670
        return 0;
6671
    }
6672
6673
    /**
6674
     * Get the boss user ID from a followed user id.
6675
     *
6676
     * @param $userId
6677
     *
6678
     * @return bool
6679
     */
6680
    public static function getFirstStudentBoss($userId)
6681
    {
6682
        $userId = (int) $userId;
6683
        if ($userId > 0) {
6684
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6685
            $row = Database::select(
6686
                'DISTINCT friend_user_id AS boss_id',
6687
                $userRelTable,
6688
                [
6689
                    'where' => [
6690
                        'user_id = ? AND relation_type = ? LIMIT 1' => [
6691
                            $userId,
6692
                            USER_RELATION_TYPE_BOSS,
6693
                        ],
6694
                    ],
6695
                ]
6696
            );
6697
            if (!empty($row)) {
6698
                return $row[0]['boss_id'];
6699
            }
6700
        }
6701
6702
        return false;
6703
    }
6704
6705
    /**
6706
     * Get the boss user ID from a followed user id.
6707
     *
6708
     * @param int $userId student id
6709
     *
6710
     * @return array
6711
     */
6712
    public static function getStudentBossList($userId)
6713
    {
6714
        $userId = (int) $userId;
6715
6716
        if ($userId > 0) {
6717
            $userRelTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
6718
6719
            return Database::select(
6720
                'DISTINCT friend_user_id AS boss_id',
6721
                $userRelTable,
6722
                [
6723
                    'where' => [
6724
                        'user_id = ? AND relation_type = ? ' => [
6725
                            $userId,
6726
                            USER_RELATION_TYPE_BOSS,
6727
                        ],
6728
                    ],
6729
                ]
6730
            );
6731
        }
6732
6733
        return [];
6734
    }
6735
6736
    /**
6737
     * @param int $bossId
6738
     * @param int $studentId
6739
     *
6740
     * @return bool
6741
     */
6742
    public static function userIsBossOfStudent($bossId, $studentId)
6743
    {
6744
        $result = false;
6745
        $bossList = self::getStudentBossList($studentId);
6746
        if (!empty($bossList)) {
6747
            $bossList = array_column($bossList, 'boss_id');
6748
            if (in_array($bossId, $bossList)) {
6749
                $result = true;
6750
            }
6751
        }
6752
6753
        return $result;
6754
    }
6755
6756
    /**
6757
     * Displays the name of the user and makes the link to the user profile.
6758
     *
6759
     * @param array $userInfo
6760
     *
6761
     * @return string
6762
     */
6763
    public static function getUserProfileLink($userInfo)
6764
    {
6765
        if (isset($userInfo) && isset($userInfo['user_id'])) {
6766
            return Display::url(
6767
                $userInfo['complete_name_with_username'],
6768
                $userInfo['profile_url']
6769
            );
6770
        }
6771
6772
        return get_lang('Anonymous');
6773
    }
6774
6775
    /**
6776
     * Get users whose name matches $firstname and $lastname.
6777
     *
6778
     * @param string $firstname Firstname to search
6779
     * @param string $lastname  Lastname to search
6780
     *
6781
     * @return array The user list
6782
     */
6783
    public static function getUsersByName($firstname, $lastname)
6784
    {
6785
        $firstname = Database::escape_string($firstname);
6786
        $lastname = Database::escape_string($lastname);
6787
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
6788
6789
        $sql = <<<SQL
6790
            SELECT id, username, lastname, firstname
6791
            FROM $userTable
6792
            WHERE
6793
                firstname LIKE '$firstname%' AND
6794
                lastname LIKE '$lastname%'
6795
SQL;
6796
        $result = Database::query($sql);
6797
        $users = [];
6798
        while ($resultData = Database::fetch_object($result)) {
6799
            $users[] = $resultData;
6800
        }
6801
6802
        return $users;
6803
    }
6804
6805
    /**
6806
     * @param int $optionSelected
6807
     *
6808
     * @return string
6809
     */
6810
    public static function getUserSubscriptionTab($optionSelected = 1)
6811
    {
6812
        $allowAdmin = api_get_setting('allow_user_course_subscription_by_course_admin');
6813
        if (($allowAdmin === 'true' && api_is_allowed_to_edit()) ||
6814
            api_is_platform_admin()
6815
        ) {
6816
            $userPath = api_get_path(WEB_CODE_PATH).'user/';
6817
6818
            $headers = [
6819
                [
6820
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.STUDENT,
6821
                    'content' => get_lang('Students'),
6822
                ],
6823
                [
6824
                    'url' => $userPath.'user.php?'.api_get_cidreq().'&type='.COURSEMANAGER,
6825
                    'content' => get_lang('Teachers'),
6826
                ],
6827
                /*[
6828
                    'url' => $userPath.'subscribe_user.php?'.api_get_cidreq(),
6829
                    'content' => get_lang('Students'),
6830
                ],
6831
                [
6832
                    'url' => $userPath.'subscribe_user.php?type=teacher&'.api_get_cidreq(),
6833
                    'content' => get_lang('Teachers'),
6834
                ],*/
6835
                [
6836
                    'url' => api_get_path(WEB_CODE_PATH).'group/group.php?'.api_get_cidreq(),
6837
                    'content' => get_lang('Groups'),
6838
                ],
6839
                'classes' => [
6840
                    'url' => $userPath.'class.php?'.api_get_cidreq(),
6841
                    'content' => get_lang('Classes'),
6842
                ],
6843
            ];
6844
6845
            if (api_get_configuration_value('session_classes_tab_disable')
6846
                && !api_is_platform_admin()
6847
                && api_get_session_id()
6848
            ) {
6849
                unset($headers['classes']);
6850
            }
6851
6852
            return Display::tabsOnlyLink($headers, $optionSelected);
6853
        }
6854
6855
        return '';
6856
    }
6857
6858
    /**
6859
     * Make sure this function is protected because it does NOT check password!
6860
     *
6861
     * This function defines globals.
6862
     *
6863
     * @param int  $userId
6864
     * @param bool $checkIfUserCanLoginAs
6865
     *
6866
     * @return bool
6867
     *
6868
     * @author Evie Embrechts
6869
     * @author Yannick Warnier <[email protected]>
6870
     */
6871
    public static function loginAsUser($userId, $checkIfUserCanLoginAs = true)
6872
    {
6873
        $userId = (int) $userId;
6874
        $userInfo = api_get_user_info($userId);
6875
6876
        // Check if the user is allowed to 'login_as'
6877
        $canLoginAs = true;
6878
        if ($checkIfUserCanLoginAs) {
6879
            $canLoginAs = api_can_login_as($userId);
6880
        }
6881
6882
        if (!$canLoginAs || empty($userInfo)) {
6883
            return false;
6884
        }
6885
6886
        if ($userId) {
6887
            $logInfo = [
6888
                'tool' => 'logout',
6889
                'tool_id' => 0,
6890
                'tool_id_detail' => 0,
6891
                'action' => '',
6892
                'info' => 'Change user (login as)',
6893
            ];
6894
            Event::registerLog($logInfo);
0 ignored issues
show
Bug introduced by
The method registerLog() does not exist on Event. ( Ignorable by Annotation )

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

6894
            Event::/** @scrutinizer ignore-call */ 
6895
                   registerLog($logInfo);

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

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

Loading history...
6895
6896
            // Logout the current user
6897
            self::loginDelete(api_get_user_id());
6898
6899
            Session::erase('_user');
6900
            Session::erase('is_platformAdmin');
6901
            Session::erase('is_allowedCreateCourse');
6902
            Session::erase('_uid');
6903
6904
            // Cleaning session variables
6905
            $_user['firstName'] = $userInfo['firstname'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$_user was never initialized. Although not strictly required by PHP, it is generally a good practice to add $_user = array(); before regardless.
Loading history...
6906
            $_user['lastName'] = $userInfo['lastname'];
6907
            $_user['mail'] = $userInfo['email'];
6908
            $_user['official_code'] = $userInfo['official_code'];
6909
            $_user['picture_uri'] = $userInfo['picture_uri'];
6910
            $_user['user_id'] = $userId;
6911
            $_user['id'] = $userId;
6912
            $_user['status'] = $userInfo['status'];
6913
6914
            // Filling session variables with new data
6915
            Session::write('_uid', $userId);
6916
            Session::write('_user', $userInfo);
6917
            Session::write('is_platformAdmin', (bool) self::is_admin($userId));
6918
            Session::write('is_allowedCreateCourse', $userInfo['status'] == 1);
6919
            // will be useful later to know if the user is actually an admin or not (example reporting)
6920
            Session::write('login_as', true);
6921
            $logInfo = [
6922
                'tool' => 'login',
6923
                'tool_id' => 0,
6924
                'tool_id_detail' => 0,
6925
                'info' => $userId,
6926
            ];
6927
            Event::registerLog($logInfo);
6928
6929
            return true;
6930
        }
6931
6932
        return false;
6933
    }
6934
6935
    /**
6936
     * Remove all login records from the track_e_online stats table,
6937
     * for the given user ID.
6938
     *
6939
     * @param int $userId User ID
6940
     */
6941
    public static function loginDelete($userId)
6942
    {
6943
        $online_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
6944
        $userId = (int) $userId;
6945
        $query = "DELETE FROM $online_table WHERE login_user_id = $userId";
6946
        Database::query($query);
6947
    }
6948
6949
    /**
6950
     * Login as first admin user registered in the platform.
6951
     *
6952
     * @return array
6953
     */
6954
    public static function logInAsFirstAdmin()
6955
    {
6956
        $adminList = self::get_all_administrators();
6957
6958
        if (!empty($adminList)) {
6959
            $userInfo = current($adminList);
6960
            if (!empty($userInfo)) {
6961
                $result = self::loginAsUser($userInfo['user_id'], false);
6962
                if ($result && api_is_platform_admin()) {
6963
                    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...
6964
                }
6965
            }
6966
        }
6967
6968
        return [];
6969
    }
6970
6971
    public static function blockIfMaxLoginAttempts(array $userInfo)
6972
    {
6973
        if (false === (bool) $userInfo['active'] || null === $userInfo['last_login']) {
6974
            return;
6975
        }
6976
6977
        $maxAllowed = (int) api_get_configuration_value('login_max_attempt_before_blocking_account');
6978
6979
        if ($maxAllowed <= 0) {
6980
            return;
6981
        }
6982
6983
        $em = Database::getManager();
6984
6985
        $countFailedAttempts = $em
6986
            ->getRepository(TrackELoginAttempt::class)
6987
            ->createQueryBuilder('la')
6988
            ->select('COUNT(la)')
6989
            ->where('la.username = :username')
6990
            ->andWhere('la.loginDate >= :last_login')
6991
            ->andWhere('la.success <> TRUE')
6992
            ->setParameters(
6993
                [
6994
                    'username' => $userInfo['username'],
6995
                    'last_login' => $userInfo['last_login'],
6996
                ]
6997
            )
6998
            ->getQuery()
6999
            ->getSingleScalarResult()
7000
        ;
7001
7002
        if ($countFailedAttempts >= $maxAllowed) {
7003
            Database::update(
7004
                Database::get_main_table(TABLE_MAIN_USER),
7005
                ['active' => false],
7006
                ['username = ?' => $userInfo['username']]
7007
            );
7008
7009
            Display::addFlash(
7010
                Display::return_message(
7011
                    sprintf(
7012
                        get_lang('XAccountDisabledByYAttempts'),
7013
                        $userInfo['username'],
7014
                        $countFailedAttempts
7015
                    ),
7016
                    'error',
7017
                    false
7018
                )
7019
            );
7020
        }
7021
    }
7022
7023
    /**
7024
     * Check if user is teacher of a student based in their courses.
7025
     *
7026
     * @param $teacherId
7027
     * @param $studentId
7028
     *
7029
     * @return array
7030
     */
7031
    public static function getCommonCoursesBetweenTeacherAndStudent($teacherId, $studentId)
7032
    {
7033
        $courses = CourseManager::getCoursesFollowedByUser(
7034
            $teacherId,
7035
            COURSEMANAGER
7036
        );
7037
        if (empty($courses)) {
7038
            return false;
7039
        }
7040
7041
        $coursesFromUser = CourseManager::get_courses_list_by_user_id($studentId);
7042
        if (empty($coursesFromUser)) {
7043
            return false;
7044
        }
7045
7046
        $coursesCodeList = array_column($courses, 'code');
7047
        $coursesCodeFromUserList = array_column($coursesFromUser, 'code');
7048
        $commonCourses = array_intersect($coursesCodeList, $coursesCodeFromUserList);
7049
        $commonCourses = array_filter($commonCourses);
7050
7051
        if (!empty($commonCourses)) {
7052
            return $commonCourses;
7053
        }
7054
7055
        return [];
7056
    }
7057
7058
    /**
7059
     * @param int $teacherId
7060
     * @param int $studentId
7061
     *
7062
     * @return bool
7063
     */
7064
    public static function isTeacherOfStudent($teacherId, $studentId)
7065
    {
7066
        $courses = self::getCommonCoursesBetweenTeacherAndStudent(
7067
            $teacherId,
7068
            $studentId
7069
        );
7070
7071
        if (!empty($courses)) {
7072
            return true;
7073
        }
7074
7075
        return false;
7076
    }
7077
7078
    /**
7079
     * Send user confirmation mail.
7080
     *
7081
     * @throws Exception
7082
     */
7083
    public static function sendUserConfirmationMail(User $user)
7084
    {
7085
        $uniqueId = api_get_unique_id();
7086
        $user->setConfirmationToken($uniqueId);
7087
7088
        Database::getManager()->persist($user);
7089
        Database::getManager()->flush();
7090
7091
        $url = api_get_path(WEB_CODE_PATH).'auth/user_mail_confirmation.php?token='.$uniqueId;
7092
7093
        // Check if the user was originally set for an automated subscription to a course or session
7094
        $courseCodeToRedirect = Session::read('course_redirect');
7095
        $sessionToRedirect = Session::read('session_redirect');
7096
        if (!empty($courseCodeToRedirect)) {
7097
            $url .= '&c='.$courseCodeToRedirect;
7098
        }
7099
        if (!empty($sessionToRedirect)) {
7100
            $url .= '&s='.$sessionToRedirect;
7101
        }
7102
        $mailSubject = get_lang('RegistrationConfirmation');
7103
        $mailBody = get_lang('RegistrationConfirmationEmailMessage')
7104
            .PHP_EOL
7105
            .Display::url($url, $url);
7106
7107
        api_mail_html(
7108
            self::formatUserFullName($user),
7109
            $user->getEmail(),
7110
            $mailSubject,
7111
            $mailBody
7112
        );
7113
        Display::addFlash(Display::return_message(get_lang('CheckYourEmailAndFollowInstructions')));
7114
    }
7115
7116
    /**
7117
     * Anonymize a user. Replace personal info by anonymous info.
7118
     *
7119
     * @param int  $userId   User id
7120
     * @param bool $deleteIP Whether to replace the IP address in logs tables by 127.0.0.1 or to leave as is
7121
     *
7122
     * @throws \Exception
7123
     *
7124
     * @return bool
7125
     * @assert (0) === false
7126
     */
7127
    public static function anonymize($userId, $deleteIP = true)
7128
    {
7129
        global $debug;
7130
7131
        $userId = (int) $userId;
7132
7133
        if (empty($userId)) {
7134
            return false;
7135
        }
7136
7137
        $em = Database::getManager();
7138
        $user = api_get_user_entity($userId);
7139
        $uniqueId = uniqid('anon', true);
7140
        $user
7141
            ->setFirstname($uniqueId)
7142
            ->setLastname($uniqueId)
7143
            ->setBiography('')
7144
            ->setAddress('')
7145
            ->setCurriculumItems(null)
7146
            ->setDateOfBirth(null)
7147
            ->setCompetences('')
7148
            ->setDiplomas('')
7149
            ->setOpenarea('')
7150
            ->setTeach('')
7151
            ->setProductions(null)
7152
            ->setOpenid('')
7153
            ->setEmailCanonical($uniqueId.'@example.com')
7154
            ->setEmail($uniqueId.'@example.com')
7155
            ->setUsername($uniqueId)
7156
            ->setUsernameCanonical($uniqueId)
7157
            ->setPhone('')
7158
            ->setOfficialCode('')
7159
        ;
7160
7161
        self::deleteUserPicture($userId);
7162
        self::cleanUserRequestsOfRemoval($userId);
7163
7164
        // The IP address is a border-case personal data, as it does
7165
        // not directly allow for personal identification (it is not
7166
        // a completely safe value in most countries - the IP could
7167
        // be used by neighbours and crackers)
7168
        if ($deleteIP) {
7169
            $substitute = '127.0.0.1';
7170
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
7171
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE access_user_id = $userId";
7172
            $res = Database::query($sql);
7173
            if ($res === false && $debug > 0) {
7174
                error_log("Could not anonymize IP address for user $userId ($sql)");
7175
            }
7176
7177
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
7178
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7179
            $res = Database::query($sql);
7180
            if ($res === false && $debug > 0) {
7181
                error_log("Could not anonymize IP address for user $userId ($sql)");
7182
            }
7183
7184
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7185
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE exe_user_id = $userId";
7186
            $res = Database::query($sql);
7187
            if ($res === false && $debug > 0) {
7188
                error_log("Could not anonymize IP address for user $userId ($sql)");
7189
            }
7190
7191
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
7192
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
7193
            $res = Database::query($sql);
7194
            if ($res === false && $debug > 0) {
7195
                error_log("Could not anonymize IP address for user $userId ($sql)");
7196
            }
7197
7198
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
7199
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE login_user_id = $userId";
7200
            $res = Database::query($sql);
7201
            if ($res === false && $debug > 0) {
7202
                error_log("Could not anonymize IP address for user $userId ($sql)");
7203
            }
7204
7205
            $table = Database::get_course_table(TABLE_WIKI);
7206
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7207
            $res = Database::query($sql);
7208
            if ($res === false && $debug > 0) {
7209
                error_log("Could not anonymize IP address for user $userId ($sql)");
7210
            }
7211
7212
            $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
7213
            $sql = "UPDATE $table set ip_address = '$substitute' WHERE sys_insert_user_id = $userId";
7214
            $res = Database::query($sql);
7215
            if ($res === false && $debug > 0) {
7216
                error_log("Could not anonymize IP address for user $userId ($sql)");
7217
            }
7218
7219
            $table = Database::get_course_table(TABLE_WIKI);
7220
            $sql = "UPDATE $table set user_ip = '$substitute' WHERE user_id = $userId";
7221
            $res = Database::query($sql);
7222
            if ($res === false && $debug > 0) {
7223
                error_log("Could not anonymize IP address for user $userId ($sql)");
7224
            }
7225
        }
7226
        $em->persist($user);
7227
        $em->flush($user);
7228
        Event::addEvent(LOG_USER_ANONYMIZE, LOG_USER_ID, $userId);
7229
7230
        return true;
7231
    }
7232
7233
    /**
7234
     * @param int $userId
7235
     *
7236
     * @throws Exception
7237
     *
7238
     * @return string
7239
     */
7240
    public static function anonymizeUserWithVerification($userId)
7241
    {
7242
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
7243
7244
        $message = '';
7245
        if (api_is_platform_admin() ||
7246
            ($allowDelete && api_is_session_admin())
7247
        ) {
7248
            $userToUpdateInfo = api_get_user_info($userId);
7249
            $currentUserId = api_get_user_id();
7250
7251
            if ($userToUpdateInfo &&
7252
                api_global_admin_can_edit_admin($userId, null, $allowDelete)
7253
            ) {
7254
                if ($userId != $currentUserId &&
7255
                    self::anonymize($userId)
7256
                ) {
7257
                    $message = Display::return_message(
7258
                        sprintf(get_lang('UserXAnonymized'), $userToUpdateInfo['complete_name_with_username']),
7259
                        'confirmation'
7260
                    );
7261
                } else {
7262
                    $message = Display::return_message(
7263
                        sprintf(get_lang('CannotAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
7264
                        'error'
7265
                    );
7266
                }
7267
            } else {
7268
                $message = Display::return_message(
7269
                    sprintf(get_lang('NoPermissionToAnonymizeUserX'), $userToUpdateInfo['complete_name_with_username']),
7270
                    'error'
7271
                );
7272
            }
7273
        }
7274
7275
        return $message;
7276
    }
7277
7278
    /**
7279
     * @param int $userId
7280
     *
7281
     * @throws Exception
7282
     *
7283
     * @return string
7284
     */
7285
    public static function deleteUserWithVerification($userId)
7286
    {
7287
        $allowDelete = api_get_configuration_value('allow_delete_user_for_session_admin');
7288
        $message = Display::return_message(get_lang('CannotDeleteUser'), 'error');
7289
        $userToUpdateInfo = api_get_user_info($userId);
7290
7291
        // User must exist.
7292
        if (empty($userToUpdateInfo)) {
7293
            return $message;
7294
        }
7295
7296
        $currentUserId = api_get_user_id();
7297
7298
        // Cannot delete myself.
7299
        if ($userId == $currentUserId) {
7300
            return $message;
7301
        }
7302
7303
        if (api_is_platform_admin() ||
7304
            ($allowDelete && api_is_session_admin())
7305
        ) {
7306
            if (api_global_admin_can_edit_admin($userId, null, $allowDelete)) {
7307
                if (self::delete_user($userId)) {
7308
                    $message = Display::return_message(
7309
                        get_lang('UserDeleted').': '.$userToUpdateInfo['complete_name_with_username'],
7310
                        'confirmation'
7311
                    );
7312
                } else {
7313
                    $message = Display::return_message(get_lang('CannotDeleteUserBecauseOwnsCourse'), 'error');
7314
                }
7315
            }
7316
        }
7317
7318
        return $message;
7319
    }
7320
7321
    /**
7322
     * @return array
7323
     */
7324
    public static function createDataPrivacyExtraFields()
7325
    {
7326
        self::create_extra_field(
7327
            'request_for_legal_agreement_consent_removal_justification',
7328
            1, //text
7329
            'Request for legal agreement consent removal justification	',
7330
            ''
7331
        );
7332
7333
        self::create_extra_field(
7334
            'request_for_delete_account_justification',
7335
            1, //text
7336
            'Request for delete account justification',
7337
            ''
7338
        );
7339
7340
        $extraFieldId = self::create_extra_field(
7341
            'request_for_legal_agreement_consent_removal',
7342
            1, //text
7343
            'Request for legal agreement consent removal',
7344
            ''
7345
        );
7346
7347
        $extraFieldIdDeleteAccount = self::create_extra_field(
7348
            'request_for_delete_account',
7349
            1, //text
7350
            'Request for delete user account',
7351
            ''
7352
        );
7353
7354
        return [
7355
            'delete_account_extra_field' => $extraFieldIdDeleteAccount,
7356
            'delete_legal' => $extraFieldId,
7357
        ];
7358
    }
7359
7360
    /**
7361
     * @param int $userId
7362
     */
7363
    public static function cleanUserRequestsOfRemoval($userId)
7364
    {
7365
        $userId = (int) $userId;
7366
7367
        $extraFieldValue = new ExtraFieldValue('user');
7368
        $extraFieldsToDelete = [
7369
            'legal_accept',
7370
            'request_for_legal_agreement_consent_removal',
7371
            'request_for_legal_agreement_consent_removal_justification',
7372
            'request_for_delete_account_justification', // just in case delete also this
7373
            'request_for_delete_account',
7374
        ];
7375
7376
        foreach ($extraFieldsToDelete as $variable) {
7377
            $value = $extraFieldValue->get_values_by_handler_and_field_variable(
7378
                $userId,
7379
                $variable
7380
            );
7381
            if ($value && isset($value['id'])) {
7382
                $extraFieldValue->delete($value['id']);
7383
            }
7384
        }
7385
    }
7386
7387
    /**
7388
     * @param int $searchYear
7389
     *
7390
     * @throws Exception
7391
     *
7392
     * @return array
7393
     */
7394
    public static function getSubscribedSessionsByYear(array $userInfo, $searchYear)
7395
    {
7396
        $timezone = new DateTimeZone(api_get_timezone());
7397
7398
        $sessions = [];
7399
        if (DRH == $userInfo['status']) {
7400
            $sessions = SessionManager::get_sessions_followed_by_drh($userInfo['id']);
7401
        } elseif (api_is_platform_admin(true)) {
7402
            $sessions = SessionManager::getSessionsForAdmin($userInfo['id']);
7403
        } else {
7404
            $sessionsByCategory = self::get_sessions_by_category($userInfo['id'], false, true, true);
7405
            $sessionsByCategory = array_column($sessionsByCategory, 'sessions');
7406
7407
            foreach ($sessionsByCategory as $sessionsInCategory) {
7408
                $sessions = array_merge($sessions, $sessionsInCategory);
7409
            }
7410
        }
7411
7412
        $sessions = array_map(
7413
            function ($sessionInfo) {
7414
                if (!isset($sessionInfo['session_id'])) {
7415
                    $sessionInfo['session_id'] = $sessionInfo['id'];
7416
                }
7417
                if (!isset($sessionInfo['session_name'])) {
7418
                    $sessionInfo['session_name'] = $sessionInfo['name'];
7419
                }
7420
7421
                return $sessionInfo;
7422
            },
7423
            $sessions
7424
        );
7425
7426
        $calendarSessions = [];
7427
7428
        foreach ($sessions as $sessionInfo) {
7429
            if (!empty($sessionInfo['duration'])) {
7430
                $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser(
7431
                    $sessionInfo['session_id'],
7432
                    $userInfo['id']
7433
                );
7434
7435
                if (empty($courseAccess)) {
7436
                    continue;
7437
                }
7438
7439
                $firstAcessDate = new DateTime(api_get_local_time($courseAccess['login_course_date']), $timezone);
7440
                $lastAccessDate = clone $firstAcessDate;
7441
                $lastAccessDate->modify("+{$sessionInfo['duration']} days");
7442
7443
                $firstAccessYear = (int) $firstAcessDate->format('Y');
7444
                $lastAccessYear = (int) $lastAccessDate->format('Y');
7445
7446
                if ($firstAccessYear <= $searchYear && $lastAccessYear >= $searchYear) {
7447
                    $calendarSessions[$sessionInfo['session_id']] = [
7448
                        'name' => $sessionInfo['session_name'],
7449
                        'access_start_date' => $firstAcessDate->format('Y-m-d h:i:s'),
7450
                        'access_end_date' => $lastAccessDate->format('Y-m-d h:i:s'),
7451
                    ];
7452
                }
7453
7454
                continue;
7455
            }
7456
7457
            $accessStartDate = !empty($sessionInfo['access_start_date'])
7458
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
7459
                : null;
7460
            $accessEndDate = !empty($sessionInfo['access_end_date'])
7461
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
7462
                : null;
7463
            $accessStartYear = $accessStartDate ? (int) $accessStartDate->format('Y') : 0;
7464
            $accessEndYear = $accessEndDate ? (int) $accessEndDate->format('Y') : 0;
7465
7466
            $isValid = false;
7467
7468
            if ($accessStartYear && $accessEndYear) {
7469
                if ($accessStartYear <= $searchYear && $accessEndYear >= $searchYear) {
7470
                    $isValid = true;
7471
                }
7472
            }
7473
7474
            if ($accessStartYear && !$accessEndYear) {
7475
                if ($accessStartYear == $searchYear) {
7476
                    $isValid = true;
7477
                }
7478
            }
7479
7480
            if (!$accessStartYear && $accessEndYear) {
7481
                if ($accessEndYear == $searchYear) {
7482
                    $isValid = true;
7483
                }
7484
            }
7485
7486
            if ($isValid) {
7487
                $calendarSessions[$sessionInfo['session_id']] = [
7488
                    'name' => $sessionInfo['session_name'],
7489
                    'access_start_date' => $accessStartDate ? $accessStartDate->format('Y-m-d h:i:s') : null,
7490
                    'access_end_date' => $accessEndDate ? $accessEndDate->format('Y-m-d h:i:s') : null,
7491
                ];
7492
            }
7493
        }
7494
7495
        return $calendarSessions;
7496
    }
7497
7498
    /**
7499
     * Get sessions info for planification calendar.
7500
     *
7501
     * @param array $sessionsList Session list from UserManager::getSubscribedSessionsByYear
7502
     * @param int   $searchYear
7503
     *
7504
     * @throws Exception
7505
     *
7506
     * @return array
7507
     */
7508
    public static function getSessionsCalendarByYear(array $sessionsList, $searchYear)
7509
    {
7510
        $timezone = new DateTimeZone(api_get_timezone());
7511
        $calendar = [];
7512
7513
        foreach ($sessionsList as $sessionId => $sessionInfo) {
7514
            $startDate = $sessionInfo['access_start_date']
7515
                ? new DateTime(api_get_local_time($sessionInfo['access_start_date']), $timezone)
7516
                : null;
7517
            $endDate = $sessionInfo['access_end_date']
7518
                ? new DateTime(api_get_local_time($sessionInfo['access_end_date']), $timezone)
7519
                : null;
7520
7521
            $startYear = $startDate ? (int) $startDate->format('Y') : 0;
7522
            $startWeekYear = $startDate ? (int) $startDate->format('o') : 0;
7523
            $startWeek = $startDate ? (int) $startDate->format('W') : 0;
7524
            $endYear = $endDate ? (int) $endDate->format('Y') : 0;
7525
            $endWeekYear = $endDate ? (int) $endDate->format('o') : 0;
7526
            $endWeek = $endDate ? (int) $endDate->format('W') : 0;
7527
7528
            $start = $startWeekYear < $searchYear ? 0 : $startWeek - 1;
7529
            $duration = $endWeekYear > $searchYear ? 52 - $start : $endWeek - $start;
7530
7531
            $calendar[] = [
7532
                'id' => $sessionId,
7533
                'name' => $sessionInfo['name'],
7534
                'human_date' => SessionManager::convertSessionDateToString($startDate, $endDate, false, true),
7535
                'start_in_last_year' => $startYear < $searchYear,
7536
                'end_in_next_year' => $endYear > $searchYear,
7537
                'no_start' => !$startWeek,
7538
                'no_end' => !$endWeek,
7539
                'start' => $start,
7540
                'duration' => $duration > 0 ? $duration : 1,
7541
            ];
7542
        }
7543
7544
        usort(
7545
            $calendar,
7546
            function ($sA, $sB) {
7547
                if ($sA['start'] == $sB['start']) {
7548
                    return 0;
7549
                }
7550
7551
                if ($sA['start'] < $sB['start']) {
7552
                    return -1;
7553
                }
7554
7555
                return 1;
7556
            }
7557
        );
7558
7559
        return $calendar;
7560
    }
7561
7562
    /**
7563
     * Return the user's full name. Optionally with the username.
7564
     *
7565
     * @param bool $includeUsername Optional. By default username is not included.
7566
     *
7567
     * @return string
7568
     */
7569
    public static function formatUserFullName(User $user, $includeUsername = false)
7570
    {
7571
        $fullName = api_get_person_name($user->getFirstname(), $user->getLastname());
7572
7573
        if ($includeUsername && api_get_configuration_value('hide_username_with_complete_name') !== true) {
7574
            $username = $user->getUsername();
7575
7576
            return "$fullName ($username)";
7577
        }
7578
7579
        return $fullName;
7580
    }
7581
7582
    /**
7583
     * @param int $userId
7584
     *
7585
     * @return array
7586
     */
7587
    public static function getUserCareers($userId)
7588
    {
7589
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7590
        $tableCareer = Database::get_main_table(TABLE_CAREER);
7591
        $userId = (int) $userId;
7592
7593
        $sql = "SELECT c.id, c.name
7594
                FROM $table uc
7595
                INNER JOIN $tableCareer c
7596
                ON uc.career_id = c.id
7597
                WHERE user_id = $userId
7598
                ORDER BY uc.created_at
7599
                ";
7600
        $result = Database::query($sql);
7601
7602
        return Database::store_result($result, 'ASSOC');
7603
    }
7604
7605
    /**
7606
     * @param int $userId
7607
     * @param int $careerId
7608
     */
7609
    public static function addUserCareer($userId, $careerId)
7610
    {
7611
        if (!api_get_configuration_value('allow_career_users')) {
7612
            return false;
7613
        }
7614
7615
        if (self::userHasCareer($userId, $careerId) === false) {
7616
            $params = [
7617
                'user_id' => $userId,
7618
                'career_id' => $careerId,
7619
                'created_at' => api_get_utc_datetime(),
7620
                'updated_at' => api_get_utc_datetime(),
7621
            ];
7622
            $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7623
            Database::insert($table, $params);
7624
        }
7625
7626
        return true;
7627
    }
7628
7629
    /**
7630
     * @param int   $userCareerId
7631
     * @param array $data
7632
     *
7633
     * @return bool
7634
     */
7635
    public static function updateUserCareer($userCareerId, $data)
7636
    {
7637
        if (!api_get_configuration_value('allow_career_users')) {
7638
            return false;
7639
        }
7640
7641
        $params = ['extra_data' => $data, 'updated_at' => api_get_utc_datetime()];
7642
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7643
        Database::update(
7644
            $table,
7645
            $params,
7646
            ['id = ?' => (int) $userCareerId]
7647
        );
7648
7649
        return true;
7650
    }
7651
7652
    /**
7653
     * @param int $userId
7654
     * @param int $careerId
7655
     *
7656
     * @return array
7657
     */
7658
    public static function getUserCareer($userId, $careerId)
7659
    {
7660
        $userId = (int) $userId;
7661
        $careerId = (int) $careerId;
7662
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7663
7664
        $sql = "SELECT * FROM $table WHERE user_id = $userId AND career_id = $careerId";
7665
        $result = Database::query($sql);
7666
7667
        return Database::fetch_array($result, 'ASSOC');
7668
    }
7669
7670
    /**
7671
     * @param int $userId
7672
     * @param int $careerId
7673
     *
7674
     * @return bool
7675
     */
7676
    public static function userHasCareer($userId, $careerId)
7677
    {
7678
        $userId = (int) $userId;
7679
        $careerId = (int) $careerId;
7680
        $table = Database::get_main_table(TABLE_MAIN_USER_CAREER);
7681
7682
        $sql = "SELECT id FROM $table WHERE user_id = $userId AND career_id = $careerId";
7683
        $result = Database::query($sql);
7684
7685
        return Database::num_rows($result) > 0;
7686
    }
7687
7688
    /**
7689
     * @param int $userId
7690
     *
7691
     * @throws Exception
7692
     */
7693
    public static function deleteUserFiles($userId)
7694
    {
7695
        $path = self::getUserPathById($userId, 'system');
7696
7697
        $fs = new Filesystem();
7698
        $fs->remove($path);
7699
    }
7700
7701
    public static function redirectToResetPassword($userId)
7702
    {
7703
        $forceRenew = api_get_configuration_value('force_renew_password_at_first_login');
7704
7705
        if ($forceRenew) {
7706
            $askPassword = self::get_extra_user_data_by_field(
7707
                $userId,
7708
                'ask_new_password'
7709
            );
7710
7711
            if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
7712
                1 === (int)$askPassword['ask_new_password']
7713
            ) {
7714
                $uniqueId = api_get_unique_id();
7715
                $userObj = api_get_user_entity($userId);
7716
7717
                $userObj->setConfirmationToken($uniqueId);
7718
                $userObj->setPasswordRequestedAt(new \DateTime());
7719
7720
                Database::getManager()->persist($userObj);
7721
                Database::getManager()->flush();
7722
7723
                $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
7724
                api_location($url);
7725
            }
7726
        }
7727
7728
        $forceRotateDays = api_get_configuration_value('security_password_rotate_days');
7729
        $forceRotate = false;
7730
7731
        if ($forceRotateDays > 0) {
7732
            // get the date of the last password update recorded
7733
            $lastUpdate = self::get_extra_user_data_by_field(
7734
                $userId,
7735
                'password_updated_at'
7736
            );
7737
7738
            if (empty($lastUpdate) or empty($lastUpdate['password_updated_at'])) {
7739
                $userObj = api_get_user_entity($userId);
7740
                $registrationDate = $userObj->getRegistrationDate();
7741
                $now = new \DateTime(null, new DateTimeZone('UTC'));
7742
                $interval = $now->diff($registrationDate);
7743
                $daysSince = $interval->format('%a');
7744
                if ($daysSince > $forceRotateDays) {
7745
                    $forceRotate = true;
7746
                }
7747
            } else {
7748
                $now = new \DateTime(null, new DateTimeZone('UTC'));
7749
                // In some cases, old records might contain an incomplete Y-m-d H:i:s format
7750
                if (strlen($lastUpdate['password_updated_at']) == 16) {
7751
                    $lastUpdate['password_updated_at'] .= ':00';
7752
                }
7753
                $date = \DateTime::createFromFormat('Y-m-d H:i:s', $lastUpdate['password_updated_at'], new DateTimeZone('UTC'));
7754
                $interval = $now->diff($date);
7755
                $daysSince = $interval->format('%a');
7756
                if ($daysSince > $forceRotateDays) {
7757
                    $forceRotate = true;
7758
                }
7759
            }
7760
            if ($forceRotate) {
7761
                $uniqueId = api_get_unique_id();
7762
                $userObj = api_get_user_entity($userId);
7763
7764
                $userObj->setConfirmationToken($uniqueId);
7765
                $userObj->setPasswordRequestedAt(new \DateTime());
7766
7767
                Database::getManager()->persist($userObj);
7768
                Database::getManager()->flush();
7769
7770
                $url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId.'&rotate=1';
7771
                api_location($url);
7772
            }
7773
        }
7774
    }
7775
7776
    /**
7777
     * It returns the list of user status available.
7778
     *
7779
     * @return array
7780
     */
7781
    public static function getUserStatusList()
7782
    {
7783
        $userStatusConfig = [];
7784
        // it gets the roles to show in creation/edition user
7785
        if (true === api_get_configuration_value('user_status_show_options_enabled')) {
7786
            $userStatusConfig = api_get_configuration_value('user_status_show_option');
7787
        }
7788
        // it gets the roles to show in creation/edition user (only for admins)
7789
        if (true === api_get_configuration_value('user_status_option_only_for_admin_enabled') && api_is_platform_admin()) {
7790
            $userStatusConfig = api_get_configuration_value('user_status_option_show_only_for_admin');
7791
        }
7792
7793
        $status = [];
7794
        if (!empty($userStatusConfig)) {
7795
            $statusLang = api_get_status_langvars();
7796
            foreach ($userStatusConfig as $role => $enabled) {
7797
                if ($enabled) {
7798
                    $constStatus = constant($role);
7799
                    $status[$constStatus] = $statusLang[$constStatus];
7800
                }
7801
            }
7802
        } else {
7803
            $status[COURSEMANAGER] = get_lang('Teacher');
7804
            $status[STUDENT] = get_lang('Learner');
7805
            $status[DRH] = get_lang('Drh');
7806
            $status[SESSIONADMIN] = get_lang('SessionsAdmin');
7807
            $status[STUDENT_BOSS] = get_lang('RoleStudentBoss');
7808
            $status[INVITEE] = get_lang('Invitee');
7809
        }
7810
7811
        return $status;
7812
    }
7813
7814
    /**
7815
     * Get the expiration date by user status from configuration value.
7816
     *
7817
     * @param $status
7818
     *
7819
     * @throws Exception
7820
     *
7821
     * @return array
7822
     */
7823
    public static function getExpirationDateByRole($status)
7824
    {
7825
        $status = (int) $status;
7826
        $nbDaysByRole = api_get_configuration_value('user_number_of_days_for_default_expiration_date_per_role');
7827
        $dates = [];
7828
        if (!empty($nbDaysByRole)) {
7829
            $date = new DateTime();
7830
            foreach ($nbDaysByRole as $strVariable => $nDays) {
7831
                $constStatus = constant($strVariable);
7832
                if ($status == $constStatus) {
7833
                    $duration = "P{$nDays}D";
7834
                    $date->add(new DateInterval($duration));
7835
                    $newExpirationDate = $date->format('Y-m-d H:i');
7836
                    $formatted = api_format_date($newExpirationDate, DATE_TIME_FORMAT_LONG_24H);
7837
                    $dates = ['formatted' => $formatted, 'date' => $newExpirationDate];
7838
                }
7839
            }
7840
        }
7841
7842
        return $dates;
7843
    }
7844
7845
    public static function getAllowedRolesAsTeacher(): array
7846
    {
7847
        return [
7848
            COURSEMANAGER,
7849
            SESSIONADMIN,
7850
        ];
7851
    }
7852
7853
    /**
7854
     * Count users in courses and if they have certificate.
7855
     * This function is resource intensive.
7856
     *
7857
     * @return array
7858
     */
7859
    public static function countUsersWhoFinishedCourses()
7860
    {
7861
        $courses = [];
7862
        $currentAccessUrlId = api_get_current_access_url_id();
7863
        $sql = "SELECT course.code, cru.user_id
7864
                FROM course_rel_user cru
7865
                    JOIN course ON cru.c_id = course.id
7866
                    JOIN access_url_rel_user auru on cru.user_id = auru.user_id
7867
                    JOIN access_url_rel_course ON course.id = access_url_rel_course.c_id
7868
                WHERE access_url_rel_course.access_url_id = $currentAccessUrlId
7869
                ORDER BY course.code
7870
        ";
7871
7872
        $res = Database::query($sql);
7873
        if (Database::num_rows($res) > 0) {
7874
            while ($row = Database::fetch_array($res)) {
7875
                if (!isset($courses[$row['code']])) {
7876
                    $courses[$row['code']] = [
7877
                        'subscribed' => 0,
7878
                        'finished' => 0,
7879
                    ];
7880
                }
7881
7882
                $courses[$row['code']]['subscribed']++;
7883
                $entityManager = Database::getManager();
7884
                $repository = $entityManager->getRepository('ChamiloCoreBundle:GradebookCategory');
7885
                //todo check when have more than 1 gradebook
7886
                /** @var \Chamilo\CoreBundle\Entity\GradebookCategory $gradebook */
7887
                $gradebook = $repository->findOneBy(['courseCode' => $row['code']]);
7888
7889
                if (!empty($gradebook)) {
7890
                    $finished = 0;
7891
                    $gb = Category::createCategoryObjectFromEntity($gradebook);
7892
                    $finished = $gb->is_certificate_available($row['user_id']);
7893
                    if (!empty($finished)) {
7894
                        $courses[$row['code']]['finished']++;
7895
                    }
7896
                }
7897
            }
7898
        }
7899
7900
        return $courses;
7901
    }
7902
7903
    /**
7904
     * Count users in sessions and if they have certificate.
7905
     * This function is resource intensive.
7906
     *
7907
     * @return array
7908
     */
7909
    public static function countUsersWhoFinishedCoursesInSessions()
7910
    {
7911
        $coursesInSessions = [];
7912
        $currentAccessUrlId = api_get_current_access_url_id();
7913
        $sql = "SELECT course.code, srcru.session_id, srcru.user_id, session.name
7914
                FROM session_rel_course_rel_user srcru
7915
                    JOIN course ON srcru.c_id = course.id
7916
                    JOIN access_url_rel_session aurs on srcru.session_id = aurs.session_id
7917
                    JOIN session ON srcru.session_id = session.id
7918
                WHERE aurs.access_url_id = $currentAccessUrlId
7919
                ORDER BY course.code, session.name
7920
        ";
7921
7922
        $res = Database::query($sql);
7923
        if (Database::num_rows($res) > 0) {
7924
            while ($row = Database::fetch_array($res)) {
7925
                $index = $row['code'].' ('.$row['name'].')';
7926
                if (!isset($coursesInSessions[$index])) {
7927
                    $coursesInSessions[$index] = [
7928
                        'subscribed' => 0,
7929
                        'finished' => 0,
7930
                    ];
7931
                }
7932
7933
                $coursesInSessions[$index]['subscribed']++;
7934
                $entityManager = Database::getManager();
7935
                $repository = $entityManager->getRepository('ChamiloCoreBundle:GradebookCategory');
7936
                /** @var \Chamilo\CoreBundle\Entity\GradebookCategory $gradebook */
7937
                $gradebook = $repository->findOneBy(
7938
                    [
7939
                        'courseCode' => $row['code'],
7940
                        'sessionId' => $row['session_id'],
7941
                    ]
7942
                );
7943
7944
                if (!empty($gradebook)) {
7945
                    $finished = 0;
7946
                    $gb = Category::createCategoryObjectFromEntity($gradebook);
7947
                    $finished = $gb->is_certificate_available($row['user_id']);
7948
                    if (!empty($finished)) {
7949
                        $coursesInSessions[$index]['finished']++;
7950
                    }
7951
                }
7952
            }
7953
        }
7954
7955
        return $coursesInSessions;
7956
    }
7957
7958
    /**
7959
     * Build the active-column of the table to lock or unlock a certain user
7960
     * lock = the user can no longer use this account.
7961
     *
7962
     * @author Patrick Cool <[email protected]>, Ghent University
7963
     *
7964
     * @return string Some HTML-code with the lock/unlock button
7965
     */
7966
    public static function getActiveFilterForTable(string $active, string $params, array $row): string
7967
    {
7968
        if ('1' == $active) {
7969
            $action = 'Lock';
7970
            $image = 'accept';
7971
        } elseif ('-1' == $active) {
7972
            $action = 'edit';
7973
            $image = 'warning';
7974
        } elseif ('0' == $active) {
7975
            $action = 'Unlock';
7976
            $image = 'error';
7977
        }
7978
7979
        if ('edit' === $action) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $action does not seem to be defined for all execution paths leading up to this point.
Loading history...
7980
            $langAccountExpired = get_lang('AccountExpired');
7981
7982
            return Display::return_icon(
7983
                $image.'.png',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $image does not seem to be defined for all execution paths leading up to this point.
Loading history...
7984
                    $langAccountExpired,
7985
                [],
7986
                ICON_SIZE_TINY
7987
            ).'<span class="sr-only" aria-hidden="true">'.$langAccountExpired.'</span>';
7988
        }
7989
7990
        if ($row['0'] != api_get_user_id()) {
7991
            $langAction = get_lang(ucfirst($action));
7992
            // you cannot lock yourself out otherwise you could disable all the
7993
            // accounts including your own => everybody is locked out and nobody
7994
            // can change it anymore.
7995
            return Display::return_icon(
7996
                $image.'.png',
7997
                $langAction,
7998
                ['onclick' => 'active_user(this);', 'id' => 'img_'.$row['0'], 'style' => 'cursor: pointer;'],
7999
                ICON_SIZE_TINY
8000
                ).'<span class="sr-only" aria-hidden="true">'.$langAction.'</span>';
8001
        }
8002
8003
        return '';
8004
    }
8005
8006
    public static function getScriptFunctionForActiveFilter(): string
8007
    {
8008
        return 'function active_user(element_div) {
8009
            id_image = $(element_div).attr("id");
8010
            image_clicked = $(element_div).attr("src");
8011
            image_clicked_info = image_clicked.split("/");
8012
            image_real_clicked = image_clicked_info[image_clicked_info.length-1];
8013
            var status = 1;
8014
            if (image_real_clicked == "accept.png") {
8015
                status = 0;
8016
            }
8017
            user_id = id_image.split("_");
8018
            ident = "#img_"+user_id[1];
8019
            if (confirm("'.get_lang('AreYouSureToEditTheUserStatus', '').'")) {
8020
                 $.ajax({
8021
                    contentType: "application/x-www-form-urlencoded",
8022
                    beforeSend: function(myObject) {
8023
                        $(ident).attr("src","'.Display::returnIconPath('loading1.gif').'"); //candy eye stuff
8024
                    },
8025
                    type: "GET",
8026
                    url: _p.web_ajax + "user_manager.ajax.php?a=active_user",
8027
                    data: "user_id=" + user_id[1] + "&status=" + status,
8028
                    success: function(data) {
8029
                        if (data == 1) {
8030
                            $(ident).attr("src", "'.Display::returnIconPath('accept.png', ICON_SIZE_TINY).'");
8031
                            $(ident).attr("title","'.get_lang('Lock').'");
8032
                        }
8033
                        if (data == 0) {
8034
                            $(ident).attr("src","'.Display::returnIconPath('error.png').'");
8035
                            $(ident).attr("title","'.get_lang('Unlock').'");
8036
                        }
8037
                        if (data == -1) {
8038
                            $(ident).attr("src", "'.Display::returnIconPath('warning.png').'");
8039
                            $(ident).attr("title","'.get_lang('ActionNotAllowed').'");
8040
                        }
8041
                    }
8042
                });
8043
            }
8044
        }';
8045
    }
8046
8047
    /**
8048
     * @return EncoderFactory
8049
     */
8050
    private static function getEncoderFactory()
8051
    {
8052
        $encryption = self::getPasswordEncryption();
8053
        $encoders = [
8054
            'Chamilo\\UserBundle\\Entity\\User' => new \Chamilo\UserBundle\Security\Encoder($encryption),
8055
        ];
8056
8057
        return new EncoderFactory($encoders);
8058
    }
8059
8060
    /**
8061
     * @return \Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface
8062
     */
8063
    private static function getEncoder(User $user)
8064
    {
8065
        $encoderFactory = self::getEncoderFactory();
8066
8067
        return $encoderFactory->getEncoder($user);
8068
    }
8069
8070
    /**
8071
     * Disables or enables a user.
8072
     *
8073
     * @param int $user_id
8074
     * @param int $active  Enable or disable
8075
     *
8076
     * @return bool True on success, false on failure
8077
     * @assert (-1,0) === false
8078
     * @assert (1,1) === true
8079
     */
8080
    private static function change_active_state($user_id, $active)
8081
    {
8082
        $user_id = (int) $user_id;
8083
        $active = (int) $active;
8084
8085
        if (empty($user_id)) {
8086
            return false;
8087
        }
8088
8089
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
8090
        $sql = "UPDATE $table_user SET active = '$active' WHERE id = $user_id";
8091
        $r = Database::query($sql);
8092
        $ev = LOG_USER_DISABLE;
8093
        if ($active == 1) {
8094
            $ev = LOG_USER_ENABLE;
8095
        }
8096
        if ($r !== false) {
8097
            Event::addEvent($ev, LOG_USER_ID, $user_id);
8098
        }
8099
8100
        return $r;
8101
    }
8102
8103
    /**
8104
     * Get either a Gravatar URL or complete image tag for a specified email address.
8105
     *
8106
     * @param string $email The email address
8107
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
8108
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
8109
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
8110
     * @param bool   $img   True to return a complete IMG tag False for just the URL
8111
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
8112
     *
8113
     * @return string containing either just a URL or a complete image tag
8114
     * @source http://gravatar.com/site/implement/images/php/
8115
     */
8116
    private static function getGravatar(
8117
        $email,
8118
        $s = 80,
8119
        $d = 'mm',
8120
        $r = 'g',
8121
        $img = false,
8122
        $atts = []
8123
    ) {
8124
        $url = 'http://www.gravatar.com/avatar/';
8125
        if (!empty($_SERVER['HTTPS'])) {
8126
            $url = 'https://secure.gravatar.com/avatar/';
8127
        }
8128
        $url .= md5(strtolower(trim($email)));
8129
        $url .= "?s=$s&d=$d&r=$r";
8130
        if ($img) {
8131
            $url = '<img src="'.$url.'"';
8132
            foreach ($atts as $key => $val) {
8133
                $url .= ' '.$key.'="'.$val.'"';
8134
            }
8135
            $url .= ' />';
8136
        }
8137
8138
        return $url;
8139
    }
8140
8141
    /**
8142
     * Get a list of users with the given e-mail address + their "active" field value (0 or 1)
8143
     *
8144
     * @param string $mail User id
8145
     *
8146
     * @return array List of users e-mails + active field
8147
     */
8148
    public static function getUsersByMail(string $mail): array
8149
    {
8150
        $resultData = Database::select(
8151
            'id, active',
8152
            Database::get_main_table(TABLE_MAIN_USER),
8153
            [
8154
                'where' => ['email = ?' => $mail],
8155
            ],
8156
            'all',
8157
            null,
8158
            true
8159
        );
8160
8161
        if ($resultData === false) {
8162
            return [];
8163
        }
8164
8165
        return $resultData;
8166
    }
8167
8168
    /**
8169
     * Get whether we can send an e-mail or not.
8170
     * If the e-mail is not in the database, send the mail.
8171
     * If the e-mail is in the database but none of its occurences is active, don't send.
8172
     * @param string $mail The e-mail address to check
8173
     * @return bool Whether we can send an e-mail or not
8174
     */
8175
    public function isEmailingAllowed(string $mail): bool
8176
    {
8177
        $list = self::getUsersByMail($mail);
8178
        if (empty($list)) {
8179
            // No e-mail matches, send the mail
8180
            return true;
8181
        }
8182
        $send = false;
8183
        foreach ($list as $id => $user) {
8184
            if ($user['active'] == 1) {
8185
                // as soon as we find at least one active user, send the mail
8186
                return true;
8187
            }
8188
        }
8189
8190
        return false;
8191
    }
8192
}
8193