CourseManager::getUserListFromCourseId()   F
last analyzed

Complexity

Conditions 63

Size

Total Lines 397
Code Lines 230

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 63
eloc 230
nop 15
dl 0
loc 397
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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