CourseManager::getCoursesFollowedByUser()   C
last analyzed

Complexity

Conditions 14

Size

Total Lines 107
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 64
nop 10
dl 0
loc 107
rs 6.2666
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
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
            'open_answers_grader',
5444
            'tutor_chatbot',
5445
            'task_grader',
5446
            'content_analyser',
5447
            'image_generator',
5448
            'glossary_terms_generator',
5449
            'video_generator',
5450
            'course_analyser',
5451
        ];
5452
5453
        $courseModels = ExerciseLib::getScoreModels();
5454
        if (!empty($courseModels)) {
5455
            $courseSettings[] = 'score_model_id';
5456
        }
5457
5458
        $allowLPReturnLink = api_get_setting('lp.allow_lp_return_link');
5459
        if ('true' === $allowLPReturnLink) {
5460
            $courseSettings[] = 'lp_return_link';
5461
        }
5462
5463
        if (!empty($pluginCourseSettings)) {
5464
            $courseSettings = array_merge(
5465
                $courseSettings,
5466
                $pluginCourseSettings
5467
            );
5468
        }
5469
5470
        return $courseSettings;
5471
    }
5472
5473
    /**
5474
     * @param string       $variable
5475
     * @param string|array $value
5476
     * @param int          $courseId
5477
     *
5478
     * @return bool
5479
     */
5480
    public static function saveCourseConfigurationSetting($variable, $value, $courseId, AppPlugin $appPlugin = null)
5481
    {
5482
        $settingList = self::getCourseSettingVariables($appPlugin);
5483
5484
        if (!in_array($variable, $settingList)) {
5485
            return false;
5486
        }
5487
5488
        $courseSettingTable = Database::get_course_table(TABLE_COURSE_SETTING);
5489
5490
        if (is_array($value)) {
5491
            $value = implode(',', $value);
5492
        }
5493
5494
        $settingFromDatabase = self::getCourseSetting($variable, $courseId);
5495
5496
        if (!empty($settingFromDatabase)) {
5497
            // Update
5498
            Database::update(
5499
                $courseSettingTable,
5500
                ['value' => $value],
5501
                ['variable = ? AND c_id = ?' => [$variable, $courseId]]
5502
            );
5503
5504
            /*if ($settingFromDatabase['value'] != $value) {
5505
                Event::addEvent(
5506
                    LOG_COURSE_SETTINGS_CHANGED,
5507
                    $variable,
5508
                    $settingFromDatabase['value']." -> $value"
5509
                );
5510
            }*/
5511
        } else {
5512
            // Create
5513
            Database::insert(
5514
                $courseSettingTable,
5515
                [
5516
                    'title' => $variable,
5517
                    'value' => $value,
5518
                    'c_id' => $courseId,
5519
                    'variable' => $variable,
5520
                ]
5521
            );
5522
5523
            /*Event::addEvent(
5524
                LOG_COURSE_SETTINGS_CHANGED,
5525
                $variable,
5526
                $value
5527
            );*/
5528
        }
5529
5530
        return true;
5531
    }
5532
5533
    /**
5534
     * Get course setting.
5535
     *
5536
     * @param string $variable
5537
     * @param int    $courseId
5538
     *
5539
     * @return array
5540
     */
5541
    public static function getCourseSetting($variable, $courseId)
5542
    {
5543
        $courseSetting = Database::get_course_table(TABLE_COURSE_SETTING);
5544
        $courseId = (int) $courseId;
5545
        $variable = Database::escape_string($variable);
5546
        $sql = "SELECT variable, value FROM $courseSetting
5547
                WHERE c_id = $courseId AND variable = '$variable'";
5548
        $result = Database::query($sql);
5549
5550
        return Database::fetch_array($result);
5551
    }
5552
5553
    public static function saveSettingChanges($courseInfo, $params)
5554
    {
5555
        if (empty($courseInfo) || empty($params)) {
5556
            return false;
5557
        }
5558
5559
        $userId = api_get_user_id();
5560
        $now = api_get_utc_datetime();
5561
5562
        foreach ($params as $name => $value) {
5563
            $emptyValue = ' - ';
5564
            if (isset($courseInfo[$name]) && $courseInfo[$name] != $value) {
5565
                if ('' !== $courseInfo[$name]) {
5566
                    $emptyValue = $courseInfo[$name];
5567
                }
5568
5569
                $changedTo = $emptyValue.' -> '.$value;
5570
5571
                Event::addEvent(
5572
                    LOG_COURSE_SETTINGS_CHANGED,
5573
                    $name,
5574
                    $changedTo,
5575
                    $now,
5576
                    $userId,
5577
                    $courseInfo['real_id']
5578
                );
5579
            }
5580
        }
5581
5582
        return true;
5583
    }
5584
5585
    /**
5586
     * Get information from the track_e_course_access table.
5587
     *
5588
     * @param int    $courseId
5589
     * @param int    $sessionId
5590
     * @param string $startDate
5591
     * @param string $endDate
5592
     *
5593
     * @return array
5594
     */
5595
    public static function getCourseAccessPerCourseAndSession(
5596
        $courseId,
5597
        $sessionId,
5598
        $startDate,
5599
        $endDate
5600
    ) {
5601
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5602
        $courseId = (int) $courseId;
5603
        $sessionId = (int) $sessionId;
5604
        $startDate = Database::escape_string($startDate);
5605
        $endDate = Database::escape_string($endDate);
5606
5607
        $sql = "SELECT * FROM $table
5608
                WHERE
5609
                    c_id = $courseId AND
5610
                    session_id = $sessionId AND
5611
                    login_course_date BETWEEN '$startDate' AND '$endDate'
5612
                ";
5613
5614
        $result = Database::query($sql);
5615
5616
        return Database::store_result($result);
5617
    }
5618
5619
    /**
5620
     * Get login information from the track_e_course_access table, for any
5621
     * course in the given session.
5622
     *
5623
     * @param int $sessionId
5624
     * @param int $userId
5625
     *
5626
     * @return array
5627
     */
5628
    public static function getFirstCourseAccessPerSessionAndUser($sessionId, $userId)
5629
    {
5630
        $sessionId = (int) $sessionId;
5631
        $userId = (int) $userId;
5632
5633
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
5634
        $sql = "SELECT * FROM $table
5635
                WHERE session_id = $sessionId AND user_id = $userId
5636
                ORDER BY login_course_date ASC
5637
                LIMIT 1";
5638
5639
        $result = Database::query($sql);
5640
        $courseAccess = [];
5641
        if (Database::num_rows($result)) {
5642
            $courseAccess = Database::fetch_assoc($result);
5643
        }
5644
5645
        return $courseAccess;
5646
    }
5647
5648
    /**
5649
     * @param int  $courseId
5650
     * @param int  $sessionId
5651
     * @param bool $getAllSessions
5652
     *
5653
     * @return mixed
5654
     */
5655
    public static function getCountForum(
5656
        $courseId,
5657
        $sessionId = 0,
5658
        $getAllSessions = false
5659
    ) {
5660
        $forum = Database::get_course_table(TABLE_FORUM);
5661
        if ($getAllSessions) {
5662
            $sql = "SELECT count(*) as count
5663
                    FROM $forum f
5664
                    WHERE f.c_id = %s";
5665
        } else {
5666
            $sql = "SELECT count(*) as count
5667
                    FROM $forum f
5668
                    WHERE f.c_id = %s and f.session_id = %s";
5669
        }
5670
5671
        $sql = sprintf($sql, intval($courseId), intval($sessionId));
5672
        $result = Database::query($sql);
5673
        $row = Database::fetch_array($result);
5674
5675
        return $row['count'];
5676
    }
5677
5678
    /**
5679
     * @param int $userId
5680
     * @param int $courseId
5681
     * @param int $sessionId
5682
     *
5683
     * @return mixed
5684
     */
5685
    public static function getCountPostInForumPerUser(
5686
        $userId,
5687
        $courseId,
5688
        $sessionId = 0
5689
    ) {
5690
        $forum = Database::get_course_table(TABLE_FORUM);
5691
        $forum_post = Database::get_course_table(TABLE_FORUM_POST);
5692
5693
        $sql = "SELECT count(distinct post_id) as count
5694
                FROM $forum_post p
5695
                INNER JOIN $forum f
5696
                ON f.forum_id = p.forum_id AND f.c_id = p.c_id
5697
                WHERE p.poster_id = %s and f.session_id = %s and p.c_id = %s";
5698
5699
        $sql = sprintf(
5700
            $sql,
5701
            intval($userId),
5702
            intval($sessionId),
5703
            intval($courseId)
5704
        );
5705
5706
        $result = Database::query($sql);
5707
        $row = Database::fetch_array($result);
5708
5709
        return $row['count'];
5710
    }
5711
5712
    /**
5713
     * @param int $userId
5714
     * @param int $courseId
5715
     * @param int $sessionId
5716
     *
5717
     * @return mixed
5718
     */
5719
    public static function getCountForumPerUser(
5720
        $userId,
5721
        $courseId,
5722
        $sessionId = 0
5723
    ) {
5724
        $forum = Database::get_course_table(TABLE_FORUM);
5725
        $forum_post = Database::get_course_table(TABLE_FORUM_POST);
5726
5727
        $sql = "SELECT count(distinct f.forum_id) as count
5728
                FROM $forum_post p
5729
                INNER JOIN $forum f
5730
                ON f.forum_id = p.forum_id AND f.c_id = p.c_id
5731
                WHERE p.poster_id = %s and f.session_id = %s and p.c_id = %s";
5732
5733
        $sql = sprintf(
5734
            $sql,
5735
            intval($userId),
5736
            intval($sessionId),
5737
            intval($courseId)
5738
        );
5739
5740
        $result = Database::query($sql);
5741
        $row = Database::fetch_array($result);
5742
5743
        return $row['count'];
5744
    }
5745
5746
    /**
5747
     * Returns the course name from a given code.
5748
     *
5749
     * @param string $code
5750
     *
5751
     * @return string
5752
     */
5753
    public static function getCourseNameFromCode($code)
5754
    {
5755
        $tbl_main_categories = Database::get_main_table(TABLE_MAIN_COURSE);
5756
        $code = Database::escape_string($code);
5757
        $sql = "SELECT title
5758
                FROM $tbl_main_categories
5759
                WHERE code = '$code'";
5760
        $result = Database::query($sql);
5761
        if ($col = Database::fetch_array($result)) {
5762
            return $col['title'];
5763
        }
5764
    }
5765
5766
    /**
5767
     * Generates a course code from a course title.
5768
     *
5769
     * @todo Such a function might be useful in other places too. It might be moved in the CourseManager class.
5770
     * @todo the function might be upgraded for avoiding code duplications (currently,
5771
     * it might suggest a code that is already in use)
5772
     *
5773
     * @param string $title A course title
5774
     *
5775
     * @return string A proposed course code
5776
     *                +
5777
     * @assert (null,null) === false
5778
     * @assert ('ABC_DEF', null) === 'ABCDEF'
5779
     * @assert ('ABC09*^[%A', null) === 'ABC09A'
5780
     */
5781
    public static function generate_course_code($title)
5782
    {
5783
        return substr(
5784
            preg_replace('/[^A-Z0-9]/', '', strtoupper(api_replace_dangerous_char($title))),
5785
            0,
5786
            self::MAX_COURSE_LENGTH_CODE
5787
        );
5788
    }
5789
5790
    /**
5791
     * this function gets all the users of the course,
5792
     * including users from linked courses.
5793
     *
5794
     * @param $filterByActive
5795
     *
5796
     * @return array
5797
     */
5798
    public static function getCourseUsers($filterByActive = null)
5799
    {
5800
        // This would return only the users from real courses:
5801
        return self::get_user_list_from_course_code(
5802
            api_get_course_id(),
5803
            api_get_session_id(),
5804
            null,
5805
            null,
5806
            null,
5807
            null,
5808
            false,
5809
            false,
5810
            [],
5811
            [],
5812
            [],
5813
            $filterByActive
5814
        );
5815
    }
5816
5817
    /**
5818
     * this function gets all the groups of the course,
5819
     * not including linked courses.
5820
     *
5821
     * @return CGroup[]
5822
     */
5823
    public static function getCourseGroups()
5824
    {
5825
        $sessionId = api_get_session_id();
5826
        $courseCode = api_get_course_id();
5827
        if (0 != $sessionId) {
5828
            $groupList = self::get_group_list_of_course(
5829
                $courseCode,
5830
                $sessionId,
5831
                1
5832
            );
5833
        } else {
5834
            $groupList = self::get_group_list_of_course(
5835
                $courseCode,
5836
                0,
5837
                1
5838
            );
5839
        }
5840
5841
        return $groupList;
5842
    }
5843
5844
    /**
5845
     * @param FormValidator $form
5846
     * @param array         $alreadySelected
5847
     *
5848
     * @return HTML_QuickForm_element
5849
     */
5850
    public static function addUserGroupMultiSelect(&$form, $alreadySelected, $addShortCut = false)
5851
    {
5852
        $userList = self::getCourseUsers(true);
5853
        $groupList = self::getCourseGroups();
5854
5855
        $array = self::buildSelectOptions(
5856
            $groupList,
5857
            $userList,
5858
            $alreadySelected
5859
        );
5860
5861
        $result = [];
5862
        foreach ($array as $content) {
5863
            $result[$content['value']] = $content['content'];
5864
        }
5865
5866
        $multiple = $form->addMultiSelect(
5867
            'users',
5868
            get_lang('Users'),
5869
            $result,
5870
            ['select_all_checkbox' => true, 'id' => 'users']
5871
        );
5872
5873
        $sessionId = api_get_session_id();
5874
        if ($addShortCut && empty($sessionId)) {
5875
            $addStudents = [];
5876
            foreach ($userList as $user) {
5877
                if (STUDENT == $user['status_rel']) {
5878
                    $addStudents[] = $user['user_id'];
5879
                }
5880
            }
5881
            if (!empty($addStudents)) {
5882
                $form->addHtml(
5883
                    '<script>
5884
                    $(function() {
5885
                        $("#add_students").on("click", function() {
5886
                            var addStudents = '.json_encode($addStudents).';
5887
                            $.each(addStudents, function( index, value ) {
5888
                                var option = $("#users option[value=\'USER:"+value+"\']");
5889
                                if (option.val()) {
5890
                                    $("#users_to").append(new Option(option.text(), option.val()))
5891
                                    option.remove();
5892
                                }
5893
                            });
5894
5895
                            return false;
5896
                        });
5897
                    });
5898
                    </script>'
5899
                );
5900
5901
                $form->addLabel(
5902
                    '',
5903
                    Display::url(get_lang('Add learners'), '#', ['id' => 'add_students', 'class' => 'btn btn--primary'])
5904
                );
5905
            }
5906
        }
5907
5908
        return $multiple;
5909
    }
5910
5911
    /**
5912
     * This function separates the users from the groups
5913
     * users have a value USER:XXX (with XXX the groups id have a value
5914
     *  GROUP:YYY (with YYY the group id).
5915
     *
5916
     * @param array $to Array of strings that define the type and id of each destination
5917
     *
5918
     * @return array Array of groups and users (each an array of IDs)
5919
     */
5920
    public static function separateUsersGroups($to)
5921
    {
5922
        $groupList = [];
5923
        $userList = [];
5924
5925
        foreach ($to as $to_item) {
5926
            if (!empty($to_item)) {
5927
                $parts = explode(':', $to_item);
5928
                $type = isset($parts[0]) ? $parts[0] : '';
5929
                $id = isset($parts[1]) ? $parts[1] : '';
5930
5931
                switch ($type) {
5932
                    case 'GROUP':
5933
                        $groupList[] = (int) $id;
5934
                        break;
5935
                    case 'USER':
5936
                        $userList[] = (int) $id;
5937
                        break;
5938
                }
5939
            }
5940
        }
5941
5942
        $send_to['groups'] = $groupList;
5943
        $send_to['users'] = $userList;
5944
5945
        return $send_to;
5946
    }
5947
5948
    /**
5949
     * Shows the form for sending a message to a specific group or user.
5950
     *
5951
     * @return HTML_QuickForm_element
5952
     */
5953
    public static function addGroupMultiSelect(FormValidator $form, CGroup $group, $to = [])
5954
    {
5955
        $groupUsers = GroupManager::get_subscribed_users($group);
5956
        $array = self::buildSelectOptions([$group], $groupUsers, $to);
5957
5958
        $result = [];
5959
        foreach ($array as $content) {
5960
            $result[$content['value']] = $content['content'];
5961
        }
5962
5963
        return $form->addMultiSelect('users', get_lang('Users'), $result);
5964
    }
5965
5966
    /**
5967
     * this function shows the form for sending a message to a specific group or user.
5968
     *
5969
     * @param CGroup[] $groupList
5970
     * @param array    $userList
5971
     * @param array    $alreadySelected
5972
     *
5973
     * @return array
5974
     */
5975
    public static function buildSelectOptions($groupList = [], $userList = [], $alreadySelected = [])
5976
    {
5977
        if (empty($alreadySelected)) {
5978
            $alreadySelected = [];
5979
        }
5980
5981
        $result = [];
5982
        // adding the groups to the select form
5983
        if ($groupList) {
5984
            foreach ($groupList as $thisGroup) {
5985
                $groupId = $thisGroup->getIid();
5986
                if (is_array($alreadySelected)) {
5987
                    if (!in_array(
5988
                        "GROUP:".$groupId,
5989
                        $alreadySelected
5990
                    )
5991
                    ) {
5992
                        $userCount = $thisGroup->getMembers()->count();
5993
5994
                        // $alreadySelected is the array containing the groups (and users) that are already selected
5995
                        $userLabel = ($userCount > 0) ? get_lang('Users') : get_lang('user');
5996
                        $userDisabled = ($userCount > 0) ? "" : "disabled=disabled";
5997
                        $result[] = [
5998
                            'disabled' => $userDisabled,
5999
                            'value' => "GROUP:".$groupId,
6000
                            // The space before "G" is needed in order to advmultiselect.php js puts groups first
6001
                            'content' => " G: ".$thisGroup->getTitle()." - ".$userCount." ".$userLabel,
6002
                        ];
6003
                    }
6004
                }
6005
            }
6006
        }
6007
6008
        // adding the individual users to the select form
6009
        if ($userList) {
6010
            foreach ($userList as $user) {
6011
                if (is_array($alreadySelected)) {
6012
                    if (!in_array(
6013
                        "USER:".$user['user_id'],
6014
                        $alreadySelected
6015
                    )
6016
                    ) {
6017
                        // $alreadySelected is the array containing the users (and groups) that are already selected
6018
                        $result[] = [
6019
                            'value' => "USER:".$user['user_id'],
6020
                            'content' => api_get_person_name($user['firstname'], $user['lastname']),
6021
                        ];
6022
                    }
6023
                }
6024
            }
6025
        }
6026
6027
        return $result;
6028
    }
6029
6030
    /**
6031
     * @return array a list (array) of all courses
6032
     */
6033
    public static function get_course_list()
6034
    {
6035
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6036
6037
        return Database::store_result(Database::query("SELECT *, id as real_id FROM $table"));
6038
    }
6039
6040
    /**
6041
     * Returns course code from a given gradebook category's id.
6042
     *
6043
     * @param int $category_id Category ID
6044
     *
6045
     * @return ?int Course ID
6046
     * @throws Exception
6047
     */
6048
    public static function get_course_by_category(int $category_id): ?int
6049
    {
6050
        $category_id = (int) $category_id;
6051
        $sql = 'SELECT c_id FROM '.Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY).'
6052
                WHERE id='.$category_id;
6053
        $info = Database::fetch_array(Database::query($sql));
6054
6055
        return $info ? $info['c_id'] : null;
6056
    }
6057
6058
    /**
6059
     * This function gets all the courses that are not in a session.
6060
     *
6061
     * @param date Start date
6062
     * @param date End date
6063
     * @param bool $includeClosed Whether to include closed and hidden courses
6064
     *
6065
     * @return array Not-in-session courses
6066
     */
6067
    public static function getCoursesWithoutSession(
6068
        $startDate = null,
6069
        $endDate = null,
6070
        $includeClosed = false
6071
    ) {
6072
        $dateConditional = ($startDate && $endDate) ?
6073
            " WHERE session_id IN (SELECT id FROM ".Database::get_main_table(TABLE_MAIN_SESSION).
6074
            " WHERE access_start_date = '$startDate' AND access_end_date = '$endDate')" : null;
6075
        $visibility = ($includeClosed ? '' : 'visibility NOT IN (0, 4) AND ');
6076
6077
        $sql = "SELECT id, code, title
6078
                FROM ".Database::get_main_table(TABLE_MAIN_COURSE)."
6079
                WHERE $visibility code NOT IN (
6080
                    SELECT DISTINCT course_code
6081
                    FROM ".Database::get_main_table(TABLE_MAIN_SESSION_COURSE).$dateConditional."
6082
                )
6083
                ORDER BY id";
6084
6085
        $result = Database::query($sql);
6086
        $courses = [];
6087
        while ($row = Database::fetch_array($result)) {
6088
            $courses[] = $row;
6089
        }
6090
6091
        return $courses;
6092
    }
6093
6094
    /**
6095
     * Get list of courses based on users of a group for a group admin.
6096
     *
6097
     * @param int $userId The user id
6098
     *
6099
     * @return array
6100
     */
6101
    public static function getCoursesFollowedByGroupAdmin($userId)
6102
    {
6103
        $coursesList = [];
6104
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
6105
        $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6106
        $userGroup = new UserGroupModel();
6107
        $userIdList = $userGroup->getGroupUsersByUser($userId);
6108
6109
        if (empty($userIdList)) {
6110
            return [];
6111
        }
6112
6113
        $sql = "SELECT DISTINCT(c.id), c.title
6114
                FROM $courseTable c
6115
                INNER JOIN $courseUserTable cru ON c.id = cru.c_id
6116
                WHERE (
6117
                    cru.user_id IN (".implode(', ', $userIdList).")
6118
                    AND cru.relation_type = 0
6119
                )";
6120
6121
        if (api_is_multiple_url_enabled()) {
6122
            $courseAccessUrlTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
6123
            $accessUrlId = api_get_current_access_url_id();
6124
6125
            if (-1 != $accessUrlId) {
6126
                $sql = "SELECT DISTINCT(c.id), c.title
6127
                        FROM $courseTable c
6128
                        INNER JOIN $courseUserTable cru ON c.id = cru.c_id
6129
                        INNER JOIN $courseAccessUrlTable crau ON c.id = crau.c_id
6130
                        WHERE crau.access_url_id = $accessUrlId
6131
                            AND (
6132
                            cru.id_user IN (".implode(', ', $userIdList).") AND
6133
                            cru.relation_type = 0
6134
                        )";
6135
            }
6136
        }
6137
6138
        $result = Database::query($sql);
6139
        while ($row = Database::fetch_assoc($result)) {
6140
            $coursesList[] = $row;
6141
        }
6142
6143
        return $coursesList;
6144
    }
6145
6146
    /**
6147
     * Direct course link see #5299.
6148
     *
6149
     * You can send to your students an URL like this
6150
     * http://chamilodev.beeznest.com/main/auth/inscription.php?c=ABC&e=3
6151
     * Where "c" is the course code and "e" is the exercise Id, after a successful
6152
     * registration the user will be sent to the course or exercise
6153
     *
6154
     * @param array $form_data
6155
     *
6156
     * @return array
6157
     */
6158
    public static function redirectToCourse($form_data)
6159
    {
6160
        $course_code_redirect = Session::read('course_redirect');
6161
        $_user = api_get_user_info();
6162
        $userId = api_get_user_id();
6163
6164
        if (!empty($course_code_redirect)) {
6165
            $course_info = api_get_course_info($course_code_redirect);
6166
            if (!empty($course_info)) {
6167
                if (in_array(
6168
                    $course_info['visibility'],
6169
                    [Course::OPEN_PLATFORM, Course::OPEN_WORLD]
6170
                )
6171
                ) {
6172
                    if (self::is_user_subscribed_in_course($userId, $course_info['code'])) {
6173
                        $form_data['action'] = $course_info['course_public_url'];
6174
                        $form_data['message'] = sprintf(get_lang('You have been registered to course %s'), $course_info['title']);
6175
                        $form_data['button'] = Display::button(
6176
                            'next',
6177
                            get_lang('Go to the course', null, $_user['language']),
6178
                            ['class' => 'btn btn--primary btn-large']
6179
                        );
6180
6181
                        $exercise_redirect = (int) Session::read('exercise_redirect');
6182
                        // Specify the course id as the current context does not
6183
                        // hold a global $_course array
6184
                        $objExercise = new Exercise($course_info['real_id']);
6185
                        $result = $objExercise->read($exercise_redirect);
6186
6187
                        if (!empty($exercise_redirect) && !empty($result)) {
6188
                            $form_data['action'] = api_get_path(WEB_CODE_PATH).
6189
                                'exercise/overview.php?exerciseId='.$exercise_redirect.'&cid='.$course_info['real_id'];
6190
                            $form_data['message'] .= '<br />'.get_lang('Go to the test');
6191
                            $form_data['button'] = Display::button(
6192
                                'next',
6193
                                get_lang('Go', null, $_user['language']),
6194
                                ['class' => 'btn btn--primary btn-large']
6195
                            );
6196
                        }
6197
6198
                        if (!empty($form_data['action'])) {
6199
                            header('Location: '.$form_data['action']);
6200
                            exit;
6201
                        }
6202
                    }
6203
                }
6204
            }
6205
        }
6206
6207
        return $form_data;
6208
    }
6209
6210
    /**
6211
     * Return tab of params to display a course title in the My Courses tab
6212
     * Check visibility, right, and notification icons, and load_dirs option
6213
     * get html course params.
6214
     *
6215
     * @param $courseId
6216
     * @param bool $loadDirs
6217
     *
6218
     * @return array with keys ['right_actions'] ['teachers'] ['notifications']
6219
     */
6220
    public static function getCourseParamsForDisplay($courseId, $loadDirs = false)
6221
    {
6222
        $userId = api_get_user_id();
6223
        $courseId = intval($courseId);
6224
        // Table definitions
6225
        $TABLECOURS = Database::get_main_table(TABLE_MAIN_COURSE);
6226
        $TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6227
        $TABLE_ACCESS_URL_REL_COURSE = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
6228
        $current_url_id = api_get_current_access_url_id();
6229
6230
        // Get course list auto-register
6231
        $special_course_list = self::get_special_course_list();
6232
6233
        $without_special_courses = '';
6234
        if (!empty($special_course_list)) {
6235
            $without_special_courses = ' AND course.id NOT IN ("'.implode('","', $special_course_list).'")';
6236
        }
6237
6238
        //AND course_rel_user.relation_type<>".COURSE_RELATION_TYPE_RRHH."
6239
        $sql = "SELECT
6240
                    course.id,
6241
                    course.title,
6242
                    course.code,
6243
                    course.subscribe subscr,
6244
                    course.unsubscribe unsubscr,
6245
                    course_rel_user.status status,
6246
                    course_rel_user.sort sort,
6247
                    course_rel_user.user_course_cat user_course_cat
6248
                FROM
6249
                $TABLECOURS course
6250
                INNER JOIN $TABLECOURSUSER course_rel_user
6251
                ON (course.id = course_rel_user.c_id)
6252
                INNER JOIN $TABLE_ACCESS_URL_REL_COURSE url
6253
                ON (url.c_id = course.id)
6254
                WHERE
6255
                    course.id = $courseId AND
6256
                    course_rel_user.user_id = $userId
6257
                    $without_special_courses
6258
                ";
6259
6260
        // If multiple URL access mode is enabled, only fetch courses
6261
        // corresponding to the current URL.
6262
        if (api_get_multiple_access_url() && -1 != $current_url_id) {
6263
            $sql .= " AND url.c_id = course.id AND access_url_id = $current_url_id";
6264
        }
6265
        // Use user's classification for courses (if any).
6266
        $sql .= " ORDER BY course_rel_user.user_course_cat, course_rel_user.sort ASC";
6267
6268
        $result = Database::query($sql);
6269
6270
        // Browse through all courses. We can only have one course because
6271
        // of the  course.id=".intval($courseId) in sql query
6272
        $course = Database::fetch_array($result);
6273
        $course_info = api_get_course_info_by_id($courseId);
6274
        if (empty($course_info)) {
6275
            return '';
6276
        }
6277
6278
        //$course['id_session'] = null;
6279
        $course_info['id_session'] = null;
6280
        $course_info['status'] = $course['status'];
6281
6282
        // New code displaying the user's status in respect to this course.
6283
        $status_icon = Display::getMdiIcon(
6284
            'account-key',
6285
            'ch-tool-icon',
6286
            null,
6287
            ICON_SIZE_LARGE,
6288
            $course_info['title']
6289
        );
6290
6291
        $params = [];
6292
        $params['right_actions'] = '';
6293
6294
        if (api_is_platform_admin()) {
6295
            if ($loadDirs) {
6296
                $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>';
6297
                $params['right_actions'] .= '<a href="'.api_get_path(WEB_CODE_PATH).'course_info/infocours.php?cid='.$course['real_id'].'">'.
6298
                    Display::getMdiIcon(ActionIcon::EDIT, 'ch-tool-icon', 'align: absmiddle;', ICON_SIZE_SMALL, get_lang('Edit')).
6299
                    '</a>';
6300
                $params['right_actions'] .= Display::div(
6301
                    '',
6302
                    [
6303
                        'id' => 'document_result_'.$course_info['real_id'].'_0',
6304
                        'class' => 'document_preview_container',
6305
                    ]
6306
                );
6307
            } else {
6308
                $params['right_actions'] .=
6309
                    '<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'].'">'.
6310
                    Display::getMdiIcon('pencil').'</a>';
6311
            }
6312
        } else {
6313
            if (Course::CLOSED != $course_info['visibility']) {
6314
                if ($loadDirs) {
6315
                    $params['right_actions'] .= '<a id="document_preview_'.$course_info['real_id'].'_0" class="document_preview" href="javascript:void(0);">'.
6316
                        Display::getMdiIcon(ObjectIcon::FOLDER, 'ch-tool-icon', 'align: absmiddle;', ICON_SIZE_SMALL, get_lang('Documents')).'</a>';
6317
                    $params['right_actions'] .= Display::div(
6318
                        '',
6319
                        [
6320
                            'id' => 'document_result_'.$course_info['real_id'].'_0',
6321
                            'class' => 'document_preview_container',
6322
                        ]
6323
                    );
6324
                } else {
6325
                    if (COURSEMANAGER == $course_info['status']) {
6326
                        $params['right_actions'] .= '<a
6327
                            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'].'">'.
6328
                            Display::getMdiIcon('pencil').'</a>';
6329
                    }
6330
                }
6331
            }
6332
        }
6333
6334
        $course_title_url = '';
6335
        if (Course::CLOSED != $course_info['visibility'] || COURSEMANAGER == $course['status']) {
6336
            $course_title_url = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/?id_session=0';
6337
            $course_title = Display::url($course_info['title'], $course_title_url);
6338
        } else {
6339
            $course_title = $course_info['title'].' '.Display::tag(
6340
                'span',
6341
                get_lang('(the course is currently closed)'),
6342
                ['class' => 'item_closed']
6343
            );
6344
        }
6345
6346
        // Start displaying the course block itself
6347
        if ('true' === api_get_setting('display_coursecode_in_courselist')) {
6348
            $course_title .= ' ('.$course_info['visual_code'].') ';
6349
        }
6350
        $teachers = '';
6351
        if ('true' === api_get_setting('display_teacher_in_courselist')) {
6352
            $teachers = self::getTeacherListFromCourseCodeToString(
6353
                $course['code'],
6354
                self::USER_SEPARATOR,
6355
                true
6356
            );
6357
        }
6358
        $params['link'] = $course_title_url;
6359
        $params['icon'] = $status_icon;
6360
        $params['title'] = $course_title;
6361
        $params['teachers'] = $teachers;
6362
6363
        return $params;
6364
    }
6365
6366
    /**
6367
     * Get the course id based on the original id and field name in the extra fields.
6368
     * Returns 0 if course was not found.
6369
     *
6370
     * @param string $original_course_id_value Original course id
6371
     * @param string $original_course_id_name  Original field name
6372
     *
6373
     * @return int Course id
6374
     */
6375
    public static function get_course_id_from_original_id($original_course_id_value, $original_course_id_name)
6376
    {
6377
        $extraFieldValue = new ExtraFieldValue('course');
6378
        $value = $extraFieldValue->get_item_id_from_field_variable_and_field_value(
6379
            $original_course_id_name,
6380
            $original_course_id_value
6381
        );
6382
6383
        if ($value) {
6384
            return $value['item_id'];
6385
        }
6386
6387
        return 0;
6388
    }
6389
6390
    /**
6391
     * Helper function to create a default gradebook (if necessary) upon course creation.
6392
     *
6393
     * @param int $modelId  The gradebook model ID
6394
     * @param int $courseId Course ID
6395
     * @return void
6396
     * @throws Exception
6397
     */
6398
    public static function createDefaultGradebook(int $modelId, int $courseId): void
6399
    {
6400
        if ('true' === api_get_setting('gradebook_enable_grade_model')) {
6401
            //Create gradebook_category for the new course and add
6402
            // a gradebook model for the course
6403
            if ('-1' != $modelId) {
6404
                GradebookUtils::create_default_course_gradebook(
6405
                    $courseId,
6406
                    $modelId
6407
                );
6408
            }
6409
        }
6410
    }
6411
6412
    /**
6413
     * Helper function to check if there is a course template and, if so, to
6414
     * copy the template as basis for the new course.
6415
     */
6416
    public static function useTemplateAsBasisIfRequired(string $courseCode, int $courseTemplate): void
6417
    {
6418
        $template = api_get_setting('course_creation_use_template');
6419
        $template = is_numeric($template) ? intval($template) : null;
6420
        $teacherCanSelectCourseTemplate = 'true' === api_get_setting('workflows.teacher_can_select_course_template');
6421
        $courseTemplate = isset($courseTemplate) ? intval($courseTemplate) : 0;
6422
6423
        $useTemplate = false;
6424
6425
        if ($teacherCanSelectCourseTemplate && $courseTemplate) {
6426
            $useTemplate = true;
6427
            $originCourse = api_get_course_info_by_id($courseTemplate);
6428
        } elseif (!empty($template)) {
6429
            $useTemplate = true;
6430
            $originCourse = api_get_course_info_by_id($template);
6431
        }
6432
6433
        if ($useTemplate && !empty($originCourse)) {
6434
            // Include the necessary libraries to generate a course copy
6435
            // Call the course copy object
6436
            $originCourse['official_code'] = $originCourse['code'];
6437
            $cb = new CourseBuilder(null, $originCourse);
6438
            $course = $cb->build(null, $originCourse['code']);
6439
            $cr = new CourseRestorer($course);
6440
            $cr->set_file_option();
6441
            $cr->restore($courseCode);
6442
        }
6443
    }
6444
6445
    /**
6446
     * Helper method to get the number of users defined with a specific course extra field.
6447
     *
6448
     * @param string $name                 Field title
6449
     * @param string $tableExtraFields     The extra fields table name
6450
     * @param string $tableUserFieldValues The user extra field value table name
6451
     *
6452
     * @return int The number of users with this extra field with a specific value
6453
     */
6454
    public static function getCountRegisteredUsersWithCourseExtraField(
6455
        $name,
6456
        $tableExtraFields = '',
6457
        $tableUserFieldValues = ''
6458
    ) {
6459
        if (empty($tableExtraFields)) {
6460
            $tableExtraFields = Database::get_main_table(TABLE_EXTRA_FIELD);
6461
        }
6462
        if (empty($tableUserFieldValues)) {
6463
            $tableUserFieldValues = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
6464
        }
6465
6466
        $registered_users_with_extra_field = 0;
6467
        if (!empty($name) && '-' != $name) {
6468
            $extraFieldType = EntityExtraField::COURSE_FIELD_TYPE;
6469
            $name = Database::escape_string($name);
6470
            $sql = "SELECT count(v.item_id) as count
6471
                    FROM $tableUserFieldValues v
6472
                    INNER JOIN $tableExtraFields f
6473
                    ON (f.id = v.field_id)
6474
                    WHERE value = '$name' AND item_type = $extraFieldType";
6475
            $result_count = Database::query($sql);
6476
            if (Database::num_rows($result_count)) {
6477
                $row_count = Database::fetch_array($result_count);
6478
                $registered_users_with_extra_field = $row_count['count'];
6479
            }
6480
        }
6481
6482
        return $registered_users_with_extra_field;
6483
    }
6484
6485
    /**
6486
     * Get the course categories form a course list.
6487
     *
6488
     * @return array
6489
     */
6490
    public static function getCourseCategoriesFromCourseList(array $courseList)
6491
    {
6492
        $allCategories = array_column($courseList, 'category');
6493
        $categories = array_unique($allCategories);
6494
6495
        sort($categories);
6496
6497
        return $categories;
6498
    }
6499
6500
    /**
6501
     * Display the description button of a course in the course catalog.
6502
     *
6503
     * @param array  $course
6504
     * @param string $url
6505
     *
6506
     * @return string HTML string
6507
     */
6508
    public static function returnDescriptionButton($course, $url = '')
6509
    {
6510
        if (empty($course)) {
6511
            return '';
6512
        }
6513
6514
        $class = '';
6515
        if ('true' === api_get_setting('catalog.show_courses_descriptions_in_catalog')) {
6516
            $title = $course['title'];
6517
            if (empty($url)) {
6518
                $class = 'ajax';
6519
                $url = api_get_path(WEB_CODE_PATH).
6520
                    'inc/ajax/course_home.ajax.php?a=show_course_information&code='.$course['code'];
6521
            } else {
6522
                if (false !== strpos($url, 'ajax')) {
6523
                    $class = 'ajax';
6524
                }
6525
            }
6526
6527
            return Display::url(
6528
                Display::getMdiIcon('information'),
6529
                $url,
6530
                [
6531
                    'class' => "$class btn btn--plain btn-sm",
6532
                    'data-title' => $title,
6533
                    'title' => get_lang('Description'),
6534
                    'aria-label' => get_lang('Description'),
6535
                    'data-size' => 'lg',
6536
                ]
6537
            );
6538
        }
6539
6540
        return '';
6541
    }
6542
6543
    /**
6544
     * @return int
6545
     */
6546
    public static function getCountOpenCourses()
6547
    {
6548
        $visibility = [
6549
            Course::REGISTERED,
6550
            Course::OPEN_PLATFORM,
6551
            Course::OPEN_WORLD,
6552
        ];
6553
6554
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6555
        $sql = "SELECT count(id) count
6556
                FROM $table
6557
                WHERE visibility IN (".implode(',', $visibility).")";
6558
        $result = Database::query($sql);
6559
        $row = Database::fetch_array($result);
6560
6561
        return (int) $row['count'];
6562
    }
6563
6564
    /**
6565
     * @return int
6566
     */
6567
    public static function getCountExercisesFromOpenCourse()
6568
    {
6569
        $visibility = [
6570
            Course::REGISTERED,
6571
            Course::OPEN_PLATFORM,
6572
            Course::OPEN_WORLD,
6573
        ];
6574
6575
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
6576
        $tableExercise = Database::get_course_table(TABLE_QUIZ_TEST);
6577
        $sql = "SELECT count(e.iid) count
6578
                FROM $table c
6579
                INNER JOIN $tableExercise e
6580
                ON (c.id = e.c_id)
6581
                WHERE e.active <> -1 AND visibility IN (".implode(',', $visibility).")";
6582
        $result = Database::query($sql);
6583
        $row = Database::fetch_array($result);
6584
6585
        return (int) $row['count'];
6586
    }
6587
6588
    /**
6589
     * retrieves all the courses that the user has already subscribed to.
6590
     *
6591
     * @param int $user_id
6592
     *
6593
     * @return array an array containing all the information of the courses of the given user
6594
     */
6595
    public static function getCoursesByUserCourseCategory($user_id)
6596
    {
6597
        $course = Database::get_main_table(TABLE_MAIN_COURSE);
6598
        $courseRelUser = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6599
        $avoidCoursesCondition = CoursesAndSessionsCatalog::getAvoidCourseCondition();
6600
        $visibilityCondition = self::getCourseVisibilitySQLCondition('course', true);
6601
6602
        // Secondly we select the courses that are in a category (user_course_cat<>0) and
6603
        // sort these according to the sort of the category
6604
        $user_id = (int) $user_id;
6605
        $sql = "SELECT
6606
                    course.code k,
6607
                    course.visual_code vc,
6608
                    course.subscribe subscr,
6609
                    course.unsubscribe unsubscr,
6610
                    course.title i,
6611
                    course.tutor_name t,
6612
                    course.category_code cat,
6613
                    course.directory dir,
6614
                    course_rel_user.status status,
6615
                    course_rel_user.sort sort,
6616
                    course_rel_user.user_course_cat user_course_cat
6617
                FROM $course course, $courseRelUser course_rel_user
6618
                WHERE
6619
                    course.id = course_rel_user.c_id AND
6620
                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
6621
                    course_rel_user.user_id = '".$user_id."'
6622
                    $avoidCoursesCondition
6623
                    $visibilityCondition
6624
                ORDER BY course_rel_user.sort ASC";
6625
6626
        $result = Database::query($sql);
6627
        $courses = [];
6628
        while ($row = Database::fetch_array($result, 'ASOC')) {
6629
            $courses[] = [
6630
                'code' => $row['k'],
6631
                'visual_code' => $row['vc'],
6632
                'title' => $row['i'],
6633
                'directory' => $row['dir'],
6634
                'status' => $row['status'],
6635
                'tutor' => $row['t'],
6636
                'subscribe' => $row['subscr'],
6637
                'category' => $row['cat'],
6638
                'unsubscribe' => $row['unsubscr'],
6639
                'sort' => $row['sort'],
6640
                'user_course_category' => $row['user_course_cat'],
6641
            ];
6642
        }
6643
6644
        return $courses;
6645
    }
6646
6647
    /**
6648
     * @param string $listType
6649
     *
6650
     * @return string
6651
     */
6652
    public static function getCourseListTabs($listType)
6653
    {
6654
        $tabs = [
6655
            'simple' => [
6656
                'content' => get_lang('Standard List'),
6657
                'url' => api_get_path(WEB_CODE_PATH).'admin/course_list.php',
6658
            ],
6659
            'admin' => [
6660
                'content' => get_lang('Management List'),
6661
                'url' => api_get_path(WEB_CODE_PATH).'admin/course_list_admin.php',
6662
            ],
6663
        ];
6664
6665
        return Display::tabsOnlyLink($tabs, $listType, 'course-list');
6666
    }
6667
6668
    public static function getUrlMarker($courseId)
6669
    {
6670
        if (UrlManager::getCountAccessUrlFromCourse($courseId) > 1) {
6671
            return '&nbsp;'.Display::getMdiIcon(
6672
                'link',
6673
                null,
6674
                null,
6675
                null,
6676
                get_lang('This course is used in at least one other portal')
6677
            );
6678
        }
6679
6680
        return '';
6681
    }
6682
6683
    /**
6684
     * @throws \Doctrine\ORM\ORMException
6685
     * @throws \Doctrine\ORM\OptimisticLockException
6686
     */
6687
    public static function insertUserInCourse(User $user, Course $course, array $relationInfo = []): ?int
6688
    {
6689
        $relationInfo = array_merge(
6690
            ['relation_type' => 0, 'status' => STUDENT, 'sort' => 0, 'user_course_cat' => 0],
6691
            $relationInfo
6692
        );
6693
6694
        $courseRelUser = (new CourseRelUser())
6695
            ->setCourse($course)
6696
            ->setUser($user)
6697
            ->setStatus($relationInfo['status'])
6698
            ->setSort($relationInfo['sort'])
6699
            ->setUserCourseCat($relationInfo['user_course_cat']);
6700
6701
        $course->addSubscription($courseRelUser);
6702
6703
        $em = Database::getManager();
6704
        $em->persist($course);
6705
        $em->flush();
6706
6707
        $insertId = $courseRelUser->getId();
6708
6709
        Event::logSubscribedUserInCourse($user, $course);
6710
6711
        return $insertId;
6712
    }
6713
6714
    public static function addVisibilityOptions(FormValidator $form): void
6715
    {
6716
        $group = [];
6717
        $group[] = $form->createElement(
6718
            'radio',
6719
            'visibility',
6720
            get_lang('Course access'),
6721
            get_lang('Public - access allowed for the whole world'),
6722
            Course::OPEN_WORLD
6723
        );
6724
        $group[] = $form->createElement(
6725
            'radio',
6726
            'visibility',
6727
            null,
6728
            get_lang('Open - access allowed for users registered on the platform'),
6729
            Course::OPEN_PLATFORM
6730
        );
6731
        $group[] = $form->createElement(
6732
            'radio',
6733
            'visibility',
6734
            null,
6735
            get_lang('Private access (access authorized to group members only)'),
6736
            Course::REGISTERED
6737
        );
6738
        $group[] = $form->createElement(
6739
            'radio',
6740
            'visibility',
6741
            null,
6742
            get_lang('Closed - the course is only accessible to the teachers'),
6743
            Course::CLOSED
6744
        );
6745
        // The "hidden" visibility is only available to portal admins
6746
        if (api_is_platform_admin()) {
6747
            $group[] = $form->createElement(
6748
                'radio',
6749
                'visibility',
6750
                null,
6751
                get_lang('Hidden - Completely hidden to all users except the administrators'),
6752
                Course::HIDDEN
6753
            );
6754
        }
6755
        $form->addGroup($group, '', get_lang('Course access'));
6756
    }
6757
6758
    /**
6759
     * Check if a specific access-url-related setting is a problem or not.
6760
     *
6761
     * @param array  $_configuration The $_configuration array
6762
     * @param int    $accessUrlId    The access URL ID
6763
     * @param string $param
6764
     * @param string $msgLabel
6765
     *
6766
     * @return bool|string
6767
     */
6768
    private static function checkCreateCourseAccessUrlParam($accessUrlId, $param, $msgLabel)
6769
    {
6770
        $hostingLimit = get_hosting_limit($accessUrlId, $param);
6771
6772
        if ($hostingLimit !== null && $hostingLimit > 0) {
6773
            $num = null;
6774
            switch ($param) {
6775
                case 'hosting_limit_courses':
6776
                    $num = self::count_courses($accessUrlId);
6777
                    break;
6778
                case 'hosting_limit_active_courses':
6779
                    $num = self::countActiveCourses($accessUrlId);
6780
                    break;
6781
            }
6782
6783
            if ($num && $num >= $hostingLimit) {
6784
                api_warn_hosting_contact($param);
6785
6786
                Display::addFlash(
6787
                    Display::return_message($msgLabel)
6788
                );
6789
6790
                return true;
6791
            }
6792
        }
6793
6794
        return false;
6795
    }
6796
6797
    /**
6798
     * Fill course with all necessary items.
6799
     * @param Course $course
6800
     * @param array $params Parameters from the course creation form
6801
     * @param ?int   $authorId
6802
     * @throws Exception
6803
     */
6804
    private static function fillCourse(Course $course, array $params, ?int $authorId = 0)
6805
    {
6806
        $authorId = empty($authorId) ? api_get_user_id() : $authorId;
6807
6808
        AddCourse::fillCourse(
6809
            $course,
6810
            $params['exemplary_content'],
6811
            $authorId
6812
        );
6813
6814
        if (isset($params['gradebook_model_id'])) {
6815
            self::createDefaultGradebook(
6816
                $params['gradebook_model_id'],
6817
                $course->getId()
6818
            );
6819
        }
6820
6821
        // If parameter defined, copy the contents from a specific
6822
        // template course into this new course
6823
        if (isset($params['course_template'])) {
6824
            self::useTemplateAsBasisIfRequired(
6825
                $course->getCode(),
6826
                (int) $params['course_template']
6827
            );
6828
        }
6829
        $params['course_code'] = $course->getCode();
6830
        $params['item_id'] = $course->getId();
6831
6832
        $courseFieldValue = new ExtraFieldValue('course');
6833
        //$courseFieldValue->saveFieldValues($params);
6834
    }
6835
6836
    /**
6837
     * Global hosting limit for users per course.
6838
     * Returns 0 when the limit is disabled.
6839
     */
6840
    public static function getGlobalUsersPerCourseLimit(): int
6841
    {
6842
        return (int) api_get_setting('platform.hosting_limit_users_per_course');
6843
    }
6844
6845
    /**
6846
     * Count current users in course for the global limit.
6847
     * Excludes HR relation type (keeps legacy behaviour).
6848
     */
6849
    public static function countUsersForGlobalLimit(int $courseId): int
6850
    {
6851
        $courseId = (int) $courseId;
6852
        if ($courseId <= 0) {
6853
            return 0;
6854
        }
6855
6856
        $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6857
6858
        $sql = "SELECT COUNT(*) AS total
6859
                FROM $table
6860
                WHERE c_id = $courseId
6861
                  AND relation_type <> ".COURSE_RELATION_TYPE_RRHH;
6862
6863
        $res = Database::query($sql);
6864
        $row = Database::fetch_array($res, 'ASSOC') ?: [];
6865
6866
        return (int) ($row['total'] ?? 0);
6867
    }
6868
6869
    /**
6870
     * Check if subscribing the given users (no session) would exceed the global limit.
6871
     *
6872
     * @param int   $courseId
6873
     * @param int[] $userIds
6874
     */
6875
    public static function wouldOperationExceedGlobalLimit(int $courseId, array $userIds): bool
6876
    {
6877
        $limit = self::getGlobalUsersPerCourseLimit();
6878
        if ($limit <= 0) {
6879
            // No global limit configured.
6880
            return false;
6881
        }
6882
6883
        $courseId = (int) $courseId;
6884
        if ($courseId <= 0 || empty($userIds)) {
6885
            return false;
6886
        }
6887
6888
        // Keep unique, positive IDs only.
6889
        $userIds = array_values(array_unique(array_map('intval', $userIds)));
6890
        $userIds = array_filter(
6891
            $userIds,
6892
            static function (int $id): bool {
6893
                return $id > 0;
6894
            }
6895
        );
6896
6897
        if (empty($userIds)) {
6898
            return false;
6899
        }
6900
6901
        $table  = Database::get_main_table(TABLE_MAIN_COURSE_USER);
6902
        $idList = implode(',', $userIds);
6903
6904
        // How many of these users are already in the course?
6905
        $sql = "SELECT COUNT(DISTINCT user_id) AS already
6906
                FROM $table
6907
                WHERE c_id = $courseId
6908
                  AND relation_type <> ".COURSE_RELATION_TYPE_RRHH."
6909
                  AND user_id IN ($idList)";
6910
6911
        $res = Database::query($sql);
6912
        $row = Database::fetch_array($res, 'ASSOC') ?: [];
6913
        $already = (int) ($row['already'] ?? 0);
6914
6915
        $newCount = count($userIds) - $already;
6916
        if ($newCount <= 0) {
6917
            // Nothing new to subscribe, cannot exceed.
6918
            return false;
6919
        }
6920
6921
        $current = self::countUsersForGlobalLimit($courseId);
6922
6923
        return ($current + $newCount) > $limit;
6924
    }
6925
6926
    /**
6927
     * Build message when a *manual* subscription operation must be cancelled.
6928
     */
6929
    public static function getGlobalLimitCancelMessage(): string
6930
    {
6931
        $limit = self::getGlobalUsersPerCourseLimit();
6932
6933
        return sprintf(
6934
            get_lang(
6935
                'This operation would exceed the limit of %d users per course set by the administrators. The whole subscription operation has been cancelled.'
6936
            ),
6937
            $limit
6938
        );
6939
    }
6940
6941
    /**
6942
     * Build message for *batch imports* when some subscriptions hit the limit.
6943
     *
6944
     * @param string[] $pairs List of "username / Course title" strings.
6945
     */
6946
    public static function getGlobalLimitPartialImportMessage(array $pairs): string
6947
    {
6948
        $limit = self::getGlobalUsersPerCourseLimit();
6949
6950
        $safePairs = array_map(
6951
            static function (string $pair): string {
6952
                return Security::remove_XSS($pair);
6953
            },
6954
            $pairs
6955
        );
6956
6957
        $list = implode(', ', $safePairs);
6958
6959
        return sprintf(
6960
            get_lang(
6961
                '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'
6962
            ),
6963
            $limit,
6964
            $list
6965
        );
6966
    }
6967
}
6968