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