CourseManager   F
last analyzed

Complexity

Total Complexity 810

Size/Duplication

Total Lines 6920
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 3327
dl 0
loc 6920
rs 0.8
c 0
b 0
f 0
wmc 810

121 Methods

Rating   Name   Duplication   Size   Complexity  
A removeUserVisibilityToCourseInCatalogue() 0 33 5
A course_code_exists() 0 9 1
A isUserSubscribedInCourseAsDrh() 0 26 5
A get_tutor_in_course_status() 0 15 1
A getUserInCourseStatus() 0 22 3
A updateUserCourseTutor() 0 19 2
A getUserCourseInfo() 0 8 1
A get_course_information() 0 8 1
F get_courses_list() 0 116 21
A update_course_ranking() 0 23 3
A getCourseListTabs() 0 14 1
A get_teacher_list_from_course_code() 0 28 3
A get_course_id_from_original_id() 0 13 2
A get_courses_followed_by_drh() 0 17 1
A getCountForum() 0 21 2
B redirectToCourse() 0 50 8
C getCourseParamsForDisplay() 0 144 14
A countActiveCourses() 0 19 2
B addUserGroupMultiSelect() 0 59 7
A getCoursesByUserCourseCategory() 0 50 2
F get_logged_user_course_html() 0 271 49
A get_course_code_from_original_id() 0 25 2
A generate_nice_next_course_code() 0 25 4
A isSpecialCourse() 0 13 3
A getCourseVisibilitySQLCondition() 0 31 6
B userCourseSort() 0 85 11
A update_course_extra_field_value() 0 13 1
D get_student_list_from_course_code() 0 114 20
A addVisibilityOptions() 0 42 2
A addGroupMultiSelect() 0 11 2
A getCountForumPerUser() 0 25 1
A get_user_list_from_course_code() 0 35 1
B subscribeCoursesToDrhManager() 0 48 7
A count_courses() 0 23 5
A get_session_category_id_by_session_id() 0 16 2
A getUrlMarker() 0 13 2
A remove_course_ranking() 0 11 2
A get_course_category() 0 7 1
B getTeacherListFromCourseCodeToString() 0 46 7
A copy_course() 0 28 2
A getCourseUsers() 0 16 1
A fillCourse() 0 29 4
B get_details_course_description_html() 0 46 6
A copy_course_simple() 0 35 5
A get_course_list_of_user_as_course_admin() 0 12 2
A email_to_tutor() 0 58 4
A getCountOpenCourses() 0 16 1
B wouldOperationExceedGlobalLimit() 0 49 7
B returnPopularCoursesHandPicked() 0 63 8
C is_user_subscribed_in_course() 0 88 14
D get_access_link_by_user() 0 65 35
A getCourseInfoFromOriginalId() 0 13 2
A getCountExercisesFromOpenCourse() 0 19 1
C autoSubscribeToCourse() 0 141 13
A insertUserInCourse() 0 25 1
C canUserSubscribeToCourse() 0 71 15
A getGlobalUsersPerCourseLimit() 0 3 1
A getGlobalLimitPartialImportMessage() 0 19 1
A getGlobalLimitCancelMessage() 0 9 1
C buildSelectOptions() 0 53 12
C getCoursesFollowedByUser() 0 107 14
A getCourseGroups() 0 19 2
B separateUsersGroups() 0 26 7
F returnCoursesCategories() 0 193 27
A getCourseCategoriesFromCourseList() 0 8 1
A course_exists() 0 7 1
A save_user_legal() 0 33 4
A generate_course_code() 0 6 1
B checkCreateCourseAccessUrlParam() 0 27 7
D delete_course() 0 207 17
A getCoursesWithoutSession() 0 25 5
A isCourseTeacher() 0 18 4
A get_course_code_from_course_id() 0 11 2
A getCountPostInForumPerUser() 0 25 1
A get_user_course_vote() 0 33 5
A createDefaultGradebook() 0 9 3
A totalSubscribedUsersInCourses() 0 23 1
A getFirstCourseAccessPerSessionAndUser() 0 18 2
A get_courses_info_from_visual_code() 0 10 2
A getCourseSetting() 0 10 1
A getCourseAccessPerCourseAndSession() 0 22 1
B get_special_course_list() 0 39 7
A getCourseCodeFromDirectory() 0 14 3
B is_user_accepted_legal() 0 45 7
B saveSettingChanges() 0 30 7
A get_course_extra_field_value() 0 12 2
A getCourseNameFromCode() 0 10 2
B deleteExclusiveResourceFiles() 0 45 9
A create_course_extra_field() 0 11 1
B addUserVisibilityToCourseInCatalogue() 0 41 6
B get_course_ranking() 0 37 9
B useTemplateAsBasisIfRequired() 0 26 8
B return_hot_courses() 0 55 6
D get_courses_list_by_user_id() 0 138 21
D updateTeachers() 0 139 28
A update_attributes() 0 18 4
B getExtraFieldsToBePresented() 0 17 7
B get_coachs_from_course_to_string() 0 50 7
A get_count_user_list_from_course_code() 0 24 1
B get_coach_list_from_course_code() 0 38 8
A get_course_list() 0 5 1
A getCoursesFollowedByGroupAdmin() 0 43 5
F getUserListFromCourseId() 0 397 63
F unsubscribe_user() 0 178 17
A get_course_by_category() 0 8 2
F subscribeUser() 0 167 23
A countUsersForGlobalLimit() 0 18 3
B add_course_vote() 0 49 7
B getCourseSettingVariables() 0 70 5
C processHotCourseItem() 0 109 15
A getExclusiveResourceFilesForCourse() 0 21 1
A get_coachs_from_course() 0 40 5
A saveCourseConfigurationSetting() 0 51 4
B create_course() 0 55 10
C returnSpecialCourses() 0 117 16
B get_group_list_of_course() 0 63 7
A get_users_count_in_course() 0 59 5
B getCatalogCourseList() 0 44 7
A getCountRegisteredUsersWithCourseExtraField() 0 29 6
A getTeachersFromCourse() 0 43 3
A returnDescriptionButton() 0 33 5

How to fix   Complexity   

Complex Class

Complex classes like CourseManager 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 CourseManager, 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\AccessUrlRelSession;
6
use Chamilo\CoreBundle\Entity\Course;
7
use Chamilo\CoreBundle\Entity\CourseRelUser;
8
use Chamilo\CoreBundle\Entity\ExtraField as EntityExtraField;
9
use Chamilo\CoreBundle\Entity\ResourceFile;
10
use Chamilo\CoreBundle\Entity\ResourceNode;
11
use Chamilo\CoreBundle\Entity\SequenceResource;
12
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
13
use Chamilo\CoreBundle\Entity\User;
14
use Chamilo\CoreBundle\Enums\ActionIcon;
15
use Chamilo\CoreBundle\Enums\ObjectIcon;
16
use Chamilo\CoreBundle\Event\AbstractEvent;
17
use Chamilo\CoreBundle\Event\CourseCreatedEvent;
18
use Chamilo\CoreBundle\Event\Events;
19
use Chamilo\CoreBundle\Framework\Container;
20
use Chamilo\CoreBundle\Repository\SequenceResourceRepository;
21
use Chamilo\CoreBundle\Search\Xapian\XapianIndexService;
22
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
23
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
24
use Chamilo\CourseBundle\Entity\CGroup;
25
use ChamiloSession as Session;
26
use Doctrine\Common\Collections\Criteria;
27
28
/**
29
 * Class CourseManager.
30
 *
31
 * This is the course library for Chamilo.
32
 *
33
 * All main course functions should be placed here.
34
 *
35
 * There are probably some places left with the wrong code.
36
 */
37
class CourseManager
38
{
39
    public const MAX_COURSE_LENGTH_CODE = 40;
40
    /** This constant is used to show separate user names in the course
41
     * list (userportal), footer, etc */
42
    public const USER_SEPARATOR = ' |';
43
44
    /**
45
     * Creates a course.
46
     *
47
     * @param array $params      Columns in the main.course table.
48
     * @param int   $authorId    Optional.
49
     * @param int   $accessUrlId Optional.
50
     *
51
     * @return Course|null
52
     */
53
    public static function create_course($params, $authorId = 0, $accessUrlId = 0)
54
    {
55
        // Check portal limits
56
        $accessUrlId = !empty($accessUrlId) ? (int) $accessUrlId : api_get_current_access_url_id();
57
        $authorId = empty($authorId) ? api_get_user_id() : (int) $authorId;
58
59
        if (empty($params['title'])) {
60
            return false;
61
        }
62
63
        if (empty($params['wanted_code'])) {
64
            $params['wanted_code'] = $params['title'];
65
            // Check whether the requested course code has already been occupied.
66
            $substring = api_substr($params['title'], 0, self::MAX_COURSE_LENGTH_CODE);
67
            if (false === $substring || empty($substring)) {
68
                return false;
69
            } else {
70
                $params['wanted_code'] = self::generate_course_code($substring);
71
            }
72
        }
73
74
        // Create the course keys
75
        $keys = AddCourse::define_course_keys($params['wanted_code']);
76
        $params['exemplary_content'] = $params['exemplary_content'] ?? false;
77
78
        if (count($keys)) {
79
            $params['code'] = $keys['currentCourseCode'];
80
            $params['visual_code'] = $keys['currentCourseId'];
81
            $params['directory'] = $keys['currentCourseRepository'];
82
            $courseInfo = api_get_course_info($params['code']);
83
84
            if (empty($courseInfo)) {
85
                $eventDispatcher = Container::getEventDispatcher();
86
87
                $eventDispatcher->dispatch(
88
                    new CourseCreatedEvent([], AbstractEvent::TYPE_PRE),
89
                    Events::COURSE_CREATED
90
                );
91
92
                $course = AddCourse::register_course($params);
93
94
                $eventDispatcher->dispatch(
95
                    new CourseCreatedEvent(['course' => $course], AbstractEvent::TYPE_POST),
96
                    Events::COURSE_CREATED
97
                );
98
99
                if (null !== $course) {
100
                    self::fillCourse($course, $params, $authorId);
101
102
                    return $course;
103
                }
104
            }
105
        }
106
107
        return null;
108
    }
109
110
    /**
111
     * Returns all the information of a given course code.
112
     *
113
     * @param string $course_code , the course code
114
     *
115
     * @return array with all the fields of the course table
116
     *
117
     * @deprecated Use api_get_course_info() instead
118
     *
119
     * @author Patrick Cool <[email protected]>, Ghent University
120
     * @assert ('') === false
121
     */
122
    public static function get_course_information($course_code)
123
    {
124
        return Database::fetch_array(
125
            Database::query(
126
                "SELECT *, id as real_id FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
127
                WHERE code = '".Database::escape_string($course_code)."'"
128
            ),
129
            'ASSOC'
130
        );
131
    }
132
133
    /**
134
     * Returns a list of courses. Should work with quickform syntax.
135
     *
136
     * @param int    $from               Offset (from the 7th = '6'). Optional.
137
     * @param int    $howmany            Number of results we want. Optional.
138
     * @param string $orderby            The column we want to order it by. Optional, defaults to first column.
139
     * @param string $orderdirection     The direction of the order (ASC or DESC). Optional, defaults to ASC.
140
     * @param int    $visibility         the visibility of the course, or all by default
141
     * @param string $startwith          If defined, only return results for which the course *title* begins with this
142
     *                                   string
143
     * @param string $urlId              The Access URL ID, if using multiple URLs
144
     * @param bool   $alsoSearchCode     An extension option to indicate that we also want to search for course codes
145
     *                                   (not *only* titles)
146
     * @param array  $conditionsLike
147
     * @param array  $onlyThisCourseList
148
     *
149
     * @return array
150
     */
151
    public static function get_courses_list(
152
        $from = 0,
153
        $howmany = 0,
154
        $orderby = 'title',
155
        $orderdirection = 'ASC',
156
        $visibility = -1,
157
        $startwith = '',
158
        $urlId = null,
159
        $alsoSearchCode = false,
160
        $conditionsLike = [],
161
        $onlyThisCourseList = []
162
    ) {
163
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
164
        $sql = "SELECT course.*, course.id as real_id
165
                FROM $courseTable course  ";
166
167
        if (!empty($urlId)) {
168
            $table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
169
            $sql .= " INNER JOIN $table url ON (url.c_id = course.id) ";
170
        }
171
172
        $visibility = (int) $visibility;
173
174
        if (!empty($startwith)) {
175
            $sql .= "WHERE (title LIKE '".Database::escape_string($startwith)."%' ";
176
            if ($alsoSearchCode) {
177
                $sql .= "OR code LIKE '".Database::escape_string($startwith)."%' ";
178
            }
179
            $sql .= ') ';
180
            if (-1 !== $visibility) {
181
                $sql .= " AND visibility = $visibility ";
182
            }
183
        } else {
184
            $sql .= 'WHERE 1 ';
185
            if (-1 !== $visibility) {
186
                $sql .= " AND visibility = $visibility ";
187
            }
188
        }
189
190
        if (!empty($urlId)) {
191
            $urlId = (int) $urlId;
192
            $sql .= " AND access_url_id = $urlId";
193
        }
194
195
        if (!empty($onlyThisCourseList)) {
196
            $onlyThisCourseList = array_map('intval', $onlyThisCourseList);
197
            $onlyThisCourseList = implode("','", $onlyThisCourseList);
198
            $sql .= " AND course.id IN ('$onlyThisCourseList') ";
199
        }
200
201
        $allowedFields = [
202
            'title',
203
            'code',
204
        ];
205
206
        if (count($conditionsLike) > 0) {
207
            $sql .= ' AND ';
208
            $temp_conditions = [];
209
            foreach ($conditionsLike as $field => $value) {
210
                if (!in_array($field, $allowedFields)) {
211
                    continue;
212
                }
213
                $field = Database::escape_string($field);
214
                $value = Database::escape_string($value);
215
                $simple_like = false;
216
                if ($simple_like) {
217
                    $temp_conditions[] = $field." LIKE '$value%'";
218
                } else {
219
                    $temp_conditions[] = $field.' LIKE \'%'.$value.'%\'';
220
                }
221
            }
222
            $condition = ' AND ';
223
            if (!empty($temp_conditions)) {
224
                $sql .= implode(' '.$condition.' ', $temp_conditions);
225
            }
226
        }
227
228
        if (empty($orderby)) {
229
            $sql .= ' ORDER BY title ';
230
        } else {
231
            if (in_array($orderby, ['title'])) {
232
                $sql .= " ORDER BY `".Database::escape_string($orderby)."` ";
233
            } else {
234
                $sql .= ' ORDER BY title ';
235
            }
236
        }
237
238
        $orderdirection = strtoupper($orderdirection);
239
        if (!in_array($orderdirection, ['ASC', 'DESC'])) {
240
            $sql .= 'ASC';
241
        } else {
242
            $sql .= 'ASC' === $orderdirection ? 'ASC' : 'DESC';
243
        }
244
245
        $howmany = (int) $howmany;
246
        if (!empty($howmany)) {
247
            $sql .= ' LIMIT '.$howmany;
248
        } else {
249
            $sql .= ' LIMIT 1000000'; //virtually no limit
250
        }
251
        if (!empty($from)) {
252
            $from = intval($from);
253
            $sql .= ' OFFSET '.intval($from);
254
        } else {
255
            $sql .= ' OFFSET 0';
256
        }
257
258
        $data = [];
259
        $res = Database::query($sql);
260
        if (Database::num_rows($res) > 0) {
261
            while ($row = Database::fetch_assoc($res)) {
262
                $data[] = $row;
263
            }
264
        }
265
266
        return $data;
267
    }
268
269
    /**
270
     * Returns the status of a user in a course, which is COURSEMANAGER or STUDENT.
271
     *
272
     * @param int $userId
273
     * @param int $courseId
274
     *
275
     * @return int|bool the status of the user in that course (or false if the user is not in that course)
276
     */
277
    public static function getUserInCourseStatus($userId, $courseId)
278
    {
279
        $courseId = (int) $courseId;
280
        $userId = (int) $userId;
281
        if (empty($courseId)) {
282
            return false;
283
        }
284
285
        $result = Database::fetch_array(
286
            Database::query(
287
                "SELECT status FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
288
                WHERE
289
                    c_id  = $courseId AND
290
                    user_id = $userId"
291
            )
292
        );
293
294
        if (empty($result['status'])) {
295
            return false;
296
        }
297
298
        return $result['status'];
299
    }
300
301
    /**
302
     * @param int $userId
303
     * @param int $courseId
304
     *
305
     * @return mixed
306
     */
307
    public static function getUserCourseInfo($userId, $courseId)
308
    {
309
        $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
310
                WHERE
311
                    c_id  = ".intval($courseId)." AND
312
                    user_id = ".intval($userId);
313
314
        return Database::fetch_array(Database::query($sql));
315
    }
316
317
    /**
318
     * @param int  $userId
319
     * @param int  $courseId
320
     * @param bool $isTutor
321
     *
322
     * @return bool
323
     */
324
    public static function updateUserCourseTutor($userId, $courseId, $isTutor)
325
    {
326
        $table = Database::escape_string(TABLE_MAIN_COURSE_USER);
327
328
        $courseId = (int) $courseId;
329
        $isTutor = (int) $isTutor;
330
331
        $sql = "UPDATE $table SET is_tutor = '".$isTutor."'
332
			    WHERE
333
				    user_id = ".$userId." AND
334
				    c_id = ".$courseId;
335
336
        $result = Database::query($sql);
337
338
        if (Database::affected_rows($result) > 0) {
339
            return true;
340
        }
341
342
        return false;
343
    }
344
345
    /**
346
     * @param int $userId
347
     * @param int $courseId
348
     *
349
     * @return mixed
350
     */
351
    public static function get_tutor_in_course_status($userId, $courseId)
352
    {
353
        $userId = intval($userId);
354
        $courseId = intval($courseId);
355
        $result = Database::fetch_array(
356
            Database::query(
357
                "SELECT is_tutor
358
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
359
                WHERE
360
                    c_id = $courseId AND
361
                    user_id = $userId"
362
            )
363
        );
364
365
        return $result['is_tutor'];
366
    }
367
368
    /**
369
     * Unsubscribe one or more users from a course.
370
     *
371
     * @param   mixed   user_id or an array with user ids
372
     * @param   string  course code
373
     * @param   int     session id
374
     *
375
     * @return bool
376
     *
377
     * @assert ('', '') === false
378
     */
379
    public static function unsubscribe_user($user_id, $course_code, $session_id = 0)
380
    {
381
        if (empty($user_id)) {
382
            return false;
383
        }
384
        if (!is_array($user_id)) {
385
            $user_id = [$user_id];
386
        }
387
388
        if (0 == count($user_id)) {
389
            return false;
390
        }
391
392
        if (!empty($session_id)) {
393
            $session_id = (int) $session_id;
394
        } else {
395
            $session_id = api_get_session_id();
396
        }
397
398
        if (empty($course_code)) {
399
            return false;
400
        }
401
402
        $userList = [];
403
        // Cleaning the $user_id variable
404
        if (is_array($user_id)) {
405
            $new_user_id_list = [];
406
            foreach ($user_id as $my_user_id) {
407
                $new_user_id_list[] = (int) $my_user_id;
408
            }
409
            $new_user_id_list = array_filter($new_user_id_list);
410
            $userList = $new_user_id_list;
411
            $user_ids = implode(',', $new_user_id_list);
412
        } else {
413
            $user_ids = (int) $user_id;
414
            $userList[] = $user_id;
415
        }
416
417
        $course_info = api_get_course_info($course_code);
418
        $course_id = $course_info['real_id'];
419
420
        // Unsubscribe user from all groups in the course.
421
        $sql = "DELETE FROM ".Database::get_course_table(TABLE_GROUP_USER)."
422
                WHERE c_id = $course_id AND user_id IN (".$user_ids.")";
423
        Database::query($sql);
424
        $sql = "DELETE FROM ".Database::get_course_table(TABLE_GROUP_TUTOR)."
425
                WHERE c_id = $course_id AND user_id IN (".$user_ids.")";
426
        Database::query($sql);
427
428
        // Erase user student publications (works) in the course - by André Boivin
429
        // @todo this should be handled by doctrine.
430
        /*if (!empty($userList)) {
431
            foreach ($userList as $userId) {
432
                // Getting all work from user
433
                $workList = getWorkPerUser($userId);
434
                if (!empty($workList)) {
435
                    foreach ($workList as $work) {
436
                        $work = $work['work'];
437
                        // Getting user results
438
                        if (!empty($work->user_results)) {
439
                            foreach ($work->user_results as $workSent) {
440
                                deleteWorkItem($workSent['id'], $course_info);
441
                            }
442
                        }
443
                    }
444
                }
445
            }
446
        }*/
447
448
        // Unsubscribe user from all blogs in the course.
449
        /*$sql = "DELETE FROM ".Database::get_course_table(TABLE_BLOGS_REL_USER)."
450
                WHERE c_id = $course_id AND user_id IN ($user_ids)";
451
        Database::query($sql);
452
453
        $sql = "DELETE FROM ".Database::get_course_table(TABLE_BLOGS_TASKS_REL_USER)."
454
                WHERE c_id = $course_id AND user_id IN ($user_ids)";
455
        Database::query($sql);*/
456
457
        // Deleting users in forum_notification and mailqueue course tables
458
        $sql = "DELETE FROM  ".Database::get_course_table(TABLE_FORUM_NOTIFICATION)."
459
                WHERE c_id = $course_id AND user_id IN ($user_ids)";
460
        Database::query($sql);
461
462
        $sql = "DELETE FROM ".Database::get_course_table(TABLE_FORUM_MAIL_QUEUE)."
463
                WHERE c_id = $course_id AND user_id IN ($user_ids)";
464
        Database::query($sql);
465
466
        // Unsubscribe user from the course.
467
        if (!empty($session_id)) {
468
            foreach ($userList as $uid) {
469
                SessionManager::unSubscribeUserFromCourseSession($uid, $course_id, $session_id);
470
471
                // check if a user is register in the session with other course
472
                $sql = "SELECT user_id FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)."
473
                        WHERE session_id = $session_id AND user_id = $uid";
474
                $rs = Database::query($sql);
475
476
                if (0 == Database::num_rows($rs)) {
477
                    SessionManager::unsubscribe_user_from_session($uid, $session_id);
478
                }
479
            }
480
481
            $user_id = api_get_user_id();
482
            Event::addEvent(
483
                LOG_UNSUBSCRIBE_USER_FROM_COURSE,
484
                LOG_COURSE_CODE,
485
                $course_code,
486
                api_get_utc_datetime(),
487
                $user_id,
488
                $course_id,
489
                $session_id
490
            );
491
        } else {
492
            $sql = "DELETE FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
493
                    WHERE
494
                        user_id IN ($user_ids) AND
495
                        relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
496
                        c_id = $course_id";
497
            Database::query($sql);
498
499
            // add event to system log
500
            $user_id = api_get_user_id();
501
502
            Event::addEvent(
503
                LOG_UNSUBSCRIBE_USER_FROM_COURSE,
504
                LOG_COURSE_CODE,
505
                $course_code,
506
                api_get_utc_datetime(),
507
                $user_id,
508
                $course_id
509
            );
510
511
            foreach ($userList as $userId) {
512
                $userInfo = api_get_user_info($userId);
513
                Event::addEvent(
514
                    LOG_UNSUBSCRIBE_USER_FROM_COURSE,
515
                    LOG_USER_OBJECT,
516
                    $userInfo,
517
                    api_get_utc_datetime(),
518
                    $user_id,
519
                    $course_id
520
                );
521
            }
522
        }
523
        if ('true' === api_get_setting('catalog.course_subscription_in_user_s_session')) {
524
            // Also unlink the course from the users' currently accessible sessions
525
            /** @var Course $course */
526
            $course = Container::getCourseRepository()->findOneBy([
527
                'code' => $course_code,
528
            ]);
529
            if (is_null($course)) {
530
                return false;
531
            }
532
            /** @var Chamilo\CoreBundle\Entity\User $user */
533
            foreach (Container::getUserRepository()->matching(
534
                Criteria::create()->where(Criteria::expr()->in('id', $userList))
535
            ) as $user) {
536
                foreach ($user->getCurrentlyAccessibleSessions() as $session) {
537
                    $session->removeCourse($course);
538
                    // unsubscribe user from course within this session
539
                    SessionManager::unSubscribeUserFromCourseSession($user->getId(), $course->getId(), $session->getId());
540
                }
541
            }
542
            try {
543
                Database::getManager()->flush();
544
            } catch (\Doctrine\ORM\OptimisticLockException $exception) {
545
                Display::addFlash(
546
                    Display::return_message(
547
                        sprintf(get_lang('Internal database error: %s'), $exception->getMessage()),
548
                        'warning'
549
                    )
550
                );
551
552
                return false;
553
            }
554
        }
555
556
        return true;
557
    }
558
559
    /**
560
     * @param string $courseCode
561
     * @param int    $status
562
     *
563
     * @return bool
564
     */
565
    public static function autoSubscribeToCourse($courseCode, $status = STUDENT)
566
    {
567
        if (api_is_anonymous()) {
568
            return false;
569
        }
570
571
        $course = Container::getCourseRepository()->findOneBy(['code' => $courseCode]);
572
573
        if (null === $course) {
574
            return false;
575
        }
576
577
        $visibility = (int) $course->getVisibility();
578
579
        if (in_array($visibility, [
580
                Course::CLOSED,
581
                //Course::REGISTERED,
582
                Course::HIDDEN,
583
        ])) {
584
            Display::addFlash(
585
                Display::return_message(
586
                    get_lang('Subscribing not allowed'),
587
                    'warning'
588
        )
589
            );
590
591
            return false;
592
        }
593
594
        // Private course can allow auto subscription
595
        if (Course::REGISTERED === $visibility && false === $course->getSubscribe()) {
596
            Display::addFlash(
597
                Display::return_message(
598
                    get_lang('Subscribing not allowed'),
599
                    'warning'
600
                )
601
            );
602
603
            return false;
604
        }
605
606
        $userId = api_get_user_id();
607
608
        if ('true' === api_get_setting('catalog.course_subscription_in_user_s_session')) {
609
            $user = api_get_user_entity($userId);
610
            $sessions = $user->getCurrentlyAccessibleSessions();
611
            if (empty($sessions)) {
612
                // user has no accessible session
613
                if ($user->getSessionsAsStudent()) {
614
                    // user has ancient or future student session(s) but not available now
615
                    Display::addFlash(
616
                        Display::return_message(
617
                            get_lang('You cannot subscribe to the course any more because your session has expired.'),
618
                            'warning'
619
                        )
620
                    );
621
622
                    return false;
623
                }
624
                // user has no session at all, create one starting now
625
                $numberOfDays = (int) api_get_setting('session.user_s_session_duration');
626
                try {
627
                    $duration = new DateInterval(sprintf('P%dD', $numberOfDays));
628
                } catch (Exception $exception) {
629
                    Display::addFlash(
630
                        Display::return_message(
631
                            sprintf(get_lang('Wrong number of days: %s: %s'), $numberOfDays, $exception->getMessage()),
632
                            'warning'
633
                        )
634
                    );
635
636
                    return false;
637
                }
638
                $endDate = new DateTime();
639
                $endDate->add($duration);
640
                $session = new SessionEntity();
641
                $session->setTitle(
642
                    sprintf(get_lang('Courses of %s %s'), $user->getFirstname(), $user->getLastname())
643
                );
644
                $session->setAccessEndDate($endDate);
645
                $session->setCoachAccessEndDate($endDate);
646
                $session->setDisplayEndDate($endDate);
647
                $session->setSendSubscriptionNotification(false);
648
                $adminId = (int) api_get_setting('session.session_automatic_creation_user_id');
649
                $session->addSessionAdmin(api_get_user_entity($adminId));
650
                $session->addUserInSession(0, $user);
651
                Database::getManager()->persist($session);
652
                try {
653
                    Database::getManager()->flush();
654
                } catch (\Doctrine\ORM\OptimisticLockException $exception) {
655
                    Display::addFlash(
656
                        Display::return_message(
657
                            sprintf(get_lang('Internal database error: %s'), $exception->getMessage()),
658
                            'warning'
659
                        )
660
                    );
661
662
                    return false;
663
                }
664
                $accessUrlRelSession = new AccessUrlRelSession();
665
                $accessUrlRelSession->setUrl(api_get_url_entity());
666
                $accessUrlRelSession->setSession($session);
667
                Database::getManager()->persist($accessUrlRelSession);
668
                try {
669
                    Database::getManager()->flush();
670
                } catch (\Doctrine\ORM\OptimisticLockException $exception) {
671
                    Display::addFlash(
672
                        Display::return_message(
673
                            sprintf(get_lang('Internal database error: %s'), $exception->getMessage()),
674
                            'warning'
675
                        )
676
                    );
677
678
                    return false;
679
                }
680
            } else {
681
                // user has at least one accessible session, let's use it
682
                $session = $sessions[0];
683
            }
684
            // add chosen course to the user session
685
            $session->addCourse($course);
686
            Database::getManager()->persist($session);
687
            try {
688
                Database::getManager()->flush();
689
            } catch (\Doctrine\ORM\OptimisticLockException $exception) {
690
                Display::addFlash(
691
                    Display::return_message(
692
                        sprintf(get_lang('Internal database error: %s'), $exception->getMessage()),
693
                        'warning'
694
                    )
695
                );
696
697
                return false;
698
            }
699
            // subscribe user to course within this session
700
            SessionManager::subscribe_users_to_session_course([$userId], $session->getId(), $course->getCode());
701
702
            return true;
703
        }
704
705
        return self::subscribeUser($userId, $course->getId(), $status, 0);
706
    }
707
708
    /**
709
     * Checks if the current user can subscribe to a given course.
710
     */
711
    public static function canUserSubscribeToCourse(string $courseCode): bool
712
    {
713
        if (api_is_anonymous()) {
714
            return false;
715
        }
716
717
        $course = Container::getCourseRepository()->findOneBy(['code' => $courseCode]);
718
719
        if (null === $course) {
720
            return false;
721
        }
722
723
        $visibility = (int) $course->getVisibility();
724
725
        if (in_array($visibility, [
726
            Course::CLOSED,
727
            Course::HIDDEN,
728
        ])) {
729
            return false;
730
        }
731
732
        if (Course::REGISTERED === $visibility && false === $course->getSubscribe()) {
733
            return false;
734
        }
735
736
        $userId = api_get_user_id();
737
738
        $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
739
            WHERE
740
                user_id = $userId AND
741
                relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
742
                c_id = ".$course->getId();
743
        if (Database::num_rows(Database::query($sql)) > 0) {
744
            return false;
745
        }
746
747
        if (SUBSCRIBE_NOT_ALLOWED === (int) $course->getSubscribe()) {
748
            return false;
749
        }
750
751
        $extraFieldValue = new ExtraFieldValue('course');
752
        $value = $extraFieldValue->get_values_by_handler_and_field_variable(
753
            $course->getId(),
754
            'max_subscribed_students'
755
        );
756
        if (!empty($value) && isset($value['value']) && '' !== $value['value']) {
757
            $maxStudents = (int) $value['value'];
758
            $count = CourseManager::get_user_list_from_course_code(
759
                $courseCode,
760
                0,
761
                null,
762
                null,
763
                STUDENT,
764
                true,
765
                false
766
            );
767
768
            if ($count >= $maxStudents) {
769
                return false;
770
            }
771
        }
772
773
        if ('true' === api_get_setting('catalog.course_subscription_in_user_s_session')) {
774
            $user = api_get_user_entity($userId);
775
            $sessions = $user->getCurrentlyAccessibleSessions();
776
            if (empty($sessions) && $user->getSessionsAsStudent()) {
777
                return false;
778
            }
779
        }
780
781
        return true;
782
    }
783
784
    /**
785
     * Subscribe a user to a course. No checks are performed here to see if
786
     * course subscription is allowed.
787
     *
788
     * @param int  $userId
789
     * @param int  $courseId
790
     * @param int  $status                 (STUDENT, COURSEMANAGER, COURSE_ADMIN, NORMAL_COURSE_MEMBER)
791
     * @param int  $sessionId
792
     * @param int  $userCourseCategoryId
793
     * @param bool $checkTeacherPermission
794
     *
795
     * @return bool True on success, false on failure
796
     *
797
     * @assert ('', '') === false
798
     */
799
    public static function subscribeUser(
800
        $userId,
801
        $courseId,
802
        $status = STUDENT,
803
        $sessionId = 0,
804
        $userCourseCategoryId = 0,
805
        $checkTeacherPermission = true
806
    ) {
807
        $userId = (int) $userId;
808
        $status = (int) $status;
809
810
        if (empty($userId) || empty($courseId)) {
811
            return false;
812
        }
813
814
        $course = api_get_course_entity($courseId);
815
816
        if (null === $course) {
817
            Display::addFlash(Display::return_message(get_lang('This course doesn\'t exist'), 'warning'));
818
819
            return false;
820
        }
821
822
        $user = api_get_user_entity($userId);
823
824
        if (null === $user) {
825
            Display::addFlash(Display::return_message(get_lang('This user doesn\'t exist'), 'warning'));
826
827
            return false;
828
        }
829
830
        $courseCode = $course->getCode();
831
        $userCourseCategoryId = (int) $userCourseCategoryId;
832
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
833
        $status = STUDENT === $status || COURSEMANAGER === $status ? $status : STUDENT;
834
835
        if (!empty($sessionId)) {
836
            SessionManager::subscribe_users_to_session_course(
837
                [$userId],
838
                $sessionId,
839
                $courseCode
840
            );
841
842
            return true;
843
        } else {
844
            // Check whether the user has not been already subscribed to the course.
845
            $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
846
                    WHERE
847
                        user_id = $userId AND
848
                        relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
849
                        c_id = $courseId
850
                    ";
851
            if (Database::num_rows(Database::query($sql)) > 0) {
852
                Display::addFlash(Display::return_message(get_lang('Already registered in course'), 'warning'));
853
854
                return false;
855
            }
856
857
            if ($checkTeacherPermission && !api_is_course_admin()) {
858
                // Check in advance whether subscription is allowed or not for this course.
859
                if (SUBSCRIBE_NOT_ALLOWED === (int) $course->getSubscribe()) {
860
                    Display::addFlash(Display::return_message(get_lang('Subscription not allowed'), 'warning'));
861
862
                    return false;
863
                }
864
            }
865
866
            if (STUDENT === $status) {
867
                // Check if max students per course extra field is set
868
                $extraFieldValue = new ExtraFieldValue('course');
869
                $value = $extraFieldValue->get_values_by_handler_and_field_variable(
870
                    $courseId,
871
                    'max_subscribed_students'
872
                );
873
                if (!empty($value) && isset($value['value']) && '' !== $value['value']) {
874
                    $maxStudents = (int) $value['value'];
875
                    $count = self::get_user_list_from_course_code(
876
                        $courseCode,
877
                        0,
878
                        null,
879
                        null,
880
                        STUDENT,
881
                        true,
882
                        false
883
                    );
884
885
                    if ($count >= $maxStudents) {
886
                        Display::addFlash(
887
                            Display::return_message(
888
                                get_lang(
889
                                    'The maximum number of student has already been reached, it is not possible to subscribe more student.'
890
                                ),
891
                                'warning'
892
                            )
893
                        );
894
895
                        return false;
896
                    }
897
                }
898
            }
899
900
            $maxSort = api_max_sort_value(0, $userId) + 1;
901
902
            $insertId = self::insertUserInCourse(
903
                $user,
904
                $course,
905
                ['status' => $status, 'sort' => $maxSort, 'user_course_cat' => $userCourseCategoryId]
906
            );
907
908
            if ($insertId) {
909
                Display::addFlash(
910
                    Display::return_message(
911
                        sprintf(
912
                            get_lang('User %s has been registered to course %s'),
913
                            UserManager::formatUserFullName($user, true),
914
                            $course->getTitle()
915
                        )
916
                    )
917
                );
918
919
                $sendToStudent = (int) api_get_course_setting('email_alert_student_on_manual_subscription', $course);
920
                if (1 === $sendToStudent) {
921
                    $subject = get_lang('You have been enrolled in the course').' '.$course->getTitle();
922
                    $message = sprintf(
923
                        get_lang('Hello %s, you have been enrolled in the course %s.'),
924
                        UserManager::formatUserFullName($user, true),
925
                        $course->getTitle()
926
                    );
927
928
                    MessageManager::send_message_simple(
929
                        $userId,
930
                        $subject,
931
                        $message,
932
                        api_get_user_id(),
933
                        false,
934
                        true
935
                    );
936
                }
937
938
                $send = (int) api_get_course_setting('email_alert_to_teacher_on_new_user_in_course', $course);
939
940
                if (1 === $send) {
941
                    self::email_to_tutor(
942
                        $userId,
943
                        $courseId,
944
                        false
945
                    );
946
                } elseif (2 === $send) {
947
                    self::email_to_tutor(
948
                        $userId,
949
                        $courseId,
950
                        true
951
                    );
952
                }
953
954
                $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications', $course);
955
                if (1 === $subscribe) {
956
                    /*$forums = get_forums(0,  true, $sessionId);
957
                    foreach ($forums as $forum) {
958
                        set_notification('forum', $forum->getIid(), false, $userInfo, $courseInfo);
959
                    }*/
960
                }
961
962
                return true;
963
            }
964
965
            return false;
966
        }
967
    }
968
969
    /**
970
     * Get the course id based on the original id and field name in the
971
     * extra fields. Returns 0 if course was not found.
972
     *
973
     * @param string $original_course_id_value
974
     * @param string $original_course_id_name
975
     *
976
     * @return int Course id
977
     *
978
     * @assert ('', '') === false
979
     */
980
    public static function get_course_code_from_original_id(
981
        $original_course_id_value,
982
        $original_course_id_name
983
    ) {
984
        $t_cfv = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
985
        $table_field = Database::get_main_table(TABLE_EXTRA_FIELD);
986
        $extraFieldType = EntityExtraField::COURSE_FIELD_TYPE;
987
        $original_course_id_value = Database::escape_string($original_course_id_value);
988
        $original_course_id_name = Database::escape_string($original_course_id_name);
989
990
        $sql = "SELECT item_id
991
                FROM $table_field cf
992
                INNER JOIN $t_cfv cfv
993
                ON cfv.field_id=cf.id
994
                WHERE
995
                    variable = '$original_course_id_name' AND
996
                    value = '$original_course_id_value' AND
997
                    cf.item_type = $extraFieldType
998
                ";
999
        $res = Database::query($sql);
1000
        $row = Database::fetch_object($res);
1001
        if ($row) {
1002
            return $row->item_id;
1003
        } else {
1004
            return 0;
1005
        }
1006
    }
1007
1008
    /**
1009
     * Gets the course code from the course id. Returns null if course id was not found.
1010
     *
1011
     * @param int $id Course id
1012
     *
1013
     * @return string Course code
1014
     * @assert ('') === false
1015
     */
1016
    public static function get_course_code_from_course_id($id)
1017
    {
1018
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
1019
        $id = intval($id);
1020
        $sql = "SELECT code FROM $table WHERE id = $id ";
1021
        $res = Database::query($sql);
1022
        $row = Database::fetch_object($res);
1023
        if ($row) {
1024
            return $row->code;
1025
        } else {
1026
            return null;
1027
        }
1028
    }
1029
1030
    /**
1031
     * Add the user $userId visibility to the course $courseCode in the catalogue.
1032
     *
1033
     * @author David Nos (https://github.com/dnos)
1034
     *
1035
     * @param int    $userId     the id of the user
1036
     * @param string $courseCode the course code
1037
     * @param int    $visible    (optional) The course visibility in the catalogue to the user (1=visible, 0=invisible)
1038
     *
1039
     * @return bool true if added successfully, false otherwise
1040
     */
1041
    public static function addUserVisibilityToCourseInCatalogue(
1042
        $userId,
1043
        $courseCode,
1044
        $visible = 1
1045
    ) {
1046
        $debug = false;
1047
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1048
        $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_CATALOGUE_USER);
1049
        $visible = (int) $visible;
1050
        if (empty($userId) || empty($courseCode) || ($userId != strval(intval($userId)))) {
1051
            return false;
1052
        }
1053
1054
        $courseCode = Database::escape_string($courseCode);
1055
        $courseInfo = api_get_course_info($courseCode);
1056
        $courseId = $courseInfo['real_id'];
1057
1058
        // Check in advance whether the user has already been registered on the platform.
1059
        $sql = "SELECT status FROM ".$userTable." WHERE user_id = $userId ";
1060
        if (0 == Database::num_rows(Database::query($sql))) {
1061
            return false; // The user has not been registered to the platform.
1062
        }
1063
1064
        // Check whether the user has already been registered to the course visibility in the catalogue.
1065
        $sql = "SELECT * FROM $courseUserTable
1066
                WHERE
1067
                    user_id = $userId AND
1068
                    visible = $visible AND
1069
                    c_id = $courseId";
1070
        if (Database::num_rows(Database::query($sql)) > 0) {
1071
            return true; // The visibility of the user to the course in the catalogue does already exist.
1072
        }
1073
1074
        // Register the user visibility to course in catalogue.
1075
        $params = [
1076
            'user_id' => $userId,
1077
            'c_id' => $courseId,
1078
            'visible' => $visible,
1079
        ];
1080
1081
        return Database::insert($courseUserTable, $params);
1082
    }
1083
1084
    /**
1085
     * Remove the user $userId visibility to the course $courseCode in the catalogue.
1086
     *
1087
     * @author David Nos (https://github.com/dnos)
1088
     *
1089
     * @param int    $userId     the id of the user
1090
     * @param string $courseCode the course code
1091
     * @param int    $visible    (optional) The course visibility in the catalogue to the user (1=visible, 0=invisible)
1092
     *
1093
     * @return bool true if removed successfully or register not found, false otherwise
1094
     */
1095
    public static function removeUserVisibilityToCourseInCatalogue(
1096
        $userId,
1097
        $courseCode,
1098
        $visible = 1
1099
    ) {
1100
        $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_CATALOGUE_USER);
1101
1102
        if (empty($userId) || empty($courseCode) || ($userId != strval(intval($userId)))) {
1103
            return false;
1104
        }
1105
1106
        $courseCode = Database::escape_string($courseCode);
1107
        $courseInfo = api_get_course_info($courseCode);
1108
        $courseId = $courseInfo['real_id'];
1109
1110
        // Check whether the user has already been registered to the course visibility in the catalogue.
1111
        $sql = "SELECT * FROM $courseUserTable
1112
                WHERE
1113
                    user_id = $userId AND
1114
                    visible = $visible AND
1115
                    c_id = $courseId";
1116
        if (Database::num_rows(Database::query($sql)) > 0) {
1117
            $cond = [
1118
                'user_id = ? AND c_id = ? AND visible = ? ' => [
1119
                    $userId,
1120
                    $courseId,
1121
                    $visible,
1122
                ],
1123
            ];
1124
1125
            return Database::delete($courseUserTable, $cond);
1126
        } else {
1127
            return true; // Register does not exist
1128
        }
1129
    }
1130
1131
    /**
1132
     * @param string $code
1133
     *
1134
     * @return bool if there already are one or more courses
1135
     *              with the same code OR visual_code (visualcode), false otherwise
1136
     */
1137
    public static function course_code_exists($code)
1138
    {
1139
        $code = Database::escape_string($code);
1140
        $sql = "SELECT COUNT(*) as number
1141
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
1142
                WHERE code = '$code' OR visual_code = '$code'";
1143
        $result = Database::fetch_array(Database::query($sql));
1144
1145
        return $result['number'] > 0;
1146
    }
1147
1148
    /**
1149
     * @param int    $userId
1150
     * @param string $keyword
1151
     *
1152
     * @return array an array with the course info of all the courses (real and virtual)
1153
     *               of which the current user is course admin
1154
     */
1155
    public static function get_course_list_of_user_as_course_admin($userId, $keyword = ''): array
1156
    {
1157
        $user = api_get_user_entity($userId);
1158
1159
        if (null === $user) {
1160
            return [];
1161
        }
1162
1163
        $url = api_get_url_entity();
1164
        $repo = Container::getCourseRepository();
1165
1166
        return $repo->getCoursesInfoByUser($user, $url, COURSEMANAGER, $keyword);
1167
    }
1168
1169
    /**
1170
     * @param int   $userId
1171
     * @param array $courseInfo
1172
     *
1173
     * @return bool|null
1174
     */
1175
    public static function isUserSubscribedInCourseAsDrh($userId, $courseInfo)
1176
    {
1177
        $userId = intval($userId);
1178
1179
        if (!api_is_drh()) {
1180
            return false;
1181
        }
1182
1183
        if (empty($courseInfo) || empty($userId)) {
1184
            return false;
1185
        }
1186
1187
        $courseId = intval($courseInfo['real_id']);
1188
        $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1189
1190
        $sql = "SELECT * FROM $table
1191
                WHERE
1192
                    user_id = $userId AND
1193
                    relation_type = ".COURSE_RELATION_TYPE_RRHH." AND
1194
                    c_id = $courseId";
1195
1196
        $result = Database::fetch_array(Database::query($sql));
1197
1198
        if (!empty($result)) {
1199
            // The user has been registered in this course.
1200
            return true;
1201
        }
1202
    }
1203
1204
    /**
1205
     * Check if user is subscribed inside a course.
1206
     *
1207
     * @param int    $userId
1208
     * @param string $course_code  , if this parameter is null, it'll check for all courses
1209
     * @param bool   $in_a_session True for checking inside sessions too, by default is not checked
1210
     * @param int    $session_id
1211
     *
1212
     * @return bool $session_id true if the user is registered in the course, false otherwise
1213
     */
1214
    public static function is_user_subscribed_in_course(
1215
        $userId,
1216
        $course_code = null,
1217
        $in_a_session = false,
1218
        $session_id = 0
1219
    ) {
1220
        $userId = (int) $userId;
1221
        $session_id = (int) $session_id;
1222
1223
        if ('true' === api_get_setting('catalog.course_subscription_in_user_s_session')) {
1224
            // with this option activated, only check whether the course is in one of the users' sessions
1225
            $course = Container::getCourseRepository()->findOneBy([
1226
                'code' => $course_code,
1227
            ]);
1228
            if (is_null($course)) {
1229
                return false;
1230
            }
1231
1232
            $user = api_get_user_entity($userId);
1233
            if (is_null($user)) {
1234
                return false;
1235
            }
1236
            foreach ($user->getSessionsAsStudent() as $session) {
1237
                if ($session->hasCourse($course)) {
1238
                    return true;
1239
                }
1240
            }
1241
1242
            return false;
1243
        }
1244
1245
        if (empty($session_id)) {
1246
            $session_id = api_get_session_id();
1247
        }
1248
1249
        $condition_course = '';
1250
        if (isset($course_code)) {
1251
            $courseInfo = api_get_course_info($course_code);
1252
            if (empty($courseInfo)) {
1253
                return false;
1254
            }
1255
            $courseId = $courseInfo['real_id'];
1256
            $condition_course = " AND c_id = $courseId";
1257
        }
1258
1259
        $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
1260
                WHERE
1261
                    user_id = $userId AND
1262
                    relation_type<>".COURSE_RELATION_TYPE_RRHH."
1263
                    $condition_course ";
1264
1265
        $result = Database::fetch_array(Database::query($sql));
1266
1267
        if (!empty($result)) {
1268
            // The user has been registered in this course.
1269
            return true;
1270
        }
1271
1272
        if (!$in_a_session) {
1273
            // The user has not been registered in this course.
1274
            return false;
1275
        }
1276
1277
        $tableSessionCourseUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1278
        $sql = "SELECT 1 FROM $tableSessionCourseUser
1279
                WHERE user_id = $userId AND session_id = $session_id $condition_course";
1280
1281
        if (Database::num_rows(Database::query($sql)) > 0) {
1282
            return true;
1283
        }
1284
1285
        $sql = "SELECT 1 FROM $tableSessionCourseUser
1286
                WHERE user_id = $userId AND session_id = $session_id AND status = ".SessionEntity::COURSE_COACH." $condition_course";
1287
1288
        if (Database::num_rows(Database::query($sql)) > 0) {
1289
            return true;
1290
        }
1291
1292
        $sql = "SELECT s.id FROM ".Database::get_main_table(TABLE_MAIN_SESSION)." s
1293
                INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION_USER)." sru
1294
                ON (sru.session_id = s.id AND sru.relation_type = ".SessionEntity::GENERAL_COACH.")
1295
                WHERE sru.user_id = $userId AND s.id = $session_id";
1296
1297
        if (Database::num_rows(Database::query($sql)) > 0) {
1298
            return true;
1299
        }
1300
1301
        return false;
1302
    }
1303
1304
    /**
1305
     * Is the user a teacher in the given course?
1306
     *
1307
     * @param int $userId
1308
     * @param int $courseId
1309
     *
1310
     * @return bool if the user is a teacher in the course, false otherwise
1311
     */
1312
    public static function isCourseTeacher($userId, $courseId)
1313
    {
1314
        $userId = (int) $userId;
1315
        $courseId = (int) $courseId;
1316
1317
        if (empty($courseId) || empty($userId)) {
1318
            return false;
1319
        }
1320
1321
        $sql = "SELECT status FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
1322
                WHERE c_id = $courseId AND user_id = $userId ";
1323
        $result = Database::query($sql);
1324
1325
        if (Database::num_rows($result) > 0) {
1326
            return 1 == Database::result($result, 0, 'status');
1327
        }
1328
1329
        return false;
1330
    }
1331
1332
    /**
1333
     * Get user info array of all users registered in a course, by course code.
1334
     * Wrapper: This method calls getUserListFromCourseId() in the background.
1335
     * @param string|null $courseCode Cours code is allowed to be null if we want all users
1336
     * @param int         $sessionId
1337
     * @param string      $limit
1338
     * @param string      $order_by the field to order the users by.
1339
     *                                    Valid values are 'lastname', 'firstname', 'username', 'email',
1340
     *                                    'official_code' OR a part of a SQL statement that starts with ORDER BY ...
1341
     * @param int|null    $filter_by_status if using the session_id: 0 or 2 (student, coach),
1342
     *                                    if using session_id = 0 STUDENT or COURSEMANAGER
1343
     * @param bool|null   $return_count
1344
     * @param bool        $add_reports
1345
     * @param bool        $resumed_report
1346
     * @param array       $extra_field
1347
     * @param array       $courseCodeList
1348
     * @param array       $userIdList
1349
     * @param bool        $filterByActive
1350
     * @param array       $sessionIdList
1351
     * @param string      $searchByKeyword
1352
     *
1353
     * @return array|int
1354
     * @throws Exception
1355
     */
1356
    public static function get_user_list_from_course_code(
1357
        $courseCode,
1358
        $sessionId = 0,
1359
        $limit = null,
1360
        $order_by = null,
1361
        $filter_by_status = null,
1362
        $return_count = null,
1363
        $add_reports = false,
1364
        $resumed_report = false,
1365
        $extra_field = [],
1366
        $courseCodeList = [],
1367
        $userIdList = [],
1368
        $filterByActive = null,
1369
        $sessionIdList = [],
1370
        $searchByKeyword = '',
1371
        $options = []
1372
    )
1373
    {
1374
        $courseId = api_get_course_int_id($courseCode);
1375
        return self::getUserListFromCourseId(
1376
            $courseId,
1377
            $sessionId,
1378
            $limit,
1379
            $order_by,
1380
            $filter_by_status,
1381
            $return_count,
1382
            $add_reports,
1383
            $resumed_report,
1384
            $extra_field,
1385
            $courseCodeList,
1386
            $userIdList,
1387
            $filterByActive,
1388
            $sessionIdList,
1389
            $searchByKeyword,
1390
            $options
1391
        );
1392
    }
1393
1394
    /**
1395
     * Return user info array of all users registered in a course (by course ID and session ID).
1396
     * This only returns the users that are registered in this actual course, not linked courses.
1397
     *
1398
     * @param ?int    $courseCode Course ID is allowed to be null if we want all users
1399
     * @param ?int    $sessionId
1400
     * @param ?string $limit
1401
     * @param ?string $order_by the field to order the users by.
1402
     *                                    Valid values are 'lastname', 'firstname', 'username', 'email',
1403
     *                                    'official_code' OR a part of a SQL statement that starts with ORDER BY ...
1404
     * @param ?int    $filter_by_status if using the session_id: 0 or 2 (student, coach),
1405
     *                                    if using session_id = 0 STUDENT or COURSEMANAGER
1406
     * @param ?bool   $return_count
1407
     * @param ?bool   $add_reports
1408
     * @param ?bool   $resumed_report
1409
     * @param ?array  $extra_field
1410
     * @param ?array  $courseCodeList
1411
     * @param ?array  $userIdList
1412
     * @param ?bool   $filterByActive
1413
     * @param ?array  $sessionIdList
1414
     * @param ?string $searchByKeyword
1415
     * @param ?array
1416
     *
1417
     * @return array|int
1418
     * @throws Exception
1419
     */
1420
    public static function getUserListFromCourseId(
1421
        ?int $courseId = null,
1422
        ?int $sessionId = 0,
1423
        ?string $limit = null,
1424
        ?string $order_by = null,
1425
        ?int $filter_by_status = null,
1426
        ?bool $return_count = null,
1427
        ?bool $add_reports = false,
1428
        ?bool $resumed_report = false,
1429
        ?array $extra_field = [],
1430
        ?array $courseIdsList = [],
1431
        ?array $userIdList = [],
1432
        ?bool $filterByActive = null,
1433
        ?array $sessionIdList = [],
1434
        ?string $searchByKeyword = '',
1435
        ?array $options = []
1436
    ) {
1437
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
1438
        $sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
1439
1440
        $sessionId = (int) $sessionId;
1441
        $session = null;
1442
        if (!empty($sessionId)) {
1443
            $session = api_get_session_entity($sessionId);
1444
        }
1445
        $where = [];
1446
        if (empty($order_by)) {
1447
            $order_by = 'user.lastname, user.firstname';
1448
            if (api_is_western_name_order()) {
1449
                $order_by = 'user.firstname, user.lastname';
1450
            }
1451
        }
1452
1453
        // if the $order_by does not contain 'ORDER BY'
1454
        // we have to check if it is a valid field that can be sorted on
1455
        if (!strstr($order_by, 'ORDER BY')) {
1456
            if (!empty($order_by)) {
1457
                $order_by = "ORDER BY $order_by";
1458
            } else {
1459
                $order_by = '';
1460
            }
1461
        }
1462
1463
        $filter_by_status_condition = null;
1464
        $whereExtraField = '';
1465
        $injectExtraFields = ' , ';
1466
        $sqlInjectJoins = '';
1467
        if (!empty($options)) {
1468
            $extraFieldModel = new ExtraField('user');
1469
            $conditions = $extraFieldModel->parseConditions($options, 'user');
1470
            if (!empty($conditions)) {
1471
                $injectExtraFields = $conditions['inject_extra_fields'];
1472
1473
                if (!empty($injectExtraFields)) {
1474
                    $injectExtraFields = ', '.$injectExtraFields;
1475
                } else {
1476
                    $injectExtraFields = ' , ';
1477
                }
1478
                $sqlInjectJoins = $conditions['inject_joins'];
1479
                $whereExtraField = $conditions['where'];
1480
            }
1481
        }
1482
1483
        if (!empty($sessionId) || !empty($sessionIdList)) {
1484
            $sql = 'SELECT DISTINCT
1485
                        user.id as user_id,
1486
                        user.email,
1487
                        session_course_user.status as status_session,
1488
                        session_id,
1489
                        user.*,
1490
                        course.*,
1491
                        course.id AS c_id
1492
                         '.$injectExtraFields.'
1493
                        session.title as session_name
1494
                    ';
1495
            if ($return_count) {
1496
                $sql = ' SELECT COUNT(user.id) as count';
1497
            }
1498
1499
            $sessionCondition = " session_course_user.session_id = $sessionId";
1500
            if (!empty($sessionIdList)) {
1501
                $sessionIdListToString = implode("','", array_map('intval', $sessionIdList));
1502
                $sessionCondition = " session_course_user.session_id IN ('$sessionIdListToString') ";
1503
            }
1504
1505
            $courseCondition = " course.id IS NOT NULL ";
1506
            if (!empty($courseId)) {
1507
                $courseCondition = " course.id = $courseId";
1508
            }
1509
            if (!empty($courseCodeList)) {
1510
                $courseCodeListForSession = array_map(['Database', 'escape_string'], $courseCodeList);
1511
                $courseCodeListForSession = implode("','", $courseCodeListForSession);
1512
                $courseCondition = " course.code IN ('$courseCodeListForSession')  ";
1513
            }
1514
1515
            $sql .= ' FROM '.Database::get_main_table(TABLE_MAIN_USER).' as user ';
1516
            $sql .= " LEFT JOIN ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)." as session_course_user
1517
                      ON
1518
                        user.id = session_course_user.user_id AND
1519
                        $sessionCondition
1520
                        INNER JOIN $course_table course
1521
                        ON session_course_user.c_id = course.id AND
1522
                        $courseCondition
1523
                        INNER JOIN $sessionTable session
1524
                        ON session_course_user.session_id = session.id
1525
                    $sqlInjectJoins
1526
                   ";
1527
            $where[] = ' session_course_user.c_id IS NOT NULL ';
1528
1529
            // 2 = coach
1530
            // 0 = student
1531
            if (isset($filter_by_status)) {
1532
                $filter_by_status_condition = " session_course_user.status = $filter_by_status AND ";
1533
            }
1534
        } else {
1535
            if ($return_count) {
1536
                $sql = " SELECT COUNT(*) as count";
1537
            } else {
1538
                if (empty($courseCode)) {
1539
                    $sql = 'SELECT DISTINCT
1540
                                course.title,
1541
                                course.code,
1542
                                course.id AS c_id,
1543
                                course_rel_user.status as status_rel,
1544
                                user.id as user_id,
1545
                                user.email,
1546
                                course_rel_user.is_tutor
1547
                                '.$injectExtraFields.'
1548
                                user.*  ';
1549
                } else {
1550
                    $sql = 'SELECT DISTINCT
1551
                                course_rel_user.status as status_rel,
1552
                                user.id as user_id,
1553
                                user.email,
1554
                                course_rel_user.is_tutor
1555
                                '.$injectExtraFields.'
1556
                                user.*  ';
1557
                }
1558
            }
1559
1560
            $sql .= " FROM ".Database::get_main_table(TABLE_MAIN_USER)." as user
1561
                      LEFT JOIN ".Database::get_main_table(TABLE_MAIN_COURSE_USER)." as course_rel_user
1562
                      ON
1563
                        user.id = course_rel_user.user_id AND
1564
                        course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
1565
                       INNER JOIN $course_table course
1566
                       ON (course_rel_user.c_id = course.id)
1567
                       $sqlInjectJoins
1568
                       ";
1569
1570
            if (!empty($courseId)) {
1571
                $sql .= " AND course_rel_user.c_id = $courseId";
1572
            }
1573
            $where[] = ' course_rel_user.c_id IS NOT NULL ';
1574
1575
            if (isset($filter_by_status)) {
1576
                $filter_by_status_condition = " course_rel_user.status = $filter_by_status AND ";
1577
            }
1578
        }
1579
1580
        $multiple_access_url = api_get_multiple_access_url();
1581
        if ($multiple_access_url) {
1582
            $sql .= ' LEFT JOIN '.Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER).' au
1583
                      ON (au.user_id = user.id) ';
1584
        }
1585
1586
        $extraFieldWasAdded = false;
1587
        if ($return_count && $resumed_report) {
1588
            foreach ($extra_field as $extraField) {
1589
                $extraFieldInfo = UserManager::get_extra_field_information_by_name($extraField);
1590
                if (!empty($extraFieldInfo)) {
1591
                    $fieldValuesTable = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1592
                    $sql .= " LEFT JOIN $fieldValuesTable as ufv
1593
                            ON (
1594
                                user.id = ufv.item_id AND
1595
                                (field_id = ".$extraFieldInfo['id']." OR field_id IS NULL)
1596
                            )";
1597
                    $extraFieldWasAdded = true;
1598
                }
1599
            }
1600
        }
1601
1602
        $sql .= " WHERE user.active <> ".USER_SOFT_DELETED." AND
1603
            $filter_by_status_condition
1604
            ".implode(' OR ', $where);
1605
1606
        if ($multiple_access_url) {
1607
            $current_access_url_id = api_get_current_access_url_id();
1608
            $sql .= " AND (access_url_id =  $current_access_url_id ) ";
1609
        }
1610
1611
        if ($return_count && $resumed_report && $extraFieldWasAdded) {
1612
            $sql .= ' AND field_id IS NOT NULL GROUP BY value ';
1613
        }
1614
1615
        if (!empty($courseIdsList)) {
1616
            $courseIdsList = array_map('intval', $courseIdsList);
1617
            $courseIdsList = implode('","', $courseIdsList);
1618
            if (empty($sessionIdList)) {
1619
                $sql .= ' AND course.id IN ("'.$courseIdsList.'")';
1620
            }
1621
        }
1622
1623
        if (!empty($userIdList)) {
1624
            $userIdList = array_map('intval', $userIdList);
1625
            $userIdList = implode('","', $userIdList);
1626
            $sql .= ' AND user.id IN ("'.$userIdList.'")';
1627
        }
1628
1629
        if (isset($filterByActive)) {
1630
            $filterByActive = (int) $filterByActive;
1631
            $sql .= " AND user.active = $filterByActive";
1632
        }
1633
1634
        if (!empty($searchByKeyword)) {
1635
            $searchByKeyword = Database::escape_string($searchByKeyword);
1636
            $sql .= " AND (
1637
                        user.firstname LIKE '$searchByKeyword' OR
1638
                        user.username LIKE '$searchByKeyword' OR
1639
                        user.lastname LIKE '$searchByKeyword'
1640
                    ) ";
1641
        }
1642
1643
        $sql .= $whereExtraField;
1644
        $sql .= " $order_by $limit";
1645
1646
        $rs = Database::query($sql);
1647
        $users = [];
1648
1649
        $extra_fields = UserManager::get_extra_fields(
1650
            0,
1651
            100,
1652
            null,
1653
            null,
1654
            true,
1655
            true
1656
        );
1657
1658
        $counter = 1;
1659
        $count_rows = Database::num_rows($rs);
1660
1661
        if ($return_count && $resumed_report) {
1662
            return $count_rows;
1663
        }
1664
        $table_user_field_value = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
1665
        $tableExtraField = Database::get_main_table(TABLE_EXTRA_FIELD);
1666
        if ($count_rows) {
1667
            while ($user = Database::fetch_array($rs)) {
1668
                if ($return_count) {
1669
                    return $user['count'];
1670
                }
1671
1672
                $report_info = [];
1673
                $user_info = $user;
1674
                $user_info['status'] = $user['status'];
1675
                if (isset($user['is_tutor'])) {
1676
                    $user_info['is_tutor'] = $user['is_tutor'];
1677
                }
1678
                if (!empty($sessionId)) {
1679
                    $user_info['status_session'] = $user['status_session'];
1680
                }
1681
1682
                $sessionId = $user['session_id'] ?? 0;
1683
                $sessionName = isset($user['session_name']) ? ' ('.$user['session_name'].') ' : '';
1684
1685
                if ($add_reports) {
1686
                    if ($resumed_report) {
1687
                        $extra = [];
1688
                        if (!empty($extra_fields)) {
1689
                            foreach ($extra_fields as $extra) {
1690
                                if (in_array($extra['1'], $extra_field)) {
1691
                                    $user_data = UserManager::get_extra_user_data_by_field(
1692
                                        $user['user_id'],
1693
                                        $extra['1']
1694
                                    );
1695
                                    break;
1696
                                }
1697
                            }
1698
                        }
1699
1700
                        $row_key = '-1';
1701
                        $name = '-';
1702
                        if (!empty($extra)) {
1703
                            if (!empty($user_data[$extra['1']])) {
1704
                                $row_key = $user_data[$extra['1']];
1705
                                $name = $user_data[$extra['1']];
1706
                                $users[$row_key]['extra_'.$extra['1']] = $name;
1707
                            }
1708
                        }
1709
1710
                        if (empty($users[$row_key])) {
1711
                            $users[$row_key] = [];
1712
                        }
1713
1714
                        if (!array_key_exists('training_hours', $users[$row_key])) {
1715
                            $users[$row_key]['training_hours'] = 0;
1716
                        }
1717
1718
                        $users[$row_key]['training_hours'] += Tracking::get_time_spent_on_the_course(
1719
                            $user['user_id'],
1720
                            $user['c_id'],
1721
                            $sessionId
1722
                        );
1723
1724
                        if (!array_key_exists('count_users', $users[$row_key])) {
1725
                            $users[$row_key]['count_users'] = 0;
1726
                        }
1727
1728
                        $users[$row_key]['count_users'] += $counter;
1729
1730
                        $registered_users_with_extra_field = self::getCountRegisteredUsersWithCourseExtraField(
1731
                            $name,
1732
                            $tableExtraField,
1733
                            $table_user_field_value
1734
                        );
1735
1736
                        $users[$row_key]['count_users_registered'] = $registered_users_with_extra_field;
1737
                        $users[$row_key]['average_hours_per_user'] = $users[$row_key]['training_hours'] / $users[$row_key]['count_users'];
1738
1739
                        $category = Category::load(
1740
                            null,
1741
                            null,
1742
                            $user['c_id'],
1743
                            null,
1744
                            null,
1745
                            $sessionId
1746
                        );
1747
1748
                        if (!isset($users[$row_key]['count_certificates'])) {
1749
                            $users[$row_key]['count_certificates'] = 0;
1750
                        }
1751
1752
                        if (isset($category[0]) && $category[0]->is_certificate_available($user['user_id'])) {
1753
                            $users[$row_key]['count_certificates']++;
1754
                        }
1755
1756
                        foreach ($extra_fields as $extra) {
1757
                            if ('ruc' === $extra['1']) {
1758
                                continue;
1759
                            }
1760
1761
                            if (!isset($users[$row_key][$extra['1']])) {
1762
                                $user_data = UserManager::get_extra_user_data_by_field($user['user_id'], $extra['1']);
1763
                                if (!empty($user_data[$extra['1']])) {
1764
                                    $users[$row_key][$extra['1']] = $user_data[$extra['1']];
1765
                                }
1766
                            }
1767
                        }
1768
                    } else {
1769
                        $report_info['course'] = $user['title'].$sessionName;
1770
                        $report_info['user'] = api_get_person_name($user['firstname'], $user['lastname']);
1771
                        $report_info['email'] = $user['email'];
1772
                        $report_info['time'] = api_time_to_hms(
1773
                            Tracking::get_time_spent_on_the_course(
1774
                                $user['user_id'],
1775
                                $user['c_id'],
1776
                                $sessionId
1777
                            )
1778
                        );
1779
1780
                        $category = Category:: load(
1781
                            null,
1782
                            null,
1783
                            $user['c_id'],
1784
                            null,
1785
                            null,
1786
                            $sessionId
1787
                        );
1788
1789
                        $report_info['certificate'] = Display::label(get_lang('No'));
1790
                        if (isset($category[0]) && $category[0]->is_certificate_available($user['user_id'])) {
1791
                            $report_info['certificate'] = Display::label(get_lang('Yes'), 'success');
1792
                        }
1793
                        $course = api_get_course_entity($user['c_id']);
1794
                        $progress = (int) Tracking::get_avg_student_progress(
1795
                            $user['user_id'],
1796
                            $course,
1797
                            [],
1798
                            $session
1799
                        );
1800
1801
                        $report_info['progress_100'] = 100 == $progress ? Display::label(get_lang('Yes'), 'success') : Display::label(get_lang('No'));
1802
                        $report_info['progress'] = $progress."%";
1803
                        foreach ($extra_fields as $extra) {
1804
                            $user_data = UserManager::get_extra_user_data_by_field($user['user_id'], $extra['1']);
1805
                            $report_info[$extra['1']] = $user_data[$extra['1']];
1806
                        }
1807
                        $report_info['user_id'] = $user['user_id'];
1808
                        $users[] = $report_info;
1809
                    }
1810
                } else {
1811
                    $users[$user['user_id']] = $user_info;
1812
                }
1813
            }
1814
        }
1815
1816
        return $users;
1817
    }
1818
1819
    /**
1820
     * @param bool  $resumed_report
1821
     * @param array $extra_field
1822
     * @param array $courseCodeList
1823
     * @param array $userIdList
1824
     * @param array $sessionIdList
1825
     * @param array $options
1826
     *
1827
     * @return array|int
1828
     */
1829
    public static function get_count_user_list_from_course_code(
1830
        $resumed_report = false,
1831
        $extra_field = [],
1832
        $courseCodeList = [],
1833
        $userIdList = [],
1834
        $sessionIdList = [],
1835
        $options = []
1836
    ) {
1837
        return self::get_user_list_from_course_code(
1838
            null,
1839
            0,
1840
            null,
1841
            null,
1842
            null,
1843
            true,
1844
            false,
1845
            $resumed_report,
1846
            $extra_field,
1847
            $courseCodeList,
1848
            $userIdList,
1849
            null,
1850
            $sessionIdList,
1851
            null,
1852
            $options
1853
        );
1854
    }
1855
1856
    /**
1857
     * Gets subscribed users in a course or in a course/session.
1858
     *
1859
     * @param string $course_code
1860
     * @param int    $session_id
1861
     *
1862
     * @return int
1863
     */
1864
    public static function get_users_count_in_course(
1865
        $course_code,
1866
        $session_id = 0,
1867
        $status = null
1868
    ) {
1869
        // variable initialisation
1870
        $session_id = (int) $session_id;
1871
        $course_code = Database::escape_string($course_code);
1872
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1873
        $tblSessionCourseUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1874
        $tblCourseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1875
        $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1876
1877
        $courseInfo = api_get_course_info($course_code);
1878
        $courseId = $courseInfo['real_id'];
1879
1880
        $sql = "
1881
            SELECT DISTINCT count(user.id) as count
1882
            FROM $tblUser as user
1883
        ";
1884
        $where = [];
1885
        if (!empty($session_id)) {
1886
            $sql .= "
1887
                LEFT JOIN $tblSessionCourseUser as session_course_user
1888
                ON user.id = session_course_user.user_id
1889
                    AND session_course_user.c_id = $courseId
1890
                    AND session_course_user.session_id = $session_id
1891
            ";
1892
1893
            $where[] = ' session_course_user.c_id IS NOT NULL ';
1894
        } else {
1895
            $sql .= "
1896
                LEFT JOIN $tblCourseUser as course_rel_user
1897
                    ON user.id = course_rel_user.user_id
1898
                    AND course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
1899
                    AND course_rel_user.c_id = $courseId
1900
            ";
1901
            $where[] = ' course_rel_user.c_id IS NOT NULL ';
1902
        }
1903
1904
        $multiple_access_url = api_get_multiple_access_url();
1905
        if ($multiple_access_url) {
1906
            $sql .= " LEFT JOIN $tblUrlUser au ON (au.user_id = user.id) ";
1907
        }
1908
1909
        $sql .= ' WHERE user.active <> '.USER_SOFT_DELETED.' AND '.implode(' OR ', $where);
1910
1911
        if ($multiple_access_url) {
1912
            $current_access_url_id = api_get_current_access_url_id();
1913
            $sql .= " AND (access_url_id =  $current_access_url_id ) ";
1914
        }
1915
        $rs = Database::query($sql);
1916
        $count = 0;
1917
        if (Database::num_rows($rs)) {
1918
            $user = Database::fetch_array($rs);
1919
            $count = $user['count'];
1920
        }
1921
1922
        return $count;
1923
    }
1924
1925
    /**
1926
     * Get a list of coaches of a course and a session.
1927
     *
1928
     * @param string $course_code
1929
     * @param int    $session_id
1930
     * @param bool   $addGeneralCoach
1931
     *
1932
     * @return array List of users
1933
     */
1934
    public static function get_coach_list_from_course_code(
1935
        $course_code,
1936
        $session_id,
1937
        $addGeneralCoach = true
1938
    ) {
1939
        if (empty($course_code) || empty($session_id)) {
1940
            return [];
1941
        }
1942
1943
        $course_code = Database::escape_string($course_code);
1944
        $courseInfo = api_get_course_info($course_code);
1945
        $courseId = $courseInfo['real_id'];
1946
        $session_id = (int) $session_id;
1947
        $users = [];
1948
1949
        // We get the coach for the given course in a given session.
1950
        $sql = 'SELECT user_id FROM '.Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER).
1951
               " WHERE session_id = $session_id AND c_id = $courseId AND status = ".SessionEntity::COURSE_COACH;
1952
        $rs = Database::query($sql);
1953
        while ($user = Database::fetch_array($rs)) {
1954
            $userInfo = api_get_user_info($user['user_id']);
1955
            if ($userInfo) {
1956
                $users[$user['user_id']] = $userInfo;
1957
            }
1958
        }
1959
1960
        if ($addGeneralCoach) {
1961
            $generalCoachesId = SessionManager::getGeneralCoachesIdForSession($session_id);
1962
1963
            foreach ($generalCoachesId as $session_id_coach) {
1964
                $userInfo = api_get_user_info($session_id_coach);
1965
                if ($userInfo) {
1966
                    $users[$session_id_coach] = $userInfo;
1967
                }
1968
            }
1969
        }
1970
1971
        return $users;
1972
    }
1973
1974
    /**
1975
     *  Return user info array of all users registered in a course
1976
     *  This only returns the users that are registered in this actual course, not linked courses.
1977
     *
1978
     * @param string $course_code
1979
     * @param bool   $with_session
1980
     * @param int    $sessionId
1981
     * @param string $date_from
1982
     * @param string $date_to
1983
     * @param bool   $includeInvitedUsers Whether include the invited users
1984
     * @param int    $groupId
1985
     * @param bool   $getCount
1986
     * @param int    $start
1987
     * @param int    $limit
1988
     *
1989
     * @return array with user id
1990
     */
1991
    public static function get_student_list_from_course_code(
1992
        $course_code,
1993
        $with_session = false,
1994
        $sessionId = 0,
1995
        $date_from = null,
1996
        $date_to = null,
1997
        $includeInvitedUsers = true,
1998
        $groupId = 0,
1999
        $getCount = false,
2000
        $start = 0,
2001
        $limit = 0
2002
    ) {
2003
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
2004
        $sessionId = (int) $sessionId;
2005
        $courseInfo = api_get_course_info($course_code);
2006
        if (empty($courseInfo)) {
2007
            return [];
2008
        }
2009
        $courseId = $courseInfo['real_id'];
2010
        $students = [];
2011
2012
        $limitCondition = '';
2013
        if (isset($start) && isset($limit) && !empty($limit)) {
2014
            $start = (int) $start;
2015
            $limit = (int) $limit;
2016
            $limitCondition = " LIMIT $start, $limit";
2017
        }
2018
2019
        $select = '*';
2020
        if ($getCount) {
2021
            $select = 'count(u.id) as count';
2022
        }
2023
2024
        if (empty($sessionId)) {
2025
            if (empty($groupId)) {
2026
                // students directly subscribed to the course
2027
                $sql = "SELECT $select
2028
                        FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)." cu
2029
                        INNER JOIN $userTable u
2030
                        ON cu.user_id = u.id
2031
                        WHERE u.active <> ".USER_SOFT_DELETED." AND c_id = $courseId AND cu.status = ".STUDENT;
2032
2033
                if (!$includeInvitedUsers) {
2034
                    $sql .= " AND u.status != ".INVITEE;
2035
                }
2036
                $sql .= $limitCondition;
2037
                $rs = Database::query($sql);
2038
2039
                if ($getCount) {
2040
                    $row = Database::fetch_array($rs);
2041
2042
                    return (int) $row['count'];
2043
                }
2044
2045
                while ($student = Database::fetch_array($rs)) {
2046
                    $students[$student['user_id']] = $student;
2047
                }
2048
            } else {
2049
                $students = GroupManager::get_users(
2050
                    $groupId,
2051
                    false,
2052
                    $start,
2053
                    $limit,
2054
                    $getCount,
2055
                    $courseInfo['real_id']
2056
                );
2057
                $students = array_flip($students);
2058
            }
2059
        }
2060
2061
        // students subscribed to the course through a session
2062
        if ($with_session) {
2063
            $joinSession = '';
2064
            //Session creation date
2065
            if (!empty($date_from) && !empty($date_to)) {
2066
                $joinSession = "INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION)." s";
2067
            }
2068
2069
            $sql = "SELECT $select
2070
                      FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)." scu
2071
                      $joinSession
2072
                      INNER JOIN $userTable u
2073
                      ON scu.user_id = u.id
2074
                      WHERE u.active <> ".USER_SOFT_DELETED." AND scu.c_id = $courseId AND scu.status = ".SessionEntity::STUDENT;
2075
2076
            if (!empty($date_from) && !empty($date_to)) {
2077
                $date_from = Database::escape_string($date_from);
2078
                $date_to = Database::escape_string($date_to);
2079
                $sql .= " AND s.access_start_date >= '$date_from' AND s.access_end_date <= '$date_to'";
2080
            }
2081
2082
            if (0 != $sessionId) {
2083
                $sql .= " AND scu.session_id = $sessionId";
2084
            }
2085
2086
            if (!$includeInvitedUsers) {
2087
                $sql .= " AND u.status != ".INVITEE;
2088
            }
2089
            $sql .= $limitCondition;
2090
2091
            $rs = Database::query($sql);
2092
2093
            if ($getCount) {
2094
                $row = Database::fetch_array($rs);
2095
2096
                return (int) $row['count'];
2097
            }
2098
2099
            while ($student = Database::fetch_array($rs)) {
2100
                $students[$student['user_id']] = $student;
2101
            }
2102
        }
2103
2104
        return $students;
2105
    }
2106
2107
    /**
2108
     * Return user info array of all teacher-users registered in a course
2109
     * This only returns the users that are registered in this actual course, not linked courses.
2110
     *
2111
     * @param string $course_code
2112
     *
2113
     * @return array with user id
2114
     */
2115
    public static function get_teacher_list_from_course_code($course_code)
2116
    {
2117
        $courseInfo = api_get_course_info($course_code);
2118
        $courseId = $courseInfo['real_id'];
2119
        if (empty($courseId)) {
2120
            return false;
2121
        }
2122
2123
        $sql = "SELECT DISTINCT
2124
                    u.id as user_id,
2125
                    u.lastname,
2126
                    u.firstname,
2127
                    u.email,
2128
                    u.username,
2129
                    u.status
2130
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)." cu
2131
                INNER JOIN ".Database::get_main_table(TABLE_MAIN_USER)." u
2132
                ON (cu.user_id = u.id)
2133
                WHERE
2134
                    cu.c_id = $courseId AND
2135
                    cu.status = 1 ";
2136
        $rs = Database::query($sql);
2137
        $teachers = [];
2138
        while ($teacher = Database::fetch_array($rs)) {
2139
            $teachers[$teacher['user_id']] = $teacher;
2140
        }
2141
2142
        return $teachers;
2143
    }
2144
2145
    /**
2146
     * Return user info array of all teacher-users registered in a course
2147
     * This only returns the users that are registered in this actual course, not linked courses.
2148
     *
2149
     * @param int  $courseId
2150
     * @param bool $loadAvatars
2151
     *
2152
     * @return array with user id
2153
     */
2154
    public static function getTeachersFromCourse($courseId, $loadAvatars = true)
2155
    {
2156
        $courseId = (int) $courseId;
2157
2158
        if (empty($courseId)) {
2159
            return false;
2160
        }
2161
2162
        $sql = "SELECT DISTINCT
2163
                    u.id as user_id,
2164
                    u.lastname,
2165
                    u.firstname,
2166
                    u.email,
2167
                    u.username,
2168
                    u.status
2169
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)." cu
2170
                INNER JOIN ".Database::get_main_table(TABLE_MAIN_USER)." u
2171
                ON (cu.user_id = u.id)
2172
                WHERE
2173
                    cu.c_id = $courseId AND
2174
                    cu.status = 1 ";
2175
        $rs = Database::query($sql);
2176
        $listTeachers = [];
2177
        $teachers = [];
2178
        $url = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&course_id='.$courseId;
2179
        while ($teacher = Database::fetch_array($rs)) {
2180
            $teachers['id'] = $teacher['user_id'];
2181
            $teachers['lastname'] = $teacher['lastname'];
2182
            $teachers['firstname'] = $teacher['firstname'];
2183
            $teachers['email'] = $teacher['email'];
2184
            $teachers['username'] = $teacher['username'];
2185
            $teachers['status'] = $teacher['status'];
2186
            $teachers['fullname'] = api_get_person_name($teacher['firstname'], $teacher['lastname']);
2187
            $teachers['avatar'] = '';
2188
            /*if ($loadAvatars) {
2189
                $userPicture = UserManager::getUserPicture($teacher['user_id'], USER_IMAGE_SIZE_SMALL);
2190
                $teachers['avatar'] = $userPicture;
2191
            }*/
2192
            $teachers['url'] = $url.'&user_id='.$teacher['user_id'];
2193
            $listTeachers[] = $teachers;
2194
        }
2195
2196
        return $listTeachers;
2197
    }
2198
2199
    /**
2200
     * Returns a string list of teachers assigned to the given course.
2201
     *
2202
     * @param string $course_code
2203
     * @param string $separator           between teachers names
2204
     * @param bool   $add_link_to_profile Whether to add a link to the teacher's profile
2205
     * @param bool   $orderList
2206
     *
2207
     * @return string List of teachers teaching the course
2208
     */
2209
    public static function getTeacherListFromCourseCodeToString(
2210
        $course_code,
2211
        $separator = self::USER_SEPARATOR,
2212
        $add_link_to_profile = false,
2213
        $orderList = false
2214
    ) {
2215
        $teacher_list = self::get_teacher_list_from_course_code($course_code);
2216
        $html = '';
2217
        $list = [];
2218
        if (!empty($teacher_list)) {
2219
            foreach ($teacher_list as $teacher) {
2220
                $teacher_name = api_get_person_name(
2221
                    $teacher['firstname'],
2222
                    $teacher['lastname']
2223
                );
2224
                if ($add_link_to_profile) {
2225
                    $url = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$teacher['user_id'];
2226
                    $teacher_name = Display::url(
2227
                        $teacher_name,
2228
                        $url,
2229
                        [
2230
                            'class' => 'ajax',
2231
                            'data-title' => $teacher_name,
2232
                        ]
2233
                    );
2234
                }
2235
                $list[] = $teacher_name;
2236
            }
2237
2238
            if (!empty($list)) {
2239
                if (true === $orderList) {
2240
                    $html .= '<ul class="user-teacher">';
2241
                    foreach ($list as $teacher) {
2242
                        $html .= '<li>';
2243
                        $html .= Display::getMdiIcon(ObjectIcon::TEACHER, 'ch-tool-icon', null, ICON_SIZE_TINY);
2244
                        $html .= ' '.$teacher;
2245
                        $html .= '</li>';
2246
                    }
2247
                    $html .= '</ul>';
2248
                } else {
2249
                    $html .= array_to_string($list, $separator);
2250
                }
2251
            }
2252
        }
2253
2254
        return $html;
2255
    }
2256
2257
    /**
2258
     * This function returns information about coachs from a course in session.
2259
     *
2260
     * @param int $session_id
2261
     * @param int $courseId
2262
     *
2263
     * @return array containing user_id, lastname, firstname, username
2264
     */
2265
    public static function get_coachs_from_course($session_id = 0, $courseId = 0)
2266
    {
2267
        if (!empty($session_id)) {
2268
            $session_id = intval($session_id);
2269
        } else {
2270
            $session_id = api_get_session_id();
2271
        }
2272
2273
        if (!empty($courseId)) {
2274
            $courseId = intval($courseId);
2275
        } else {
2276
            $courseId = api_get_course_int_id();
2277
        }
2278
2279
        $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
2280
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2281
2282
        $sql = "SELECT DISTINCT
2283
                    u.id as user_id,
2284
                    u.lastname,
2285
                    u.firstname,
2286
                    u.username
2287
                FROM $tbl_user u
2288
                INNER JOIN $tbl_session_course_user scu
2289
                ON (u.id = scu.user_id)
2290
                WHERE
2291
                    scu.session_id = $session_id AND
2292
                    scu.c_id = $courseId AND
2293
                    scu.status = ".SessionEntity::COURSE_COACH;
2294
        $rs = Database::query($sql);
2295
2296
        $coaches = [];
2297
        if (Database::num_rows($rs) > 0) {
2298
            while ($row = Database::fetch_array($rs)) {
2299
                $completeName = api_get_person_name($row['firstname'], $row['lastname']);
2300
                $coaches[] = $row + ['full_name' => $completeName];
2301
            }
2302
        }
2303
2304
        return $coaches;
2305
    }
2306
2307
    /**
2308
     * @param int    $session_id
2309
     * @param int    $courseId
2310
     * @param string $separator
2311
     * @param bool   $add_link_to_profile
2312
     * @param bool   $orderList
2313
     *
2314
     * @return string
2315
     */
2316
    public static function get_coachs_from_course_to_string(
2317
        $session_id = 0,
2318
        $courseId = 0,
2319
        $separator = self::USER_SEPARATOR,
2320
        $add_link_to_profile = false,
2321
        $orderList = false
2322
    ) {
2323
        $coachList = self::get_coachs_from_course($session_id, $courseId);
2324
        $course_coachs = [];
2325
        if (!empty($coachList)) {
2326
            foreach ($coachList as $coach_course) {
2327
                $coach_name = $coach_course['full_name'];
2328
                if ($add_link_to_profile) {
2329
                    $url = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$coach_course['user_id'].'&course_id='.$courseId.'&session_id='.$session_id;
2330
                    $coach_name = Display::url(
2331
                        $coach_name,
2332
                        $url,
2333
                        [
2334
                            'class' => 'ajax',
2335
                            'data-title' => $coach_name,
2336
                        ]
2337
                    );
2338
                }
2339
                $course_coachs[] = $coach_name;
2340
            }
2341
        }
2342
2343
        $html = '';
2344
        if (!empty($course_coachs)) {
2345
            if (true === $orderList) {
2346
                $html .= '<ul class="user-coachs">';
2347
                foreach ($course_coachs as $coachs) {
2348
                    $html .= Display::tag(
2349
                        'li',
2350
                        Display::getMdiIcon(
2351
                            ObjectIcon::TEACHER,
2352
                            'ch-tool-icon',
2353
                            null,
2354
                            ICON_SIZE_TINY,
2355
                            get_lang('Coach')
2356
                        ).' '.$coachs
2357
                    );
2358
                }
2359
                $html .= '</ul>';
2360
            } else {
2361
                $html = array_to_string($course_coachs, $separator);
2362
            }
2363
        }
2364
2365
        return $html;
2366
    }
2367
2368
    /**
2369
     * Get the list of groups from the course.
2370
     *
2371
     * @param string $course_code
2372
     * @param int    $session_id     Session ID (optional)
2373
     * @param int    $getEmptyGroups get empty groups (optional)
2374
     *
2375
     * @return CGroup[]
2376
     */
2377
    public static function get_group_list_of_course(
2378
        $course_code,
2379
        $session_id = 0,
2380
        $getEmptyGroups = 0,
2381
        $asArray = false
2382
    ) {
2383
        $course_info = api_get_course_info($course_code);
2384
2385
        if (empty($course_info)) {
2386
            return [];
2387
        }
2388
        $course_id = $course_info['real_id'];
2389
2390
        if (empty($course_id)) {
2391
            return [];
2392
        }
2393
2394
        $repo = Container::getGroupRepository();
2395
2396
        $course = api_get_course_entity($course_info['real_id']);
2397
        $session = api_get_session_entity($session_id);
2398
        $qb = $repo->getResourcesByCourse($course, $session);
2399
        $groups = $qb->getQuery()->getResult();
2400
        $groupList = [];
2401
        /** @var CGroup $group */
2402
        foreach ($groups as $group) {
2403
            if (0 === $getEmptyGroups) {
2404
                if (!$group->hasMembers()) {
2405
                    continue;
2406
                }
2407
            }
2408
            if ($asArray) {
2409
                $groupList[$group->getIid()] = ['id' => $group->getIid(), 'name' => $group->getTitle()];
2410
            } else {
2411
                $groupList[$group->getIid()] = $group;
2412
            }
2413
        }
2414
2415
        /* 0 != $session_id ? $session_condition = ' WHERE g.session_id IN(1,'.intval($session_id).')' : $session_condition = ' WHERE g.session_id = 0';
2416
         if (0 == $in_get_empty_group) {
2417
             // get only groups that are not empty
2418
             $sql = "SELECT DISTINCT g.iid, g.title
2419
                     FROM ".Database::get_course_table(TABLE_GROUP)." AS g
2420
                     INNER JOIN ".Database::get_course_table(TABLE_GROUP_USER)." gu
2421
                     ON (g.iid = gu.group_id)
2422
                     $session_condition
2423
                     ORDER BY g.title";
2424
         } else {
2425
             // get all groups even if they are empty
2426
             $sql = "SELECT g.iid, g.title
2427
                     FROM ".Database::get_course_table(TABLE_GROUP)." AS g
2428
                     $session_condition
2429
                     AND c_id = $course_id";
2430
         }
2431
2432
         $result = Database::query($sql);
2433
         $groupList = [];
2434
         while ($groupData = Database::fetch_array($result)) {
2435
             $groupData['userNb'] = GroupManager::number_of_students($groupData['iid'], $course_id);
2436
             $groupList[$groupData['iid']] = $groupData;
2437
         }*/
2438
2439
        return $groupList;
2440
    }
2441
2442
    /**
2443
     * Delete a course and all its related data.
2444
     *
2445
     * Optionally, also delete ResourceFile entries (and their physical files)
2446
     * that are only used in this course and become unused after deletion.
2447
     *
2448
     * @param string $code                    Course code
2449
     * @param bool   $deleteExclusiveDocuments If true, delete documents that are
2450
     *                                         only used in this course.
2451
     *
2452
     * @return bool
2453
     */
2454
    public static function delete_course($code, bool $deleteExclusiveDocuments = false)
2455
    {
2456
        $table_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
2457
        $table_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
2458
        $table_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2459
2460
        $table_stats_hotpots = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
2461
        $table_stats_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2462
        $table_stats_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2463
        $table_stats_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
2464
        $table_stats_lastaccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
2465
        $table_stats_course_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2466
        $table_stats_online = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
2467
        $table_stats_downloads = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
2468
        $table_stats_links = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
2469
        $table_stats_uploads = Database::get_main_table(TABLE_STATISTIC_TRACK_E_UPLOADS);
2470
2471
        if (empty($code)) {
2472
            return false;
2473
        }
2474
2475
        $courseRepo = Container::getCourseRepository();
2476
        /** @var Course $course */
2477
        $course = $courseRepo->findOneBy(['code' => $code]);
2478
2479
        if (null === $course) {
2480
            return false;
2481
        }
2482
2483
        $courseId = $course->getId();
2484
2485
        /** @var SequenceResourceRepository $repo */
2486
        $repo = Database::getManager()->getRepository(SequenceResource::class);
2487
        $sequenceResource = $repo->findRequirementForResource(
2488
            $courseId,
2489
            SequenceResource::COURSE_TYPE
2490
        );
2491
2492
        if ($sequenceResource) {
2493
            Display::addFlash(
2494
                Display::return_message(
2495
                    get_lang('There is a sequence resource linked to this course. You must delete this link first.'),
2496
                    'error'
2497
                )
2498
            );
2499
2500
            return false;
2501
        }
2502
2503
        // Collect exclusive documents before removing the course, so we still
2504
        // have access to the course links when evaluating exclusivity.
2505
        $exclusiveFiles = [];
2506
        if ($deleteExclusiveDocuments) {
2507
            $exclusiveFiles = self::getExclusiveResourceFilesForCourse($course);
2508
        }
2509
2510
        $count = 0;
2511
        if (api_is_multiple_url_enabled()) {
2512
            $url_id = 1;
2513
            if (-1 != api_get_current_access_url_id()) {
2514
                $url_id = api_get_current_access_url_id();
2515
            }
2516
            UrlManager::delete_url_rel_course($courseId, $url_id);
2517
            $count = UrlManager::getCountUrlRelCourse($courseId);
2518
        }
2519
2520
        if (0 === $count) {
2521
            // Cleaning group categories
2522
            $groupCategories = GroupManager::get_categories($course);
2523
            if (!empty($groupCategories)) {
2524
                foreach ($groupCategories as $category) {
2525
                    GroupManager::delete_category($category['iid'], $course->getCode());
2526
                }
2527
            }
2528
2529
            $course_tables = AddCourse::get_course_tables();
2530
            // Cleaning c_x tables
2531
            if (!empty($courseId)) {
2532
                foreach ($course_tables as $table) {
2533
                    if ('document' === $table) {
2534
                        // Table document will be deleted by Doctrine.
2535
                        continue;
2536
                    }
2537
                    $table = Database::get_course_table($table);
2538
                }
2539
            }
2540
2541
            $resourceLink = $course->getFirstResourceLink();
2542
            $resourceLinkId = $resourceLink?->getId();
2543
2544
            // Unsubscribe all users from the course
2545
            $sql = "DELETE FROM $table_course_user WHERE c_id = $courseId";
2546
            Database::query($sql);
2547
            // Delete the course from the sessions tables
2548
            $sql = "DELETE FROM $table_session_course WHERE c_id = $courseId";
2549
            Database::query($sql);
2550
            $sql = "DELETE FROM $table_session_course_user WHERE c_id = $courseId";
2551
            Database::query($sql);
2552
2553
            // Delete from Course - URL
2554
            // Already deleted because of entities.
2555
            //$sql = "DELETE FROM $table_course_rel_url WHERE c_id = $courseId";
2556
            //Database::query($sql);
2557
2558
            // Delete the course from the stats tables
2559
            $sql = "DELETE FROM $table_stats_hotpots WHERE c_id = $courseId";
2560
            Database::query($sql);
2561
            $sql = "DELETE FROM $table_stats_access WHERE c_id = $courseId";
2562
            Database::query($sql);
2563
            $sql = "DELETE FROM $table_stats_lastaccess WHERE c_id = $courseId";
2564
            Database::query($sql);
2565
            $sql = "DELETE FROM $table_stats_course_access WHERE c_id = $courseId";
2566
            Database::query($sql);
2567
            $sql = "DELETE FROM $table_stats_online WHERE c_id = $courseId";
2568
            Database::query($sql);
2569
            // Do not delete rows from track_e_default as these include course
2570
            // creation and other important things that do not take much space
2571
            // but give information on the course history
2572
            //$sql = "DELETE FROM $table_stats_default WHERE c_id = $courseId";
2573
            //Database::query($sql);
2574
            if ($resourceLinkId) {
2575
                $sql = "DELETE FROM $table_stats_downloads WHERE resource_link_id = $resourceLinkId";
2576
                Database::query($sql);
2577
            }
2578
            $sql = "DELETE FROM $table_stats_links WHERE c_id = $courseId";
2579
            Database::query($sql);
2580
            $sql = "DELETE FROM $table_stats_uploads WHERE c_id = $courseId";
2581
            Database::query($sql);
2582
2583
            // Update ticket
2584
            $table = Database::get_main_table(TABLE_TICKET_TICKET);
2585
            $sql = "UPDATE $table SET course_id = NULL WHERE course_id = $courseId";
2586
            Database::query($sql);
2587
2588
            $repo->deleteSequenceResource(
2589
                $courseId,
2590
                SequenceResource::COURSE_TYPE
2591
            );
2592
2593
            // Class
2594
            $table = Database::get_main_table(TABLE_USERGROUP_REL_COURSE);
2595
            $sql = "DELETE FROM $table
2596
                WHERE course_id = $courseId";
2597
            Database::query($sql);
2598
2599
            // Skills
2600
            $table = Database::get_main_table(TABLE_MAIN_SKILL_REL_USER);
2601
            $argumentation = Database::escape_string(
2602
                sprintf(
2603
                    get_lang('This skill was obtained through course %s which has been removed since then.'),
2604
                    $course->getCode()
2605
                )
2606
            );
2607
            $sql = "UPDATE $table SET course_id = NULL, session_id = NULL, argumentation = '$argumentation'
2608
                WHERE course_id = $courseId";
2609
            Database::query($sql);
2610
2611
            // Should be deleted by doctrine
2612
            //$sql = "DELETE FROM skill_rel_course WHERE c_id = $courseId";
2613
            //Database::query($sql);
2614
2615
            // Deletes all groups, group-users, group-tutors information
2616
            // To prevent FK mix up on some tables
2617
            //GroupManager::deleteAllGroupsFromCourse($courseId);
2618
2619
            $appPlugin = new AppPlugin();
2620
            $appPlugin->performActionsWhenDeletingItem('course', $courseId);
2621
2622
            //$repo = Container::getQuizRepository();
2623
            //$repo->deleteAllByCourse($courseEntity);
2624
2625
            // Purge Xapian index BEFORE deleting the course entity (resource links still exist)
2626
            try {
2627
                /** @var XapianIndexService $xapian */
2628
                $xapian = Container::$container->get(XapianIndexService::class);
2629
                $xapian->purgeCourseIndex($courseId);
2630
            } catch (\Throwable $e) {
2631
                error_log('[Xapian] purgeCourseIndex: failed for courseId='.$courseId.': '.$e->getMessage());
2632
            }
2633
2634
            // Delete the course from the database
2635
            $courseRepo->deleteCourse($course);
2636
2637
            // Delete documents that were exclusively used by this course,
2638
            // if the administrator explicitly requested it.
2639
            if ($deleteExclusiveDocuments && !empty($exclusiveFiles)) {
2640
                self::deleteExclusiveResourceFiles($exclusiveFiles);
2641
            }
2642
2643
            // delete extra course fields
2644
            $extraFieldValues = new ExtraFieldValue('course');
2645
            $extraFieldValues->deleteValuesByItem($courseId);
2646
2647
            // Add event to system log
2648
            Event::addEvent(
2649
                LOG_COURSE_DELETE,
2650
                LOG_COURSE_CODE,
2651
                $code,
2652
                api_get_utc_datetime(),
2653
                api_get_user_id(),
2654
                $courseId
2655
            );
2656
2657
            return true;
2658
        }
2659
2660
        return false;
2661
    }
2662
2663
    /**
2664
     * Return ResourceFile entities that are only used in the given course.
2665
     *
2666
     * A "course-exclusive" file is defined as:
2667
     *  - At least one ResourceLink exists for this course.
2668
     *  - No ResourceLink exists for any other course (or other context)
2669
     *    for the same ResourceNode.
2670
     *
2671
     * These are the files that will become completely unused once the course
2672
     * is deleted, and are candidates for physical deletion.
2673
     *
2674
     * @param Course $course
2675
     *
2676
     * @return ResourceFile[]
2677
     */
2678
    private static function getExclusiveResourceFilesForCourse(Course $course): array
2679
    {
2680
        $em = Database::getManager();
2681
2682
        $dql = '
2683
        SELECT DISTINCT rf
2684
          FROM Chamilo\CoreBundle\Entity\ResourceFile rf
2685
          JOIN rf.resourceNode rn
2686
          JOIN rn.resourceLinks rl
2687
         WHERE rl.course = :course
2688
           AND NOT EXISTS (
2689
                SELECT 1
2690
                  FROM Chamilo\CoreBundle\Entity\ResourceLink rl2
2691
                 WHERE rl2.resourceNode = rn
2692
                   AND rl2.course != :course
2693
           )
2694
    ';
2695
2696
        return $em->createQuery($dql)
2697
            ->setParameter('course', $course)
2698
            ->getResult();
2699
    }
2700
2701
    /**
2702
     * Delete the given ResourceFile entries and their physical files
2703
     * under var/upload/resource.
2704
     *
2705
     * @param ResourceFile[] $files
2706
     */
2707
    private static function deleteExclusiveResourceFiles(array $files): void
2708
    {
2709
        if (empty($files)) {
2710
            return;
2711
        }
2712
2713
        $em = Database::getManager();
2714
        $nodeRepo = Container::getResourceNodeRepository();
2715
2716
        // Base directory for stored resources; we rely on getFilename($file)
2717
        // returning a path relative to this root (usually starting with "/").
2718
        $basePath = api_get_path(SYS_PATH).'var/upload/resource';
2719
2720
        foreach ($files as $file) {
2721
            if (!$file instanceof ResourceFile) {
2722
                continue;
2723
            }
2724
2725
            // Compute physical path before manipulating Doctrine state
2726
            $relativePath = $nodeRepo->getFilename($file);
2727
            $absolutePath = $basePath.$relativePath;
2728
2729
            if (is_file($absolutePath) && is_writable($absolutePath)) {
2730
                @unlink($absolutePath);
2731
            }
2732
2733
            // Ensure ResourceFile is managed before removal
2734
            if (!$em->contains($file)) {
2735
                $file = $em->getReference(ResourceFile::class, $file->getId());
2736
            }
2737
2738
            $resourceNode = $file->getResourceNode();
2739
            if ($resourceNode instanceof ResourceNode) {
2740
                // Ensure ResourceNode is managed before removal
2741
                if (!$em->contains($resourceNode)) {
2742
                    $resourceNode = $em->getReference(ResourceNode::class, $resourceNode->getId());
2743
                }
2744
2745
                $em->remove($resourceNode);
2746
            }
2747
2748
            $em->remove($file);
2749
        }
2750
2751
        $em->flush();
2752
    }
2753
2754
    /**
2755
     * Sort courses for a specific user ??
2756
     *
2757
     * @param int    $user_id     User ID
2758
     * @param string $course_code Course code
2759
     *
2760
     * @return int Minimum course order
2761
     *
2762
     * @todo Review documentation
2763
     */
2764
    public static function userCourseSort($user_id, $course_code)
2765
    {
2766
        $user_id = (int) $user_id;
2767
2768
        if (empty($user_id) || empty($course_code)) {
2769
            return 0;
2770
        }
2771
2772
        $course_code = Database::escape_string($course_code);
2773
        $TABLECOURSE = Database::get_main_table(TABLE_MAIN_COURSE);
2774
        $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
2775
2776
        $course_title = Database::result(
2777
            Database::query(
2778
                "SELECT title FROM $TABLECOURSE WHERE code = '$course_code'"
2779
            ),
2780
            0,
2781
            0
2782
        );
2783
        if (false === $course_title) {
2784
            $course_title = '';
2785
        }
2786
2787
        $sql = "SELECT course.code as code, course.title as title, cu.sort as sort
2788
                FROM $TABLECOURSUSER as cu, $TABLECOURSE as course
2789
                WHERE   course.id = cu.c_id AND user_id = $user_id AND
2790
                        cu.relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
2791
                        user_course_cat = 0
2792
                ORDER BY cu.sort";
2793
        $result = Database::query($sql);
2794
2795
        $course_title_precedent = '';
2796
        $counter = 0;
2797
        $course_found = false;
2798
        $course_sort = 1;
2799
2800
        if (Database::num_rows($result) > 0) {
2801
            while ($courses = Database::fetch_array($result)) {
2802
                if ('' == $course_title_precedent) {
2803
                    $course_title_precedent = $courses['title'];
2804
                }
2805
                if (api_strcasecmp($course_title_precedent, $course_title) < 0) {
2806
                    $course_found = true;
2807
                    if (!empty($courses['sort'])) {
2808
                        $course_sort = $courses['sort'];
2809
                    }
2810
                    if (0 == $counter) {
2811
                        $sql = "UPDATE $TABLECOURSUSER
2812
                                SET sort = sort+1
2813
                                WHERE
2814
                                    user_id= $user_id AND
2815
                                    relation_type <> ".COURSE_RELATION_TYPE_RRHH."
2816
                                    AND user_course_cat = 0
2817
                                    AND sort > $course_sort";
2818
                        $course_sort++;
2819
                    } else {
2820
                        $sql = "UPDATE $TABLECOURSUSER SET sort = sort+1
2821
                                WHERE
2822
                                    user_id= $user_id AND
2823
                                    relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
2824
                                    user_course_cat = 0 AND
2825
                                    sort >= $course_sort";
2826
                    }
2827
                    Database::query($sql);
2828
                    break;
2829
                } else {
2830
                    $course_title_precedent = $courses['title'];
2831
                }
2832
                $counter++;
2833
            }
2834
2835
            // We must register the course in the beginning of the list
2836
            if (!$course_found) {
2837
                $course_sort = Database::result(
2838
                    Database::query(
2839
                        'SELECT min(sort) as min_sort FROM '.$TABLECOURSUSER.' WHERE user_id = "'.$user_id.'" AND user_course_cat="0"'
2840
                    ),
2841
                    0,
2842
                    0
2843
                );
2844
                Database::query("UPDATE $TABLECOURSUSER SET sort = sort+1 WHERE user_id = $user_id AND user_course_cat = 0");
2845
            }
2846
        }
2847
2848
        return (int) $course_sort;
2849
    }
2850
2851
    /**
2852
     * check if course exists.
2853
     *
2854
     * @param string $courseCode
2855
     *
2856
     * @return int if exists, false else
2857
     */
2858
    public static function course_exists($courseCode)
2859
    {
2860
        $courseCode = Database::escape_string($courseCode);
2861
        $sql = "SELECT 1 FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
2862
                WHERE code = '$courseCode'";
2863
2864
        return Database::num_rows(Database::query($sql));
2865
    }
2866
2867
    /**
2868
     * Send an email to tutor after the auth-suscription of a student in your course.
2869
     *
2870
     * @author Carlos Vargas <[email protected]>, Dokeos Latino
2871
     *
2872
     * @param int    $user_id            the id of the user
2873
     * @param string $courseId           the course code
2874
     * @param bool   $send_to_tutor_also
2875
     *
2876
     * @return false|null we return the message that is displayed when the action is successful
2877
     */
2878
    public static function email_to_tutor($user_id, $courseId, $send_to_tutor_also = false)
2879
    {
2880
        $user_id = (int) $user_id;
2881
        $courseId = (int) $courseId;
2882
        $information = api_get_course_info_by_id($courseId);
2883
        $course_code = $information['code'];
2884
        $student = api_get_user_info($user_id);
2885
2886
        $name_course = $information['title'];
2887
        $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."
2888
                WHERE c_id = $courseId";
2889
2890
        // TODO: Ivan: This is a mistake, please, have a look at it. Intention here is diffcult to be guessed.
2891
        //if ($send_to_tutor_also = true)
2892
        // Proposed change:
2893
        if ($send_to_tutor_also) {
2894
            $sql .= ' AND is_tutor = 1';
2895
        } else {
2896
            $sql .= ' AND status = 1';
2897
        }
2898
2899
        $result = Database::query($sql);
2900
        while ($row = Database::fetch_array($result)) {
2901
            $tutor = api_get_user_info($row['user_id']);
2902
            $emailto = $tutor['email'];
2903
            $emailsubject = get_lang('New user in the course').': '.$name_course;
2904
            $emailbody = get_lang('Dear').': '.api_get_person_name($tutor['firstname'], $tutor['lastname'])."\n";
2905
            $emailbody .= get_lang('There is a new user in the course').': '.$name_course."\n";
2906
            $emailbody .= get_lang('Username').': '.$student['username']."\n";
2907
            if (api_is_western_name_order()) {
2908
                $emailbody .= get_lang('First name').': '.$student['firstname']."\n";
2909
                $emailbody .= get_lang('Last name').': '.$student['lastname']."\n";
2910
            } else {
2911
                $emailbody .= get_lang('Last name').': '.$student['lastname']."\n";
2912
                $emailbody .= get_lang('First name').': '.$student['firstname']."\n";
2913
            }
2914
            $emailbody .= get_lang('E-mail').': <a href="mailto:'.$student['email'].'">'.$student['email']."</a>\n\n";
2915
            $recipient_name = api_get_person_name(
2916
                $tutor['firstname'],
2917
                $tutor['lastname'],
2918
                null,
2919
                PERSON_NAME_EMAIL_ADDRESS
2920
            );
2921
            $sender_name = api_get_person_name(
2922
                api_get_setting('administratorName'),
2923
                api_get_setting('administratorSurname'),
2924
                null,
2925
                PERSON_NAME_EMAIL_ADDRESS
2926
            );
2927
            $email_admin = api_get_setting('emailAdministrator');
2928
2929
            api_mail_html(
2930
                $recipient_name,
2931
                $emailto,
2932
                $emailsubject,
2933
                $emailbody,
2934
                $sender_name,
2935
                $email_admin
2936
            );
2937
        }
2938
    }
2939
2940
    /**
2941
     * @return array
2942
     */
2943
    public static function get_special_course_list()
2944
    {
2945
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
2946
        $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
2947
2948
        $courseList = [];
2949
2950
        // Get list of special courses (appear to all)
2951
        $sql = "SELECT id FROM $courseTable WHERE sticky = 1";
2952
        $result = Database::query($sql);
2953
        while ($row = Database::fetch_assoc($result)) {
2954
            $courseList[] = $row['id'];
2955
        }
2956
2957
        if (count($courseList) < 1) {
2958
            return $courseList;
2959
        }
2960
2961
        if (api_get_multiple_access_url()) {
2962
            // We filter the courses by the active URL
2963
            $coursesSelect = '';
2964
            if (1 == count($courseList)) {
2965
                $coursesSelect = $courseList[0];
2966
            } else {
2967
                $coursesSelect = implode(',', $courseList);
2968
            }
2969
            $access_url_id = api_get_current_access_url_id();
2970
            if (-1 != $access_url_id) {
2971
                $sql = "SELECT c_id FROM $tbl_url_course
2972
                    WHERE access_url_id = $access_url_id
2973
                    AND c_id IN ($coursesSelect)";
2974
                $result = Database::query($sql);
2975
                while ($row = Database::fetch_assoc($result)) {
2976
                    $courseList[] = $row['c_id'];
2977
                }
2978
            }
2979
        }
2980
2981
        return $courseList;
2982
    }
2983
2984
    /**
2985
     * Get the course codes that have been restricted in the catalogue, and if byUserId is set
2986
     * then the courses that the user is allowed or not to see in catalogue.
2987
     *
2988
     * @param bool $allowed  Either if the courses have some users that are or are not allowed to see in catalogue
2989
     * @param int  $byUserId if the courses are or are not allowed to see to the user
2990
     *
2991
     * @return array Course codes allowed or not to see in catalogue by some user or the user
2992
     */
2993
    public static function getCatalogCourseList($allowed = true, $byUserId = -1)
2994
    {
2995
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
2996
        $tblCourseRelUserCatalogue = Database::get_main_table(TABLE_MAIN_COURSE_CATALOGUE_USER);
2997
        $visibility = $allowed ? 1 : 0;
2998
2999
        // Restriction by user id
3000
        $currentUserRestriction = '';
3001
3002
        $byUserId = (int) $byUserId;
3003
        if ($byUserId > 0) {
3004
            $currentUserRestriction = " AND tcruc.user_id = $byUserId ";
3005
        }
3006
3007
        //we filter the courses from the URL
3008
        $joinAccessUrl = '';
3009
        $whereAccessUrl = '';
3010
        if (api_get_multiple_access_url()) {
3011
            $accessUrlId = api_get_current_access_url_id();
3012
            if (-1 != $accessUrlId) {
3013
                $tblUrlCourse = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3014
                $joinAccessUrl = "LEFT JOIN $tblUrlCourse url_rel_course
3015
                                  ON url_rel_course.c_id = c.id ";
3016
                $whereAccessUrl = " AND access_url_id = $accessUrlId ";
3017
            }
3018
        }
3019
3020
        // get course list auto-register
3021
        $sql = "SELECT DISTINCT(c.code)
3022
                FROM $tblCourseRelUserCatalogue tcruc
3023
                INNER JOIN $courseTable c
3024
                ON (c.id = tcruc.c_id) $joinAccessUrl
3025
                WHERE tcruc.visible = $visibility $currentUserRestriction $whereAccessUrl";
3026
3027
        $result = Database::query($sql);
3028
        $courseList = [];
3029
3030
        if (Database::num_rows($result) > 0) {
3031
            while ($resultRow = Database::fetch_array($result)) {
3032
                $courseList[] = $resultRow['code'];
3033
            }
3034
        }
3035
3036
        return $courseList;
3037
    }
3038
3039
    /**
3040
     * Get list of courses for a given user.
3041
     *
3042
     * @param int   $user_id
3043
     * @param bool  $include_sessions                   Whether to include courses from session or not
3044
     * @param bool  $adminGetsAllCourses                If the user is platform admin,
3045
     *                                                  whether he gets all the courses or just his. Note: This does
3046
     *                                                  *not* include all sessions
3047
     * @param bool  $loadSpecialCourses
3048
     * @param array $skipCourseList                     List of course ids to skip
3049
     * @param bool  $useUserLanguageFilterIfAvailable
3050
     * @param bool  $showCoursesSessionWithDifferentKey
3051
     *
3052
     * @return array List of codes and db name
3053
     *
3054
     * @author isaac flores paz
3055
     */
3056
    public static function get_courses_list_by_user_id(
3057
        $user_id,
3058
        $include_sessions = false,
3059
        $adminGetsAllCourses = false,
3060
        $loadSpecialCourses = true,
3061
        $skipCourseList = [],
3062
        $useUserLanguageFilterIfAvailable = true,
3063
        $showCoursesSessionWithDifferentKey = false
3064
    ) {
3065
        $user_id = intval($user_id);
3066
        $urlId = api_get_current_access_url_id();
3067
        $course_list = [];
3068
        $codes = [];
3069
3070
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3071
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3072
        $tableCourseUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3073
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
3074
3075
        $languageCondition = '';
3076
        $onlyInUserLanguage = ('true' === api_get_setting('course.my_courses_show_courses_in_user_language_only'));
3077
        if ($useUserLanguageFilterIfAvailable && $onlyInUserLanguage) {
3078
            $userInfo = api_get_user_info(api_get_user_id());
3079
            if (!empty($userInfo['language'])) {
3080
                $languageCondition = " AND course.course_language = '".$userInfo['language']."' ";
3081
            }
3082
        }
3083
3084
        if ($adminGetsAllCourses && UserManager::is_admin($user_id)) {
3085
            // get the whole courses list
3086
            $sql = "SELECT DISTINCT(course.code), course.id as real_id, course.title
3087
                    FROM $tbl_course course
3088
                    INNER JOIN $tableCourseUrl url
3089
                    ON (course.id = url.c_id)
3090
                    WHERE
3091
                        url.access_url_id = $urlId
3092
                        $languageCondition
3093
                ";
3094
        } else {
3095
            $withSpecialCourses = $withoutSpecialCourses = '';
3096
3097
            if ($loadSpecialCourses) {
3098
                $specialCourseList = self::get_special_course_list();
3099
                if (!empty($specialCourseList)) {
3100
                    $specialCourseToString = '"'.implode('","', $specialCourseList).'"';
3101
                    $withSpecialCourses = ' AND course.id IN ('.$specialCourseToString.')';
3102
                    $withoutSpecialCourses = ' AND course.id NOT IN ('.$specialCourseToString.')';
3103
                }
3104
3105
                if (!empty($withSpecialCourses)) {
3106
                    $sql = "SELECT DISTINCT (course.code),
3107
                            course.id as real_id,
3108
                            course.title
3109
                            FROM $tbl_course_user course_rel_user
3110
                            LEFT JOIN $tbl_course course
3111
                            ON course.id = course_rel_user.c_id
3112
                            INNER JOIN $tableCourseUrl url
3113
                            ON (course.id = url.c_id)
3114
                            WHERE url.access_url_id = $urlId
3115
                            $withSpecialCourses
3116
                            $languageCondition
3117
                            GROUP BY course.code
3118
                            ORDER BY course.title, course_rel_user.sort ASC
3119
                    ";
3120
                    $result = Database::query($sql);
3121
                    if (Database::num_rows($result) > 0) {
3122
                        while ($result_row = Database::fetch_assoc($result)) {
3123
                            $result_row['special_course'] = 1;
3124
                            $course_list[] = $result_row;
3125
                            $codes[] = $result_row['real_id'];
3126
                        }
3127
                    }
3128
                }
3129
            }
3130
3131
            // get course list not auto-register. Use Distinct to avoid multiple
3132
            // entries when a course is assigned to a HRD (DRH) as watcher
3133
            $sql = "SELECT
3134
                        DISTINCT(course.code),
3135
                        course.id as real_id,
3136
                        course.title
3137
                    FROM $tbl_course course
3138
                    INNER JOIN $tbl_course_user cru
3139
                    ON (course.id = cru.c_id)
3140
                    INNER JOIN $tableCourseUrl url
3141
                    ON (course.id = url.c_id)
3142
                    WHERE
3143
                        url.access_url_id = $urlId AND
3144
                        cru.user_id = $user_id
3145
                        $withoutSpecialCourses
3146
                        $languageCondition
3147
                    ORDER BY course.title
3148
                    ";
3149
        }
3150
        $result = Database::query($sql);
3151
3152
        if (Database::num_rows($result)) {
3153
            while ($row = Database::fetch_assoc($result)) {
3154
                if (!empty($skipCourseList)) {
3155
                    if (in_array($row['real_id'], $skipCourseList)) {
3156
                        continue;
3157
                    }
3158
                }
3159
                $course_list[] = $row;
3160
                $codes[] = $row['real_id'];
3161
            }
3162
        }
3163
3164
        if (true === $include_sessions) {
3165
            $sql = "SELECT DISTINCT (c.code),
3166
                        c.id as real_id,
3167
                        s.id as session_id,
3168
                        s.name as session_name
3169
                    FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER)." scu
3170
                    INNER JOIN $tbl_course c
3171
                    ON (scu.c_id = c.id)
3172
                    INNER JOIN ".Database::get_main_table(TABLE_MAIN_SESSION)." s
3173
                    ON (s.id = scu.session_id)
3174
                    WHERE user_id = $user_id ";
3175
            $r = Database::query($sql);
3176
            while ($row = Database::fetch_assoc($r)) {
3177
                if (!empty($skipCourseList)) {
3178
                    if (in_array($row['real_id'], $skipCourseList)) {
3179
                        continue;
3180
                    }
3181
                }
3182
3183
                if ($showCoursesSessionWithDifferentKey) {
3184
                    $course_list[] = $row;
3185
                } else {
3186
                    if (!in_array($row['real_id'], $codes)) {
3187
                        $course_list[] = $row;
3188
                    }
3189
                }
3190
            }
3191
        }
3192
3193
        return $course_list;
3194
    }
3195
3196
    /**
3197
     * Get course ID from a given course directory name.
3198
     *
3199
     * @param string $path Course directory (without any slash)
3200
     *
3201
     * @return string Course code, or false if not found
3202
     */
3203
    public static function getCourseCodeFromDirectory($path)
3204
    {
3205
        $path = Database::escape_string(str_replace('.', '', str_replace('/', '', $path)));
3206
        $res = Database::query("SELECT code FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
3207
                WHERE directory LIKE BINARY '$path'");
3208
        if (false === $res) {
3209
            return false;
3210
        }
3211
        if (1 != Database::num_rows($res)) {
3212
            return false;
3213
        }
3214
        $row = Database::fetch_array($res);
3215
3216
        return $row['code'];
3217
    }
3218
3219
    /**
3220
     * Get course code(s) from visual code.
3221
     *
3222
     * @param   string  Visual code
3223
     *
3224
     * @return array List of codes for the given visual code
3225
     */
3226
    public static function get_courses_info_from_visual_code($code)
3227
    {
3228
        $result = [];
3229
        $sql_result = Database::query("SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
3230
                WHERE visual_code = '".Database::escape_string($code)."'");
3231
        while ($virtual_course = Database::fetch_array($sql_result)) {
3232
            $result[] = $virtual_course;
3233
        }
3234
3235
        return $result;
3236
    }
3237
3238
    /**
3239
     * Creates a new extra field for a given course.
3240
     *
3241
     * @param string $variable    Field's internal variable name
3242
     * @param int    $fieldType   Field's type
3243
     * @param string $displayText Field's language var name
3244
     * @param string $default     Optional. The default value
3245
     *
3246
     * @return int New extra field ID
3247
     */
3248
    public static function create_course_extra_field($variable, $fieldType, $displayText, $default = '')
3249
    {
3250
        $extraField = new ExtraField('course');
3251
        $params = [
3252
            'variable' => $variable,
3253
            'value_type' => $fieldType,
3254
            'display_text' => $displayText,
3255
            'default_value' => $default,
3256
        ];
3257
3258
        return $extraField->save($params);
3259
    }
3260
3261
    /**
3262
     * Update course attributes. Will only update attributes with a non-empty value.
3263
     * Note that you NEED to check that your attributes are valid before using this function.
3264
     *
3265
     * @param int Course id
3266
     * @param array Associative array with field names as keys and field values as values
3267
     *
3268
     * @return Doctrine\DBAL\Driver\Statement|null True if update was successful, false otherwise
3269
     */
3270
    public static function update_attributes($id, $attributes)
3271
    {
3272
        $id = (int) $id;
3273
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
3274
        $sql = "UPDATE $table SET ";
3275
        $i = 0;
3276
        foreach ($attributes as $name => $value) {
3277
            if ('' != $value) {
3278
                if ($i > 0) {
3279
                    $sql .= ", ";
3280
                }
3281
                $sql .= " $name = '".Database::escape_string($value)."'";
3282
                $i++;
3283
            }
3284
        }
3285
        $sql .= " WHERE id = $id";
3286
3287
        return Database::query($sql);
3288
    }
3289
3290
    /**
3291
     * Update an extra field value for a given course.
3292
     *
3293
     * @param string $course_code Course code
3294
     * @param string $variable    Field variable name
3295
     * @param string $value       Optional. Default field value
3296
     *
3297
     * @return bool|int An integer when register a new extra field. And boolean when update the extrafield
3298
     */
3299
    public static function update_course_extra_field_value($course_code, $variable, $value = '')
3300
    {
3301
        $courseInfo = api_get_course_info($course_code);
3302
        $courseId = $courseInfo['real_id'];
3303
3304
        $extraFieldValues = new ExtraFieldValue('course');
3305
        $params = [
3306
            'item_id' => $courseId,
3307
            'variable' => $variable,
3308
            'value' => $value,
3309
        ];
3310
3311
        return $extraFieldValues->save($params);
3312
    }
3313
3314
    /**
3315
     * @param int $sessionId
3316
     *
3317
     * @return mixed
3318
     */
3319
    public static function get_session_category_id_by_session_id($sessionId)
3320
    {
3321
        if (empty($sessionId)) {
3322
            return [];
3323
        }
3324
        $sessionId = intval($sessionId);
3325
        $sql = 'SELECT sc.id session_category
3326
                FROM '.Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY).' sc
3327
                INNER JOIN '.Database::get_main_table(TABLE_MAIN_SESSION).' s
3328
                ON sc.id = s.session_category_id
3329
                WHERE s.id = '.$sessionId;
3330
3331
        return Database::result(
3332
            Database::query($sql),
3333
            0,
3334
            'session_category'
3335
        );
3336
    }
3337
3338
    /**
3339
     * Gets the value of a course extra field. Returns null if it was not found.
3340
     *
3341
     * @param string $variable Name of the extra field
3342
     * @param string $code     Course code
3343
     *
3344
     * @return string Value
3345
     */
3346
    public static function get_course_extra_field_value($variable, $code)
3347
    {
3348
        $courseInfo = api_get_course_info($code);
3349
        $courseId = $courseInfo['real_id'];
3350
3351
        $extraFieldValues = new ExtraFieldValue('course');
3352
        $result = $extraFieldValues->get_values_by_handler_and_field_variable($courseId, $variable);
3353
        if (!empty($result['value'])) {
3354
            return $result['value'];
3355
        }
3356
3357
        return null;
3358
    }
3359
3360
    /**
3361
     * Gets extra field value data and formatted values of a course
3362
     * for extra fields listed in configuration.php in my_course_course_extrafields_to_be_presented
3363
     * (array of variables as value of key 'fields').
3364
     *
3365
     * @param $courseId  int The numeric identifier of the course
3366
     *
3367
     * @return array of data and formatted values as returned by ExtraField::getDataAndFormattedValues
3368
     */
3369
    public static function getExtraFieldsToBePresented($courseId)
3370
    {
3371
        $extraFields = [];
3372
        $fields = api_get_configuration_sub_value('my_course_course_extrafields_to_be_presented/fields');
3373
        if (!empty($fields) && is_array($fields)) {
3374
            $extraFieldManager = new ExtraField('course');
3375
            $dataAndFormattedValues = $extraFieldManager->getDataAndFormattedValues($courseId);
3376
            foreach ($fields as $variable) {
3377
                foreach ($dataAndFormattedValues as $value) {
3378
                    if ($value['variable'] === $variable && !empty($value['value'])) {
3379
                        $extraFields[] = $value;
3380
                    }
3381
                }
3382
            }
3383
        }
3384
3385
        return $extraFields;
3386
    }
3387
3388
    /**
3389
     * Lists details of the course description.
3390
     *
3391
     * @param array        The course description
3392
     * @param string    The encoding
3393
     * @param bool        If true is displayed if false is hidden
3394
     *
3395
     * @return string The course description in html
3396
     */
3397
    public static function get_details_course_description_html(
3398
        $descriptions,
3399
        $charset,
3400
        $action_show = true
3401
    ) {
3402
        $data = null;
3403
        if (isset($descriptions) && count($descriptions) > 0) {
3404
            foreach ($descriptions as $description) {
3405
                $data .= '<div class="sectiontitle">';
3406
                if (api_is_allowed_to_edit() && $action_show) {
3407
                    //delete
3408
                    $data .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=delete&description_id='.$description->id.'" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(
3409
                        get_lang('Please confirm your choice'),
3410
                                ENT_QUOTES,
3411
                        $charset
3412
                    )).'\')) return false;">';
3413
                    $data .= Display::getMdiIcon(
3414
                        ActionIcon::DELETE,
3415
                        'ch-tool-icon',
3416
                        'vertical-align:middle;float:right;',
3417
                        ICON_SIZE_SMALL,
3418
                        get_lang('Delete')
3419
                    );
3420
                    $data .= '</a> ';
3421
                    //edit
3422
                    $data .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&description_id='.$description->id.'">';
3423
                    $data .= Display::getMdiIcon(
3424
                        ActionIcon::EDIT,
3425
                        'ch-tool-icon',
3426
                        'vertical-align:middle;float:right; padding-right:4px;',
3427
                        ICON_SIZE_SMALL,
3428
                        get_lang('Edit')
3429
                    );
3430
                    $data .= '</a> ';
3431
                }
3432
                $data .= $description->title;
3433
                $data .= '</div>';
3434
                $data .= '<div class="sectioncomment">';
3435
                $data .= Security::remove_XSS($description->content);
3436
                $data .= '</div>';
3437
            }
3438
        } else {
3439
            $data .= '<em>'.get_lang('There is no course description so far.').'</em>';
3440
        }
3441
3442
        return $data;
3443
    }
3444
3445
    /**
3446
     * Returns the details of a course category.
3447
     *
3448
     * @param string $code Category code
3449
     *
3450
     * @return array Course category
3451
     */
3452
    public static function get_course_category($code)
3453
    {
3454
        $table = Database::get_main_table(TABLE_MAIN_CATEGORY);
3455
        $code = Database::escape_string($code);
3456
        $sql = "SELECT * FROM $table WHERE code = '$code'";
3457
3458
        return Database::fetch_array(Database::query($sql));
3459
    }
3460
3461
    /**
3462
     * Subscribes courses to human resource manager (Dashboard feature).
3463
     *
3464
     * @param int   $hr_manager_id Human Resource Manager id
3465
     * @param array $courses_list  Courses code
3466
     *
3467
     * @return int
3468
     */
3469
    public static function subscribeCoursesToDrhManager($hr_manager_id, $courses_list)
3470
    {
3471
        $tbl_course_rel_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3472
        $tbl_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3473
3474
        $hr_manager_id = intval($hr_manager_id);
3475
        $affected_rows = 0;
3476
3477
        //Deleting assigned courses to hrm_id
3478
        if (api_is_multiple_url_enabled()) {
3479
            $sql = "SELECT s.c_id FROM $tbl_course_rel_user s
3480
                    INNER JOIN $tbl_course_rel_access_url a
3481
                    ON (a.c_id = s.c_id)
3482
                    WHERE
3483
                        user_id = $hr_manager_id AND
3484
                        relation_type = ".COURSE_RELATION_TYPE_RRHH." AND
3485
                        access_url_id = ".api_get_current_access_url_id();
3486
        } else {
3487
            $sql = "SELECT c_id FROM $tbl_course_rel_user
3488
                    WHERE user_id = $hr_manager_id AND relation_type = ".COURSE_RELATION_TYPE_RRHH;
3489
        }
3490
        $result = Database::query($sql);
3491
        if (Database::num_rows($result) > 0) {
3492
            while ($row = Database::fetch_array($result)) {
3493
                $sql = "DELETE FROM $tbl_course_rel_user
3494
                        WHERE
3495
                            c_id = {$row['c_id']} AND
3496
                            user_id = $hr_manager_id AND
3497
                            relation_type = ".COURSE_RELATION_TYPE_RRHH;
3498
                Database::query($sql);
3499
            }
3500
        }
3501
3502
        // inserting new courses list
3503
        if (is_array($courses_list)) {
3504
            foreach ($courses_list as $course_code) {
3505
                $courseInfo = api_get_course_info($course_code);
3506
                $courseId = $courseInfo['real_id'];
3507
                $sql = "INSERT IGNORE INTO $tbl_course_rel_user(c_id, user_id, status, relation_type)
3508
                        VALUES($courseId, $hr_manager_id, ".DRH.", ".COURSE_RELATION_TYPE_RRHH.")";
3509
                $result = Database::query($sql);
3510
                if (Database::affected_rows($result)) {
3511
                    $affected_rows++;
3512
                }
3513
            }
3514
        }
3515
3516
        return $affected_rows;
3517
    }
3518
3519
    /**
3520
     * get courses followed by human resources manager.
3521
     *
3522
     * @param int    $user_id
3523
     * @param int    $status
3524
     * @param int    $from
3525
     * @param int    $limit
3526
     * @param string $column
3527
     * @param string $direction
3528
     * @param bool   $getCount
3529
     *
3530
     * @return array courses
3531
     */
3532
    public static function get_courses_followed_by_drh(
3533
        $user_id,
3534
        $status = DRH,
3535
        $from = null,
3536
        $limit = null,
3537
        $column = null,
3538
        $direction = null,
3539
        $getCount = false
3540
    ) {
3541
        return self::getCoursesFollowedByUser(
3542
            $user_id,
3543
            $status,
3544
            $from,
3545
            $limit,
3546
            $column,
3547
            $direction,
3548
            $getCount
3549
        );
3550
    }
3551
3552
    /**
3553
     * get courses followed by user.
3554
     *
3555
     * @param int    $user_id
3556
     * @param int    $status
3557
     * @param int    $from
3558
     * @param int    $limit
3559
     * @param string $column
3560
     * @param string $direction
3561
     * @param bool   $getCount
3562
     * @param string $keyword
3563
     * @param int    $sessionId
3564
     * @param bool   $showAllAssignedCourses
3565
     *
3566
     * @return array courses
3567
     */
3568
    public static function getCoursesFollowedByUser(
3569
        $user_id,
3570
        $status = null,
3571
        $from = null,
3572
        $limit = null,
3573
        $column = null,
3574
        $direction = null,
3575
        $getCount = false,
3576
        $keyword = null,
3577
        $sessionId = 0,
3578
        $showAllAssignedCourses = false
3579
    ) {
3580
        // Database Table Definitions
3581
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3582
        $tbl_course_rel_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3583
        $tbl_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3584
        $sessionId = (int) $sessionId;
3585
        $user_id = (int) $user_id;
3586
        $select = "SELECT DISTINCT c.*, c.id as real_id ";
3587
3588
        if ($getCount) {
3589
            $select = "SELECT COUNT(DISTINCT c.id) as count";
3590
        }
3591
3592
        $whereConditions = '';
3593
        switch ($status) {
3594
            case COURSEMANAGER:
3595
                $whereConditions .= " AND cru.user_id = $user_id";
3596
                if (!$showAllAssignedCourses) {
3597
                    $whereConditions .= " AND cru.status = ".COURSEMANAGER;
3598
                } else {
3599
                    $whereConditions .= " AND relation_type = ".COURSE_RELATION_TYPE_COURSE_MANAGER;
3600
                }
3601
                break;
3602
            case DRH:
3603
                $whereConditions .= " AND
3604
                    cru.user_id = $user_id AND
3605
                    cru.status = ".DRH." AND
3606
                    relation_type = '".COURSE_RELATION_TYPE_RRHH."'
3607
                ";
3608
                break;
3609
        }
3610
3611
        $keywordCondition = null;
3612
        if (!empty($keyword)) {
3613
            $keyword = Database::escape_string($keyword);
3614
            $keywordCondition = " AND (c.code LIKE '%$keyword%' OR c.title LIKE '%$keyword%' ) ";
3615
        }
3616
3617
        $orderBy = null;
3618
        $extraInnerJoin = null;
3619
3620
        if (!empty($sessionId)) {
3621
            if (COURSEMANAGER == $status) {
3622
                // Teacher of course or teacher inside session
3623
                $whereConditions = " AND (cru.status = ".COURSEMANAGER." OR srcru.status = ".SessionEntity::COURSE_COACH.") ";
3624
            }
3625
            $courseList = SessionManager::get_course_list_by_session_id($sessionId);
3626
            if (!empty($courseList)) {
3627
                $courseListToString = implode("','", array_keys($courseList));
3628
                $whereConditions .= " AND c.id IN ('".$courseListToString."')";
3629
            }
3630
            $tableSessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3631
            $tableSessionRelCourseRelUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3632
            $orderBy = ' ORDER BY position';
3633
            $extraInnerJoin = " INNER JOIN $tableSessionRelCourse src
3634
                                ON (c.id = src.c_id AND src.session_id = $sessionId)
3635
                                INNER JOIN $tableSessionRelCourseRelUser srcru
3636
                                ON (src.session_id = srcru.session_id AND srcru.c_id = src.c_id)
3637
                            ";
3638
        }
3639
3640
        $whereConditions .= $keywordCondition;
3641
        $sql = "$select
3642
                FROM $tbl_course c
3643
                INNER JOIN $tbl_course_rel_user cru
3644
                ON (cru.c_id = c.id)
3645
                INNER JOIN $tbl_course_rel_access_url a
3646
                ON (a.c_id = c.id)
3647
                $extraInnerJoin
3648
                WHERE
3649
                    access_url_id = ".api_get_current_access_url_id()."
3650
                    $whereConditions
3651
                $orderBy
3652
                ";
3653
        if (isset($from) && isset($limit)) {
3654
            $from = intval($from);
3655
            $limit = intval($limit);
3656
            $sql .= " LIMIT $from, $limit";
3657
        }
3658
3659
        $result = Database::query($sql);
3660
3661
        if ($getCount) {
3662
            $row = Database::fetch_array($result);
3663
3664
            return $row['count'];
3665
        }
3666
3667
        $courses = [];
3668
        if (Database::num_rows($result) > 0) {
3669
            while ($row = Database::fetch_array($result)) {
3670
                $courses[$row['code']] = $row;
3671
            }
3672
        }
3673
3674
        return $courses;
3675
    }
3676
3677
    /**
3678
     * check if a course is special (autoregister).
3679
     *
3680
     * @param int $courseId
3681
     *
3682
     * @return bool
3683
     */
3684
    public static function isSpecialCourse($courseId)
3685
    {
3686
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
3687
3688
        $sql = "SELECT sticky FROM $courseTable WHERE id = " . intval($courseId);
3689
        $result = Database::query($sql);
3690
        $row = Database::fetch_assoc($result);
3691
3692
        if (!empty($row) &&  1 === (int) $row['sticky']) {
3693
            return true;
3694
        }
3695
3696
        return false;
3697
    }
3698
3699
    /**
3700
     * Display special courses (and only these) as several HTML divs of class userportal-course-item.
3701
     *
3702
     * Special courses are courses that stick on top of the list and are "auto-registerable"
3703
     * in the sense that any user clicking them is registered as a student
3704
     *
3705
     * @param int  $user_id                          User id
3706
     * @param bool $load_dirs                        Whether to show the document quick-loader or not
3707
     * @param bool $useUserLanguageFilterIfAvailable
3708
     *
3709
     * @return array
3710
     */
3711
    public static function returnSpecialCourses(
3712
        $user_id,
3713
        $load_dirs = false,
3714
        $useUserLanguageFilterIfAvailable = true
3715
    ) {
3716
        $user_id = (int) $user_id;
3717
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
3718
        $specialCourseList = self::get_special_course_list();
3719
3720
        if (empty($specialCourseList)) {
3721
            return [];
3722
        }
3723
3724
        // Filter by language
3725
        $languageCondition = '';
3726
        $onlyInUserLanguage = ('true' === api_get_setting('course.my_courses_show_courses_in_user_language_only'));
3727
        if ($useUserLanguageFilterIfAvailable && $onlyInUserLanguage) {
3728
            $userInfo = api_get_user_info(api_get_user_id());
3729
            if (!empty($userInfo['language'])) {
3730
                $languageCondition = " AND course_language = '".$userInfo['language']."' ";
3731
            }
3732
        }
3733
3734
        $sql = "SELECT
3735
                    id,
3736
                    code,
3737
                    subscribe subscr,
3738
                    unsubscribe unsubscr
3739
                FROM $table
3740
                WHERE
3741
                    id IN ('".implode("','", $specialCourseList)."')
3742
                    $languageCondition
3743
                GROUP BY code";
3744
3745
        $rs_special_course = Database::query($sql);
3746
        $number_of_courses = Database::num_rows($rs_special_course);
3747
        $showCustomIcon = api_get_setting('course_images_in_courses_list');
3748
3749
        $courseList = [];
3750
        if ($number_of_courses > 0) {
3751
            while ($course = Database::fetch_array($rs_special_course)) {
3752
                $course_info = api_get_course_info($course['code']);
3753
                $courseId = $course['id'];
3754
                if (Course::HIDDEN == $course_info['visibility']) {
3755
                    continue;
3756
                }
3757
3758
                $params = [];
3759
                $params['c_id'] = $courseId;
3760
                //Param (course_code) needed to get the student info in page "My courses"
3761
                $params['course_code'] = $course['code'];
3762
                $params['code'] = $course['code'];
3763
                // Get notifications.
3764
                $course_info['id_session'] = null;
3765
                $courseUserInfo = self::getUserCourseInfo($user_id, $courseId);
3766
3767
                if (empty($courseUserInfo)) {
3768
                    $course_info['status'] = STUDENT;
3769
                } else {
3770
                    $course_info['status'] = $courseUserInfo['status'];
3771
                }
3772
3773
                $params['edit_actions'] = '';
3774
                $params['document'] = '';
3775
                if (api_is_platform_admin()) {
3776
                    $params['edit_actions'] .= api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course['real_id'];
3777
                    if ($load_dirs) {
3778
                        $params['document'] = '<a id="document_preview_'.$courseId.'_0" class="document_preview btn btn--secondary-outline btn-sm" href="javascript:void(0);">'
3779
                           .Display::getMdiIcon('folder-open-outline').'</a>';
3780
                        $params['document'] .= Display::div('', ['id' => 'document_result_'.$courseId.'_0', 'class' => 'document_preview_container']);
3781
                    }
3782
                } else {
3783
                    if (Course::CLOSED != $course_info['visibility'] && $load_dirs) {
3784
                        $params['document'] = '<a id="document_preview_'.$courseId.'_0" class="document_preview btn btn--secondary-outline btn-sm" href="javascript:void(0);">'
3785
                           .Display::getMdiIcon('folder-open-outline').'</a>';
3786
                        $params['document'] .= Display::div('', ['id' => 'document_result_'.$courseId.'_0', 'class' => 'document_preview_container']);
3787
                    }
3788
                }
3789
3790
                $params['visibility'] = $course_info['visibility'];
3791
                $params['status'] = $course_info['status'];
3792
                $params['category'] = $course_info['categoryName'];
3793
                $params['category_code'] = $course_info['categoryCode'];
3794
                $params['icon'] = Display::getMdiIcon(
3795
                    ObjectIcon::PIN,
3796
                    'ch-tool-icon',
3797
                    null,
3798
                    ICON_SIZE_LARGE
3799
                );
3800
3801
                if ('true' == api_get_setting('display_coursecode_in_courselist')) {
3802
                    $params['code_course'] = '('.$course_info['visual_code'].')';
3803
                }
3804
3805
                $params['title'] = $course_info['title'];
3806
                $params['title_cut'] = $course_info['title'];
3807
                $params['link'] = $course_info['course_public_url'].'?sid=0&autoreg=1';
3808
                if ('true' === api_get_setting('display_teacher_in_courselist')) {
3809
                    $params['teachers'] = self::getTeachersFromCourse(
3810
                        $courseId,
3811
                        true
3812
                    );
3813
                }
3814
3815
                $params['extrafields'] = CourseManager::getExtraFieldsToBePresented($course_info['real_id']);
3816
3817
                if ('true' === $showCustomIcon) {
3818
                    $params['thumbnails'] = $course_info['course_image'];
3819
                    $params['image'] = $course_info['course_image_large'];
3820
                }
3821
3822
                $params['is_special_course'] = true;
3823
                $courseList[] = $params;
3824
            }
3825
        }
3826
3827
        return $courseList;
3828
    }
3829
3830
    /**
3831
     *  Display courses inside a category (without special courses) as HTML dics of
3832
     *  class userportal-course-item.
3833
     *
3834
     * @param int  $user_category_id                 User category id
3835
     * @param bool $load_dirs                        Whether to show the document quick-loader or not
3836
     * @param int  $user_id
3837
     * @param bool $useUserLanguageFilterIfAvailable
3838
     *
3839
     * @return array
3840
     */
3841
    public static function returnCoursesCategories(
3842
        $user_category_id,
3843
        $load_dirs = false,
3844
        $user_id = 0,
3845
        $useUserLanguageFilterIfAvailable = true
3846
    ) {
3847
        $user_id = $user_id ? (int) $user_id : api_get_user_id();
3848
        $user_category_id = (int) $user_category_id;
3849
3850
        // Table definitions
3851
        $TABLECOURS = Database::get_main_table(TABLE_MAIN_COURSE);
3852
        $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3853
        $TABLE_ACCESS_URL_REL_COURSE = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3854
        $current_url_id = api_get_current_access_url_id();
3855
3856
        // Get course list auto-register
3857
        $special_course_list = self::get_special_course_list();
3858
        $without_special_courses = '';
3859
        if (!empty($special_course_list)) {
3860
            $without_special_courses = ' AND course.id NOT IN ("'.implode('","', $special_course_list).'")';
3861
        }
3862
3863
        $userCategoryCondition = " (course_rel_user.user_course_cat = $user_category_id) ";
3864
        if (empty($user_category_id)) {
3865
            $userCategoryCondition = ' (course_rel_user.user_course_cat = 0 OR course_rel_user.user_course_cat IS NULL) ';
3866
        }
3867
3868
        $languageCondition = '';
3869
        $onlyInUserLanguage = ('true' === api_get_setting('course.my_courses_show_courses_in_user_language_only'));
3870
        if ($useUserLanguageFilterIfAvailable && $onlyInUserLanguage) {
3871
            $userInfo = api_get_user_info(api_get_user_id());
3872
            if (!empty($userInfo['language'])) {
3873
                $languageCondition = " AND course.course_language = '".$userInfo['language']."' ";
3874
            }
3875
        }
3876
3877
        $sql = "SELECT DISTINCT
3878
                    course.id,
3879
                    course_rel_user.status status,
3880
                    course.code as course_code,
3881
                    user_course_cat,
3882
                    course_rel_user.sort
3883
                FROM $TABLECOURS course
3884
                INNER JOIN $TABLECOURSUSER course_rel_user
3885
                ON (course.id = course_rel_user.c_id)
3886
                INNER JOIN $TABLE_ACCESS_URL_REL_COURSE url
3887
                ON (url.c_id = course.id)
3888
                WHERE
3889
                    course_rel_user.user_id = $user_id AND
3890
                    $userCategoryCondition
3891
                    $without_special_courses
3892
                    $languageCondition
3893
                ";
3894
        // If multiple URL access mode is enabled, only fetch courses
3895
        // corresponding to the current URL.
3896
        if (api_get_multiple_access_url() && -1 != $current_url_id) {
3897
            $sql .= " AND access_url_id = $current_url_id";
3898
        }
3899
        // Use user's classification for courses (if any).
3900
        $sql .= ' ORDER BY course_rel_user.user_course_cat, course_rel_user.sort ASC';
3901
        $result = Database::query($sql);
3902
3903
        $showCustomIcon = api_get_setting('course_images_in_courses_list');
3904
        // Browse through all courses.
3905
        $courseAdded = [];
3906
        $courseList = [];
3907
3908
        while ($row = Database::fetch_array($result)) {
3909
            $course_info = api_get_course_info_by_id($row['id']);
3910
            if (empty($course_info)) {
3911
                continue;
3912
            }
3913
3914
            if (isset($course_info['visibility']) &&
3915
                Course::HIDDEN == $course_info['visibility']
3916
            ) {
3917
                continue;
3918
            }
3919
3920
            // Skip if already in list
3921
            if (in_array($course_info['real_id'], $courseAdded)) {
3922
                continue;
3923
            }
3924
            $course_info['id_session'] = null;
3925
            $course_info['status'] = $row['status'];
3926
            $iconName = basename($course_info['course_image']);
3927
3928
            $params = [];
3929
            //Param (course_code) needed to get the student process
3930
            $params['course_code'] = $row['course_code'];
3931
            $params['code'] = $row['course_code'];
3932
3933
            if ('true' === $showCustomIcon && 'course.png' != $iconName) {
3934
                $params['thumbnails'] = $course_info['course_image'];
3935
                $params['image'] = $course_info['course_image_large'];
3936
            }
3937
3938
            $thumbnails = null;
3939
            $image = null;
3940
            if ('true' === $showCustomIcon && 'course.png' != $iconName) {
3941
                $thumbnails = $course_info['course_image'];
3942
                $image = $course_info['course_image_large'];
3943
            } else {
3944
                $image = Display::return_icon(
3945
                    'session_default.png',
3946
                    null,
3947
                    null,
3948
                    null,
3949
                    null,
3950
                    true
3951
                );
3952
            }
3953
3954
            $params['course_id'] = $course_info['real_id'];
3955
            $params['edit_actions'] = '';
3956
            $params['document'] = '';
3957
            if (api_is_platform_admin()) {
3958
                $params['edit_actions'] .= api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course_info['real_id'];
3959
                if ($load_dirs) {
3960
                    $params['document'] = '<a id="document_preview_'.$course_info['real_id'].'_0" class="document_preview btn btn--plain btn-sm" href="javascript:void(0);">'
3961
                               .Display::getMdiIcon('folder-open-outline').'</a>';
3962
                    $params['document'] .= Display::div(
3963
                        '',
3964
                        [
3965
                            'id' => 'document_result_'.$course_info['real_id'].'_0',
3966
                            'class' => 'document_preview_container',
3967
                        ]
3968
                    );
3969
                }
3970
            }
3971
            if ($load_dirs) {
3972
                $params['document'] = '<a id="document_preview_'.$course_info['real_id'].'_0" class="document_preview btn btn--plain btn-sm" href="javascript:void(0);">'
3973
                    .Display::getMdiIcon('folder-open-outline').'</a>';
3974
                $params['document'] .= Display::div(
3975
                    '',
3976
                    [
3977
                        'id' => 'document_result_'.$course_info['real_id'].'_0',
3978
                        'class' => 'document_preview_container',
3979
                    ]
3980
                );
3981
            }
3982
3983
            $courseUrl = $course_info['course_public_url'].'?sid=0';
3984
            $teachers = [];
3985
            if ('true' === api_get_setting('display_teacher_in_courselist')) {
3986
                $teachers = self::getTeachersFromCourse(
3987
                    $course_info['real_id'],
3988
                    true
3989
                );
3990
            }
3991
3992
            $params['status'] = $row['status'];
3993
            if ('true' === api_get_setting('display_coursecode_in_courselist')) {
3994
                $params['code_course'] = '('.$course_info['visual_code'].') ';
3995
            }
3996
3997
            $params['current_user_is_teacher'] = false;
3998
            /** @var array $teacher */
3999
            foreach ($teachers as $teacher) {
4000
                if ($teacher['id'] != $user_id) {
4001
                    continue;
4002
                }
4003
                $params['current_user_is_teacher'] = true;
4004
            }
4005
4006
            $params['visibility'] = $course_info['visibility'];
4007
            $params['link'] = $courseUrl;
4008
            $params['thumbnails'] = $thumbnails;
4009
            $params['image'] = $image;
4010
            $params['title'] = $course_info['title'];
4011
            $params['title_cut'] = $params['title'];
4012
            $params['category'] = $course_info['categoryName'];
4013
            $params['category_code'] = $course_info['categoryCode'];
4014
            $params['teachers'] = $teachers;
4015
            $params['extrafields'] = CourseManager::getExtraFieldsToBePresented($course_info['real_id']);
4016
            $params['real_id'] = $course_info['real_id'];
4017
4018
            if ('true' === api_get_setting('course.enable_unsubscribe_button_on_my_course_page')
4019
                && '1' === $course_info['unsubscribe']
4020
            ) {
4021
                $params['unregister_button'] = CoursesAndSessionsCatalog::return_unregister_button(
4022
                    $course_info,
4023
                    Security::get_existing_token(),
4024
                    '',
4025
                    ''
4026
                );
4027
            }
4028
4029
            $courseAdded[] = $course_info['real_id'];
4030
            $courseList[] = $params;
4031
        }
4032
4033
        return $courseList;
4034
    }
4035
4036
    /**
4037
     * Get the course id based on the original id and field name in the extra fields.
4038
     * Returns 0 if course was not found.
4039
     *
4040
     * @param string $value    Original course code
4041
     * @param string $variable Original field name
4042
     *
4043
     * @return array
4044
     */
4045
    public static function getCourseInfoFromOriginalId($value, $variable)
4046
    {
4047
        $extraFieldValue = new ExtraFieldValue('course');
4048
        $result = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
4049
            $variable,
4050
            $value
4051
        );
4052
4053
        if (!empty($result)) {
4054
            return api_get_course_info_by_id($result['item_id']);
4055
        }
4056
4057
        return [];
4058
    }
4059
4060
    /**
4061
     * Display code for one specific course a logged in user is subscribed to.
4062
     * Shows a link to the course, what's new icons...
4063
     *
4064
     * $my_course['d'] - course directory
4065
     * $my_course['i'] - course title
4066
     * $my_course['c'] - visual course code
4067
     * $my_course['k']  - system course code
4068
     *
4069
     * @param   array       Course details
4070
     * @param   int     Session ID
4071
     * @param   string      CSS class to apply to course entry
4072
     * @param   bool     Whether the session is supposedly accessible now
4073
     * (not in the case it has passed and is in invisible/unaccessible mode)
4074
     * @param bool      Whether to show the document quick-loader or not
4075
     *
4076
     * @return string The HTML to be printed for the course entry
4077
     *
4078
     * @version 1.0.3
4079
     *
4080
     * @todo refactor into different functions for database calls | logic | display
4081
     * @todo replace single-character $my_course['d'] indices
4082
     * @todo move code for what's new icons to a separate function to clear things up
4083
     * @todo add a parameter user_id so that it is possible to show the
4084
     * courselist of other users (=generalisation).
4085
     * This will prevent having to write a new function for this.
4086
     */
4087
    public static function get_logged_user_course_html(
4088
        $course,
4089
        $session_id = 0,
4090
        $class = 'courses',
4091
        $session_accessible = true,
4092
        $load_dirs = false
4093
    ) {
4094
        $now = date('Y-m-d h:i:s');
4095
        $user_id = api_get_user_id();
4096
        $course_info = api_get_course_info_by_id($course['real_id']);
4097
        $course_visibility = (int) $course_info['visibility'];
4098
        $allowUnsubscribe = ('true' === api_get_setting('course.enable_unsubscribe_button_on_my_course_page'));
4099
4100
        if (Course::HIDDEN === $course_visibility) {
4101
            return '';
4102
        }
4103
4104
        $sessionInfo = [];
4105
        if (!empty($session_id)) {
4106
            $sessionInfo = api_get_session_info($session_id);
4107
        }
4108
        $userInCourseStatus = self::getUserInCourseStatus($user_id, $course_info['real_id']);
4109
        $course_info['status'] = empty($session_id) ? $userInCourseStatus : STUDENT;
4110
        $course_info['id_session'] = $session_id;
4111
4112
        $is_coach = api_is_coach($session_id, $course_info['real_id']);
4113
        $isAdmin = api_is_platform_admin();
4114
4115
        // Display course entry.
4116
        // Show a hyperlink to the course, unless the course is closed and user is not course admin.
4117
        $session_url = '';
4118
        $params = [];
4119
        $params['icon'] = Display::getMdiIcon(
4120
            ObjectIcon::SESSION,
4121
            'ch-tool-icon',
4122
            null,
4123
            ICON_SIZE_LARGE
4124
        );
4125
        $params['real_id'] = $course_info['real_id'];
4126
        $params['visibility'] = $course_info['visibility'];
4127
4128
        $sessionCourseAvailable = false;
4129
        if ($session_accessible) {
4130
            if (Course::CLOSED != $course_visibility ||
4131
                COURSEMANAGER == $userInCourseStatus
4132
            ) {
4133
                if (empty($course_info['id_session'])) {
4134
                    $course_info['id_session'] = 0;
4135
                }
4136
4137
                $sessionCourseStatus = api_get_session_visibility($session_id, $course_info['real_id']);
4138
4139
                if (in_array(
4140
                    $sessionCourseStatus,
4141
                    [SESSION_VISIBLE_READ_ONLY, SESSION_VISIBLE, SESSION_AVAILABLE]
4142
                )) {
4143
                    $sessionCourseAvailable = true;
4144
                }
4145
4146
                if (COURSEMANAGER === $userInCourseStatus || $sessionCourseAvailable) {
4147
                    $session_url = $course_info['course_public_url'].'?sid='.$course_info['id_session'];
4148
                    $session_title = '<a title="'.$course_info['name'].'" href="'.$session_url.'">'.
4149
                        $course_info['name'].'</a>';
4150
                } else {
4151
                    $session_title = $course_info['name'];
4152
                }
4153
            } else {
4154
                $session_title =
4155
                    $course_info['name'].' '.
4156
                    Display::tag('span', get_lang('(the course is currently closed)'), ['class' => 'item_closed']);
4157
            }
4158
        } else {
4159
            $session_title = $course_info['name'];
4160
        }
4161
4162
        $thumbnails = null;
4163
        $image = null;
4164
        $showCustomIcon = api_get_setting('course_images_in_courses_list');
4165
        $iconName = basename($course_info['course_image']);
4166
4167
        if ('true' === $showCustomIcon && 'course.png' != $iconName) {
4168
            $thumbnails = $course_info['course_image'];
4169
            $image = $course_info['course_image_large'];
4170
        } else {
4171
            $image = Display::return_icon(
4172
                'session_default.png',
4173
                null,
4174
                null,
4175
                null,
4176
                null,
4177
                true
4178
            );
4179
        }
4180
        $params['thumbnails'] = $thumbnails;
4181
        $params['image'] = $image;
4182
        $params['html_image'] = '';
4183
        if (!empty($thumbnails)) {
4184
            $params['html_image'] = Display::img($thumbnails, $course_info['name'], ['class' => 'img-responsive']);
4185
        } else {
4186
            $params['html_image'] = Display::getMdiIcon(
4187
                ObjectIcon::SESSION,
4188
                'ch-tool-icon img-responsive',
4189
                null,
4190
                ICON_SIZE_LARGE,
4191
                $course_info['name']
4192
            );
4193
        }
4194
        $params['link'] = $session_url;
4195
        $entityManager = Database::getManager();
4196
        /** @var SequenceResourceRepository $repo */
4197
        $repo = $entityManager->getRepository(SequenceResource::class);
4198
4199
        $sequences = $repo->getRequirements($course_info['real_id'], SequenceResource::COURSE_TYPE);
4200
        $sequenceList = $repo->checkRequirementsForUser(
4201
            $sequences,
4202
            SequenceResource::COURSE_TYPE,
4203
            $user_id,
4204
            $session_id
4205
        );
4206
        $completed = $repo->checkSequenceAreCompleted($sequenceList);
4207
        //var_dump($course_info['real_id'], $completed);
4208
        $params['completed'] = $completed;
4209
        $params['requirements'] = '';
4210
4211
        if ($isAdmin ||
4212
            COURSEMANAGER === $userInCourseStatus ||
4213
            $is_coach ||
4214
            SessionManager::sessionHasSessionAdmin($session_id, $user_id)
4215
        ) {
4216
            $params['completed'] = true;
4217
            $params['requirements'] = '';
4218
        } else {
4219
            if ($sequences && false === $completed) {
4220
                $hasRequirements = false;
4221
                foreach ($sequences as $sequence) {
4222
                    if (!empty($sequence['requirements'])) {
4223
                        $hasRequirements = true;
4224
                        break;
4225
                    }
4226
                }
4227
                if ($hasRequirements) {
4228
                    $params['requirements'] = CoursesAndSessionsCatalog::getRequirements(
4229
                    $course_info['real_id'],
4230
                    SequenceResource::COURSE_TYPE,
4231
                    false,
4232
                        false,
4233
                        $session_id
4234
                );
4235
                }
4236
            }
4237
        }
4238
4239
        $params['title'] = $session_title;
4240
        $params['name'] = $course_info['name'];
4241
        $params['edit_actions'] = '';
4242
        $params['document'] = '';
4243
        $params['category'] = $course_info['categoryName'];
4244
        if (Course::CLOSED != $course_visibility &&
4245
            false === $is_coach && $allowUnsubscribe && '1' === $course_info['unsubscribe']) {
4246
            $params['unregister_button'] =
4247
                CoursesAndSessionsCatalog::return_unregister_button(
4248
                    ['code' => $course_info['code']],
4249
                    Security::get_existing_token(),
4250
                    '',
4251
                    '',
4252
                    $session_id
4253
                );
4254
        }
4255
4256
        if (Course::CLOSED != $course_visibility &&
4257
            Course::HIDDEN != $course_visibility
4258
        ) {
4259
            if ($isAdmin) {
4260
                $params['edit_actions'] .= api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cidReq='.$course_info['code'];
4261
                if ($load_dirs) {
4262
                    $params['document'] .= '<a
4263
                        id="document_preview_'.$course_info['real_id'].'_'.$session_id.'"
4264
                        class="document_preview btn btn--plain btn-sm"
4265
                        href="javascript:void(0);">'.
4266
                        Display::getMdiIcon('folder-open-outline').'</a>';
4267
                    $params['document'] .= Display::div('', [
4268
                        'id' => 'document_result_'.$course_info['real_id'].'_'.$session_id,
4269
                        'class' => 'document_preview_container',
4270
                    ]);
4271
                }
4272
            }
4273
        }
4274
        if ('true' === api_get_setting('display_teacher_in_courselist')) {
4275
            $teacher_list = self::getTeachersFromCourse($course_info['real_id'], true);
4276
            $course_coachs = self::get_coachs_from_course(
4277
                $session_id,
4278
                $course_info['real_id'],
4279
                true
4280
            );
4281
            $params['teachers'] = $teacher_list;
4282
4283
            if ((STUDENT == $course_info['status'] && !empty($session_id)) ||
4284
                ($is_coach && COURSEMANAGER != $course_info['status'])
4285
            ) {
4286
                $params['coaches'] = $course_coachs;
4287
            }
4288
        }
4289
        $special = isset($course['special_course']) ? true : false;
4290
        $params['title'] = $session_title;
4291
        $params['special'] = $special;
4292
        if ('true' === api_get_setting('display_coursecode_in_courselist')) {
4293
            $params['visual_code'] = '('.$course_info['visual_code'].')';
4294
        }
4295
        $params['extra'] = '';
4296
        $html = $params;
4297
4298
        $session_category_id = null;
4299
        $active = false;
4300
        if (!empty($session_id)) {
4301
            $sessionCoachName = implode(
4302
                ' - ',
4303
                SessionManager::getGeneralCoachesNamesForSession($session_id)
4304
            );
4305
4306
            $session_category_id = self::get_session_category_id_by_session_id($course_info['id_session']);
4307
4308
            if (
4309
                '0000-00-00 00:00:00' === $sessionInfo['access_start_date'] ||
4310
                empty($sessionInfo['access_start_date']) ||
4311
                '0000-00-00' === $sessionInfo['access_start_date']
4312
                ) {
4313
                $sessionInfo['dates'] = '';
4314
                if ('true' === api_get_setting('show_session_coach')) {
4315
                    $sessionInfo['coach'] = get_lang('General coach').': '.$sessionCoachName;
4316
                }
4317
                $active = true;
4318
            } else {
4319
                $sessionInfo['dates'] = ' - '.
4320
                    get_lang('From').' '.$sessionInfo['access_start_date'].' '.
4321
                    get_lang('To').' '.$sessionInfo['access_end_date'];
4322
                if ('true' === api_get_setting('show_session_coach')) {
4323
                    $sessionInfo['coach'] = get_lang('General coach').': '.$sessionCoachName;
4324
                }
4325
                $date_start = $sessionInfo['access_start_date'];
4326
                $date_end = $sessionInfo['access_end_date'];
4327
                $active = !$date_end ? ($date_start <= $now) : ($date_start <= $now && $date_end >= $now);
4328
            }
4329
        }
4330
        $user_course_category = '';
4331
        if (isset($course_info['user_course_cat'])) {
4332
            $user_course_category = $course_info['user_course_cat'];
4333
        }
4334
        $output = [
4335
                $user_course_category,
4336
                $html,
4337
                $course_info['id_session'],
4338
                $sessionInfo,
4339
                'active' => $active,
4340
                'session_category_id' => $session_category_id,
4341
            ];
4342
4343
        if (SkillModel::isAllowed($user_id, false)) {
4344
            $em = Database::getManager();
4345
            $objUser = api_get_user_entity($user_id);
4346
            $objCourse = api_get_course_entity($course['real_id']);
4347
            $objSession = api_get_session_entity($session_id);
4348
            $skill = $em->getRepository(\Chamilo\CoreBundle\Entity\Skill::class)->getLastByUser($objUser, $objCourse, $objSession);
4349
4350
            $output['skill'] = null;
4351
            if ($skill) {
4352
                $output['skill']['name'] = $skill->getTitle();
4353
                $output['skill']['icon'] = $skill->getIcon();
4354
            }
4355
        }
4356
4357
        return $output;
4358
    }
4359
4360
    /**
4361
     * @param string $source_course_code
4362
     * @param int    $source_session_id
4363
     * @param string $destination_course_code
4364
     * @param int    $destination_session_id
4365
     * @param array  $params
4366
     *
4367
     * @return bool
4368
     */
4369
    public static function copy_course(
4370
        $source_course_code,
4371
        $source_session_id,
4372
        $destination_course_code,
4373
        $destination_session_id,
4374
        $params = [],
4375
        bool $withBaseContent = true,
4376
        bool $copySessionContent = false
4377
    ) {
4378
        $course_info = api_get_course_info($source_course_code);
4379
4380
        if (!empty($course_info)) {
4381
            $cb = new CourseBuilder('', $course_info);
4382
            $course = $cb->build($source_session_id, $source_course_code, $withBaseContent);
4383
            $restorer = new CourseRestorer($course);
4384
            $restorer->copySessionContent = $copySessionContent;
4385
            $restorer->skip_content = $params;
4386
            $restorer->restore(
4387
                $destination_course_code,
4388
                $destination_session_id,
4389
                true,
4390
                $withBaseContent
4391
            );
4392
4393
            return true;
4394
        }
4395
4396
        return false;
4397
    }
4398
4399
    /**
4400
     * A simpler version of the copy_course, the function creates an empty course with an autogenerated course code.
4401
     *
4402
     * @param string $new_title new course title
4403
     * @param string source course code
4404
     * @param int source session id
4405
     * @param int destination session id
4406
     * @param array $params
4407
     * @param bool  $copySessionContent
4408
     *
4409
     * @return Course|null
4410
     */
4411
    public static function copy_course_simple(
4412
        $new_title,
4413
        $source_course_code,
4414
        $source_session_id = 0,
4415
        $destination_session_id = 0,
4416
        $params = [],
4417
        bool $copySessionContent = false
4418
    ) {
4419
        $source_course_info = api_get_course_info($source_course_code);
4420
        if (!empty($source_course_info)) {
4421
            $new_course_code = self::generate_nice_next_course_code($source_course_code);
4422
            if ($new_course_code) {
4423
                $newCourse = self::create_course(
4424
                    $new_title,
4425
                    $new_course_code,
4426
                    false
4427
                );
4428
                if (null !== $newCourse) {
4429
                    $result = self::copy_course(
4430
                        $source_course_code,
4431
                        $source_session_id,
4432
                        $newCourse->getCode(),
4433
                        $destination_session_id,
4434
                        $params,
4435
                        true,
4436
                        $copySessionContent
4437
                    );
4438
                    if ($result) {
4439
                        return $newCourse;
4440
                    }
4441
                }
4442
            }
4443
        }
4444
4445
        return false;
4446
    }
4447
4448
    /**
4449
     * Creates a new course code based in a given code.
4450
     *
4451
     * @param string    wanted code
4452
     * <code>    $wanted_code = 'curse' if there are in the DB codes like curse1 curse2 the function will return:
4453
     * course3</code> if the course code doest not exist in the DB the same course code will be returned
4454
     *
4455
     * @return string wanted unused code
4456
     */
4457
    public static function generate_nice_next_course_code($wanted_code)
4458
    {
4459
        $course_code_ok = !self::course_code_exists($wanted_code);
4460
        if (!$course_code_ok) {
4461
            $wanted_code = self::generate_course_code($wanted_code);
4462
            $table = Database::get_main_table(TABLE_MAIN_COURSE);
4463
            $wanted_code = Database::escape_string($wanted_code);
4464
            $sql = "SELECT count(id) as count
4465
                    FROM $table
4466
                    WHERE code LIKE '$wanted_code%'";
4467
            $result = Database::query($sql);
4468
            if (Database::num_rows($result) > 0) {
4469
                $row = Database::fetch_array($result);
4470
                $count = $row['count'] + 1;
4471
                $wanted_code = $wanted_code.'_'.$count;
4472
                $result = api_get_course_info($wanted_code);
4473
                if (empty($result)) {
4474
                    return $wanted_code;
4475
                }
4476
            }
4477
4478
            return false;
4479
        }
4480
4481
        return $wanted_code;
4482
    }
4483
4484
    /**
4485
     * Gets the status of the users agreement in a course course-session.
4486
     *
4487
     * @param int    $user_id
4488
     * @param string $course_code
4489
     * @param int    $session_id
4490
     *
4491
     * @return bool
4492
     */
4493
    public static function is_user_accepted_legal($user_id, $course_code, $session_id = 0)
4494
    {
4495
        $user_id = (int) $user_id;
4496
        $session_id = (int) $session_id;
4497
        $course_code = Database::escape_string($course_code);
4498
4499
        $courseInfo = api_get_course_info($course_code);
4500
        $courseId = $courseInfo['real_id'];
4501
4502
        // Course legal
4503
        $enabled = Container::getPluginHelper()->isPluginEnabled('CourseLegal');
4504
4505
        if ('true' == $enabled) {
4506
            require_once api_get_path(SYS_PLUGIN_PATH).'courselegal/config.php';
4507
            $plugin = CourseLegalPlugin::create();
4508
4509
            return $plugin->isUserAcceptedLegal($user_id, $course_code, $session_id);
4510
        }
4511
4512
        if (empty($session_id)) {
4513
            $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4514
            $sql = "SELECT legal_agreement FROM $table
4515
                    WHERE user_id = $user_id AND c_id = $courseId ";
4516
            $result = Database::query($sql);
4517
            if (Database::num_rows($result) > 0) {
4518
                $result = Database::fetch_array($result);
4519
                if (1 == $result['legal_agreement']) {
4520
                    return true;
4521
                }
4522
            }
4523
4524
            return false;
4525
        } else {
4526
            $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4527
            $sql = "SELECT legal_agreement FROM $table
4528
                    WHERE user_id = $user_id AND c_id = $courseId AND session_id = $session_id";
4529
            $result = Database::query($sql);
4530
            if (Database::num_rows($result) > 0) {
4531
                $result = Database::fetch_array($result);
4532
                if (1 == $result['legal_agreement']) {
4533
                    return true;
4534
                }
4535
            }
4536
4537
            return false;
4538
        }
4539
    }
4540
4541
    /**
4542
     * Saves the user-course legal agreement.
4543
     *
4544
     * @param   int user id
4545
     * @param   string course code
4546
     * @param   int session id
4547
     *
4548
     * @return bool
4549
     */
4550
    public static function save_user_legal($user_id, $courseInfo, $session_id = 0)
4551
    {
4552
        if (empty($courseInfo)) {
4553
            return false;
4554
        }
4555
        $course_code = $courseInfo['code'];
4556
4557
        // Course plugin legal
4558
        $enabled = Container::getPluginHelper()->isPluginEnabled('CourseLegal');
4559
        if ('true' == $enabled) {
4560
            require_once api_get_path(SYS_PLUGIN_PATH).'courselegal/config.php';
4561
            $plugin = CourseLegalPlugin::create();
4562
4563
            return $plugin->saveUserLegal($user_id, $course_code, $session_id);
4564
        }
4565
4566
        $user_id = (int) $user_id;
4567
        $session_id = (int) $session_id;
4568
        $courseId = $courseInfo['real_id'];
4569
4570
        if (empty($session_id)) {
4571
            $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4572
            $sql = "UPDATE $table SET legal_agreement = '1'
4573
                    WHERE user_id = $user_id AND c_id  = $courseId ";
4574
            Database::query($sql);
4575
        } else {
4576
            $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4577
            $sql = "UPDATE  $table SET legal_agreement = '1'
4578
                    WHERE user_id = $user_id AND c_id = $courseId AND session_id = $session_id";
4579
            Database::query($sql);
4580
        }
4581
4582
        return true;
4583
    }
4584
4585
    /**
4586
     * @param int $user_id
4587
     * @param int $course_id
4588
     * @param int $session_id
4589
     * @param int $url_id
4590
     *
4591
     * @return bool
4592
     */
4593
    public static function get_user_course_vote($user_id, $course_id, $session_id = 0, $url_id = 0)
4594
    {
4595
        $table_user_course_vote = Database::get_main_table(TABLE_MAIN_USER_REL_COURSE_VOTE);
4596
        $session_id = !isset($session_id) ? api_get_session_id() : intval($session_id);
4597
        $url_id = empty($url_id) ? api_get_current_access_url_id() : intval($url_id);
4598
        $user_id = intval($user_id);
4599
4600
        if (empty($user_id)) {
4601
            return false;
4602
        }
4603
4604
        $params = [
4605
            'user_id' => $user_id,
4606
            'c_id' => $course_id,
4607
            'session_id' => $session_id,
4608
            'url_id' => $url_id,
4609
        ];
4610
4611
        $result = Database::select(
4612
            'vote',
4613
            $table_user_course_vote,
4614
            [
4615
                'where' => [
4616
                    'user_id = ? AND c_id = ? AND session_id = ? AND url_id = ?' => $params,
4617
                ],
4618
            ],
4619
            'first'
4620
        );
4621
        if (!empty($result)) {
4622
            return $result['vote'];
4623
        }
4624
4625
        return false;
4626
    }
4627
4628
    /**
4629
     * Gets the course ranking based on user votes.
4630
     */
4631
    public static function get_course_ranking(
4632
        int $courseId,
4633
        int $sessionId = 0,
4634
        int $urlId = 0
4635
    ): array
4636
    {
4637
        $tableUserCourseVote = Database::get_main_table(TABLE_MAIN_USER_REL_COURSE_VOTE);
4638
4639
        if (empty($courseId)) {
4640
            return [];
4641
        }
4642
4643
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
4644
        $urlId = empty($urlId) ? api_get_current_access_url_id() : $urlId;
4645
4646
        $result = Database::select(
4647
            'COUNT(DISTINCT user_id) AS users, SUM(vote) AS totalScore',
4648
            $tableUserCourseVote,
4649
            ['where' => ['c_id = ?' => $courseId]],
4650
            'first'
4651
        );
4652
4653
        $usersWhoVoted = $result ? (int) $result['users'] : 0;
4654
        $totalScore = $result ? (int) $result['totalScore'] : 0;
4655
4656
        $pointAverageInPercentage = $usersWhoVoted > 0 ? round(($totalScore / $usersWhoVoted) * 100 / 5, 2) : 0;
4657
        $pointAverageInStar = $usersWhoVoted > 0 ? round($totalScore / $usersWhoVoted, 1) : 0;
4658
4659
        $userVote = !api_is_anonymous() && self::get_user_course_vote(api_get_user_id(), $courseId, $sessionId, $urlId);
4660
4661
        return [
4662
            'c_id' => $courseId,
4663
            'users' => $usersWhoVoted,
4664
            'total_score' => $totalScore,
4665
            'point_average' => $pointAverageInPercentage,
4666
            'point_average_star' => $pointAverageInStar,
4667
            'user_vote' => $userVote,
4668
        ];
4669
    }
4670
4671
    /**
4672
     * Updates the course ranking (popularity) based on unique user votes.
4673
     */
4674
    public static function update_course_ranking($courseId = 0): void
4675
    {
4676
        $tableUserCourseVote = Database::get_main_table(TABLE_MAIN_USER_REL_COURSE_VOTE);
4677
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
4678
4679
        $courseId = intval($courseId);
4680
        if (empty($courseId)) {
4681
            return;
4682
        }
4683
4684
        $result = Database::select(
4685
            'COUNT(DISTINCT user_id) AS popularity',
4686
            $tableUserCourseVote,
4687
            ['where' => ['c_id = ?' => $courseId]],
4688
            'first'
4689
        );
4690
4691
        $popularity = $result ? (int) $result['popularity'] : 0;
4692
4693
        Database::update(
4694
            $tableCourse,
4695
            ['popularity' => $popularity],
4696
            ['id = ?' => $courseId]
4697
        );
4698
    }
4699
4700
    /**
4701
     * Add or update user vote for a course and update course ranking.
4702
     */
4703
    public static function add_course_vote(
4704
        int $userId,
4705
        int $vote,
4706
        int $courseId,
4707
        int $sessionId = 0,
4708
        int $urlId = 0
4709
    ): false|string
4710
    {
4711
        $tableUserCourseVote = Database::get_main_table(TABLE_MAIN_USER_REL_COURSE_VOTE);
4712
4713
        if (empty($courseId) || empty($userId) || !in_array($vote, [1, 2, 3, 4, 5])) {
4714
            return false;
4715
        }
4716
4717
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
4718
        $urlId = empty($urlId) ? api_get_current_access_url_id() : $urlId;
4719
4720
        $params = [
4721
            'user_id' => $userId,
4722
            'c_id' => $courseId,
4723
            'session_id' => $sessionId,
4724
            'url_id' => $urlId,
4725
            'vote' => $vote,
4726
        ];
4727
4728
        $actionDone = 'nothing';
4729
4730
        $existingVote = Database::select(
4731
            'id',
4732
            $tableUserCourseVote,
4733
            ['where' => ['user_id = ? AND c_id = ?' => [$userId, $courseId]]],
4734
            'first'
4735
        );
4736
4737
        if (empty($existingVote)) {
4738
            Database::insert($tableUserCourseVote, $params);
4739
            $actionDone = 'added';
4740
        } else {
4741
            Database::update(
4742
                $tableUserCourseVote,
4743
                ['vote' => $vote, 'session_id' => $sessionId, 'url_id' => $urlId],
4744
                ['id = ?' => $existingVote['id']]
4745
            );
4746
            $actionDone = 'updated';
4747
        }
4748
4749
        self::update_course_ranking($courseId);
4750
4751
        return $actionDone;
4752
    }
4753
4754
    /**
4755
     * Remove all votes for a course and update ranking.
4756
     */
4757
    public static function remove_course_ranking(int $courseId): void
4758
    {
4759
        $tableUserCourseVote = Database::get_main_table(TABLE_MAIN_USER_REL_COURSE_VOTE);
4760
4761
        if (empty($courseId)) {
4762
            return;
4763
        }
4764
4765
        Database::delete($tableUserCourseVote, ['c_id = ?' => $courseId]);
4766
4767
        self::update_course_ranking($courseId);
4768
    }
4769
4770
    /**
4771
     * Returns an array with the hottest courses.
4772
     *
4773
     * @param int $days  number of days
4774
     * @param int $limit number of hottest courses
4775
     *
4776
     * @return array
4777
     */
4778
    public static function return_hot_courses($days = 30, $limit = 6)
4779
    {
4780
        if (api_is_invitee()) {
4781
            return [];
4782
        }
4783
4784
        $limit = (int) $limit;
4785
        $userId = api_get_user_id();
4786
4787
        // Getting my courses
4788
        $my_course_list = self::get_courses_list_by_user_id($userId);
4789
4790
        $codeList = [];
4791
        foreach ($my_course_list as $course) {
4792
            $codeList[$course['real_id']] = $course['real_id'];
4793
        }
4794
4795
        if (api_is_drh()) {
4796
            $courses = self::get_courses_followed_by_drh($userId);
4797
            foreach ($courses as $course) {
4798
                $codeList[$course['real_id']] = $course['real_id'];
4799
            }
4800
        }
4801
4802
        $table_course_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
4803
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
4804
        $table_course_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
4805
        $urlId = api_get_current_access_url_id();
4806
        //$table_course_access table uses the now() and interval ...
4807
        $now = api_get_utc_datetime();
4808
        $sql = "SELECT COUNT(course_access_id) course_count, a.c_id, visibility
4809
                FROM $table_course c
4810
                INNER JOIN $table_course_access a
4811
                ON (c.id = a.c_id)
4812
                INNER JOIN $table_course_url u
4813
                ON u.c_id = c.id
4814
                WHERE
4815
                    u.access_url_id = $urlId AND
4816
                    login_course_date <= '$now' AND
4817
                    login_course_date > DATE_SUB('$now', INTERVAL $days DAY) AND
4818
                    visibility <> ".Course::CLOSED." AND
4819
                    visibility <> ".Course::HIDDEN."
4820
                GROUP BY a.c_id
4821
                ORDER BY course_count DESC
4822
                LIMIT $limit
4823
            ";
4824
4825
        $result = Database::query($sql);
4826
        $courses = [];
4827
        if (Database::num_rows($result)) {
4828
            $courses = Database::store_result($result, 'ASSOC');
4829
            $courses = self::processHotCourseItem($courses, $codeList);
4830
        }
4831
4832
        return $courses;
4833
    }
4834
4835
    /**
4836
     * Returns an array with the "hand picked" popular courses.
4837
     * Courses only appear in this list if their extra field 'popular_courses'
4838
     * has been selected in the admin page of the course.
4839
     *
4840
     * @return array
4841
     */
4842
    public static function returnPopularCoursesHandPicked()
4843
    {
4844
        if (api_is_invitee()) {
4845
            return [];
4846
        }
4847
4848
        $userId = api_get_user_id();
4849
4850
        // Getting my courses
4851
        $my_course_list = self::get_courses_list_by_user_id($userId);
4852
4853
        $codeList = [];
4854
        foreach ($my_course_list as $course) {
4855
            $codeList[$course['real_id']] = $course['real_id'];
4856
        }
4857
4858
        if (api_is_drh()) {
4859
            $courses = self::get_courses_followed_by_drh($userId);
4860
            foreach ($courses as $course) {
4861
                $codeList[$course['real_id']] = $course['real_id'];
4862
            }
4863
        }
4864
4865
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4866
        $tbl_course_field = Database::get_main_table(TABLE_EXTRA_FIELD);
4867
        $tbl_course_field_value = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
4868
4869
        //we filter the courses from the URL
4870
        $join_access_url = $where_access_url = '';
4871
        if (api_get_multiple_access_url()) {
4872
            $access_url_id = api_get_current_access_url_id();
4873
            if (-1 != $access_url_id) {
4874
                $tbl_url_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
4875
                $join_access_url = "LEFT JOIN $tbl_url_course url_rel_course
4876
                ON url_rel_course.c_id = tcfv.item_id ";
4877
                $where_access_url = " AND access_url_id = $access_url_id ";
4878
            }
4879
        }
4880
4881
        $extraFieldType = EntityExtraField::COURSE_FIELD_TYPE;
4882
4883
        // get course list auto-register
4884
        $sql = "SELECT DISTINCT(c.id) AS c_id
4885
                FROM $tbl_course_field_value tcfv
4886
                INNER JOIN $tbl_course_field tcf
4887
                ON tcfv.field_id =  tcf.id $join_access_url
4888
                INNER JOIN $courseTable c
4889
                ON (c.id = tcfv.item_id)
4890
                WHERE
4891
                    tcf.item_type = $extraFieldType AND
4892
                    tcf.variable = 'popular_courses' AND
4893
                    tcfv.field_value = 1 AND
4894
                    visibility <> ".Course::CLOSED." AND
4895
                    visibility <> ".Course::HIDDEN." $where_access_url";
4896
4897
        $result = Database::query($sql);
4898
        $courses = [];
4899
        if (Database::num_rows($result)) {
4900
            $courses = Database::store_result($result, 'ASSOC');
4901
            $courses = self::processHotCourseItem($courses, $codeList);
4902
        }
4903
4904
        return $courses;
4905
    }
4906
4907
    /**
4908
     * @param array $courses
4909
     * @param array $codeList
4910
     *
4911
     * @return mixed
4912
     */
4913
    public static function processHotCourseItem($courses, $codeList = [])
4914
    {
4915
        $hotCourses = [];
4916
        $ajax_url = api_get_path(WEB_AJAX_PATH).'course.ajax.php?a=add_course_vote';
4917
        $stok = Security::get_existing_token();
4918
        $user_id = api_get_user_id();
4919
4920
        foreach ($courses as $courseId) {
4921
            $course_info = api_get_course_info_by_id($courseId['c_id']);
4922
            $courseCode = $course_info['code'];
4923
            $categoryCode = !empty($course_info['categoryCode']) ? $course_info['categoryCode'] : "";
4924
            $my_course = $course_info;
4925
            $my_course['go_to_course_button'] = '';
4926
            $my_course['register_button'] = '';
4927
4928
            $access_link = self::get_access_link_by_user(
4929
                $user_id,
4930
                $course_info,
4931
                $codeList
4932
            );
4933
4934
            $userRegisteredInCourse = self::is_user_subscribed_in_course($user_id, $course_info['code']);
4935
            $userRegisteredInCourseAsTeacher = self::isCourseTeacher($user_id, $courseId['c_id']);
4936
            $userRegistered = $userRegisteredInCourse && $userRegisteredInCourseAsTeacher;
4937
            $my_course['is_course_student'] = $userRegisteredInCourse;
4938
            $my_course['is_course_teacher'] = $userRegisteredInCourseAsTeacher;
4939
            $my_course['is_registered'] = $userRegistered;
4940
            $my_course['title_cut'] = cut($course_info['title'], 45);
4941
4942
            // Course visibility
4943
            if ($access_link && in_array('register', $access_link)) {
4944
                $my_course['register_button'] = Display::url(
4945
                    get_lang('Subscribe').' '.
4946
                    Display::getMdiIcon('login'),
4947
                    api_get_path(WEB_COURSE_PATH).$course_info['path'].
4948
                     '/index.php?action=subscribe&sec_token='.$stok,
4949
                    [
4950
                        'class' => 'btn btn--success btn-sm',
4951
                        'title' => get_lang('Subscribe'),
4952
                        'aria-label' => get_lang('Subscribe'),
4953
                    ]
4954
                );
4955
            }
4956
4957
            if ($access_link && in_array('enter', $access_link) ||
4958
                Course::OPEN_WORLD == $course_info['visibility']
4959
            ) {
4960
                $my_course['go_to_course_button'] = Display::url(
4961
                    get_lang('Go to the course').' '.
4962
                    Display::getMdiIcon('share'),
4963
                    api_get_path(WEB_COURSE_PATH).$course_info['path'].'/index.php',
4964
                    [
4965
                        'class' => 'btn btn--plain btn-sm',
4966
                        'title' => get_lang('Go to the course'),
4967
                        'aria-label' => get_lang('Go to the course'),
4968
                    ]
4969
                );
4970
            }
4971
4972
            if ($access_link && in_array('unsubscribe', $access_link)) {
4973
                $my_course['unsubscribe_button'] = Display::url(
4974
                    get_lang('Unsubscribe').' '.
4975
                    Display::getMdiIcon('logout'),
4976
                    api_get_path(WEB_CODE_PATH).'auth/courses.php?action=unsubscribe&unsubscribe='.$courseCode
4977
                    .'&sec_token='.$stok.'&category_code='.$categoryCode,
4978
                    [
4979
                        'class' => 'btn btn--danger btn-sm',
4980
                        'title' => get_lang('Unsubscribe'),
4981
                        'aria-label' => get_lang('Unsubscribe'),
4982
                    ]
4983
                );
4984
            }
4985
4986
            // start buycourse validation
4987
            // display the course price and buy button if the BuyCourses plugin is enabled and this course is configured
4988
            $plugin = BuyCoursesPlugin::create();
4989
            $isThisCourseInSale = $plugin->buyCoursesForGridCatalogValidator(
4990
                $course_info['real_id'],
4991
                BuyCoursesPlugin::PRODUCT_TYPE_COURSE
4992
            );
4993
            if ($isThisCourseInSale) {
4994
                // set the price label
4995
                $my_course['price'] = $isThisCourseInSale['html'];
4996
                // set the Buy button instead register.
4997
                if ($isThisCourseInSale['verificator'] && !empty($my_course['register_button'])) {
4998
                    $my_course['register_button'] = $plugin->returnBuyCourseButton(
4999
                        $course_info['real_id'],
5000
                        BuyCoursesPlugin::PRODUCT_TYPE_COURSE
5001
                    );
5002
                }
5003
            }
5004
            // end buycourse validation
5005
5006
            // Description
5007
            $my_course['description_button'] = self::returnDescriptionButton($course_info);
5008
            $my_course['teachers'] = self::getTeachersFromCourse($course_info['real_id'], true);
5009
            $point_info = self::get_course_ranking($course_info['real_id'], 0);
5010
            $my_course['rating_html'] = '';
5011
            if (('false' === api_get_setting('course.hide_course_rating'))) {
5012
                $my_course['rating_html'] = Display::return_rating_system(
5013
                    'star_'.$course_info['real_id'],
5014
                    $ajax_url.'&course_id='.$course_info['real_id'],
5015
                    $point_info
5016
                );
5017
            }
5018
            $hotCourses[] = $my_course;
5019
        }
5020
5021
        return $hotCourses;
5022
    }
5023
5024
    public function totalSubscribedUsersInCourses($urlId)
5025
    {
5026
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
5027
        $table_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
5028
        $courseUsers = Database::get_main_table(TABLE_MAIN_COURSE_USER);
5029
5030
        $urlId = (int) $urlId;
5031
5032
        $sql = "SELECT count(cu.user_id) count
5033
                FROM $courseUsers cu
5034
                INNER JOIN $table_course_rel_access_url u
5035
                ON cu.c_id = u.c_id
5036
                WHERE
5037
                    relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
5038
                    u.access_url_id = $urlId AND
5039
                    visibility <> ".Course::CLOSED." AND
5040
                    visibility <> ".Course::HIDDEN."
5041
                     ";
5042
5043
        $res = Database::query($sql);
5044
        $row = Database::fetch_array($res);
5045
5046
        return $row['count'];
5047
    }
5048
5049
    /**
5050
     * Get courses count.
5051
     *
5052
     * @param int $access_url_id Access URL ID (optional)
5053
     * @param int $visibility
5054
     *
5055
     * @return int Number of courses
5056
     */
5057
    public static function count_courses($access_url_id = null, $visibility = null)
5058
    {
5059
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
5060
        $table_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
5061
        $sql = "SELECT count(c.id) FROM $table_course c";
5062
        if (!empty($access_url_id) && $access_url_id == intval($access_url_id)) {
5063
            $sql .= ", $table_course_rel_access_url u
5064
                    WHERE c.id = u.c_id AND u.access_url_id = $access_url_id";
5065
            if (!empty($visibility)) {
5066
                $visibility = intval($visibility);
5067
                $sql .= " AND visibility = $visibility ";
5068
            }
5069
        } else {
5070
            if (!empty($visibility)) {
5071
                $visibility = intval($visibility);
5072
                $sql .= " WHERE visibility = $visibility ";
5073
            }
5074
        }
5075
5076
        $res = Database::query($sql);
5077
        $row = Database::fetch_row($res);
5078
5079
        return $row[0];
5080
    }
5081
5082
    /**
5083
     * Get active courses count.
5084
     * Active = all courses except the ones with hidden visibility.
5085
     *
5086
     * @param int $urlId Access URL ID (optional)
5087
     *
5088
     * @return int Number of courses
5089
     */
5090
    public static function countActiveCourses($urlId = null)
5091
    {
5092
        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
5093
        $table_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
5094
        $sql = "SELECT count(c.id) FROM $table_course c";
5095
        if (!empty($urlId)) {
5096
            $urlId = (int) $urlId;
5097
            $sql .= ", $table_course_rel_access_url u
5098
                    WHERE
5099
                        c.id = u.c_id AND
5100
                        u.access_url_id = $urlId AND
5101
                        visibility <> ".Course::HIDDEN;
5102
        } else {
5103
            $sql .= " WHERE visibility <> ".Course::HIDDEN;
5104
        }
5105
        $res = Database::query($sql);
5106
        $row = Database::fetch_row($res);
5107
5108
        return $row[0];
5109
    }
5110
5111
    /**
5112
     * Returns the SQL conditions to filter course only visible by the user in the catalogue.
5113
     *
5114
     * @param string $courseTableAlias Alias of the course table
5115
     * @param bool   $hideClosed       Whether to hide closed and hidden courses
5116
     * @param bool   $checkHidePrivate
5117
     *
5118
     * @return string SQL conditions
5119
     */
5120
    public static function getCourseVisibilitySQLCondition($courseTableAlias, $hideClosed = false, $checkHidePrivate = true)
5121
    {
5122
        $visibilityCondition = '';
5123
        if ($checkHidePrivate) {
5124
            $hidePrivateSetting = api_get_setting('catalog.course_catalog_hide_private');
5125
            if ('true' === $hidePrivateSetting) {
5126
                $visibilityCondition .= " AND $courseTableAlias.visibility <> ".Course::REGISTERED;
5127
            }
5128
        }
5129
        if ($hideClosed) {
5130
            $visibilityCondition .= " AND $courseTableAlias.visibility NOT IN (".Course::CLOSED.','.Course::HIDDEN.')';
5131
        }
5132
5133
        // Check if course have users allowed to see it in the catalogue, then show only if current user is allowed to see it
5134
        $currentUserId = api_get_user_id();
5135
        $restrictedCourses = self::getCatalogCourseList(true);
5136
        $allowedCoursesToCurrentUser = self::getCatalogCourseList(true, $currentUserId);
5137
        if (!empty($restrictedCourses)) {
5138
            $visibilityCondition .= ' AND ('.$courseTableAlias.'.code NOT IN ("'.implode('","', $restrictedCourses).'")';
5139
            $visibilityCondition .= ' OR '.$courseTableAlias.'.code IN ("'.implode('","', $allowedCoursesToCurrentUser).'"))';
5140
        }
5141
5142
        // Check if course have users denied to see it in the catalogue, then show only if current user is not denied to see it
5143
        $restrictedCourses = self::getCatalogCourseList(false);
5144
        $notAllowedCoursesToCurrentUser = self::getCatalogCourseList(false, $currentUserId);
5145
        if (!empty($restrictedCourses)) {
5146
            $visibilityCondition .= ' AND ('.$courseTableAlias.'.code NOT IN ("'.implode('","', $restrictedCourses).'")';
5147
            $visibilityCondition .= ' OR '.$courseTableAlias.'.code NOT IN ("'.implode('","', $notAllowedCoursesToCurrentUser).'"))';
5148
        }
5149
5150
        return $visibilityCondition;
5151
    }
5152
5153
    /**
5154
     * Return a link to go to the course, validating the visibility of the
5155
     * course and the user status.
5156
     *
5157
     * @param int $uid User ID
5158
     * @param array Course details array
5159
     * @param array  List of courses to which the user is subscribed (if not provided, will be generated)
5160
     *
5161
     * @return mixed 'enter' for a link to go to the course or 'register' for a link to subscribe, or false if no access
5162
     */
5163
    public static function get_access_link_by_user($uid, $course, $user_courses = [])
5164
    {
5165
        if (empty($uid) || empty($course)) {
5166
            return false;
5167
        }
5168
5169
        if (empty($user_courses)) {
5170
            // get the array of courses to which the user is subscribed
5171
            $user_courses = self::get_courses_list_by_user_id($uid);
5172
            foreach ($user_courses as $k => $v) {
5173
                $user_courses[$k] = $v['real_id'];
5174
            }
5175
        }
5176
5177
        if (!isset($course['real_id']) && empty($course['real_id'])) {
5178
            $course = api_get_course_info($course['code']);
5179
        }
5180
5181
        if (Course::HIDDEN == $course['visibility']) {
5182
            return [];
5183
        }
5184
5185
        $is_admin = api_is_platform_admin_by_id($uid);
5186
        $options = [];
5187
        // Register button
5188
        if (!api_is_anonymous($uid) &&
5189
            (
5190
            (Course::OPEN_WORLD == $course['visibility'] || Course::OPEN_PLATFORM == $course['visibility'])
5191
                //$course['visibility'] == Course::REGISTERED && $course['subscribe'] == SUBSCRIBE_ALLOWED
5192
            ) &&
5193
            SUBSCRIBE_ALLOWED == $course['subscribe'] &&
5194
            (!in_array($course['real_id'], $user_courses) || empty($user_courses))
5195
        ) {
5196
            $options[] = 'register';
5197
        }
5198
5199
        $isLogin = !api_is_anonymous();
5200
5201
        // Go To Course button (only if admin, if course public or if student already subscribed)
5202
        if ($is_admin ||
5203
            Course::OPEN_WORLD == $course['visibility'] && empty($course['registration_code']) ||
5204
            ($isLogin && Course::OPEN_PLATFORM == $course['visibility'] && empty($course['registration_code'])) ||
5205
            (in_array($course['real_id'], $user_courses) && Course::CLOSED != $course['visibility'])
5206
        ) {
5207
            $options[] = 'enter';
5208
        }
5209
5210
        if ($is_admin ||
5211
            Course::OPEN_WORLD == $course['visibility'] && empty($course['registration_code']) ||
5212
            ($isLogin && Course::OPEN_PLATFORM == $course['visibility'] && empty($course['registration_code'])) ||
5213
            (in_array($course['real_id'], $user_courses) && Course::CLOSED != $course['visibility'])
5214
        ) {
5215
            $options[] = 'enter';
5216
        }
5217
5218
        if (Course::HIDDEN != $course['visibility'] &&
5219
            empty($course['registration_code']) &&
5220
            UNSUBSCRIBE_ALLOWED == $course['unsubscribe'] &&
5221
            $isLogin &&
5222
            in_array($course['real_id'], $user_courses)
5223
        ) {
5224
            $options[] = 'unsubscribe';
5225
        }
5226
5227
        return $options;
5228
    }
5229
5230
    /**
5231
     * @param array          $courseInfo
5232
     * @param array          $teachers
5233
     * @param bool           $deleteTeachersNotInList
5234
     * @param bool           $editTeacherInSessions
5235
     * @param bool           $deleteSessionTeacherNotInList
5236
     * @param array          $teacherBackup
5237
     * @param Monolog\Logger $logger
5238
     *
5239
     * @return false|null
5240
     */
5241
    public static function updateTeachers(
5242
        $courseInfo,
5243
        $teachers,
5244
        $deleteTeachersNotInList = true,
5245
        $editTeacherInSessions = false,
5246
        $deleteSessionTeacherNotInList = false,
5247
        $teacherBackup = [],
5248
        $logger = null
5249
    ) {
5250
        if (!is_array($teachers)) {
5251
            $teachers = [$teachers];
5252
        }
5253
5254
        if (empty($courseInfo) || !isset($courseInfo['real_id'])) {
5255
            return false;
5256
        }
5257
5258
        $teachers = array_filter($teachers);
5259
        $courseId = $courseInfo['real_id'];
5260
        $course_code = $courseInfo['code'];
5261
5262
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
5263
        $alreadyAddedTeachers = self::get_teacher_list_from_course_code($course_code);
5264
5265
        if ($deleteTeachersNotInList) {
5266
            // Delete only teacher relations that doesn't match the selected teachers
5267
            $cond = null;
5268
            if (count($teachers) > 0) {
5269
                foreach ($teachers as $key) {
5270
                    $key = Database::escape_string($key);
5271
                    $cond .= " AND user_id <> '".$key."'";
5272
                }
5273
            }
5274
5275
            // Recover user categories
5276
            $sql = "SELECT * FROM $course_user_table
5277
                    WHERE c_id = $courseId AND status = 1 AND relation_type = 0 ".$cond;
5278
            $result = Database::query($sql);
5279
            if (Database::num_rows($result)) {
5280
                $teachersToDelete = Database::store_result($result, 'ASSOC');
5281
                foreach ($teachersToDelete as $data) {
5282
                    $userId = $data['user_id'];
5283
                    $teacherBackup[$userId][$course_code] = $data;
5284
                }
5285
            }
5286
5287
            $sql = "DELETE FROM $course_user_table
5288
                    WHERE c_id = $courseId AND status = 1 AND relation_type = 0 ".$cond;
5289
5290
            Database::query($sql);
5291
        }
5292
5293
        if (count($teachers) > 0) {
5294
            foreach ($teachers as $userId) {
5295
                $userId = intval($userId);
5296
                // We check if the teacher is already subscribed in this course
5297
                $sql = "SELECT 1 FROM $course_user_table
5298
                        WHERE user_id = $userId AND c_id = $courseId";
5299
                $result = Database::query($sql);
5300
                if (Database::num_rows($result)) {
5301
                    $sql = "UPDATE $course_user_table
5302
                            SET status = 1
5303
                            WHERE c_id = $courseId AND user_id = $userId ";
5304
                } else {
5305
                    $userCourseCategory = '0';
5306
                    if (isset($teacherBackup[$userId]) &&
5307
                        isset($teacherBackup[$userId][$course_code])
5308
                    ) {
5309
                        $courseUserData = $teacherBackup[$userId][$course_code];
5310
                        $userCourseCategory = $courseUserData['user_course_cat'];
5311
                        if ($logger) {
5312
                            $logger->debug("Recovering user_course_cat: $userCourseCategory");
5313
                        }
5314
                    }
5315
5316
                    $sql = "INSERT INTO $course_user_table SET
5317
                            c_id = $courseId,
5318
                            user_id = $userId,
5319
                            status = 1,
5320
                            is_tutor = 0,
5321
                            sort = 0,
5322
                            relation_type = 0,
5323
                            user_course_cat = $userCourseCategory,
5324
                            progress = 0
5325
                    ";
5326
                }
5327
                Database::query($sql);
5328
            }
5329
        }
5330
5331
        if ($editTeacherInSessions) {
5332
            $sessions = SessionManager::get_session_by_course($courseId);
5333
            if (!empty($sessions)) {
5334
                if ($logger) {
5335
                    $logger->debug("Edit teachers in sessions");
5336
                }
5337
                foreach ($sessions as $session) {
5338
                    $sessionId = $session['id'];
5339
                    // Remove old and add new
5340
                    if ($deleteSessionTeacherNotInList) {
5341
                        foreach ($teachers as $userId) {
5342
                            if ($logger) {
5343
                                $logger->debug("Set coach #$userId in session #$sessionId of course #$courseId ");
5344
                            }
5345
                            SessionManager::set_coach_to_course_session(
5346
                                $userId,
5347
                                $sessionId,
5348
                                $courseId
5349
                            );
5350
                        }
5351
5352
                        $teachersToDelete = [];
5353
                        if (!empty($alreadyAddedTeachers)) {
5354
                            $teachersToDelete = array_diff(array_keys($alreadyAddedTeachers), $teachers);
5355
                        }
5356
5357
                        if (!empty($teachersToDelete)) {
5358
                            foreach ($teachersToDelete as $userId) {
5359
                                if ($logger) {
5360
                                    $logger->debug("Delete coach #$userId in session #$sessionId of course #$courseId ");
5361
                                }
5362
                                SessionManager::set_coach_to_course_session(
5363
                                    $userId,
5364
                                    $sessionId,
5365
                                    $courseId,
5366
                                    true
5367
                                );
5368
                            }
5369
                        }
5370
                    } else {
5371
                        // Add new teachers only
5372
                        foreach ($teachers as $userId) {
5373
                            if ($logger) {
5374
                                $logger->debug("Add coach #$userId in session #$sessionId of course #$courseId ");
5375
                            }
5376
                            SessionManager::set_coach_to_course_session(
5377
                                $userId,
5378
                                $sessionId,
5379
                                $courseId
5380
                            );
5381
                        }
5382
                    }
5383
                }
5384
            }
5385
        }
5386
    }
5387
5388
    /**
5389
     * Course available settings variables see c_course_setting table.
5390
     *
5391
     * @return array
5392
     */
5393
    public static function getCourseSettingVariables(AppPlugin $appPlugin = null)
5394
    {
5395
        $pluginCourseSettings = [];
5396
        if ($appPlugin) {
5397
            $pluginCourseSettings = $appPlugin->getAllPluginCourseSettings();
5398
        }
5399
        $courseSettings = [
5400
            // Get allow_learning_path_theme from table
5401
            'allow_learning_path_theme',
5402
            // Get allow_open_chat_window from table
5403
            'allow_open_chat_window',
5404
            'allow_public_certificates',
5405
            // Get allow_user_edit_agenda from table
5406
            'allow_user_edit_agenda',
5407
            // Get allow_user_edit_announcement from table
5408
            'allow_user_edit_announcement',
5409
            // Get allow_user_image_forum from table
5410
            'allow_user_image_forum',
5411
            //Get allow show user list
5412
            'allow_user_view_user_list',
5413
            // Get course_theme from table
5414
            'course_theme',
5415
            //Get allow show user list
5416
            'display_info_advance_inside_homecourse',
5417
            'documents_default_visibility',
5418
            // Get send_mail_setting (work)from table
5419
            'email_alert_manager_on_new_doc',
5420
            // Get send_mail_setting (work)from table
5421
            'email_alert_manager_on_new_quiz',
5422
            // Get send_mail_setting (dropbox) from table
5423
            'email_alert_on_new_doc_dropbox',
5424
            'email_alert_students_on_new_homework',
5425
            // Get send_mail_setting (auth)from table
5426
            'email_alert_to_teacher_on_new_user_in_course',
5427
            'email_alert_student_on_manual_subscription',
5428
            'enable_lp_auto_launch',
5429
            'enable_exercise_auto_launch',
5430
            'enable_document_auto_launch',
5431
            'pdf_export_watermark_text',
5432
            'show_system_folders',
5433
            'exercise_invisible_in_session',
5434
            'enable_forum_auto_launch',
5435
            'show_course_in_user_language',
5436
            'email_to_teachers_on_new_work_feedback',
5437
            'student_delete_own_publication',
5438
            'hide_forum_notifications',
5439
            'quiz_question_limit_per_day',
5440
            'subscribe_users_to_forum_notifications',
5441
            'learning_path_generator',
5442
            'exercise_generator',
5443
        ];
5444
5445
        $courseModels = ExerciseLib::getScoreModels();
5446
        if (!empty($courseModels)) {
5447
            $courseSettings[] = 'score_model_id';
5448
        }
5449
5450
        $allowLPReturnLink = api_get_setting('lp.allow_lp_return_link');
5451
        if ('true' === $allowLPReturnLink) {
5452
            $courseSettings[] = 'lp_return_link';
5453
        }
5454
5455
        if (!empty($pluginCourseSettings)) {
5456
            $courseSettings = array_merge(
5457
                $courseSettings,
5458
                $pluginCourseSettings
5459
            );
5460
        }
5461
5462
        return $courseSettings;
5463
    }
5464
5465
    /**
5466
     * @param string       $variable
5467
     * @param string|array $value
5468
     * @param int          $courseId
5469
     *
5470
     * @return bool
5471
     */
5472
    public static function saveCourseConfigurationSetting($variable, $value, $courseId, AppPlugin $appPlugin = null)
5473
    {
5474
        $settingList = self::getCourseSettingVariables($appPlugin);
5475
5476
        if (!in_array($variable, $settingList)) {
5477
            return false;
5478
        }
5479
5480
        $courseSettingTable = Database::get_course_table(TABLE_COURSE_SETTING);
5481
5482
        if (is_array($value)) {
5483
            $value = implode(',', $value);
5484
        }
5485
5486
        $settingFromDatabase = self::getCourseSetting($variable, $courseId);
5487
5488
        if (!empty($settingFromDatabase)) {
5489
            // Update
5490
            Database::update(
5491
                $courseSettingTable,
5492
                ['value' => $value],
5493
                ['variable = ? AND c_id = ?' => [$variable, $courseId]]
5494
            );
5495
5496
            /*if ($settingFromDatabase['value'] != $value) {
5497
                Event::addEvent(
5498
                    LOG_COURSE_SETTINGS_CHANGED,
5499
                    $variable,
5500
                    $settingFromDatabase['value']." -> $value"
5501
                );
5502
            }*/
5503
        } else {
5504
            // Create
5505
            Database::insert(
5506
                $courseSettingTable,
5507
                [
5508
                    'title' => $variable,
5509
                    'value' => $value,
5510
                    'c_id' => $courseId,
5511
                    'variable' => $variable,
5512
                ]
5513
            );
5514
5515
            /*Event::addEvent(
5516
                LOG_COURSE_SETTINGS_CHANGED,
5517
                $variable,
5518
                $value
5519
            );*/
5520
        }
5521
5522
        return true;
5523
    }
5524
5525
    /**
5526
     * Get course setting.
5527
     *
5528
     * @param string $variable
5529
     * @param int    $courseId
5530
     *
5531
     * @return array
5532
     */
5533
    public static function getCourseSetting($variable, $courseId)
5534
    {
5535
        $courseSetting = Database::get_course_table(TABLE_COURSE_SETTING);
5536
        $courseId = (int) $courseId;
5537
        $variable = Database::escape_string($variable);
5538
        $sql = "SELECT variable, value FROM $courseSetting
5539
                WHERE c_id = $courseId AND variable = '$variable'";
5540
        $result = Database::query($sql);
5541
5542
        return Database::fetch_array($result);
5543
    }
5544
5545
    public static function saveSettingChanges($courseInfo, $params)
5546
    {
5547
        if (empty($courseInfo) || empty($params)) {
5548
            return false;
5549
        }
5550
5551
        $userId = api_get_user_id();
5552
        $now = api_get_utc_datetime();
5553
5554
        foreach ($params as $name => $value) {
5555
            $emptyValue = ' - ';
5556
            if (isset($courseInfo[$name]) && $courseInfo[$name] != $value) {
5557
                if ('' !== $courseInfo[$name]) {
5558
                    $emptyValue = $courseInfo[$name];
5559
                }
5560
5561
                $changedTo = $emptyValue.' -> '.$value;
5562
5563
                Event::addEvent(
5564
                    LOG_COURSE_SETTINGS_CHANGED,
5565
                    $name,
5566
                    $changedTo,
5567
                    $now,
5568
                    $userId,
5569
                    $courseInfo['real_id']
5570
                );
5571
            }
5572
        }
5573
5574
        return true;
5575
    }
5576
5577
    /**
5578
     * Get information from the track_e_course_access table.
5579
     *
5580
     * @param int    $courseId
5581
     * @param int    $sessionId
5582
     * @param string $startDate
5583
     * @param string $endDate
5584
     *
5585
     * @return array
5586
     */
5587
    public static function getCourseAccessPerCourseAndSession(
5588
        $courseId,
5589
        $sessionId,
5590
        $startDate,
5591
        $endDate
5592
    ) {
5593
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5594
        $courseId = (int) $courseId;
5595
        $sessionId = (int) $sessionId;
5596
        $startDate = Database::escape_string($startDate);
5597
        $endDate = Database::escape_string($endDate);
5598
5599
        $sql = "SELECT * FROM $table
5600
                WHERE
5601
                    c_id = $courseId AND
5602
                    session_id = $sessionId AND
5603
                    login_course_date BETWEEN '$startDate' AND '$endDate'
5604
                ";
5605
5606
        $result = Database::query($sql);
5607
5608
        return Database::store_result($result);
5609
    }
5610
5611
    /**
5612
     * Get login information from the track_e_course_access table, for any
5613
     * course in the given session.
5614
     *
5615
     * @param int $sessionId
5616
     * @param int $userId
5617
     *
5618
     * @return array
5619
     */
5620
    public static function getFirstCourseAccessPerSessionAndUser($sessionId, $userId)
5621
    {
5622
        $sessionId = (int) $sessionId;
5623
        $userId = (int) $userId;
5624
5625
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5626
        $sql = "SELECT * FROM $table
5627
                WHERE session_id = $sessionId AND user_id = $userId
5628
                ORDER BY login_course_date ASC
5629
                LIMIT 1";
5630
5631
        $result = Database::query($sql);
5632
        $courseAccess = [];
5633
        if (Database::num_rows($result)) {
5634
            $courseAccess = Database::fetch_assoc($result);
5635
        }
5636
5637
        return $courseAccess;
5638
    }
5639
5640
    /**
5641
     * @param int  $courseId
5642
     * @param int  $sessionId
5643
     * @param bool $getAllSessions
5644
     *
5645
     * @return mixed
5646
     */
5647
    public static function getCountForum(
5648
        $courseId,
5649
        $sessionId = 0,
5650
        $getAllSessions = false
5651
    ) {
5652
        $forum = Database::get_course_table(TABLE_FORUM);
5653
        if ($getAllSessions) {
5654
            $sql = "SELECT count(*) as count
5655
                    FROM $forum f
5656
                    WHERE f.c_id = %s";
5657
        } else {
5658
            $sql = "SELECT count(*) as count
5659
                    FROM $forum f
5660
                    WHERE f.c_id = %s and f.session_id = %s";
5661
        }
5662
5663
        $sql = sprintf($sql, intval($courseId), intval($sessionId));
5664
        $result = Database::query($sql);
5665
        $row = Database::fetch_array($result);
5666
5667
        return $row['count'];
5668
    }
5669
5670
    /**
5671
     * @param int $userId
5672
     * @param int $courseId
5673
     * @param int $sessionId
5674
     *
5675
     * @return mixed
5676
     */
5677
    public static function getCountPostInForumPerUser(
5678
        $userId,
5679
        $courseId,
5680
        $sessionId = 0
5681
    ) {
5682
        $forum = Database::get_course_table(TABLE_FORUM);
5683
        $forum_post = Database::get_course_table(TABLE_FORUM_POST);
5684
5685
        $sql = "SELECT count(distinct post_id) as count
5686
                FROM $forum_post p
5687
                INNER JOIN $forum f
5688
                ON f.forum_id = p.forum_id AND f.c_id = p.c_id
5689
                WHERE p.poster_id = %s and f.session_id = %s and p.c_id = %s";
5690
5691
        $sql = sprintf(
5692
            $sql,
5693
            intval($userId),
5694
            intval($sessionId),
5695
            intval($courseId)
5696
        );
5697
5698
        $result = Database::query($sql);
5699
        $row = Database::fetch_array($result);
5700
5701
        return $row['count'];
5702
    }
5703
5704
    /**
5705
     * @param int $userId
5706
     * @param int $courseId
5707
     * @param int $sessionId
5708
     *
5709
     * @return mixed
5710
     */
5711
    public static function getCountForumPerUser(
5712
        $userId,
5713
        $courseId,
5714
        $sessionId = 0
5715
    ) {
5716
        $forum = Database::get_course_table(TABLE_FORUM);
5717
        $forum_post = Database::get_course_table(TABLE_FORUM_POST);
5718
5719
        $sql = "SELECT count(distinct f.forum_id) as count
5720
                FROM $forum_post p
5721
                INNER JOIN $forum f
5722
                ON f.forum_id = p.forum_id AND f.c_id = p.c_id
5723
                WHERE p.poster_id = %s and f.session_id = %s and p.c_id = %s";
5724
5725
        $sql = sprintf(
5726
            $sql,
5727
            intval($userId),
5728
            intval($sessionId),
5729
            intval($courseId)
5730
        );
5731
5732
        $result = Database::query($sql);
5733
        $row = Database::fetch_array($result);
5734
5735
        return $row['count'];
5736
    }
5737
5738
    /**
5739
     * Returns the course name from a given code.
5740
     *
5741
     * @param string $code
5742
     *
5743
     * @return string
5744
     */
5745
    public static function getCourseNameFromCode($code)
5746
    {
5747
        $tbl_main_categories = Database::get_main_table(TABLE_MAIN_COURSE);
5748
        $code = Database::escape_string($code);
5749
        $sql = "SELECT title
5750
                FROM $tbl_main_categories
5751
                WHERE code = '$code'";
5752
        $result = Database::query($sql);
5753
        if ($col = Database::fetch_array($result)) {
5754
            return $col['title'];
5755
        }
5756
    }
5757
5758
    /**
5759
     * Generates a course code from a course title.
5760
     *
5761
     * @todo Such a function might be useful in other places too. It might be moved in the CourseManager class.
5762
     * @todo the function might be upgraded for avoiding code duplications (currently,
5763
     * it might suggest a code that is already in use)
5764
     *
5765
     * @param string $title A course title
5766
     *
5767
     * @return string A proposed course code
5768
     *                +
5769
     * @assert (null,null) === false
5770
     * @assert ('ABC_DEF', null) === 'ABCDEF'
5771
     * @assert ('ABC09*^[%A', null) === 'ABC09A'
5772
     */
5773
    public static function generate_course_code($title)
5774
    {
5775
        return substr(
5776
            preg_replace('/[^A-Z0-9]/', '', strtoupper(api_replace_dangerous_char($title))),
5777
            0,
5778
            self::MAX_COURSE_LENGTH_CODE
5779
        );
5780
    }
5781
5782
    /**
5783
     * this function gets all the users of the course,
5784
     * including users from linked courses.
5785
     *
5786
     * @param $filterByActive
5787
     *
5788
     * @return array
5789
     */
5790
    public static function getCourseUsers($filterByActive = null)
5791
    {
5792
        // This would return only the users from real courses:
5793
        return self::get_user_list_from_course_code(
5794
            api_get_course_id(),
5795
            api_get_session_id(),
5796
            null,
5797
            null,
5798
            null,
5799
            null,
5800
            false,
5801
            false,
5802
            [],
5803
            [],
5804
            [],
5805
            $filterByActive
5806
        );
5807
    }
5808
5809
    /**
5810
     * this function gets all the groups of the course,
5811
     * not including linked courses.
5812
     *
5813
     * @return CGroup[]
5814
     */
5815
    public static function getCourseGroups()
5816
    {
5817
        $sessionId = api_get_session_id();
5818
        $courseCode = api_get_course_id();
5819
        if (0 != $sessionId) {
5820
            $groupList = self::get_group_list_of_course(
5821
                $courseCode,
5822
                $sessionId,
5823
                1
5824
            );
5825
        } else {
5826
            $groupList = self::get_group_list_of_course(
5827
                $courseCode,
5828
                0,
5829
                1
5830
            );
5831
        }
5832
5833
        return $groupList;
5834
    }
5835
5836
    /**
5837
     * @param FormValidator $form
5838
     * @param array         $alreadySelected
5839
     *
5840
     * @return HTML_QuickForm_element
5841
     */
5842
    public static function addUserGroupMultiSelect(&$form, $alreadySelected, $addShortCut = false)
5843
    {
5844
        $userList = self::getCourseUsers(true);
5845
        $groupList = self::getCourseGroups();
5846
5847
        $array = self::buildSelectOptions(
5848
            $groupList,
5849
            $userList,
5850
            $alreadySelected
5851
        );
5852
5853
        $result = [];
5854
        foreach ($array as $content) {
5855
            $result[$content['value']] = $content['content'];
5856
        }
5857
5858
        $multiple = $form->addMultiSelect(
5859
            'users',
5860
            get_lang('Users'),
5861
            $result,
5862
            ['select_all_checkbox' => true, 'id' => 'users']
5863
        );
5864
5865
        $sessionId = api_get_session_id();
5866
        if ($addShortCut && empty($sessionId)) {
5867
            $addStudents = [];
5868
            foreach ($userList as $user) {
5869
                if (STUDENT == $user['status_rel']) {
5870
                    $addStudents[] = $user['user_id'];
5871
                }
5872
            }
5873
            if (!empty($addStudents)) {
5874
                $form->addHtml(
5875
                    '<script>
5876
                    $(function() {
5877
                        $("#add_students").on("click", function() {
5878
                            var addStudents = '.json_encode($addStudents).';
5879
                            $.each(addStudents, function( index, value ) {
5880
                                var option = $("#users option[value=\'USER:"+value+"\']");
5881
                                if (option.val()) {
5882
                                    $("#users_to").append(new Option(option.text(), option.val()))
5883
                                    option.remove();
5884
                                }
5885
                            });
5886
5887
                            return false;
5888
                        });
5889
                    });
5890
                    </script>'
5891
                );
5892
5893
                $form->addLabel(
5894
                    '',
5895
                    Display::url(get_lang('Add learners'), '#', ['id' => 'add_students', 'class' => 'btn btn--primary'])
5896
                );
5897
            }
5898
        }
5899
5900
        return $multiple;
5901
    }
5902
5903
    /**
5904
     * This function separates the users from the groups
5905
     * users have a value USER:XXX (with XXX the groups id have a value
5906
     *  GROUP:YYY (with YYY the group id).
5907
     *
5908
     * @param array $to Array of strings that define the type and id of each destination
5909
     *
5910
     * @return array Array of groups and users (each an array of IDs)
5911
     */
5912
    public static function separateUsersGroups($to)
5913
    {
5914
        $groupList = [];
5915
        $userList = [];
5916
5917
        foreach ($to as $to_item) {
5918
            if (!empty($to_item)) {
5919
                $parts = explode(':', $to_item);
5920
                $type = isset($parts[0]) ? $parts[0] : '';
5921
                $id = isset($parts[1]) ? $parts[1] : '';
5922
5923
                switch ($type) {
5924
                    case 'GROUP':
5925
                        $groupList[] = (int) $id;
5926
                        break;
5927
                    case 'USER':
5928
                        $userList[] = (int) $id;
5929
                        break;
5930
                }
5931
            }
5932
        }
5933
5934
        $send_to['groups'] = $groupList;
5935
        $send_to['users'] = $userList;
5936
5937
        return $send_to;
5938
    }
5939
5940
    /**
5941
     * Shows the form for sending a message to a specific group or user.
5942
     *
5943
     * @return HTML_QuickForm_element
5944
     */
5945
    public static function addGroupMultiSelect(FormValidator $form, CGroup $group, $to = [])
5946
    {
5947
        $groupUsers = GroupManager::get_subscribed_users($group);
5948
        $array = self::buildSelectOptions([$group], $groupUsers, $to);
5949
5950
        $result = [];
5951
        foreach ($array as $content) {
5952
            $result[$content['value']] = $content['content'];
5953
        }
5954
5955
        return $form->addMultiSelect('users', get_lang('Users'), $result);
5956
    }
5957
5958
    /**
5959
     * this function shows the form for sending a message to a specific group or user.
5960
     *
5961
     * @param CGroup[] $groupList
5962
     * @param array    $userList
5963
     * @param array    $alreadySelected
5964
     *
5965
     * @return array
5966
     */
5967
    public static function buildSelectOptions($groupList = [], $userList = [], $alreadySelected = [])
5968
    {
5969
        if (empty($alreadySelected)) {
5970
            $alreadySelected = [];
5971
        }
5972
5973
        $result = [];
5974
        // adding the groups to the select form
5975
        if ($groupList) {
5976
            foreach ($groupList as $thisGroup) {
5977
                $groupId = $thisGroup->getIid();
5978
                if (is_array($alreadySelected)) {
5979
                    if (!in_array(
5980
                        "GROUP:".$groupId,
5981
                        $alreadySelected
5982
                    )
5983
                    ) {
5984
                        $userCount = $thisGroup->getMembers()->count();
5985
5986
                        // $alreadySelected is the array containing the groups (and users) that are already selected
5987
                        $userLabel = ($userCount > 0) ? get_lang('Users') : get_lang('user');
5988
                        $userDisabled = ($userCount > 0) ? "" : "disabled=disabled";
5989
                        $result[] = [
5990
                            'disabled' => $userDisabled,
5991
                            'value' => "GROUP:".$groupId,
5992
                            // The space before "G" is needed in order to advmultiselect.php js puts groups first
5993
                            'content' => " G: ".$thisGroup->getTitle()." - ".$userCount." ".$userLabel,
5994
                        ];
5995
                    }
5996
                }
5997
            }
5998
        }
5999
6000
        // adding the individual users to the select form
6001
        if ($userList) {
6002
            foreach ($userList as $user) {
6003
                if (is_array($alreadySelected)) {
6004
                    if (!in_array(
6005
                        "USER:".$user['user_id'],
6006
                        $alreadySelected
6007
                    )
6008
                    ) {
6009
                        // $alreadySelected is the array containing the users (and groups) that are already selected
6010
                        $result[] = [
6011
                            'value' => "USER:".$user['user_id'],
6012
                            'content' => api_get_person_name($user['firstname'], $user['lastname']),
6013
                        ];
6014
                    }
6015
                }
6016
            }
6017
        }
6018
6019
        return $result;
6020
    }
6021
6022
    /**
6023
     * @return array a list (array) of all courses
6024
     */
6025
    public static function get_course_list()
6026
    {
6027
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6028
6029
        return Database::store_result(Database::query("SELECT *, id as real_id FROM $table"));
6030
    }
6031
6032
    /**
6033
     * Returns course code from a given gradebook category's id.
6034
     *
6035
     * @param int $category_id Category ID
6036
     *
6037
     * @return ?int Course ID
6038
     * @throws Exception
6039
     */
6040
    public static function get_course_by_category(int $category_id): ?int
6041
    {
6042
        $category_id = (int) $category_id;
6043
        $sql = 'SELECT c_id FROM '.Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY).'
6044
                WHERE id='.$category_id;
6045
        $info = Database::fetch_array(Database::query($sql));
6046
6047
        return $info ? $info['c_id'] : null;
6048
    }
6049
6050
    /**
6051
     * This function gets all the courses that are not in a session.
6052
     *
6053
     * @param date Start date
6054
     * @param date End date
6055
     * @param bool $includeClosed Whether to include closed and hidden courses
6056
     *
6057
     * @return array Not-in-session courses
6058
     */
6059
    public static function getCoursesWithoutSession(
6060
        $startDate = null,
6061
        $endDate = null,
6062
        $includeClosed = false
6063
    ) {
6064
        $dateConditional = ($startDate && $endDate) ?
6065
            " WHERE session_id IN (SELECT id FROM ".Database::get_main_table(TABLE_MAIN_SESSION).
6066
            " WHERE access_start_date = '$startDate' AND access_end_date = '$endDate')" : null;
6067
        $visibility = ($includeClosed ? '' : 'visibility NOT IN (0, 4) AND ');
6068
6069
        $sql = "SELECT id, code, title
6070
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
6071
                WHERE $visibility code NOT IN (
6072
                    SELECT DISTINCT course_code
6073
                    FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE).$dateConditional."
6074
                )
6075
                ORDER BY id";
6076
6077
        $result = Database::query($sql);
6078
        $courses = [];
6079
        while ($row = Database::fetch_array($result)) {
6080
            $courses[] = $row;
6081
        }
6082
6083
        return $courses;
6084
    }
6085
6086
    /**
6087
     * Get list of courses based on users of a group for a group admin.
6088
     *
6089
     * @param int $userId The user id
6090
     *
6091
     * @return array
6092
     */
6093
    public static function getCoursesFollowedByGroupAdmin($userId)
6094
    {
6095
        $coursesList = [];
6096
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
6097
        $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6098
        $userGroup = new UserGroupModel();
6099
        $userIdList = $userGroup->getGroupUsersByUser($userId);
6100
6101
        if (empty($userIdList)) {
6102
            return [];
6103
        }
6104
6105
        $sql = "SELECT DISTINCT(c.id), c.title
6106
                FROM $courseTable c
6107
                INNER JOIN $courseUserTable cru ON c.id = cru.c_id
6108
                WHERE (
6109
                    cru.user_id IN (".implode(', ', $userIdList).")
6110
                    AND cru.relation_type = 0
6111
                )";
6112
6113
        if (api_is_multiple_url_enabled()) {
6114
            $courseAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
6115
            $accessUrlId = api_get_current_access_url_id();
6116
6117
            if (-1 != $accessUrlId) {
6118
                $sql = "SELECT DISTINCT(c.id), c.title
6119
                        FROM $courseTable c
6120
                        INNER JOIN $courseUserTable cru ON c.id = cru.c_id
6121
                        INNER JOIN $courseAccessUrlTable crau ON c.id = crau.c_id
6122
                        WHERE crau.access_url_id = $accessUrlId
6123
                            AND (
6124
                            cru.id_user IN (".implode(', ', $userIdList).") AND
6125
                            cru.relation_type = 0
6126
                        )";
6127
            }
6128
        }
6129
6130
        $result = Database::query($sql);
6131
        while ($row = Database::fetch_assoc($result)) {
6132
            $coursesList[] = $row;
6133
        }
6134
6135
        return $coursesList;
6136
    }
6137
6138
    /**
6139
     * Direct course link see #5299.
6140
     *
6141
     * You can send to your students an URL like this
6142
     * http://chamilodev.beeznest.com/main/auth/inscription.php?c=ABC&e=3
6143
     * Where "c" is the course code and "e" is the exercise Id, after a successful
6144
     * registration the user will be sent to the course or exercise
6145
     *
6146
     * @param array $form_data
6147
     *
6148
     * @return array
6149
     */
6150
    public static function redirectToCourse($form_data)
6151
    {
6152
        $course_code_redirect = Session::read('course_redirect');
6153
        $_user = api_get_user_info();
6154
        $userId = api_get_user_id();
6155
6156
        if (!empty($course_code_redirect)) {
6157
            $course_info = api_get_course_info($course_code_redirect);
6158
            if (!empty($course_info)) {
6159
                if (in_array(
6160
                    $course_info['visibility'],
6161
                    [Course::OPEN_PLATFORM, Course::OPEN_WORLD]
6162
                )
6163
                ) {
6164
                    if (self::is_user_subscribed_in_course($userId, $course_info['code'])) {
6165
                        $form_data['action'] = $course_info['course_public_url'];
6166
                        $form_data['message'] = sprintf(get_lang('You have been registered to course %s'), $course_info['title']);
6167
                        $form_data['button'] = Display::button(
6168
                            'next',
6169
                            get_lang('Go to the course', null, $_user['language']),
6170
                            ['class' => 'btn btn--primary btn-large']
6171
                        );
6172
6173
                        $exercise_redirect = (int) Session::read('exercise_redirect');
6174
                        // Specify the course id as the current context does not
6175
                        // hold a global $_course array
6176
                        $objExercise = new Exercise($course_info['real_id']);
6177
                        $result = $objExercise->read($exercise_redirect);
6178
6179
                        if (!empty($exercise_redirect) && !empty($result)) {
6180
                            $form_data['action'] = api_get_path(WEB_CODE_PATH).
6181
                                'exercise/overview.php?exerciseId='.$exercise_redirect.'&cid='.$course_info['real_id'];
6182
                            $form_data['message'] .= '<br />'.get_lang('Go to the test');
6183
                            $form_data['button'] = Display::button(
6184
                                'next',
6185
                                get_lang('Go', null, $_user['language']),
6186
                                ['class' => 'btn btn--primary btn-large']
6187
                            );
6188
                        }
6189
6190
                        if (!empty($form_data['action'])) {
6191
                            header('Location: '.$form_data['action']);
6192
                            exit;
6193
                        }
6194
                    }
6195
                }
6196
            }
6197
        }
6198
6199
        return $form_data;
6200
    }
6201
6202
    /**
6203
     * Return tab of params to display a course title in the My Courses tab
6204
     * Check visibility, right, and notification icons, and load_dirs option
6205
     * get html course params.
6206
     *
6207
     * @param $courseId
6208
     * @param bool $loadDirs
6209
     *
6210
     * @return array with keys ['right_actions'] ['teachers'] ['notifications']
6211
     */
6212
    public static function getCourseParamsForDisplay($courseId, $loadDirs = false)
6213
    {
6214
        $userId = api_get_user_id();
6215
        $courseId = intval($courseId);
6216
        // Table definitions
6217
        $TABLECOURS = Database::get_main_table(TABLE_MAIN_COURSE);
6218
        $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6219
        $TABLE_ACCESS_URL_REL_COURSE = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
6220
        $current_url_id = api_get_current_access_url_id();
6221
6222
        // Get course list auto-register
6223
        $special_course_list = self::get_special_course_list();
6224
6225
        $without_special_courses = '';
6226
        if (!empty($special_course_list)) {
6227
            $without_special_courses = ' AND course.id NOT IN ("'.implode('","', $special_course_list).'")';
6228
        }
6229
6230
        //AND course_rel_user.relation_type<>".COURSE_RELATION_TYPE_RRHH."
6231
        $sql = "SELECT
6232
                    course.id,
6233
                    course.title,
6234
                    course.code,
6235
                    course.subscribe subscr,
6236
                    course.unsubscribe unsubscr,
6237
                    course_rel_user.status status,
6238
                    course_rel_user.sort sort,
6239
                    course_rel_user.user_course_cat user_course_cat
6240
                FROM
6241
                $TABLECOURS course
6242
                INNER JOIN $TABLECOURSUSER course_rel_user
6243
                ON (course.id = course_rel_user.c_id)
6244
                INNER JOIN $TABLE_ACCESS_URL_REL_COURSE url
6245
                ON (url.c_id = course.id)
6246
                WHERE
6247
                    course.id = $courseId AND
6248
                    course_rel_user.user_id = $userId
6249
                    $without_special_courses
6250
                ";
6251
6252
        // If multiple URL access mode is enabled, only fetch courses
6253
        // corresponding to the current URL.
6254
        if (api_get_multiple_access_url() && -1 != $current_url_id) {
6255
            $sql .= " AND url.c_id = course.id AND access_url_id = $current_url_id";
6256
        }
6257
        // Use user's classification for courses (if any).
6258
        $sql .= " ORDER BY course_rel_user.user_course_cat, course_rel_user.sort ASC";
6259
6260
        $result = Database::query($sql);
6261
6262
        // Browse through all courses. We can only have one course because
6263
        // of the  course.id=".intval($courseId) in sql query
6264
        $course = Database::fetch_array($result);
6265
        $course_info = api_get_course_info_by_id($courseId);
6266
        if (empty($course_info)) {
6267
            return '';
6268
        }
6269
6270
        //$course['id_session'] = null;
6271
        $course_info['id_session'] = null;
6272
        $course_info['status'] = $course['status'];
6273
6274
        // New code displaying the user's status in respect to this course.
6275
        $status_icon = Display::getMdiIcon(
6276
            'account-key',
6277
            'ch-tool-icon',
6278
            null,
6279
            ICON_SIZE_LARGE,
6280
            $course_info['title']
6281
        );
6282
6283
        $params = [];
6284
        $params['right_actions'] = '';
6285
6286
        if (api_is_platform_admin()) {
6287
            if ($loadDirs) {
6288
                $params['right_actions'] .= '<a id="document_preview_'.$course_info['real_id'].'_0" class="document_preview" href="javascript:void(0);">'.Display::getMdiIcon(ObjectIcon::FOLDER, 'ch-tool-icon', 'align: absmiddle;', ICON_SIZE_SMALL, get_lang('Documents')).'</a>';
6289
                $params['right_actions'] .= '<a href="'.api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course['real_id'].'">'.
6290
                    Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', 'align: absmiddle;', ICON_SIZE_SMALL, get_lang('Edit')).
6291
                    '</a>';
6292
                $params['right_actions'] .= Display::div(
6293
                    '',
6294
                    [
6295
                        'id' => 'document_result_'.$course_info['real_id'].'_0',
6296
                        'class' => 'document_preview_container',
6297
                    ]
6298
                );
6299
            } else {
6300
                $params['right_actions'] .=
6301
                    '<a class="btn btn--plain btn-sm" title="'.get_lang('Edit').'" href="'.api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course['real_id'].'">'.
6302
                    Display::getMdiIcon('pencil').'</a>';
6303
            }
6304
        } else {
6305
            if (Course::CLOSED != $course_info['visibility']) {
6306
                if ($loadDirs) {
6307
                    $params['right_actions'] .= '<a id="document_preview_'.$course_info['real_id'].'_0" class="document_preview" href="javascript:void(0);">'.
6308
                        Display::getMdiIcon(ObjectIcon::FOLDER, 'ch-tool-icon', 'align: absmiddle;', ICON_SIZE_SMALL, get_lang('Documents')).'</a>';
6309
                    $params['right_actions'] .= Display::div(
6310
                        '',
6311
                        [
6312
                            'id' => 'document_result_'.$course_info['real_id'].'_0',
6313
                            'class' => 'document_preview_container',
6314
                        ]
6315
                    );
6316
                } else {
6317
                    if (COURSEMANAGER == $course_info['status']) {
6318
                        $params['right_actions'] .= '<a
6319
                            class="btn btn--plain btn-sm" title="'.get_lang('Edit').'" href="'.api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course['real_id'].'">'.
6320
                            Display::getMdiIcon('pencil').'</a>';
6321
                    }
6322
                }
6323
            }
6324
        }
6325
6326
        $course_title_url = '';
6327
        if (Course::CLOSED != $course_info['visibility'] || COURSEMANAGER == $course['status']) {
6328
            $course_title_url = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/?id_session=0';
6329
            $course_title = Display::url($course_info['title'], $course_title_url);
6330
        } else {
6331
            $course_title = $course_info['title'].' '.Display::tag(
6332
                'span',
6333
                get_lang('(the course is currently closed)'),
6334
                ['class' => 'item_closed']
6335
            );
6336
        }
6337
6338
        // Start displaying the course block itself
6339
        if ('true' === api_get_setting('display_coursecode_in_courselist')) {
6340
            $course_title .= ' ('.$course_info['visual_code'].') ';
6341
        }
6342
        $teachers = '';
6343
        if ('true' === api_get_setting('display_teacher_in_courselist')) {
6344
            $teachers = self::getTeacherListFromCourseCodeToString(
6345
                $course['code'],
6346
                self::USER_SEPARATOR,
6347
                true
6348
            );
6349
        }
6350
        $params['link'] = $course_title_url;
6351
        $params['icon'] = $status_icon;
6352
        $params['title'] = $course_title;
6353
        $params['teachers'] = $teachers;
6354
6355
        return $params;
6356
    }
6357
6358
    /**
6359
     * Get the course id based on the original id and field name in the extra fields.
6360
     * Returns 0 if course was not found.
6361
     *
6362
     * @param string $original_course_id_value Original course id
6363
     * @param string $original_course_id_name  Original field name
6364
     *
6365
     * @return int Course id
6366
     */
6367
    public static function get_course_id_from_original_id($original_course_id_value, $original_course_id_name)
6368
    {
6369
        $extraFieldValue = new ExtraFieldValue('course');
6370
        $value = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
6371
            $original_course_id_name,
6372
            $original_course_id_value
6373
        );
6374
6375
        if ($value) {
6376
            return $value['item_id'];
6377
        }
6378
6379
        return 0;
6380
    }
6381
6382
    /**
6383
     * Helper function to create a default gradebook (if necessary) upon course creation.
6384
     *
6385
     * @param int $modelId  The gradebook model ID
6386
     * @param int $courseId Course ID
6387
     * @return void
6388
     * @throws Exception
6389
     */
6390
    public static function createDefaultGradebook(int $modelId, int $courseId): void
6391
    {
6392
        if ('true' === api_get_setting('gradebook_enable_grade_model')) {
6393
            //Create gradebook_category for the new course and add
6394
            // a gradebook model for the course
6395
            if ('-1' != $modelId) {
6396
                GradebookUtils::create_default_course_gradebook(
6397
                    $courseId,
6398
                    $modelId
6399
                );
6400
            }
6401
        }
6402
    }
6403
6404
    /**
6405
     * Helper function to check if there is a course template and, if so, to
6406
     * copy the template as basis for the new course.
6407
     */
6408
    public static function useTemplateAsBasisIfRequired(string $courseCode, int $courseTemplate): void
6409
    {
6410
        $template = api_get_setting('course_creation_use_template');
6411
        $template = is_numeric($template) ? intval($template) : null;
6412
        $teacherCanSelectCourseTemplate = 'true' === api_get_setting('workflows.teacher_can_select_course_template');
6413
        $courseTemplate = isset($courseTemplate) ? intval($courseTemplate) : 0;
6414
6415
        $useTemplate = false;
6416
6417
        if ($teacherCanSelectCourseTemplate && $courseTemplate) {
6418
            $useTemplate = true;
6419
            $originCourse = api_get_course_info_by_id($courseTemplate);
6420
        } elseif (!empty($template)) {
6421
            $useTemplate = true;
6422
            $originCourse = api_get_course_info_by_id($template);
6423
        }
6424
6425
        if ($useTemplate && !empty($originCourse)) {
6426
            // Include the necessary libraries to generate a course copy
6427
            // Call the course copy object
6428
            $originCourse['official_code'] = $originCourse['code'];
6429
            $cb = new CourseBuilder(null, $originCourse);
6430
            $course = $cb->build(null, $originCourse['code']);
6431
            $cr = new CourseRestorer($course);
6432
            $cr->set_file_option();
6433
            $cr->restore($courseCode);
6434
        }
6435
    }
6436
6437
    /**
6438
     * Helper method to get the number of users defined with a specific course extra field.
6439
     *
6440
     * @param string $name                 Field title
6441
     * @param string $tableExtraFields     The extra fields table name
6442
     * @param string $tableUserFieldValues The user extra field value table name
6443
     *
6444
     * @return int The number of users with this extra field with a specific value
6445
     */
6446
    public static function getCountRegisteredUsersWithCourseExtraField(
6447
        $name,
6448
        $tableExtraFields = '',
6449
        $tableUserFieldValues = ''
6450
    ) {
6451
        if (empty($tableExtraFields)) {
6452
            $tableExtraFields = Database::get_main_table(TABLE_EXTRA_FIELD);
6453
        }
6454
        if (empty($tableUserFieldValues)) {
6455
            $tableUserFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
6456
        }
6457
6458
        $registered_users_with_extra_field = 0;
6459
        if (!empty($name) && '-' != $name) {
6460
            $extraFieldType = EntityExtraField::COURSE_FIELD_TYPE;
6461
            $name = Database::escape_string($name);
6462
            $sql = "SELECT count(v.item_id) as count
6463
                    FROM $tableUserFieldValues v
6464
                    INNER JOIN $tableExtraFields f
6465
                    ON (f.id = v.field_id)
6466
                    WHERE value = '$name' AND item_type = $extraFieldType";
6467
            $result_count = Database::query($sql);
6468
            if (Database::num_rows($result_count)) {
6469
                $row_count = Database::fetch_array($result_count);
6470
                $registered_users_with_extra_field = $row_count['count'];
6471
            }
6472
        }
6473
6474
        return $registered_users_with_extra_field;
6475
    }
6476
6477
    /**
6478
     * Get the course categories form a course list.
6479
     *
6480
     * @return array
6481
     */
6482
    public static function getCourseCategoriesFromCourseList(array $courseList)
6483
    {
6484
        $allCategories = array_column($courseList, 'category');
6485
        $categories = array_unique($allCategories);
6486
6487
        sort($categories);
6488
6489
        return $categories;
6490
    }
6491
6492
    /**
6493
     * Display the description button of a course in the course catalog.
6494
     *
6495
     * @param array  $course
6496
     * @param string $url
6497
     *
6498
     * @return string HTML string
6499
     */
6500
    public static function returnDescriptionButton($course, $url = '')
6501
    {
6502
        if (empty($course)) {
6503
            return '';
6504
        }
6505
6506
        $class = '';
6507
        if ('true' === api_get_setting('catalog.show_courses_descriptions_in_catalog')) {
6508
            $title = $course['title'];
6509
            if (empty($url)) {
6510
                $class = 'ajax';
6511
                $url = api_get_path(WEB_CODE_PATH).
6512
                    'inc/ajax/course_home.ajax.php?a=show_course_information&code='.$course['code'];
6513
            } else {
6514
                if (false !== strpos($url, 'ajax')) {
6515
                    $class = 'ajax';
6516
                }
6517
            }
6518
6519
            return Display::url(
6520
                Display::getMdiIcon('information'),
6521
                $url,
6522
                [
6523
                    'class' => "$class btn btn--plain btn-sm",
6524
                    'data-title' => $title,
6525
                    'title' => get_lang('Description'),
6526
                    'aria-label' => get_lang('Description'),
6527
                    'data-size' => 'lg',
6528
                ]
6529
            );
6530
        }
6531
6532
        return '';
6533
    }
6534
6535
    /**
6536
     * @return int
6537
     */
6538
    public static function getCountOpenCourses()
6539
    {
6540
        $visibility = [
6541
            Course::REGISTERED,
6542
            Course::OPEN_PLATFORM,
6543
            Course::OPEN_WORLD,
6544
        ];
6545
6546
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6547
        $sql = "SELECT count(id) count
6548
                FROM $table
6549
                WHERE visibility IN (".implode(',', $visibility).")";
6550
        $result = Database::query($sql);
6551
        $row = Database::fetch_array($result);
6552
6553
        return (int) $row['count'];
6554
    }
6555
6556
    /**
6557
     * @return int
6558
     */
6559
    public static function getCountExercisesFromOpenCourse()
6560
    {
6561
        $visibility = [
6562
            Course::REGISTERED,
6563
            Course::OPEN_PLATFORM,
6564
            Course::OPEN_WORLD,
6565
        ];
6566
6567
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6568
        $tableExercise = Database::get_course_table(TABLE_QUIZ_TEST);
6569
        $sql = "SELECT count(e.iid) count
6570
                FROM $table c
6571
                INNER JOIN $tableExercise e
6572
                ON (c.id = e.c_id)
6573
                WHERE e.active <> -1 AND visibility IN (".implode(',', $visibility).")";
6574
        $result = Database::query($sql);
6575
        $row = Database::fetch_array($result);
6576
6577
        return (int) $row['count'];
6578
    }
6579
6580
    /**
6581
     * retrieves all the courses that the user has already subscribed to.
6582
     *
6583
     * @param int $user_id
6584
     *
6585
     * @return array an array containing all the information of the courses of the given user
6586
     */
6587
    public static function getCoursesByUserCourseCategory($user_id)
6588
    {
6589
        $course = Database::get_main_table(TABLE_MAIN_COURSE);
6590
        $courseRelUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6591
        $avoidCoursesCondition = CoursesAndSessionsCatalog::getAvoidCourseCondition();
6592
        $visibilityCondition = self::getCourseVisibilitySQLCondition('course', true);
6593
6594
        // Secondly we select the courses that are in a category (user_course_cat<>0) and
6595
        // sort these according to the sort of the category
6596
        $user_id = (int) $user_id;
6597
        $sql = "SELECT
6598
                    course.code k,
6599
                    course.visual_code vc,
6600
                    course.subscribe subscr,
6601
                    course.unsubscribe unsubscr,
6602
                    course.title i,
6603
                    course.tutor_name t,
6604
                    course.category_code cat,
6605
                    course.directory dir,
6606
                    course_rel_user.status status,
6607
                    course_rel_user.sort sort,
6608
                    course_rel_user.user_course_cat user_course_cat
6609
                FROM $course course, $courseRelUser course_rel_user
6610
                WHERE
6611
                    course.id = course_rel_user.c_id AND
6612
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
6613
                    course_rel_user.user_id = '".$user_id."'
6614
                    $avoidCoursesCondition
6615
                    $visibilityCondition
6616
                ORDER BY course_rel_user.sort ASC";
6617
6618
        $result = Database::query($sql);
6619
        $courses = [];
6620
        while ($row = Database::fetch_array($result, 'ASOC')) {
6621
            $courses[] = [
6622
                'code' => $row['k'],
6623
                'visual_code' => $row['vc'],
6624
                'title' => $row['i'],
6625
                'directory' => $row['dir'],
6626
                'status' => $row['status'],
6627
                'tutor' => $row['t'],
6628
                'subscribe' => $row['subscr'],
6629
                'category' => $row['cat'],
6630
                'unsubscribe' => $row['unsubscr'],
6631
                'sort' => $row['sort'],
6632
                'user_course_category' => $row['user_course_cat'],
6633
            ];
6634
        }
6635
6636
        return $courses;
6637
    }
6638
6639
    /**
6640
     * @param string $listType
6641
     *
6642
     * @return string
6643
     */
6644
    public static function getCourseListTabs($listType)
6645
    {
6646
        $tabs = [
6647
            'simple' => [
6648
                'content' => get_lang('Standard List'),
6649
                'url' => api_get_path(WEB_CODE_PATH).'admin/course_list.php',
6650
            ],
6651
            'admin' => [
6652
                'content' => get_lang('Management List'),
6653
                'url' => api_get_path(WEB_CODE_PATH).'admin/course_list_admin.php',
6654
            ],
6655
        ];
6656
6657
        return Display::tabsOnlyLink($tabs, $listType, 'course-list');
6658
    }
6659
6660
    public static function getUrlMarker($courseId)
6661
    {
6662
        if (UrlManager::getCountAccessUrlFromCourse($courseId) > 1) {
6663
            return '&nbsp;'.Display::getMdiIcon(
6664
                'link',
6665
                null,
6666
                null,
6667
                null,
6668
                get_lang('This course is used in at least one other portal')
6669
            );
6670
        }
6671
6672
        return '';
6673
    }
6674
6675
    /**
6676
     * @throws \Doctrine\ORM\ORMException
6677
     * @throws \Doctrine\ORM\OptimisticLockException
6678
     */
6679
    public static function insertUserInCourse(User $user, Course $course, array $relationInfo = []): ?int
6680
    {
6681
        $relationInfo = array_merge(
6682
            ['relation_type' => 0, 'status' => STUDENT, 'sort' => 0, 'user_course_cat' => 0],
6683
            $relationInfo
6684
        );
6685
6686
        $courseRelUser = (new CourseRelUser())
6687
            ->setCourse($course)
6688
            ->setUser($user)
6689
            ->setStatus($relationInfo['status'])
6690
            ->setSort($relationInfo['sort'])
6691
            ->setUserCourseCat($relationInfo['user_course_cat']);
6692
6693
        $course->addSubscription($courseRelUser);
6694
6695
        $em = Database::getManager();
6696
        $em->persist($course);
6697
        $em->flush();
6698
6699
        $insertId = $courseRelUser->getId();
6700
6701
        Event::logSubscribedUserInCourse($user, $course);
6702
6703
        return $insertId;
6704
    }
6705
6706
    public static function addVisibilityOptions(FormValidator $form): void
6707
    {
6708
        $group = [];
6709
        $group[] = $form->createElement(
6710
            'radio',
6711
            'visibility',
6712
            get_lang('Course access'),
6713
            get_lang('Public - access allowed for the whole world'),
6714
            Course::OPEN_WORLD
6715
        );
6716
        $group[] = $form->createElement(
6717
            'radio',
6718
            'visibility',
6719
            null,
6720
            get_lang('Open - access allowed for users registered on the platform'),
6721
            Course::OPEN_PLATFORM
6722
        );
6723
        $group[] = $form->createElement(
6724
            'radio',
6725
            'visibility',
6726
            null,
6727
            get_lang('Private access (access authorized to group members only)'),
6728
            Course::REGISTERED
6729
        );
6730
        $group[] = $form->createElement(
6731
            'radio',
6732
            'visibility',
6733
            null,
6734
            get_lang('Closed - the course is only accessible to the teachers'),
6735
            Course::CLOSED
6736
        );
6737
        // The "hidden" visibility is only available to portal admins
6738
        if (api_is_platform_admin()) {
6739
            $group[] = $form->createElement(
6740
                'radio',
6741
                'visibility',
6742
                null,
6743
                get_lang('Hidden - Completely hidden to all users except the administrators'),
6744
                Course::HIDDEN
6745
            );
6746
        }
6747
        $form->addGroup($group, '', get_lang('Course access'));
6748
    }
6749
6750
    /**
6751
     * Check if a specific access-url-related setting is a problem or not.
6752
     *
6753
     * @param array  $_configuration The $_configuration array
6754
     * @param int    $accessUrlId    The access URL ID
6755
     * @param string $param
6756
     * @param string $msgLabel
6757
     *
6758
     * @return bool|string
6759
     */
6760
    private static function checkCreateCourseAccessUrlParam($accessUrlId, $param, $msgLabel)
6761
    {
6762
        $hostingLimit = get_hosting_limit($accessUrlId, $param);
6763
6764
        if ($hostingLimit !== null && $hostingLimit > 0) {
6765
            $num = null;
6766
            switch ($param) {
6767
                case 'hosting_limit_courses':
6768
                    $num = self::count_courses($accessUrlId);
6769
                    break;
6770
                case 'hosting_limit_active_courses':
6771
                    $num = self::countActiveCourses($accessUrlId);
6772
                    break;
6773
            }
6774
6775
            if ($num && $num >= $hostingLimit) {
6776
                api_warn_hosting_contact($param);
6777
6778
                Display::addFlash(
6779
                    Display::return_message($msgLabel)
6780
                );
6781
6782
                return true;
6783
            }
6784
        }
6785
6786
        return false;
6787
    }
6788
6789
    /**
6790
     * Fill course with all necessary items.
6791
     * @param Course $course
6792
     * @param array $params Parameters from the course creation form
6793
     * @param ?int   $authorId
6794
     * @throws Exception
6795
     */
6796
    private static function fillCourse(Course $course, array $params, ?int $authorId = 0)
6797
    {
6798
        $authorId = empty($authorId) ? api_get_user_id() : $authorId;
6799
6800
        AddCourse::fillCourse(
6801
            $course,
6802
            $params['exemplary_content'],
6803
            $authorId
6804
        );
6805
6806
        if (isset($params['gradebook_model_id'])) {
6807
            self::createDefaultGradebook(
6808
                $params['gradebook_model_id'],
6809
                $course->getId()
6810
            );
6811
        }
6812
6813
        // If parameter defined, copy the contents from a specific
6814
        // template course into this new course
6815
        if (isset($params['course_template'])) {
6816
            self::useTemplateAsBasisIfRequired(
6817
                $course->getCode(),
6818
                (int) $params['course_template']
6819
            );
6820
        }
6821
        $params['course_code'] = $course->getCode();
6822
        $params['item_id'] = $course->getId();
6823
6824
        $courseFieldValue = new ExtraFieldValue('course');
6825
        //$courseFieldValue->saveFieldValues($params);
6826
    }
6827
6828
    /**
6829
     * Global hosting limit for users per course.
6830
     * Returns 0 when the limit is disabled.
6831
     */
6832
    public static function getGlobalUsersPerCourseLimit(): int
6833
    {
6834
        return (int) api_get_setting('platform.hosting_limit_users_per_course');
6835
    }
6836
6837
    /**
6838
     * Count current users in course for the global limit.
6839
     * Excludes HR relation type (keeps legacy behaviour).
6840
     */
6841
    public static function countUsersForGlobalLimit(int $courseId): int
6842
    {
6843
        $courseId = (int) $courseId;
6844
        if ($courseId <= 0) {
6845
            return 0;
6846
        }
6847
6848
        $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6849
6850
        $sql = "SELECT COUNT(*) AS total
6851
                FROM $table
6852
                WHERE c_id = $courseId
6853
                  AND relation_type <> ".COURSE_RELATION_TYPE_RRHH;
6854
6855
        $res = Database::query($sql);
6856
        $row = Database::fetch_array($res, 'ASSOC') ?: [];
6857
6858
        return (int) ($row['total'] ?? 0);
6859
    }
6860
6861
    /**
6862
     * Check if subscribing the given users (no session) would exceed the global limit.
6863
     *
6864
     * @param int   $courseId
6865
     * @param int[] $userIds
6866
     */
6867
    public static function wouldOperationExceedGlobalLimit(int $courseId, array $userIds): bool
6868
    {
6869
        $limit = self::getGlobalUsersPerCourseLimit();
6870
        if ($limit <= 0) {
6871
            // No global limit configured.
6872
            return false;
6873
        }
6874
6875
        $courseId = (int) $courseId;
6876
        if ($courseId <= 0 || empty($userIds)) {
6877
            return false;
6878
        }
6879
6880
        // Keep unique, positive IDs only.
6881
        $userIds = array_values(array_unique(array_map('intval', $userIds)));
6882
        $userIds = array_filter(
6883
            $userIds,
6884
            static function (int $id): bool {
6885
                return $id > 0;
6886
            }
6887
        );
6888
6889
        if (empty($userIds)) {
6890
            return false;
6891
        }
6892
6893
        $table  = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6894
        $idList = implode(',', $userIds);
6895
6896
        // How many of these users are already in the course?
6897
        $sql = "SELECT COUNT(DISTINCT user_id) AS already
6898
                FROM $table
6899
                WHERE c_id = $courseId
6900
                  AND relation_type <> ".COURSE_RELATION_TYPE_RRHH."
6901
                  AND user_id IN ($idList)";
6902
6903
        $res = Database::query($sql);
6904
        $row = Database::fetch_array($res, 'ASSOC') ?: [];
6905
        $already = (int) ($row['already'] ?? 0);
6906
6907
        $newCount = count($userIds) - $already;
6908
        if ($newCount <= 0) {
6909
            // Nothing new to subscribe, cannot exceed.
6910
            return false;
6911
        }
6912
6913
        $current = self::countUsersForGlobalLimit($courseId);
6914
6915
        return ($current + $newCount) > $limit;
6916
    }
6917
6918
    /**
6919
     * Build message when a *manual* subscription operation must be cancelled.
6920
     */
6921
    public static function getGlobalLimitCancelMessage(): string
6922
    {
6923
        $limit = self::getGlobalUsersPerCourseLimit();
6924
6925
        return sprintf(
6926
            get_lang(
6927
                'This operation would exceed the limit of %d users per course set by the administrators. The whole subscription operation has been cancelled.'
6928
            ),
6929
            $limit
6930
        );
6931
    }
6932
6933
    /**
6934
     * Build message for *batch imports* when some subscriptions hit the limit.
6935
     *
6936
     * @param string[] $pairs List of "username / Course title" strings.
6937
     */
6938
    public static function getGlobalLimitPartialImportMessage(array $pairs): string
6939
    {
6940
        $limit = self::getGlobalUsersPerCourseLimit();
6941
6942
        $safePairs = array_map(
6943
            static function (string $pair): string {
6944
                return Security::remove_XSS($pair);
6945
            },
6946
            $pairs
6947
        );
6948
6949
        $list = implode(', ', $safePairs);
6950
6951
        return sprintf(
6952
            get_lang(
6953
                'Some or all the subscriptions could not be executed because they reached the limit of %d users per course set by the administrators. Please make sure that limit is raised and try executing this operation again. Users/Courses affected: %s'
6954
            ),
6955
            $limit,
6956
            $list
6957
        );
6958
    }
6959
}
6960