CoursesAndSessionsCatalog   F
last analyzed

Complexity

Total Complexity 289

Size/Duplication

Total Lines 2440
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 289
eloc 1340
c 3
b 0
f 0
dl 0
loc 2440
rs 0.8

46 Methods

Rating   Name   Duplication   Size   Complexity  
A showCourses() 0 5 2
A is() 0 5 1
A showSessions() 0 5 2
B getCoursesToAvoid() 0 38 7
B getCourseCategoriesTree() 0 66 10
A getCoursesToShowInCatalogue() 0 25 3
A getAvoidCourseCondition() 0 15 4
A getLimitFilterFromArray() 0 10 5
A countAvailableCoursesToShowInCatalog() 0 30 2
D getCoursesInCategory() 0 168 16
A getLimitArray() 0 9 3
A sessionsListByName() 0 35 1
A getSessionsByName() 0 42 3
A buildCourseCategoryTree() 0 27 5
B browseSessions() 0 70 6
A childrenCategories() 0 18 6
A courseStandardSortOrder() 0 11 1
A getCourseExtraFieldsAvailableForSorting() 0 10 3
F searchCourses() 0 131 13
A courseSortOrder() 0 3 1
A getTabList() 0 26 5
B getRegisteredInSessionButton() 0 74 7
B return_teacher() 0 52 6
A returnThumbnail() 0 17 2
A getCatalogSearchSettings() 0 20 2
A getAlreadyRegisteredInSessionLabel() 0 9 1
B return_register_button() 0 67 9
A browseSessionsByTags() 0 67 3
A getSessionPagination() 0 16 2
A sessionsListByCoursesTag() 0 36 1
A hideFromSessionCatalogCondition() 0 26 2
A courseSortOptions() 0 20 4
A getRequirements() 0 26 1
A courseExtraFieldSortingOrder() 0 9 2
D searchAndSortCourses() 0 68 20
A return_unregister_button() 0 15 1
A return_already_registered_label() 0 21 2
A sessionList() 0 45 2
B getCatalogPagination() 0 81 7
F getFormattedSessionsBlock() 0 129 16
A getPageNumberItem() 0 29 6
B generateRedirectUrlAfterSubscription() 0 23 7
F displayCoursesList() 0 368 47
F getCatalogUrl() 0 60 16
A userCanView() 0 15 5
D getCoursesToShowInCatalogueCondition() 0 48 19

How to fix   Complexity   

Complex Class

Complex classes like CoursesAndSessionsCatalog often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CoursesAndSessionsCatalog, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\ExtraField;
6
use Chamilo\CoreBundle\Entity\Repository\SequenceResourceRepository;
7
use Chamilo\CoreBundle\Entity\SequenceResource;
8
use Chamilo\CoreBundle\Entity\SessionRelCourse;
9
use Chamilo\CoreBundle\Entity\Tag;
10
use Doctrine\ORM\Query\Expr\Join;
11
use ExtraField as ExtraFieldModel;
12
13
/**
14
 * @todo change class name
15
 */
16
class CoursesAndSessionsCatalog
17
{
18
    public const PAGE_LENGTH = 12;
19
20
    /**
21
     * Check the configuration for the courses and sessions catalog.
22
     */
23
    public static function is(int $value = CATALOG_COURSES): bool
24
    {
25
        $showCoursesSessions = (int) api_get_setting('catalog_show_courses_sessions');
26
27
        return $showCoursesSessions == $value;
28
    }
29
30
    /**
31
     * Check whether to display the sessions list.
32
     */
33
    public static function showSessions(): bool
34
    {
35
        $catalogShow = (int) api_get_setting('catalog_show_courses_sessions');
36
37
        return $catalogShow == CATALOG_SESSIONS || $catalogShow == CATALOG_COURSES_SESSIONS;
38
    }
39
40
    /**
41
     * Check whether to display the courses list.
42
     */
43
    public static function showCourses(): bool
44
    {
45
        $catalogShow = (int) api_get_setting('catalog_show_courses_sessions');
46
47
        return $catalogShow == CATALOG_COURSES || $catalogShow == CATALOG_COURSES_SESSIONS;
48
    }
49
50
    /**
51
     * @return array
52
     */
53
    public static function getCoursesToAvoid()
54
    {
55
        $TABLE_COURSE_FIELD = Database::get_main_table(TABLE_EXTRA_FIELD);
56
        $TABLE_COURSE_FIELD_VALUE = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
57
58
        // Check special courses
59
        $courseListToAvoid = CourseManager::get_special_course_list();
60
61
        $categoryToAvoid = api_get_configuration_value('course_category_code_to_use_as_model');
62
        if (!empty($categoryToAvoid) && api_is_student()) {
63
            $coursesInCategoryToAvoid = CourseCategory::getCoursesInCategory($categoryToAvoid, '', false);
64
            if (!empty($coursesInCategoryToAvoid)) {
65
                foreach ($coursesInCategoryToAvoid as $courseToAvoid) {
66
                    $courseListToAvoid[] = $courseToAvoid['id'];
67
                }
68
            }
69
        }
70
71
        // Checks "hide_from_catalog" extra field
72
        $extraFieldType = ExtraField::COURSE_FIELD_TYPE;
73
74
        $sql = "SELECT item_id FROM $TABLE_COURSE_FIELD_VALUE tcfv
75
                INNER JOIN $TABLE_COURSE_FIELD tcf
76
                ON tcfv.field_id =  tcf.id
77
                WHERE
78
                    tcf.extra_field_type = $extraFieldType AND
79
                    tcf.variable = 'hide_from_catalog' AND
80
                    tcfv.value = 1
81
                ";
82
83
        $result = Database::query($sql);
84
        if (Database::num_rows($result) > 0) {
85
            while ($row = Database::fetch_array($result)) {
86
                $courseListToAvoid[] = $row['item_id'];
87
            }
88
        }
89
90
        return $courseListToAvoid;
91
    }
92
93
    public static function getCoursesToShowInCatalogue()
94
    {
95
        $tblCourseField = Database::get_main_table(TABLE_EXTRA_FIELD);
96
        $tblCourseFieldValue = Database::get_main_table(TABLE_EXTRA_FIELD_VALUES);
97
        $courseListToShow = [];
98
99
        // Checks "show_in_catalog" extra field
100
        $extraFieldType = ExtraField::COURSE_FIELD_TYPE;
101
        $sql = "SELECT item_id FROM $tblCourseFieldValue tcfv
102
                INNER JOIN $tblCourseField tcf
103
                ON tcfv.field_id =  tcf.id
104
                WHERE
105
                    tcf.extra_field_type = $extraFieldType AND
106
                    tcf.variable = 'show_in_catalogue' AND
107
                    tcfv.value = 1
108
                ";
109
110
        $result = Database::query($sql);
111
        if (Database::num_rows($result) > 0) {
112
            while ($row = Database::fetch_array($result)) {
113
                $courseListToShow[] = $row['item_id'];
114
            }
115
        }
116
117
        return $courseListToShow;
118
    }
119
120
    /**
121
     * @return string
122
     */
123
    public static function getCoursesToShowInCatalogueCondition()
124
    {
125
        $categoriesToShow = api_get_configuration_value('courses_catalogue_show_only_category');
126
        $coursesToShow = api_get_configuration_value('show_courses_in_catalogue');
127
        $coursesCategoryInCatalogue = [];
128
        if (!empty($categoriesToShow)) {
129
            foreach ($categoriesToShow as $categoryCode) {
130
                $courseCategories = CourseCategory::getCoursesInCategory($categoryCode, '', false);
131
                if (!empty($courseCategories)) {
132
                    foreach ($courseCategories as $course) {
133
                        $coursesCategoryInCatalogue[] = $course['id'];
134
                    }
135
                }
136
            }
137
        }
138
139
        $courseListToShow = self::getCoursesToShowInCatalogue();
140
        $courses = [];
141
        if (!empty($coursesCategoryInCatalogue)) {
142
            foreach ($coursesCategoryInCatalogue as $courseId) {
143
                if ($coursesToShow && !in_array($courseId, $courseListToShow)) {
144
                    continue;
145
                }
146
                $courses[] = '"'.$courseId.'"';
147
            }
148
        }
149
150
        if ($coursesToShow && !empty($courseListToShow)) {
151
            foreach ($courseListToShow as $courseId) {
152
                if (!empty($categoriesToShow) && !in_array($courseId, $coursesCategoryInCatalogue)) {
153
                    continue;
154
                }
155
                $courses[] = '"'.$courseId.'"';
156
            }
157
        }
158
159
        if (empty($courses)) {
160
            if ($categoriesToShow || (!$categoriesToShow && $coursesToShow)) {
161
                $courses[] = 0;
162
            }
163
        }
164
165
        $condition = '';
166
        if (!empty($courses)) {
167
            $condition = ' AND course.id IN ('.implode(',', $courses).')';
168
        }
169
170
        return $condition;
171
    }
172
173
    /**
174
     * @return string
175
     */
176
    public static function getAvoidCourseCondition()
177
    {
178
        $courseListToAvoid = self::getCoursesToAvoid();
179
        $condition = '';
180
        if (!empty($courseListToAvoid)) {
181
            $courses = [];
182
            foreach ($courseListToAvoid as $courseId) {
183
                $courses[] = '"'.$courseId.'"';
184
            }
185
            if (!empty($courses)) {
186
                $condition = ' AND course.id NOT IN ('.implode(',', $courses).')';
187
            }
188
        }
189
190
        return $condition;
191
    }
192
193
    /**
194
     * Get available le courses count.
195
     *
196
     * @param int $accessUrlId (optional)
197
     *
198
     * @return int Number of courses
199
     */
200
    public static function countAvailableCoursesToShowInCatalog($accessUrlId = 1)
201
    {
202
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
203
        $tableCourseRelAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
204
        $courseToAvoidCondition = self::getAvoidCourseCondition();
205
        $courseToShowCondition = self::getCoursesToShowInCatalogueCondition();
206
        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition('course', true);
207
208
        $accessUrlId = (int) $accessUrlId;
209
        if (empty($accessUrlId)) {
210
            $accessUrlId = 1;
211
        }
212
213
        $sql = "SELECT count(course.id)
214
                FROM $tableCourse course
215
                INNER JOIN $tableCourseRelAccessUrl u
216
                ON (course.id = u.c_id)
217
                WHERE
218
                    u.access_url_id = $accessUrlId AND
219
                    course.visibility != 0 AND
220
                    course.visibility != 4
221
                    $courseToAvoidCondition
222
                    $courseToShowCondition
223
                    $visibilityCondition
224
                ";
225
226
        $res = Database::query($sql);
227
        $row = Database::fetch_row($res);
228
229
        return $row[0];
230
    }
231
232
    public static function getCourseCategoriesTree()
233
    {
234
        $urlId = 1;
235
        if (api_is_multiple_url_enabled()) {
236
            $urlId = api_get_current_access_url_id();
237
        }
238
239
        $countCourses = self::countAvailableCoursesToShowInCatalog($urlId);
240
        $categories = [];
241
        $list = [];
242
243
        $categories['ALL'] = [
244
            'id' => 0,
245
            'name' => get_lang('DisplayAll'),
246
            'code' => 'ALL',
247
            'parent_id' => null,
248
            'tree_pos' => 0,
249
            'number_courses' => $countCourses,
250
            'level' => 0,
251
        ];
252
253
        $allCategories = CourseCategory::getAllCategories();
254
        $categoryToAvoid = '';
255
        if (api_is_student()) {
256
            $categoryToAvoid = api_get_configuration_value('course_category_code_to_use_as_model');
257
        }
258
259
        $showOnlyCategory = api_get_configuration_value('courses_catalogue_show_only_category');
260
        foreach ($allCategories as $category) {
261
            $categoryCode = $category['code'];
262
            if (!(empty($showOnlyCategory) || in_array($categoryCode, $showOnlyCategory))) {
263
                continue;
264
            }
265
            if (!empty($categoryToAvoid) && $categoryToAvoid == $categoryCode) {
266
                continue;
267
            }
268
269
            if (empty($category['parent_id'])) {
270
                $list[$categoryCode] = $category;
271
                $list[$categoryCode]['level'] = 0;
272
                list($subList, $childrenCount) = self::buildCourseCategoryTree($allCategories, $categoryCode, 0);
273
                foreach ($subList as $item) {
274
                    $list[$item['code']] = $item;
275
                }
276
                // Real course count
277
                $countCourses = CourseCategory::countCoursesInCategory($categoryCode);
278
                $list[$categoryCode]['number_courses'] = $childrenCount + $countCourses;
279
            }
280
        }
281
282
        // count courses that are in no category
283
        $countCourses = CourseCategory::countCoursesInCategory('NONE');
284
        $categories['NONE'] = [
285
            'id' => 0,
286
            'name' => get_lang('WithoutCategory'),
287
            'code' => 'NONE',
288
            'parent_id' => null,
289
            'tree_pos' => 0,
290
            'children_count' => 0,
291
            'auth_course_child' => true,
292
            'auth_cat_child' => true,
293
            'number_courses' => $countCourses,
294
            'level' => 0,
295
        ];
296
297
        return array_merge($list, $categories);
298
    }
299
300
    /**
301
     * Return LIMIT to filter SQL query.
302
     *
303
     * @param array $limit
304
     *
305
     * @return string
306
     */
307
    public static function getLimitFilterFromArray($limit)
308
    {
309
        $limitFilter = '';
310
        if (!empty($limit) && is_array($limit)) {
311
            $limitStart = isset($limit['start']) ? (int) $limit['start'] : 0;
312
            $limitLength = isset($limit['length']) ? (int) $limit['length'] : 12;
313
            $limitFilter = 'LIMIT '.$limitStart.', '.$limitLength;
314
        }
315
316
        return $limitFilter;
317
    }
318
319
    /**
320
     * @param string $categoryCode
321
     * @param int    $randomValue
322
     * @param array  $limit        will be used if $randomValue is not set.
323
     *                             This array should contain 'start' and 'length' keys
324
     *
325
     * @return array
326
     */
327
    public static function getCoursesInCategory(string $categoryCode, $randomValue = null, $limit = [])
328
    {
329
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
330
        $avoidCoursesCondition = self::getAvoidCourseCondition();
331
        $showCoursesCondition = self::getCoursesToShowInCatalogueCondition();
332
        $visibilityCondition = CourseManager::getCourseVisibilitySQLCondition('course', true);
333
334
        if (!empty($randomValue)) {
335
            $randomValue = (int) $randomValue;
336
337
            $sql = "SELECT COUNT(*) FROM $tbl_course";
338
            $result = Database::query($sql);
339
            list($num_records) = Database::fetch_row($result);
340
341
            if (api_is_multiple_url_enabled()) {
342
                $urlId = api_get_current_access_url_id();
343
                $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
344
345
                $urlCondition = ' access_url_id = '.$urlId.' ';
346
                $allowBaseCategories = api_get_configuration_value('allow_base_course_category');
347
                if ($allowBaseCategories) {
348
                    $urlCondition = ' (access_url_id = '.$urlId.' OR access_url_id = 1)  ';
349
                }
350
351
                $sql = "SELECT COUNT(*)
352
                        FROM $tbl_course course
353
                        INNER JOIN $tbl_url_rel_course as url_rel_course
354
                        ON (url_rel_course.c_id = course.id)
355
                        WHERE access_url_id = $urlId $showCoursesCondition";
356
                $result = Database::query($sql);
357
                list($num_records) = Database::fetch_row($result);
358
359
                $sql = "SELECT course.id, course.id as real_id
360
                        FROM $tbl_course course
361
                        INNER JOIN $tbl_url_rel_course as url_rel_course
362
                        ON (url_rel_course.c_id = course.id)
363
                        WHERE
364
                            $urlCondition AND
365
                            RAND()*$num_records< $randomValue
366
                            $avoidCoursesCondition
367
                            $showCoursesCondition
368
                            $visibilityCondition
369
                        ORDER BY RAND()
370
                        LIMIT 0, $randomValue";
371
            } else {
372
                $sql = "SELECT id, id as real_id
373
                        FROM $tbl_course course
374
                        WHERE
375
                            RAND()*$num_records< $randomValue
376
                            $avoidCoursesCondition
377
                            $showCoursesCondition
378
                            $visibilityCondition
379
                        ORDER BY RAND()
380
                        LIMIT 0, $randomValue";
381
            }
382
383
            $result = Database::query($sql);
384
            $id_in = null;
385
            while (list($id) = Database::fetch_row($result)) {
386
                if ($id_in) {
387
                    $id_in .= ",$id";
388
                } else {
389
                    $id_in = "$id";
390
                }
391
            }
392
            if (null === $id_in) {
393
                return [];
394
            }
395
            $sql = "SELECT *, id as real_id FROM $tbl_course WHERE id IN($id_in)";
396
        } else {
397
            $limitFilter = self::getLimitFilterFromArray($limit);
398
            $categoryCode = Database::escape_string($categoryCode);
399
            $listCode = self::childrenCategories($categoryCode);
400
            $conditionCode = ' ';
401
402
            if (empty($listCode)) {
403
                if ($categoryCode === 'NONE') {
404
                    $conditionCode .= " category_code='' ";
405
                } else {
406
                    $conditionCode .= " category_code='$categoryCode' ";
407
                }
408
            } else {
409
                foreach ($listCode as $code) {
410
                    $conditionCode .= " category_code='$code' OR ";
411
                }
412
                $conditionCode .= " category_code='$categoryCode' ";
413
            }
414
415
            if (empty($categoryCode) || $categoryCode === 'ALL') {
416
                $sql = "SELECT *, id as real_id
417
                        FROM $tbl_course course
418
                        WHERE
419
                          1=1
420
                          $avoidCoursesCondition
421
                          $showCoursesCondition
422
                          $visibilityCondition
423
                        ORDER BY title $limitFilter ";
424
            } else {
425
                $sql = "SELECT *, id as real_id FROM $tbl_course course
426
                        WHERE
427
                            $conditionCode
428
                            $avoidCoursesCondition
429
                            $showCoursesCondition
430
                            $visibilityCondition
431
                        ORDER BY title $limitFilter ";
432
            }
433
434
            // Showing only the courses of the current Chamilo access_url_id
435
            if (api_is_multiple_url_enabled()) {
436
                $urlId = api_get_current_access_url_id();
437
                $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
438
439
                $urlCondition = ' access_url_id = '.$urlId.' ';
440
                if ($categoryCode !== 'ALL') {
441
                    $sql = "SELECT *, course.id real_id
442
                            FROM $tbl_course as course
443
                            INNER JOIN $tbl_url_rel_course as url_rel_course
444
                            ON (url_rel_course.c_id = course.id)
445
                            WHERE
446
                                $urlCondition AND
447
                                $conditionCode
448
                                $avoidCoursesCondition
449
                                $showCoursesCondition
450
                                $visibilityCondition
451
                            ORDER BY title $limitFilter";
452
                } else {
453
                    $sql = "SELECT *, course.id real_id FROM $tbl_course as course
454
                            INNER JOIN $tbl_url_rel_course as url_rel_course
455
                            ON (url_rel_course.c_id = course.id)
456
                            WHERE
457
                                $urlCondition
458
                                $avoidCoursesCondition
459
                                $showCoursesCondition
460
                                $visibilityCondition
461
                            ORDER BY title $limitFilter";
462
                }
463
            }
464
        }
465
466
        $result = Database::query($sql);
467
        $courses = [];
468
        while ($row = Database::fetch_array($result)) {
469
            $row['registration_code'] = !empty($row['registration_code']);
470
            $count_users = CourseManager::get_users_count_in_course($row['code']);
471
472
            if ($row['tutor_name'] == '0') {
473
                $row['tutor_name'] = get_lang('NoManager');
474
            }
475
476
            $courses[] = [
477
                'real_id' => $row['real_id'],
478
                'point_info' => CourseManager::get_course_ranking($row['id'], 0),
479
                'code' => $row['code'],
480
                'directory' => $row['directory'],
481
                'visual_code' => $row['visual_code'],
482
                'title' => $row['title'],
483
                'tutor' => $row['tutor_name'],
484
                'subscribe' => $row['subscribe'],
485
                'unsubscribe' => $row['unsubscribe'],
486
                'registration_code' => $row['registration_code'],
487
                'creation_date' => $row['creation_date'],
488
                'visibility' => $row['visibility'],
489
                'category' => $row['category_code'],
490
                'count_users' => $count_users,
491
            ];
492
        }
493
494
        return $courses;
495
    }
496
497
    /**
498
     * Search the courses database for a course that matches the search term.
499
     * The search is done on the code, title and tutor field of the course table.
500
     *
501
     * @param string $categoryCode
502
     * @param string $keyword              The string that the user submitted
503
     * @param array  $limit
504
     * @param bool   $justVisible          search only on visible courses in the catalogue
505
     * @param array  $conditions
506
     * @param string $courseLanguageFilter search only courses in the indicated language
507
     *
508
     * @return array an array containing a list of all the courses matching the the search term
509
     */
510
    public static function searchCourses(
511
        $categoryCode,
512
        $keyword,
513
        $limit,
514
        $justVisible = false,
515
        $conditions = [],
516
        $courseLanguageFilter = null
517
    ) {
518
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
519
        $limitFilter = self::getLimitFilterFromArray($limit);
520
        $avoidCoursesCondition = self::getAvoidCourseCondition();
521
        $showCoursesCondition = self::getCoursesToShowInCatalogueCondition();
522
        $visibilityCondition = $justVisible ? CourseManager::getCourseVisibilitySQLCondition('course', true) : '';
523
524
        $keyword = Database::escape_string($keyword);
525
        $categoryCode = Database::escape_string($categoryCode);
526
527
        $sqlInjectJoins = '';
528
        $courseLanguageWhere = '';
529
        $where = 'AND 1 = 1 ';
530
        $sqlInjectWhere = '';
531
        $injectExtraFields = '1';
532
        if (!empty($conditions)) {
533
            $sqlInjectJoins = $conditions['inject_joins'];
534
            $where = $conditions['where'];
535
            $sqlInjectWhere = $conditions['inject_where'];
536
            $injectExtraFields = !empty($conditions['inject_extra_fields']) ? $conditions['inject_extra_fields'] : 1;
537
            $injectExtraFields = rtrim($injectExtraFields, ', ');
538
        }
539
540
        // If have courseLanguageFilter, search for it
541
        if (!empty($courseLanguageFilter)) {
542
            $courseLanguageFilter = Database::escape_string($courseLanguageFilter);
543
            $courseLanguageWhere = "AND course.course_language = '$courseLanguageFilter'";
544
        }
545
546
        $categoryFilter = '';
547
        if ($categoryCode === 'ALL' || empty($categoryCode)) {
548
            // Nothing to do
549
        } elseif ($categoryCode === 'NONE') {
550
            $categoryFilter = ' AND category_code = "" ';
551
        } else {
552
            $categoryFilter = ' AND category_code = "'.$categoryCode.'" ';
553
        }
554
555
        //$sql = "SELECT DISTINCT course.*, $injectExtraFields
556
        $sql = "SELECT DISTINCT(course.id)
557
                FROM $courseTable course
558
                $sqlInjectJoins
559
                WHERE (
560
                        course.code LIKE '%".$keyword."%' OR
561
                        course.title LIKE '%".$keyword."%' OR
562
                        course.tutor_name LIKE '%".$keyword."%'
563
                    )
564
                    $where
565
                    $categoryFilter
566
                    $sqlInjectWhere
567
                    $courseLanguageWhere
568
                    $avoidCoursesCondition
569
                    $showCoursesCondition
570
                    $visibilityCondition
571
                ORDER BY title, visual_code ASC
572
                $limitFilter
573
                ";
574
575
        if (api_is_multiple_url_enabled()) {
576
            $urlId = api_get_current_access_url_id();
577
            if (-1 != $urlId) {
578
                $tbl_url_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
579
                $urlCondition = ' access_url_id = '.$urlId.' AND';
580
                $allowBaseCategories = api_get_configuration_value('allow_base_course_category');
581
                if ($allowBaseCategories) {
582
                    $urlCondition = ' (access_url_id = '.$urlId.' OR access_url_id = 1) AND ';
583
                }
584
                //SELECT DISTINCT course.*, $injectExtraFields
585
                $sql = "SELECT DISTINCT(course.id)
586
                        FROM $courseTable as course
587
                        INNER JOIN $tbl_url_rel_course as url_rel_course
588
                        ON (url_rel_course.c_id = course.id)
589
                        $sqlInjectJoins
590
                        WHERE
591
                            access_url_id = $urlId AND
592
                            (
593
                                code LIKE '%".$keyword."%' OR
594
                                title LIKE '%".$keyword."%' OR
595
                                tutor_name LIKE '%".$keyword."%'
596
                            )
597
                            $where
598
                            $categoryFilter
599
                            $sqlInjectWhere
600
                            $courseLanguageWhere
601
                            $avoidCoursesCondition
602
                            $showCoursesCondition
603
                            $visibilityCondition
604
                        ORDER BY title, visual_code ASC
605
                        $limitFilter
606
                       ";
607
            }
608
        }
609
610
        $result = Database::query($sql);
611
        $courses = [];
612
        while ($row = Database::fetch_array($result)) {
613
            $courseId = $row['id'];
614
            $courseInfo = api_get_course_info_by_id($courseId);
615
            if (empty($courseInfo)) {
616
                continue;
617
            }
618
            $courseCode = $courseInfo['code'];
619
620
            $countUsers = CourseManager::get_user_list_from_course_code(
621
                $courseCode,
622
                0,
623
                null,
624
                null,
625
                null,
626
                true
627
            );
628
629
            $courseInfo['point_info'] = CourseManager::get_course_ranking($courseId, 0);
630
            $courseInfo['tutor'] = $courseInfo['tutor_name'];
631
            $courseInfo['registration_code'] = !empty($courseInfo['registration_code']);
632
            $courseInfo['count_users'] = $countUsers;
633
            $courseInfo['course_language'] = api_get_language_info(
634
                api_get_language_id($courseInfo['course_language'])
635
            )['original_name'];
636
637
            $courses[] = $courseInfo;
638
        }
639
640
        return $courses;
641
    }
642
643
    /**
644
     * Gets extra fields listed in configuration option course_catalog_settings/extra_field_sort_options sorting order.
645
     *
646
     * @return array "extra_field_$id" => order (1 = ascending, -1 = descending)
647
     */
648
    public static function courseExtraFieldSortingOrder()
649
    {
650
        $order = [];
651
        $variableOrder = api_get_configuration_sub_value('course_catalog_settings/extra_field_sort_options', []);
652
        foreach (self::getCourseExtraFieldsAvailableForSorting() as $extraField) {
653
            $order['extra_field_'.$extraField->getId()] = $variableOrder[$extraField->getVariable()];
654
        }
655
656
        return $order;
657
    }
658
659
    /**
660
     * Gets the extra fields listed in configuration option course_catalog_settings/extra_field_sort_options.
661
     *
662
     * @return ExtraField[]
663
     */
664
    public static function getCourseExtraFieldsAvailableForSorting()
665
    {
666
        $variables = array_keys(
667
            api_get_configuration_sub_value('course_catalog_settings/extra_field_sort_options', [])
668
        );
669
        if (is_array($variables) && !empty($variables)) {
670
            return ExtraField::getExtraFieldsFromVariablesOrdered($variables, ExtraField::COURSE_FIELD_TYPE);
671
        }
672
673
        return [];
674
    }
675
676
    /**
677
     * Builds the list of possible course standard sort criteria.
678
     *
679
     * @return array option name => order (1 = ascending, -1 = descending)
680
     */
681
    public static function courseStandardSortOrder()
682
    {
683
        return api_get_configuration_sub_value(
684
            'course_catalog_settings/standard_sort_options',
685
            [
686
                'title' => 1,
687
                'creation_date' => -1,
688
                'count_users' => -1, // subscription count
689
                'point_info/point_average' => -1, // average score
690
                'point_info/total_score' => -1, // score sum
691
                'point_info/users' => -1, // vote count
692
            ]
693
        );
694
    }
695
696
    /**
697
     * Builds the list of possible course sort criteria to be used in an HTML select element.
698
     *
699
     * @return array select option name => display text
700
     */
701
    public static function courseSortOptions()
702
    {
703
        /** @var $extraFields ExtraField[] */
704
        $standardLabels = [
705
            'title' => get_lang('Title'),
706
            'creation_date' => get_lang('CreationDate'),
707
            'count_users' => get_lang('SubscriptionCount'),
708
            'point_info/point_average' => get_lang('PointAverage'),
709
            'point_info/total_score' => get_lang('TotalScore'),
710
            'point_info/users' => get_lang('VoteCount'),
711
        ];
712
        $options = [];
713
        foreach (array_keys(self::courseStandardSortOrder()) as $name) {
714
            $options[$name] = $standardLabels[$name] ?: $name;
715
        }
716
        foreach (self::getCourseExtraFieldsAvailableForSorting() as $extraField) {
717
            $options['extra_field_'.$extraField->getId()] = $extraField->getDisplayText();
718
        }
719
720
        return $options;
721
    }
722
723
    public static function courseSortOrder()
724
    {
725
        return self::courseStandardSortOrder() + self::courseExtraFieldSortingOrder();
726
    }
727
728
    /**
729
     * Wrapper for self::searchCourses which locally sorts the results according to $sortKey.
730
     *
731
     * @param string   $categoryCode         can be 'ALL', 'NONE' or any existing course category code
732
     * @param string   $keyword              search pattern to be found in course code, title or tutor_name
733
     * @param array    $limit                associative array generated by \CoursesAndSessionsCatalog::getLimitArray()
734
     * @param bool     $justVisible          search only on visible courses in the catalogue
735
     * @param array    $conditions           associative array generated using \ExtraField::parseConditions
736
     * @param string[] $sortKeys             a subset of the keys of the array returned by courseSortOptions()
737
     * @param string   $courseLanguageFilter search only courses in the indicated language
738
     *
739
     * @return array list of all the courses matching the the search term
740
     */
741
    public static function searchAndSortCourses(
742
        $categoryCode,
743
        $keyword,
744
        $limit,
745
        $justVisible = false,
746
        $conditions = [],
747
        $sortKeys = [],
748
        $courseLanguageFilter = null
749
    ) {
750
        // Get ALL matching courses (no limit)
751
        $courses = self::searchCourses($categoryCode, $keyword, null, $justVisible, $conditions, $courseLanguageFilter);
752
        // Do we have extra fields to sort on ?
753
        $extraFieldsToSortOn = [];
754
        foreach (self::getCourseExtraFieldsAvailableForSorting() as $extraField) {
755
            if (in_array('extra_field_'.$extraField->getId(), $sortKeys)) {
756
                $extraFieldsToSortOn[] = $extraField;
757
            }
758
        }
759
        if (!empty($extraFieldsToSortOn)) {
760
            // load extra field values and store them in $courses
761
            $courseIds = [];
762
            foreach ($courses as $course) {
763
                $courseIds[] = $course['real_id'];
764
            }
765
            $values = ExtraField::getValueForEachExtraFieldForEachItem($extraFieldsToSortOn, $courseIds);
766
            foreach ($courses as &$course) {
767
                $courseId = $course['real_id'];
768
                if (array_key_exists($courseId, $values)) {
769
                    foreach ($values[$courseId] as $extraFieldId => $value) {
770
                        $course['extra_field_'.$extraFieldId] = $value;
771
                    }
772
                }
773
            }
774
            unset($course);
775
        }
776
777
        // do we have $course['groupKey']['subKey'] to sort on, such as 'point_info/users' ?
778
        foreach ($sortKeys as $key) {
779
            if (false !== strpos($key, '/')) {
780
                foreach ($courses as &$course) {
781
                    $subValue = api_array_sub_value($course, $key);
782
                    if (!is_null($subValue)) {
783
                        $course[$key] = $subValue;
784
                    }
785
                }
786
                unset($course);
787
            }
788
        }
789
        $sortOrder = self::courseSortOrder();
790
        usort($courses, function ($a, $b) use ($sortKeys, $sortOrder) {
791
            foreach ($sortKeys as $key) {
792
                $valueA = array_key_exists($key, $a) ? $a[$key] : null;
793
                $valueB = array_key_exists($key, $b) ? $b[$key] : null;
794
                if ($valueA !== $valueB) {
795
                    $aIsLessThanB = (is_string($valueA) && is_string($valueB))
796
                        ? strtolower($valueA) < strtolower($valueB)
797
                        : $valueA < $valueB;
798
                    $reverseOrder = (array_key_exists($key, $sortOrder) && -1 === $sortOrder[$key]);
799
                    $aIsBeforeB = ($aIsLessThanB xor $reverseOrder);
800
801
                    return $aIsBeforeB ? -1 : 1;
802
                }
803
            }
804
805
            return 0;
806
        });
807
808
        return array_slice($courses, $limit['start'], $limit['length']);
809
    }
810
811
    /**
812
     * List the sessions.
813
     *
814
     * @param string $date
815
     * @param array  $limit
816
     * @param bool   $returnQueryBuilder
817
     * @param bool   $getCount
818
     *
819
     * @return array|\Doctrine\ORM\Query The session list
820
     */
821
    public static function browseSessions($date = null, $limit = [], $returnQueryBuilder = false, $getCount = false)
822
    {
823
        $urlId = api_get_current_access_url_id();
824
        $em = Database::getManager();
825
        $qb = $em->createQueryBuilder();
826
        $qb2 = $em->createQueryBuilder();
827
828
        $qb = $qb
829
            ->select('s')
830
            ->from('ChamiloCoreBundle:Session', 's')
831
            ->where(
832
                $qb->expr()->in(
833
                    's',
834
                    $qb2
835
                        ->select('s2')
836
                        ->from('ChamiloCoreBundle:AccessUrlRelSession', 'url')
837
                        ->join('ChamiloCoreBundle:Session', 's2')
838
                        ->where(
839
                            $qb->expr()->eq('url.sessionId ', 's2.id')
840
                        )->andWhere(
841
                            $qb->expr()->eq('url.accessUrlId ', $urlId))
842
                        ->getDQL()
843
                )
844
            )
845
            ->andWhere($qb->expr()->gt('s.nbrCourses', 0))
846
        ;
847
848
        if (!empty($date)) {
849
            $qb->andWhere(
850
                $qb->expr()->orX(
851
                    $qb->expr()->isNull('s.accessEndDate'),
852
                    $qb->expr()->andX(
853
                        $qb->expr()->isNotNull('s.accessStartDate'),
854
                        $qb->expr()->isNotNull('s.accessEndDate'),
855
                        $qb->expr()->lte('s.accessStartDate', $date),
856
                        $qb->expr()->gte('s.accessEndDate', $date)
857
                    ),
858
                    $qb->expr()->andX(
859
                        $qb->expr()->isNull('s.accessStartDate'),
860
                        $qb->expr()->isNotNull('s.accessEndDate'),
861
                        $qb->expr()->gte('s.accessEndDate', $date)
862
                    )
863
                )
864
            );
865
        }
866
867
        if ($getCount) {
868
            $qb->select('count(s)');
869
        }
870
871
        $qb = self::hideFromSessionCatalogCondition($qb);
872
873
        if (!empty($limit)) {
874
            $qb
875
                ->setFirstResult($limit['start'])
876
                ->setMaxResults($limit['length'])
877
            ;
878
        }
879
880
        $query = $qb->getQuery();
881
882
        if ($returnQueryBuilder) {
883
            return $query;
884
        }
885
886
        if ($getCount) {
887
            return $query->getSingleScalarResult();
888
        }
889
890
        return $query->getResult();
891
    }
892
893
    /**
894
     * @param \Doctrine\ORM\QueryBuilder $qb
895
     *
896
     * @return mixed
897
     */
898
    public static function hideFromSessionCatalogCondition($qb)
899
    {
900
        $em = Database::getManager();
901
        $qb3 = $em->createQueryBuilder();
902
903
        $extraField = new ExtraFieldModel('session');
904
        $extraFieldInfo = $extraField->get_handler_field_info_by_field_variable('hide_from_catalog');
905
        if (!empty($extraFieldInfo)) {
906
            $qb->andWhere(
907
                $qb->expr()->notIn(
908
                    's',
909
                    $qb3
910
                        ->select('s3')
911
                        ->from('ChamiloCoreBundle:ExtraFieldValues', 'fv')
912
                        ->innerJoin('ChamiloCoreBundle:Session', 's3', Join::WITH, 'fv.itemId = s3.id')
913
                        ->where(
914
                            $qb->expr()->eq('fv.field', $extraFieldInfo['id'])
915
                        )->andWhere(
916
                            $qb->expr()->eq('fv.value ', 1)
917
                        )
918
                        ->getDQL()
919
                )
920
            );
921
        }
922
923
        return $qb;
924
    }
925
926
    /**
927
     * Search sessions by the tags in their courses.
928
     *
929
     * @param string $termTag Term for search in tags
930
     * @param array  $limit   Limit info
931
     *
932
     * @return array The sessions
933
     */
934
    public static function browseSessionsByTags($termTag, array $limit, $getCount = false)
935
    {
936
        $em = Database::getManager();
937
        $qb = $em->createQueryBuilder();
938
939
        $urlId = api_get_current_access_url_id();
940
941
        $qb->select('s')
942
            ->distinct()
943
            ->from('ChamiloCoreBundle:Session', 's')
944
            ->innerJoin(
945
                'ChamiloCoreBundle:SessionRelCourse',
946
                'src',
947
                Join::WITH,
948
                's.id = src.session'
949
            )
950
            ->innerJoin(
951
                'ChamiloCoreBundle:AccessUrlRelSession',
952
                'url',
953
                Join::WITH,
954
                'url.sessionId = s.id'
955
            )
956
            ->innerJoin(
957
                'ChamiloCoreBundle:ExtraFieldRelTag',
958
                'frt',
959
                Join::WITH,
960
                'src.course = frt.itemId'
961
            )
962
            ->innerJoin(
963
                'ChamiloCoreBundle:Tag',
964
                't',
965
                Join::WITH,
966
                'frt.tagId = t.id'
967
            )
968
            ->innerJoin(
969
                'ChamiloCoreBundle:ExtraField',
970
                'f',
971
                Join::WITH,
972
                'frt.fieldId = f.id'
973
            )
974
            ->where(
975
                $qb->expr()->like('t.tag', ':tag')
976
            )
977
            ->andWhere(
978
                $qb->expr()->eq('f.extraFieldType', ExtraField::COURSE_FIELD_TYPE)
979
            )
980
            ->andWhere($qb->expr()->gt('s.nbrCourses', 0))
981
            ->andWhere($qb->expr()->eq('url.accessUrlId', $urlId))
982
            ->setParameter('tag', "$termTag%")
983
        ;
984
985
        if (!empty($limit)) {
986
            $qb
987
                ->setFirstResult($limit['start'])
988
                ->setMaxResults($limit['length'])
989
            ;
990
        }
991
992
        $qb = self::hideFromSessionCatalogCondition($qb);
993
994
        if ($getCount) {
995
            $qb->select('count(s)');
996
997
            return $qb->getQuery()->getSingleScalarResult();
998
        }
999
1000
        return $qb->getQuery()->getResult();
1001
    }
1002
1003
    /**
1004
     * Search sessions by the title.
1005
     *
1006
     * @param string $keyword
1007
     * @param array  $limit   Limit info
1008
     *
1009
     * @return array The sessions
1010
     */
1011
    public static function getSessionsByName($keyword, array $limit, $getCount = false)
1012
    {
1013
        $em = Database::getManager();
1014
        $qb = $em->createQueryBuilder();
1015
        $urlId = api_get_current_access_url_id();
1016
1017
        $qb->select('s')
1018
            ->distinct()
1019
            ->from('ChamiloCoreBundle:Session', 's')
1020
            ->innerJoin(
1021
                'ChamiloCoreBundle:SessionRelCourse',
1022
                'src',
1023
                Join::WITH,
1024
                's.id = src.session'
1025
            )
1026
            ->innerJoin(
1027
                'ChamiloCoreBundle:AccessUrlRelSession',
1028
                'url',
1029
                Join::WITH,
1030
                'url.sessionId = s.id'
1031
            )
1032
            ->andWhere($qb->expr()->eq('url.accessUrlId', $urlId))
1033
            ->andWhere('s.name LIKE :keyword')
1034
            ->andWhere($qb->expr()->gt('s.nbrCourses', 0))
1035
            ->setParameter('keyword', "%$keyword%")
1036
        ;
1037
1038
        if (!empty($limit)) {
1039
            $qb
1040
                ->setFirstResult($limit['start'])
1041
                ->setMaxResults($limit['length']);
1042
        }
1043
1044
        $qb = self::hideFromSessionCatalogCondition($qb);
1045
1046
        if ($getCount) {
1047
            $qb->select('count(s)');
1048
1049
            return $qb->getQuery()->getSingleScalarResult();
1050
        }
1051
1052
        return $qb->getQuery()->getResult();
1053
    }
1054
1055
    /**
1056
     * Build a recursive tree of course categories.
1057
     *
1058
     * @param array $categories
1059
     * @param int   $parentId
1060
     * @param int   $level
1061
     *
1062
     * @return array
1063
     */
1064
    public static function buildCourseCategoryTree($categories, $parentId = 0, $level = 0)
1065
    {
1066
        $list = [];
1067
        $count = 0;
1068
        $level++;
1069
        foreach ($categories as $category) {
1070
            if (empty($category['parent_id'])) {
1071
                continue;
1072
            }
1073
            if ($category['parent_id'] == $parentId) {
1074
                $list[$category['code']] = $category;
1075
                $count += $category['number_courses'];
1076
                $list[$category['code']]['level'] = $level;
1077
                list($subList, $childrenCount) = self::buildCourseCategoryTree(
1078
                    $categories,
1079
                    $category['code'],
1080
                    $level
1081
                );
1082
                $list[$category['code']]['number_courses'] += $childrenCount;
1083
                foreach ($subList as $item) {
1084
                    $list[$item['code']] = $item;
1085
                }
1086
                $count += $childrenCount;
1087
            }
1088
        }
1089
1090
        return [$list, $count];
1091
    }
1092
1093
    /**
1094
     * List Code Search Category.
1095
     *
1096
     * @param string $code
1097
     *
1098
     * @return array
1099
     */
1100
    public static function childrenCategories($code)
1101
    {
1102
        $allCategories = CourseCategory::getAllCategories();
1103
        $list = [];
1104
        $row = [];
1105
1106
        if ($code !== 'ALL' && $code !== 'NONE') {
1107
            foreach ($allCategories as $category) {
1108
                if ($category['code'] === $code) {
1109
                    $list = self::buildCourseCategoryTree($allCategories, $category['code'], 0);
1110
                }
1111
            }
1112
            foreach ($list[0] as $item) {
1113
                $row[] = $item['code'];
1114
            }
1115
        }
1116
1117
        return $row;
1118
    }
1119
1120
    /**
1121
     * Get the image of a course for the catalog.
1122
     */
1123
    public static function returnThumbnail(array $course): string
1124
    {
1125
        $course_path = api_get_path(SYS_COURSE_PATH).$course['directory'];
1126
1127
        if (file_exists($course_path.'/course-pic.png')) {
1128
            // redimensioned image 85x85
1129
            return api_get_path(WEB_COURSE_PATH).$course['directory'].'/course-pic.png';
1130
        }
1131
1132
        // without picture
1133
        return Display::return_icon(
1134
            'session_default.png',
1135
            null,
1136
            null,
1137
            null,
1138
            null,
1139
            true
1140
        );
1141
    }
1142
1143
    public static function return_teacher(array $courseInfo): string
1144
    {
1145
        $teachers = CourseManager::getTeachersFromCourse($courseInfo['real_id']);
1146
        $length = count($teachers);
1147
1148
        if (!$length) {
1149
            return '';
1150
        }
1151
1152
        $html = '<div class="block-author">';
1153
        if ($length > 6) {
1154
            $html .= '<a
1155
            id="plist"
1156
            data-trigger="focus"
1157
            tabindex="0" role="button"
1158
            class="btn btn-default panel_popover"
1159
            data-toggle="popover"
1160
            title="'.addslashes(get_lang('CourseTeachers')).'"
1161
            data-html="true"
1162
        >
1163
            <i class="fa fa-graduation-cap" aria-hidden="true"></i>
1164
        </a>';
1165
            $html .= '<div id="popover-content-plist" class="hide">';
1166
            foreach ($teachers as $value) {
1167
                $name = $value['firstname'].' '.$value['lastname'];
1168
                $html .= '<div class="popover-teacher">';
1169
                $html .= '<a href="'.$value['url'].'" class="ajax" data-title="'.$name.'" title="'.$name.'">
1170
                        <img src="'.$value['avatar'].'" title="'.$name.'" alt="'.get_lang('UserPicture').'"/></a>';
1171
                $html .= '<div class="teachers-details"><h5>
1172
                        <a href="'.$value['url'].'" class="ajax" data-title="'.$name.'" title="'.$name.'">'
1173
                    .$name.'</a></h5></div>';
1174
                $html .= '</div>';
1175
            }
1176
            $html .= '</div>';
1177
        } else {
1178
            foreach ($teachers as $value) {
1179
                $name = $value['firstname'].' '.$value['lastname'];
1180
                if ($length > 2) {
1181
                    $html .= '<a href="'.$value['url'].'" class="ajax" data-title="'.$name.'" title="'.$name.'">
1182
                        <img src="'.$value['avatar'].'" title="'.$name.'" alt="'.get_lang('UserPicture').'"/></a>';
1183
                } else {
1184
                    $html .= '<a href="'.$value['url'].'" class="ajax" data-title="'.$name.'" title="'.$name.'">
1185
                        <img src="'.$value['avatar'].'" title="'.$name.'" alt="'.get_lang('UserPicture').'"/></a>';
1186
                    $html .= '<div class="teachers-details"><h5>
1187
                        <a href="'.$value['url'].'" class="ajax" data-title="'.$name.'">'
1188
                        .$name.'</a></h5><p>'.get_lang('Teacher').'</p></div>';
1189
                }
1190
            }
1191
        }
1192
        $html .= '</div>';
1193
1194
        return $html;
1195
    }
1196
1197
    /**
1198
     * Display the already registerd text in a course in the course catalog.
1199
     *
1200
     * @param $status
1201
     *
1202
     * @return string HTML string
1203
     */
1204
    public static function return_already_registered_label($status)
1205
    {
1206
        $icon = '<em class="fa fa-check"></em>';
1207
        $title = get_lang('YouAreATeacherOfThisCourse');
1208
        if ($status === 'student') {
1209
            $icon = '<em class="fa fa-check"></em>';
1210
            $title = get_lang('AlreadySubscribed');
1211
        }
1212
1213
        $html = Display::tag(
1214
            'span',
1215
            $icon.' '.$title,
1216
            [
1217
                'id' => 'register',
1218
                'class' => 'label-subscribed text-success',
1219
                'title' => $title,
1220
                'aria-label' => $title,
1221
            ]
1222
        );
1223
1224
        return $html.PHP_EOL;
1225
    }
1226
1227
    /**
1228
     * Display the register button of a course in the course catalog.
1229
     *
1230
     * @param $course
1231
     * @param $stok
1232
     * @param $categoryCode
1233
     * @param $search_term
1234
     *
1235
     * @return string
1236
     */
1237
    public static function return_register_button($course, $stok, $categoryCode, $search_term)
1238
    {
1239
        $title = get_lang('Subscribe');
1240
        $action = 'subscribe_course';
1241
        if (!empty($course['registration_code'])) {
1242
            $action = 'subscribe_course_validation';
1243
        }
1244
1245
        $em = Database::getManager();
1246
        /** @var SequenceResourceRepository $sequenceResourceRepo */
1247
        $sequenceResourceRepo = $em->getRepository('ChamiloCoreBundle:SequenceResource');
1248
        $requirements = $sequenceResourceRepo->getRequirements(
1249
            $course['real_id'],
1250
            SequenceResource::COURSE_TYPE
1251
        );
1252
        $hasRequirements = false;
1253
        foreach ($requirements as $sequence) {
1254
            if (!empty($sequence['requirements'])) {
1255
                $hasRequirements = true;
1256
                break;
1257
            }
1258
        }
1259
        $allowSubscribe = true;
1260
        $reqSubscribeBlock = '';
1261
        $btnSubscribe = '';
1262
        if ($hasRequirements) {
1263
            $sequenceList = $sequenceResourceRepo->checkRequirementsForUser($requirements, SequenceResource::COURSE_TYPE, api_get_user_id());
1264
            $allowSubscribe = $sequenceResourceRepo->checkSequenceAreCompleted($sequenceList);
1265
            $btnReqSubscribe = CoursesAndSessionsCatalog::getRequirements(
1266
                $course['real_id'],
1267
                SequenceResource::COURSE_TYPE,
1268
                true,
1269
                true
1270
            );
1271
            $reqSubscribeBlock = '<div class="session-requirements2">
1272
                                <h5>'.get_lang('RequiredCourses').'</h5>
1273
                                <p>'.$btnReqSubscribe.'</p>';
1274
            $seq = '';
1275
            foreach ($requirements as $sequence) {
1276
                if (!empty($sequence['requirements'])) {
1277
                    $seq .= $sequence['name'].':';
1278
                    foreach ($sequence['requirements'] as $req) {
1279
                        $seq .= '<a href="'.api_get_path(WEB_PATH).'course/'.$req->getId().'/about/">'.$req->getTitle().'</a>';
1280
                    }
1281
                }
1282
            }
1283
            $reqSubscribeBlock .= '<p>'.$seq.'</p>';
1284
            $reqSubscribeBlock .= '</div>';
1285
        }
1286
1287
        if ($allowSubscribe) {
1288
            $btnSubscribe = Display::url(
1289
                Display::returnFontAwesomeIcon('check').PHP_EOL.$title,
1290
                api_get_path(WEB_CODE_PATH).'auth/courses.php'
1291
                .'?action='.$action.'&sec_token='.$stok
1292
                .'&course_code='.$course['code'].'&search_term='.$search_term.'&category_code='.$categoryCode,
1293
                ['class' => 'btn btn-success btn-sm', 'title' => $title, 'aria-label' => $title]
1294
            );
1295
        } else {
1296
            $btnSubscribe = Display::url(
1297
                Display::returnFontAwesomeIcon('check').PHP_EOL.$title,
1298
                '#',
1299
                ['class' => 'btn btn-default btn-sm', 'title' => $title, 'aria-label' => $title, 'disabled' => true]
1300
            );
1301
        }
1302
1303
        return $reqSubscribeBlock.$btnSubscribe;
1304
    }
1305
1306
    /**
1307
     * Display the unregister button of a course in the course catalog.
1308
     *
1309
     * @param array  $course
1310
     * @param string $stok
1311
     * @param string $search_term
1312
     * @param string $categoryCode
1313
     * @param int    $sessionId
1314
     *
1315
     * @return string
1316
     */
1317
    public static function return_unregister_button($course, $stok, $search_term, $categoryCode, $sessionId = 0)
1318
    {
1319
        $title = get_lang('Unsubscription');
1320
        $search_term = Security::remove_XSS($search_term);
1321
        $categoryCode = Security::remove_XSS($categoryCode);
1322
        $sessionId = (int) $sessionId;
1323
1324
        $url = api_get_path(WEB_CODE_PATH).'auth/courses.php'
1325
            .'?action=unsubscribe&sec_token='.$stok.'&sid='.$sessionId.'&course_code='.$course['code'].
1326
            '&search_term='.$search_term.'&category_code='.$categoryCode;
1327
1328
        return Display::url(
1329
            Display::returnFontAwesomeIcon('sign-in').PHP_EOL.$title,
1330
            $url,
1331
            ['class' => 'btn btn-danger btn-sm', 'title' => $title, 'aria-label' => $title]
1332
        );
1333
    }
1334
1335
    /**
1336
     * Get a HTML button for subscribe to session.
1337
     *
1338
     * @param int    $sessionId         The session ID
1339
     * @param string $sessionName       The session name
1340
     * @param bool   $checkRequirements Optional.
1341
     *                                  Whether the session has requirement. Default is false
1342
     * @param bool   $includeText       Optional. Whether show the text in button
1343
     * @param bool   $btnBing
1344
     *
1345
     * @return string The button HTML
1346
     */
1347
    public static function getRegisteredInSessionButton(
1348
        $sessionId,
1349
        $sessionName,
1350
        $checkRequirements = false,
1351
        $includeText = false,
1352
        $btnBing = false
1353
    ) {
1354
        $sessionId = (int) $sessionId;
1355
        $class = 'btn-sm';
1356
        if ($btnBing) {
1357
            $class = 'btn-lg btn-block';
1358
        }
1359
1360
        if ($checkRequirements) {
1361
            return self::getRequirements($sessionId, SequenceResource::SESSION_TYPE, $includeText, $class);
1362
        }
1363
1364
        $catalogSessionAutoSubscriptionAllowed = false;
1365
        if (api_get_setting('catalog_allow_session_auto_subscription') === 'true') {
1366
            $catalogSessionAutoSubscriptionAllowed = true;
1367
        }
1368
1369
        $url = api_get_path(WEB_CODE_PATH);
1370
1371
        if ($catalogSessionAutoSubscriptionAllowed) {
1372
            $url .= 'auth/courses.php?';
1373
            $url .= http_build_query([
1374
                'action' => 'subscribe_to_session',
1375
                'session_id' => $sessionId,
1376
            ]);
1377
1378
            $result = Display::toolbarButton(
1379
                get_lang('Subscribe'),
1380
                $url,
1381
                'pencil',
1382
                'primary',
1383
                [
1384
                    'class' => $class.' ajax',
1385
                    'data-title' => get_lang('AreYouSureToSubscribe'),
1386
                    'data-size' => 'md',
1387
                    'title' => get_lang('Subscribe'),
1388
                ],
1389
                $includeText
1390
            );
1391
        } else {
1392
            $url .= 'inc/email_editor.php?';
1393
            $url .= http_build_query([
1394
                'action' => 'subscribe_me_to_session',
1395
                'session' => Security::remove_XSS($sessionName),
1396
            ]);
1397
1398
            $result = Display::toolbarButton(
1399
                get_lang('SubscribeToSessionRequest'),
1400
                $url,
1401
                'pencil',
1402
                'primary',
1403
                ['class' => $class],
1404
                $includeText
1405
            );
1406
        }
1407
1408
        $hook = HookResubscribe::create();
1409
        if (!empty($hook)) {
1410
            $hook->setEventData([
1411
                'session_id' => $sessionId,
1412
            ]);
1413
            try {
1414
                $hook->notifyResubscribe(HOOK_EVENT_TYPE_PRE);
1415
            } catch (Exception $exception) {
1416
                $result = $exception->getMessage();
1417
            }
1418
        }
1419
1420
        return $result;
1421
    }
1422
1423
    public static function getRequirements($id, $type, $includeText, $class, $sessionId = 0)
1424
    {
1425
        $id = (int) $id;
1426
        $type = (int) $type;
1427
        $url = api_get_path(WEB_AJAX_PATH).'sequence.ajax.php?';
1428
        $url .= http_build_query(
1429
            [
1430
                'a' => 'get_requirements',
1431
                'id' => $id,
1432
                'type' => $type,
1433
                'sid' => $sessionId,
1434
            ]
1435
        );
1436
1437
        return Display::toolbarButton(
1438
            get_lang('CheckRequirements'),
1439
            $url,
1440
            'shield',
1441
            'info',
1442
            [
1443
                'class' => $class.' ajax',
1444
                'data-title' => get_lang('CheckRequirements'),
1445
                'data-size' => 'md',
1446
                'title' => get_lang('CheckRequirements'),
1447
            ],
1448
            $includeText
1449
        );
1450
    }
1451
1452
    /**
1453
     * Generate a label if the user has been  registered in session.
1454
     *
1455
     * @return string The label
1456
     */
1457
    public static function getAlreadyRegisteredInSessionLabel()
1458
    {
1459
        $icon = '<em class="fa fa-graduation-cap"></em>';
1460
1461
        return Display::div(
1462
            $icon,
1463
            [
1464
                'class' => 'btn btn-default btn-sm registered',
1465
                'title' => get_lang("AlreadyRegisteredToSession"),
1466
            ]
1467
        );
1468
    }
1469
1470
    public static function getSessionPagination($action, $countSessions, $limit)
1471
    {
1472
        $pageTotal = ceil($countSessions / $limit['length']);
1473
        $pagination = '';
1474
        // Do NOT show pagination if only one page or less
1475
        if ($pageTotal > 1) {
1476
            $pagination = self::getCatalogPagination(
1477
                $limit['current'],
1478
                $limit['length'],
1479
                $pageTotal,
1480
                null,
1481
                $action
1482
            );
1483
        }
1484
1485
        return $pagination;
1486
    }
1487
1488
    /**
1489
     * Return Session catalog rendered view.
1490
     */
1491
    public static function sessionList(bool $returnHtml = false): ?string
1492
    {
1493
        $date = $_POST['date'] ?? '';
1494
        $limit = self::getLimitArray();
1495
1496
        $countSessions = self::browseSessions($date, [], false, true);
1497
        $sessions = self::browseSessions($date, $limit);
1498
1499
        $pagination = self::getSessionPagination('display_sessions', $countSessions, $limit);
1500
        $sessionsBlocks = self::getFormattedSessionsBlock($sessions);
1501
1502
        // Get session search catalogue URL
1503
        $courseUrl = self::getCatalogUrl(
1504
            1,
1505
            $limit['length'],
1506
            null,
1507
            'subscribe'
1508
        );
1509
1510
        $tpl = new Template();
1511
        $tpl->assign('actions', self::getTabList(2));
1512
        $tpl->assign('show_courses', self::showCourses());
1513
        $tpl->assign('show_sessions', self::showSessions());
1514
        $tpl->assign('show_tutor', api_get_setting('show_session_coach') === 'true');
1515
        $tpl->assign('course_url', $courseUrl);
1516
        $tpl->assign('catalog_pagination', $pagination);
1517
        $tpl->assign('search_token', Security::get_token());
1518
        $tpl->assign('search_date', $date);
1519
        $tpl->assign('web_session_courses_ajax_url', api_get_path(WEB_AJAX_PATH).'course.ajax.php');
1520
        $tpl->assign('sessions', $sessionsBlocks);
1521
        $tpl->assign('already_subscribed_label', self::getAlreadyRegisteredInSessionLabel());
1522
        $tpl->assign('catalog_settings', self::getCatalogSearchSettings());
1523
1524
        $templateContent = $tpl->fetch(
1525
            $tpl->get_template('catalog/session_catalog.tpl')
1526
        );
1527
1528
        if ($returnHtml) {
1529
            return $templateContent;
1530
        }
1531
1532
        $tpl->assign('content', $templateContent);
1533
        $tpl->display_one_col_template();
1534
1535
        return null;
1536
    }
1537
1538
    /**
1539
     * Show the Session Catalogue with filtered session by course tags.
1540
     */
1541
    public static function sessionsListByName()
1542
    {
1543
        $limit = self::getLimitArray();
1544
        $keyword = $_REQUEST['keyword'] ?? null;
1545
        $courseUrl = self::getCatalogUrl(
1546
            1,
1547
            $limit['length'],
1548
            null,
1549
            'subscribe'
1550
        );
1551
1552
        $count = self::getSessionsByName($keyword, [], true);
1553
        $sessions = self::getSessionsByName($keyword, $limit);
1554
        $sessionsBlocks = self::getFormattedSessionsBlock($sessions);
1555
        $pagination = self::getSessionPagination('search_session_title', $count, $limit);
1556
1557
        $tpl = new Template();
1558
        $tpl->assign('catalog_pagination', $pagination);
1559
        $tpl->assign('actions', self::getTabList(2));
1560
        $tpl->assign('show_courses', self::showCourses());
1561
        $tpl->assign('show_sessions', self::showSessions());
1562
        $tpl->assign('show_tutor', api_get_setting('show_session_coach') === 'true');
1563
        $tpl->assign('course_url', $courseUrl);
1564
        $tpl->assign('already_subscribed_label', self::getAlreadyRegisteredInSessionLabel());
1565
        $tpl->assign('search_token', Security::get_token());
1566
        $tpl->assign('keyword', Security::remove_XSS($keyword));
1567
        $tpl->assign('sessions', $sessionsBlocks);
1568
        $tpl->assign('catalog_settings', self::getCatalogSearchSettings());
1569
1570
        $templateContent = $tpl->fetch(
1571
            $tpl->get_template('catalog/session_catalog.tpl')
1572
        );
1573
1574
        $tpl->assign('content', $templateContent);
1575
        $tpl->display_one_col_template();
1576
    }
1577
1578
    public static function getCatalogSearchSettings()
1579
    {
1580
        $settings = api_get_configuration_value('catalog_settings');
1581
        if (empty($settings)) {
1582
            // Default everything is visible
1583
            $settings = [
1584
                'sessions' => [
1585
                    'by_title' => true,
1586
                    'by_date' => true,
1587
                    'by_tag' => true,
1588
                    'show_session_info' => true,
1589
                    'show_session_date' => true,
1590
                ],
1591
                'courses' => [
1592
                    'by_title' => true,
1593
                ],
1594
            ];
1595
        }
1596
1597
        return $settings;
1598
    }
1599
1600
    /**
1601
     * @param int $active
1602
     *
1603
     * @return string
1604
     */
1605
    public static function getTabList($active = 1)
1606
    {
1607
        $pageLength = isset($_GET['pageLength']) ? (int) $_GET['pageLength'] : self::PAGE_LENGTH;
1608
1609
        $url = self::getCatalogUrl(1, $pageLength, null, 'display_sessions');
1610
        $headers = [];
1611
        if (self::showCourses()) {
1612
            $headers[] = [
1613
                'url' => api_get_path(WEB_CODE_PATH).'auth/courses.php',
1614
                'content' => get_lang('CourseManagement'),
1615
            ];
1616
        }
1617
1618
        if (self::showSessions()) {
1619
            $headers[] = [
1620
                'url' => $url,
1621
                'content' => get_lang('SessionList'),
1622
            ];
1623
        }
1624
1625
        // If only one option hide menu.
1626
        if (1 === count($headers)) {
1627
            return '';
1628
        }
1629
1630
        return Display::tabsOnlyLink($headers, $active);
1631
    }
1632
1633
    /**
1634
     * Show the Session Catalogue with filtered session by course tags.
1635
     */
1636
    public static function sessionsListByCoursesTag()
1637
    {
1638
        $limit = self::getLimitArray();
1639
        $searchTag = $_REQUEST['search_tag'] ?? '';
1640
        $searchDate = $_REQUEST['date'] ?? date('Y-m-d');
1641
        $courseUrl = self::getCatalogUrl(
1642
            1,
1643
            $limit['length'],
1644
            null,
1645
            'subscribe'
1646
        );
1647
1648
        $sessions = self::browseSessionsByTags($searchTag, $limit);
1649
        $sessionsBlocks = self::getFormattedSessionsBlock($sessions);
1650
1651
        $count = self::browseSessionsByTags($searchTag, [], true);
1652
        $pagination = self::getSessionPagination('search_tag', $count, $limit);
1653
1654
        $tpl = new Template();
1655
        $tpl->assign('catalog_pagination', $pagination);
1656
        $tpl->assign('show_courses', self::showCourses());
1657
        $tpl->assign('show_sessions', self::showSessions());
1658
        $tpl->assign('show_tutor', api_get_setting('show_session_coach') === 'true');
1659
        $tpl->assign('course_url', $courseUrl);
1660
        $tpl->assign('already_subscribed_label', self::getAlreadyRegisteredInSessionLabel());
1661
        $tpl->assign('search_token', Security::get_token());
1662
        $tpl->assign('search_date', Security::remove_XSS($searchDate));
1663
        $tpl->assign('search_tag', Security::remove_XSS($searchTag));
1664
        $tpl->assign('sessions', $sessionsBlocks);
1665
1666
        $templateContent = $tpl->fetch(
1667
            $tpl->get_template('catalog/session_catalog.tpl')
1668
        );
1669
1670
        $tpl->assign('content', $templateContent);
1671
        $tpl->display_one_col_template();
1672
    }
1673
1674
    /**
1675
     * @return array
1676
     */
1677
    public static function getLimitArray()
1678
    {
1679
        $pageCurrent = isset($_REQUEST['pageCurrent']) ? (int) $_GET['pageCurrent'] : 1;
1680
        $pageLength = isset($_REQUEST['pageLength']) ? (int) $_GET['pageLength'] : self::PAGE_LENGTH;
1681
1682
        return [
1683
            'start' => ($pageCurrent - 1) * $pageLength,
1684
            'current' => $pageCurrent,
1685
            'length' => $pageLength,
1686
        ];
1687
    }
1688
1689
    /**
1690
     * Get the formatted data for sessions block to be displayed on Session Catalog page.
1691
     *
1692
     * @param array $sessions The session list
1693
     *
1694
     * @return array
1695
     */
1696
    public static function getFormattedSessionsBlock(array $sessions)
1697
    {
1698
        $extraFieldValue = new ExtraFieldValue('session');
1699
        $userId = api_get_user_id();
1700
        $sessionsBlocks = [];
1701
        $entityManager = Database::getManager();
1702
        $sessionRelCourseRepo = $entityManager->getRepository('ChamiloCoreBundle:SessionRelCourse');
1703
        $extraFieldRepo = $entityManager->getRepository('ChamiloCoreBundle:ExtraField');
1704
        $extraFieldRelTagRepo = $entityManager->getRepository('ChamiloCoreBundle:ExtraFieldRelTag');
1705
1706
        $tagsField = $extraFieldRepo->findOneBy([
1707
            'extraFieldType' => Chamilo\CoreBundle\Entity\ExtraField::COURSE_FIELD_TYPE,
1708
            'variable' => 'tags',
1709
        ]);
1710
1711
        /** @var \Chamilo\CoreBundle\Entity\Session $session */
1712
        foreach ($sessions as $session) {
1713
            $sessionDates = SessionManager::parseSessionDates([
1714
                'display_start_date' => $session->getDisplayStartDate(),
1715
                'display_end_date' => $session->getDisplayEndDate(),
1716
                'access_start_date' => $session->getAccessStartDate(),
1717
                'access_end_date' => $session->getAccessEndDate(),
1718
                'coach_access_start_date' => $session->getCoachAccessStartDate(),
1719
                'coach_access_end_date' => $session->getCoachAccessEndDate(),
1720
            ]);
1721
1722
            $imageField = $extraFieldValue->get_values_by_handler_and_field_variable(
1723
                $session->getId(),
1724
                'image'
1725
            );
1726
            $sessionCourseTags = [];
1727
            if (!is_null($tagsField)) {
1728
                $sessionRelCourses = $sessionRelCourseRepo->findBy([
1729
                    'session' => $session,
1730
                ]);
1731
                /** @var SessionRelCourse $sessionRelCourse */
1732
                foreach ($sessionRelCourses as $sessionRelCourse) {
1733
                    $courseTags = $extraFieldRelTagRepo->getTags(
1734
                        $tagsField,
1735
                        $sessionRelCourse->getCourse()->getId()
1736
                    );
1737
                    /** @var Tag $tag */
1738
                    foreach ($courseTags as $tag) {
1739
                        $sessionCourseTags[] = $tag->getTag();
1740
                    }
1741
                }
1742
            }
1743
1744
            if (!empty($sessionCourseTags)) {
1745
                $sessionCourseTags = array_unique($sessionCourseTags);
1746
            }
1747
1748
            /** @var SequenceResourceRepository $repo */
1749
            $repo = $entityManager->getRepository('ChamiloCoreBundle:SequenceResource');
1750
            $sequences = $repo->getRequirementsAndDependenciesWithinSequences(
1751
                $session->getId(),
1752
                SequenceResource::SESSION_TYPE
1753
            );
1754
1755
            $hasRequirements = false;
1756
            foreach ($sequences as $sequence) {
1757
                if (count($sequence['requirements']) === 0) {
1758
                    continue;
1759
                }
1760
                $hasRequirements = true;
1761
                break;
1762
            }
1763
            $cat = $session->getCategory();
1764
            if (empty($cat)) {
1765
                $cat = null;
1766
                $catName = '';
1767
            } else {
1768
                $catName = $cat->getName();
1769
            }
1770
1771
            $generalCoach = $session->getGeneralCoach();
1772
            $coachId = $generalCoach ? $generalCoach->getId() : 0;
1773
            $coachName = $generalCoach ? UserManager::formatUserFullName($session->getGeneralCoach()) : '';
1774
1775
            $actions = null;
1776
            if (api_is_platform_admin()) {
1777
                $actions = api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$session->getId();
1778
            }
1779
1780
            $plugin = \BuyCoursesPlugin::create();
1781
            $isThisSessionOnSale = $plugin->getBuyCoursePluginPrice($session);
1782
1783
            $userIdHash = UserManager::generateUserHash($coachId);
1784
            $sessionsBlock = [
1785
                'id' => $session->getId(),
1786
                'name' => $session->getName(),
1787
                'image' => isset($imageField['value']) ? $imageField['value'] : null,
1788
                'nbr_courses' => $session->getNbrCourses(),
1789
                'nbr_users' => $session->getNbrUsers(),
1790
                'coach_id' => $coachId,
1791
                'coach_url' => $generalCoach
1792
                    ? api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&hash='.$userIdHash
1793
                    : '',
1794
                'coach_name' => $coachName,
1795
                'coach_avatar' => UserManager::getUserPicture($coachId, USER_IMAGE_SIZE_SMALL),
1796
                'is_subscribed' => SessionManager::isUserSubscribedAsStudent($session->getId(), $userId),
1797
                '' => Display::return_icon(
1798
                    'window_list.png',
1799
                    $session->getName(),
1800
                    [],
1801
                    ICON_SIZE_MEDIUM
1802
                ),
1803
                'date' => $sessionDates['display'],
1804
                'price' => !empty($isThisSessionOnSale['html']) ? $isThisSessionOnSale['html'] : '',
1805
                'subscribe_button' => isset($isThisSessionOnSale['buy_button']) ? $isThisSessionOnSale['buy_button'] : self::getRegisteredInSessionButton(
1806
                    $session->getId(),
1807
                    $session->getName(),
1808
                    $hasRequirements
1809
                ),
1810
                'show_description' => $session->getShowDescription(),
1811
                'description' => $session->getDescription(),
1812
                'category' => $catName,
1813
                'tags' => $sessionCourseTags,
1814
                'edit_actions' => $actions,
1815
                'duration' => SessionManager::getDayLeftInSession(
1816
                    ['id' => $session->getId(), 'duration' => $session->getDuration()],
1817
                    $userId
1818
                ),
1819
            ];
1820
1821
            $sessionsBlocks[] = array_merge($sessionsBlock, $sequences);
1822
        }
1823
1824
        return $sessionsBlocks;
1825
    }
1826
1827
    /**
1828
     * Get Pagination HTML div.
1829
     *
1830
     * @param int    $pageCurrent
1831
     * @param int    $pageLength
1832
     * @param int    $pageTotal
1833
     * @param string $categoryCode
1834
     * @param string $action
1835
     * @param array  $fields
1836
     * @param array  $sortKeys
1837
     *
1838
     * @return string
1839
     */
1840
    public static function getCatalogPagination(
1841
        $pageCurrent,
1842
        $pageLength,
1843
        $pageTotal,
1844
        $categoryCode = '',
1845
        $action = '',
1846
        $fields = [],
1847
        $sortKeys = []
1848
    ) {
1849
        // Start empty html
1850
        $pageDiv = '';
1851
        $html = '';
1852
        $pageBottom = max(1, $pageCurrent - 3);
1853
        $pageTop = min($pageTotal, $pageCurrent + 3);
1854
1855
        if ($pageBottom > 1) {
1856
            $pageDiv .= self::getPageNumberItem(1, $pageLength);
1857
            if ($pageBottom > 2) {
1858
                $pageDiv .= self::getPageNumberItem(
1859
                    $pageBottom - 1,
1860
                    $pageLength,
1861
                    null,
1862
                    '...',
1863
                    $categoryCode,
1864
                    $action,
1865
                    $fields,
1866
                    $sortKeys
1867
                );
1868
            }
1869
        }
1870
1871
        // For each page add its page button to html
1872
        for ($i = $pageBottom; $i <= $pageTop; $i++) {
1873
            if ($i === $pageCurrent) {
1874
                $pageItemAttributes = ['class' => 'active'];
1875
            } else {
1876
                $pageItemAttributes = [];
1877
            }
1878
            $pageDiv .= self::getPageNumberItem(
1879
                $i,
1880
                $pageLength,
1881
                $pageItemAttributes,
1882
                '',
1883
                $categoryCode,
1884
                $action,
1885
                $fields,
1886
                $sortKeys
1887
            );
1888
        }
1889
1890
        // Check if current page is the last page
1891
        if ($pageTop < $pageTotal) {
1892
            if ($pageTop < ($pageTotal - 1)) {
1893
                $pageDiv .= self::getPageNumberItem(
1894
                    $pageTop + 1,
1895
                    $pageLength,
1896
                    null,
1897
                    '...',
1898
                    $categoryCode,
1899
                    $action,
1900
                    $fields,
1901
                    $sortKeys
1902
                );
1903
            }
1904
            $pageDiv .= self::getPageNumberItem(
1905
                $pageTotal,
1906
                $pageLength,
1907
                [],
1908
                '',
1909
                $categoryCode,
1910
                $action,
1911
                $fields,
1912
                $sortKeys
1913
            );
1914
        }
1915
1916
        // Complete pagination html
1917
        $pageDiv = Display::tag('ul', $pageDiv, ['class' => 'pagination']);
1918
        $html .= '<nav>'.$pageDiv.'</nav>';
1919
1920
        return $html;
1921
    }
1922
1923
    /**
1924
     * Get li HTML of page number.
1925
     *
1926
     * @param $pageNumber
1927
     * @param $pageLength
1928
     * @param array  $liAttributes
1929
     * @param string $content
1930
     * @param string $categoryCode
1931
     * @param string $action
1932
     * @param array  $fields
1933
     * @param array  $sortKeys
1934
     *
1935
     * @return string
1936
     */
1937
    public static function getPageNumberItem(
1938
        $pageNumber,
1939
        $pageLength,
1940
        $liAttributes = [],
1941
        $content = '',
1942
        $categoryCode = '',
1943
        $action = '',
1944
        $fields = [],
1945
        $sortKeys = []
1946
    ) {
1947
        // Get page URL
1948
        $url = self::getCatalogUrl($pageNumber, $pageLength, $categoryCode, $action, $fields, $sortKeys);
1949
1950
        // If is current page ('active' class) clear URL
1951
        if (isset($liAttributes) && is_array($liAttributes) && isset($liAttributes['class'])) {
1952
            if (strpos('active', $liAttributes['class']) !== false) {
1953
                $url = '';
1954
            }
1955
        }
1956
1957
        $content = !empty($content) ? $content : $pageNumber;
1958
1959
        return Display::tag(
1960
            'li',
1961
            Display::url(
1962
                $content,
1963
                $url
1964
            ),
1965
            $liAttributes
1966
        );
1967
    }
1968
1969
    /**
1970
     * Return URL to course catalog.
1971
     *
1972
     * @param int    $pageCurrent
1973
     * @param int    $pageLength
1974
     * @param string $categoryCode
1975
     * @param string $action
1976
     * @param array  $extraFields
1977
     * @param array  $sortKeys
1978
     *
1979
     * @return string
1980
     */
1981
    public static function getCatalogUrl(
1982
        $pageCurrent,
1983
        $pageLength,
1984
        $categoryCode = null,
1985
        $action = null,
1986
        $extraFields = [],
1987
        $sortKeys = []
1988
    ) {
1989
        $requestAction = isset($_REQUEST['action']) ? Security::remove_XSS($_REQUEST['action']) : '';
1990
        $action = isset($action) ? Security::remove_XSS($action) : $requestAction;
1991
        $searchTerm = isset($_REQUEST['search_term']) ? Security::remove_XSS($_REQUEST['search_term']) : '';
1992
        $keyword = isset($_REQUEST['keyword']) ? Security::remove_XSS($_REQUEST['keyword']) : '';
1993
        $searchTag = $_REQUEST['search_tag'] ? Security::remove_XSS($_REQUEST['search_tag']) : '';
1994
        $languageSelect = isset($_REQUEST['course_language']) ? Security::remove_XSS($_REQUEST['course_language']) : '';
1995
1996
        if ($action === 'subscribe_user_with_password') {
1997
            $action = 'subscribe';
1998
        }
1999
2000
        $categoryCode = !empty($categoryCode) ? Security::remove_XSS($categoryCode) : 'ALL';
2001
2002
        // Start URL with params
2003
        $pageUrl = api_get_path(WEB_CODE_PATH).'auth/courses.php'.
2004
            '?action='.$action.
2005
            '&search_term='.$searchTerm.
2006
            '&keyword='.$keyword.
2007
            '&search_tag='.$searchTag.
2008
            '&category_code='.$categoryCode.
2009
            '&pageCurrent='.$pageCurrent.
2010
            '&pageLength='.$pageLength.
2011
            '&course_language='.$languageSelect;
2012
2013
        if (!empty($extraFields)) {
2014
            $params = [];
2015
            foreach ($extraFields as $variable => $value) {
2016
                $params[Security::remove_XSS($variable)] = Security::remove_XSS($value);
2017
            }
2018
            if (!empty($params)) {
2019
                $pageUrl .= '&'.http_build_query($params);
2020
            }
2021
        }
2022
2023
        if (!empty($sortKeys)) {
2024
            foreach ($sortKeys as $sortKey) {
2025
                $pageUrl .= '&sortKeys%5B%5D='.Security::remove_XSS($sortKey);
2026
            }
2027
        }
2028
2029
        switch ($action) {
2030
            case 'subscribe':
2031
                // for search
2032
                $pageUrl .=
2033
                    '&sec_token='.Security::getTokenFromSession();
2034
                break;
2035
            case 'display_courses':
2036
            default:
2037
                break;
2038
        }
2039
2040
        return $pageUrl;
2041
    }
2042
2043
    public static function generateRedirectUrlAfterSubscription(string $coursePublicUrl): string
2044
    {
2045
        $settings = api_get_configuration_value('course_catalog_settings');
2046
2047
        $redirectAfterSubscription = 'course_home';
2048
2049
        if (!empty($settings) && isset($settings['redirect_after_subscription'])) {
2050
            $redirectAfterSubscription = $settings['redirect_after_subscription'];
2051
        }
2052
2053
        if ('course_home' !== $redirectAfterSubscription) {
2054
            return api_get_path(WEB_CODE_PATH).'auth/courses.php';
2055
        }
2056
2057
        if (api_get_configuration_value('catalog_course_subscription_in_user_s_session')) {
2058
            $user = api_get_user_entity(api_get_user_id());
2059
2060
            if ($user && $accesibleSessions = $user->getCurrentlyAccessibleSessions()) {
2061
                return $coursePublicUrl.'?id_session='.$accesibleSessions[0]->getId();
2062
            }
2063
        }
2064
2065
        return $coursePublicUrl;
2066
    }
2067
2068
    /**
2069
     * @throws Exception
2070
     */
2071
    public static function displayCoursesList(
2072
        string $action = '',
2073
        string $searchTerm = '',
2074
        string $categoryCode = '',
2075
        bool $returnHtml = false
2076
    ): ?string {
2077
        $settings = api_get_configuration_value('course_catalog_settings');
2078
        $preFilterOnLanguage = false;
2079
        if (!empty($settings['pre_filter_on_language'])) {
2080
            $preFilterOnLanguage = true;
2081
        }
2082
2083
        $courseCatalogSettings = [
2084
            'info_url' => 'course_description_popup',
2085
            'title_url' => 'course_home',
2086
            'image_url' => 'course_about',
2087
        ];
2088
        // By default, all extra fields are shown (visible and filterable)
2089
        $extraFieldsInSearchForm = [];
2090
        $extraFieldsInCourseBlock = [];
2091
2092
        if (!empty($settings)) {
2093
            if (isset($settings['link_settings'])) {
2094
                $courseCatalogSettings = $settings['link_settings'];
2095
            }
2096
2097
            if (isset($settings['extra_fields_in_search_form'])) {
2098
                $extraFieldsInSearchForm = $settings['extra_fields_in_search_form'];
2099
            }
2100
2101
            if (isset($settings['extra_fields_in_course_block'])) {
2102
                $extraFieldsInCourseBlock = $settings['extra_fields_in_course_block'];
2103
            }
2104
        }
2105
2106
        $settings = CoursesAndSessionsCatalog::getCatalogSearchSettings();
2107
2108
        $form = new FormValidator('search', 'get', '', null, null, FormValidator::LAYOUT_HORIZONTAL);
2109
        $form->addHidden('action', 'search_course');
2110
2111
        if (isset($settings['courses']) && true === $settings['courses']['by_title']) {
2112
            $form->addText('search_term', get_lang('Title'), false);
2113
        }
2114
2115
        $select = $form->addSelect(
2116
            'category_code',
2117
            get_lang('CourseCategories'),
2118
            [],
2119
            ['placeholder' => get_lang('SelectAnOption')]
2120
        );
2121
2122
        $defaults = [];
2123
        $listCategories = CoursesAndSessionsCatalog::getCourseCategoriesTree();
2124
2125
        foreach ($listCategories as $category) {
2126
            $countCourse = (int) $category['number_courses'];
2127
            if (empty($countCourse)) {
2128
                continue;
2129
            }
2130
2131
            $categoryCodeItem = Security::remove_XSS($category['code']);
2132
            $categoryName = Security::remove_XSS($category['name']);
2133
            $level = $category['level'];
2134
            $separate = '';
2135
            if ($level > 0) {
2136
                $separate = str_repeat('--', $level);
2137
            }
2138
            $select->addOption($separate.' '.$categoryName.' ('.$countCourse.')', $categoryCodeItem);
2139
        }
2140
2141
        $allowExtraFields = api_get_configuration_value('allow_course_extra_field_in_catalog');
2142
2143
        $jqueryReadyContent = '';
2144
        if ($allowExtraFields) {
2145
            $extraField = new ExtraFieldModel('course');
2146
            $returnParams = $extraField->addElements($form, null, [], true, false, $extraFieldsInSearchForm);
2147
            $jqueryReadyContent = $returnParams['jquery_ready_content'];
2148
        }
2149
2150
        $sortKeySelect = $form->addSelect(
2151
            'sortKeys',
2152
            get_lang('SortKeys'),
2153
            CoursesAndSessionsCatalog::courseSortOptions(),
2154
            ['multiple' => true]
2155
        );
2156
2157
        if (api_get_setting('show_different_course_language') === 'true') {
2158
            $form->addSelectLanguage(
2159
                'course_language',
2160
                get_lang('Language'),
2161
                ['' => '--'],
2162
                ['style' => 'width:150px']
2163
            );
2164
        }
2165
2166
        $sortKeys = isset($_REQUEST['sortKeys']) ? Security::remove_XSS($_REQUEST['sortKeys']) : '';
2167
        $languageSelect = isset($_REQUEST['course_language']) ? Security::remove_XSS($_REQUEST['course_language']) : '';
2168
        if ($preFilterOnLanguage && empty($languageSelect)) {
2169
            $languageSelect = api_get_user_info()['language'];
2170
        }
2171
        // Check the language is active
2172
        $languagesList = SubLanguageManager::getAllLanguages(true);
2173
        if (empty($languagesList[$languageSelect])) {
2174
            $languageSelect = '';
2175
        }
2176
        $defaults['sortKeys'] = $sortKeys;
2177
        $defaults['search_term'] = $searchTerm;
2178
        $defaults['category_code'] = $categoryCode;
2179
        $defaults['course_language'] = $languageSelect;
2180
2181
        $conditions = [];
2182
        $fields = [];
2183
2184
        if ('display_random_courses' === $action) {
2185
            // Random value is used instead limit filter
2186
            $courses = CoursesAndSessionsCatalog::getCoursesInCategory(null, 12);
2187
            $countCoursesInCategory = count($courses);
2188
        } else {
2189
            $values = $_REQUEST;
2190
            if ($allowExtraFields) {
2191
                $extraResult = $extraField->processExtraFieldSearch($values, $form, 'course', 'AND');
2192
                $conditions = $extraResult['condition'];
2193
                $fields = $extraResult['fields'];
2194
                $defaults = $extraResult['defaults'];
2195
2196
                $defaults['sortKeys'] = $sortKeys;
2197
                $defaults['search_term'] = $searchTerm;
2198
                $defaults['category_code'] = $categoryCode;
2199
                $defaults['course_language'] = $languageSelect;
2200
            }
2201
2202
            $courses = CoursesAndSessionsCatalog::searchAndSortCourses(
2203
                $categoryCode,
2204
                $searchTerm,
2205
                self::getLimitArray(),
2206
                true,
2207
                $conditions,
2208
                $sortKeySelect->getValue(),
2209
                $languageSelect
2210
            );
2211
            $countCoursesInCategory = CourseCategory::countCoursesInCategory(
2212
                $categoryCode,
2213
                $searchTerm,
2214
                true,
2215
                true,
2216
                $conditions,
2217
                $languageSelect,
2218
                true
2219
            );
2220
        }
2221
2222
        $pageCurrent = isset($_GET['pageCurrent']) ? (int) $_GET['pageCurrent'] : 1;
2223
        $pageLength = isset($_GET['pageLength']) ? (int) $_GET['pageLength'] : CoursesAndSessionsCatalog::PAGE_LENGTH;
2224
        $pageTotal = (int) ceil($countCoursesInCategory / $pageLength);
2225
2226
        $url = CoursesAndSessionsCatalog::getCatalogUrl(1, $pageLength, 'ALL', 'search_course', $fields);
2227
        $urlNoExtraFields = CoursesAndSessionsCatalog::getCatalogUrl(1, $pageLength, 'ALL', 'search_course');
2228
        $urlNoCategory = CoursesAndSessionsCatalog::getCatalogUrl(1, $pageLength, '', 'search_course', $fields);
2229
        $urlNoCategory = str_replace('&category_code=ALL', '', $urlNoCategory);
2230
2231
        $form->setAttribute('action', $url);
2232
        $form->addButtonSearch(get_lang('Search'));
2233
        $form->setDefaults($defaults);
2234
2235
        $catalogPagination = '';
2236
2237
        if ($pageTotal > 1) {
2238
            $catalogPagination = CoursesAndSessionsCatalog::getCatalogPagination(
2239
                $pageCurrent,
2240
                $pageLength,
2241
                $pageTotal,
2242
                $categoryCode,
2243
                $action,
2244
                $fields,
2245
                $sortKeySelect->getValue()
2246
            );
2247
        }
2248
2249
        $stok = Security::get_token();
2250
2251
        $showTeacher = 'true' === api_get_setting('display_teacher_in_courselist');
2252
        $ajax_url = api_get_path(WEB_AJAX_PATH).'course.ajax.php?a=add_course_vote';
2253
        $user_id = api_get_user_id();
2254
        $categoryListFromDatabase = CourseCategory::getAllCategories();
2255
2256
        $categoryList = [];
2257
        if (!empty($categoryListFromDatabase)) {
2258
            foreach ($categoryListFromDatabase as $categoryItem) {
2259
                $categoryList[$categoryItem['code']] = $categoryItem['name'];
2260
            }
2261
        }
2262
2263
        $courseUrl = api_get_path(WEB_COURSE_PATH);
2264
        $hideRating = api_get_configuration_value('hide_course_rating');
2265
2266
        if (!empty($courses)) {
2267
            foreach ($courses as &$course) {
2268
                $courseId = $course['real_id'];
2269
                if (COURSE_VISIBILITY_HIDDEN == $course['visibility']) {
2270
                    continue;
2271
                }
2272
2273
                $aboutPage = api_get_path(WEB_PATH).'course/'.$course['real_id'].'/about';
2274
                $settingsUrl = [
2275
                    'course_description_popup' => api_get_path(WEB_CODE_PATH).'inc/ajax/course_home.ajax.php?a=show_course_information&code='.$course['code'],
2276
                    'course_about' => $aboutPage,
2277
                    'course_home' => $courseUrl.$course['directory'].'/index.php?id_session=0',
2278
                ];
2279
2280
                $infoUrl = $settingsUrl[$courseCatalogSettings['info_url']];
2281
                $course['title_url'] = $settingsUrl[$courseCatalogSettings['title_url']];
2282
                $course['image_url'] = $settingsUrl[$courseCatalogSettings['image_url']];
2283
2284
                $userRegisteredInCourse = CourseManager::is_user_subscribed_in_course($user_id, $course['code']);
2285
                $userRegisteredInCourseAsTeacher = CourseManager::is_course_teacher($user_id, $course['code']);
2286
2287
                $course_private = COURSE_VISIBILITY_REGISTERED == $course['visibility'];
2288
                $courseClosed = COURSE_VISIBILITY_CLOSED == $course['visibility'];
2289
                $course_subscribe_allowed = 1 == $course['subscribe'];
2290
                $course_unsubscribe_allowed = 1 == $course['unsubscribe'];
2291
2292
                // display the course bloc
2293
                $course['category_title'] = '';
2294
2295
                if (!empty($course['category_code'])) {
2296
                    $course['category_title'] = $categoryList[$course['category_code']] ?? '';
2297
                    $course['category_code_link'] = $urlNoCategory.'&category_code='.$course['category_code'];
2298
                }
2299
2300
                // Display thumbnail
2301
                $course['thumbnail'] = CoursesAndSessionsCatalog::returnThumbnail($course);
2302
                $course['description_button'] = CourseManager::returnDescriptionButton($course, $infoUrl);
2303
                $subscribeButton = CoursesAndSessionsCatalog::return_register_button(
2304
                    $course,
2305
                    $stok,
2306
                    $categoryCode,
2307
                    $searchTerm
2308
                );
2309
2310
                // Start buy course validation
2311
                // display the course price and buy button if the buycourses plugin is enabled and this course is configured
2312
                $plugin = BuyCoursesPlugin::create();
2313
                $isThisCourseInSale = $plugin->buyCoursesForGridCatalogValidator(
2314
                    $courseId,
2315
                    BuyCoursesPlugin::PRODUCT_TYPE_COURSE
2316
                );
2317
2318
                $separator = '';
2319
2320
                if ($isThisCourseInSale) {
2321
                    // set the Price label
2322
                    $separator = $isThisCourseInSale['html'];
2323
                    // set the Buy button instead register.
2324
                    if ($isThisCourseInSale['verificator']) {
2325
                        $subscribeButton = $plugin->returnBuyCourseButton(
2326
                            $courseId,
2327
                            BuyCoursesPlugin::PRODUCT_TYPE_COURSE
2328
                        );
2329
                    }
2330
                }
2331
2332
                $course['rating'] = '';
2333
2334
                if ($hideRating === false) {
2335
                    $rating = Display::return_rating_system(
2336
                        'star_'.$course['real_id'],
2337
                        $ajax_url.'&course_id='.$course['real_id'],
2338
                        $course['point_info']
2339
                    );
2340
                    $course['rating'] = '<div class="ranking">'.$rating.'</div>';
2341
                }
2342
2343
                if ($showTeacher) {
2344
                    $course['teacher_info'] = CoursesAndSessionsCatalog::return_teacher($course);
2345
                }
2346
2347
                // display button line
2348
                $course['buy_course'] = $separator;
2349
                $course['extra_data'] = '';
2350
2351
                if ($allowExtraFields) {
2352
                    $course['extra_data'] = $extraField->getDataAndFormattedValues(
2353
                        $courseId,
2354
                        true,
2355
                        $extraFieldsInCourseBlock
2356
                    );
2357
                }
2358
2359
                // if user registered as student
2360
                if ($userRegisteredInCourse) {
2361
                    $course['already_registered_formatted'] = Display::url(
2362
                        Display::returnFontAwesomeIcon('external-link').PHP_EOL.
2363
                        get_lang('GoToCourse'),
2364
                        $courseUrl.$course['directory'].'/index.php?id_session=0',
2365
                        ['class' => 'btn btn-primary btn-sm']
2366
                    );
2367
2368
                    if (!$courseClosed && $course_unsubscribe_allowed
2369
                        && false === $userRegisteredInCourseAsTeacher
2370
                    ) {
2371
                        $course['unregister_formatted'] = CoursesAndSessionsCatalog::return_unregister_button(
2372
                            $course,
2373
                            $stok,
2374
                            $searchTerm,
2375
                            $categoryCode
2376
                        );
2377
                    }
2378
                } elseif ($userRegisteredInCourseAsTeacher) {
2379
                    // if user registered as teacher
2380
                    // Updated teacher cannot unregister himself.
2381
                    /*if ($course_unsubscribe_allowed) {
2382
                        $course['unregister_formatted'] = CoursesAndSessionsCatalog::return_unregister_button(
2383
                            $course,
2384
                            $stok,
2385
                            $searchTerm,
2386
                            $categoryCode
2387
                        );
2388
                    }*/
2389
                } else {
2390
                    // if user not registered in the course
2391
                    if (!$courseClosed && !$course_private && $course_subscribe_allowed) {
2392
                        $course['subscribe_formatted'] = $subscribeButton;
2393
                    }
2394
                }
2395
            }
2396
        } else {
2397
            if (!isset($_REQUEST['subscribe_user_with_password']) &&
2398
                !isset($_REQUEST['subscribe_course'])
2399
            ) {
2400
                Display::addFlash(Display::return_message(get_lang('NoResults'), 'warning'));
2401
            }
2402
        }
2403
2404
        if (api_is_course_admin()) {
2405
            foreach ($courses as &$course) {
2406
                $course['admin_url'] = api_get_path(WEB_CODE_PATH).'/admin/course_list.php?keyword='.$course['code'];
2407
            }
2408
        }
2409
2410
        $toolTitle = get_lang('CourseCatalog');
2411
2412
        $template = new Template($toolTitle, true, true, false, false, false);
2413
        $template->assign('tabs', CoursesAndSessionsCatalog::getTabList());
2414
        $template->assign('frm_filter', $form->returnForm());
2415
        $template->assign('courses', $courses);
2416
        $template->assign(
2417
            'total_number_of_courses',
2418
            CoursesAndSessionsCatalog::countAvailableCoursesToShowInCatalog(
2419
                api_get_current_access_url_id()
2420
            )
2421
        );
2422
        $template->assign('total_number_of_matching_courses', $countCoursesInCategory);
2423
        $template->assign('catalog_url_no_extra_fields', $urlNoExtraFields);
2424
        $template->assign('pagination', $catalogPagination);
2425
        $template->assign('jquery_ready_content', $jqueryReadyContent);
2426
2427
        $templateContent = $template->fetch(
2428
            $template->get_template('catalog/course_catalog.tpl')
2429
        );
2430
2431
        if ($returnHtml) {
2432
            return $templateContent;
2433
        }
2434
2435
        $template->assign('content', $templateContent);
2436
        $template->display_one_col_template();
2437
2438
        return null;
2439
    }
2440
2441
    public static function userCanView(): bool
2442
    {
2443
        // For students
2444
        $userCanViewPage = true;
2445
2446
        if ('false' === api_get_setting('allow_students_to_browse_courses')) {
2447
            $userCanViewPage = false;
2448
        }
2449
2450
        //For teachers/admins
2451
        if (api_is_platform_admin() || api_is_course_admin() || api_is_allowed_to_create_course()) {
2452
            $userCanViewPage = true;
2453
        }
2454
2455
        return $userCanViewPage;
2456
    }
2457
}
2458