CourseManager::getCoursesFollowedByUser()   C
last analyzed

Complexity

Conditions 14

Size

Total Lines 107
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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