Passed
Push — master ( 2f6187...423fc0 )
by
unknown
24:41 queued 17:14
created

learnpath::get_js_dropdown_array()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 76
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 35
nc 12
nop 0
dl 0
loc 76
rs 8.7377
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\ResourceLink;
7
use Chamilo\CoreBundle\Entity\User;
8
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
9
use Chamilo\CourseBundle\Entity\CLpRelUser;
10
use Chamilo\CoreBundle\Framework\Container;
11
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
12
use Chamilo\CourseBundle\Repository\CLpRelUserRepository;
13
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
14
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
15
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
16
use Chamilo\CourseBundle\Entity\CDocument;
17
use Chamilo\CourseBundle\Entity\CForumCategory;
18
use Chamilo\CourseBundle\Entity\CForumThread;
19
use Chamilo\CourseBundle\Entity\CLink;
20
use Chamilo\CourseBundle\Entity\CLp;
21
use Chamilo\CourseBundle\Entity\CLpCategory;
22
use Chamilo\CourseBundle\Entity\CLpItem;
23
use Chamilo\CourseBundle\Entity\CLpItemView;
24
use Chamilo\CourseBundle\Entity\CQuiz;
25
use Chamilo\CourseBundle\Entity\CStudentPublication;
26
use Chamilo\CourseBundle\Entity\CTool;
27
use \Chamilo\CoreBundle\Entity\ResourceNode;
28
use ChamiloSession as Session;
29
use Doctrine\Common\Collections\Criteria;
30
use PhpZip\ZipFile;
31
use Symfony\Component\Finder\Finder;
32
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
33
use Chamilo\CoreBundle\Component\Utils\ObjectIcon;
34
35
/**
36
 * Class learnpath
37
 * This class defines the parent attributes and methods for Chamilo learnpaths
38
 * and SCORM learnpaths. It is used by the scorm class.
39
 *
40
 * @todo decouple class
41
 *
42
 * @author  Yannick Warnier <[email protected]>
43
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
44
 */
45
class learnpath
46
{
47
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
48
    public const STATUS_CSS_CLASS_NAME = [
49
        'not attempted' => 'scorm_not_attempted',
50
        'incomplete' => 'scorm_not_attempted',
51
        'failed' => 'scorm_failed',
52
        'completed' => 'scorm_completed',
53
        'passed' => 'scorm_completed',
54
        'succeeded' => 'scorm_completed',
55
        'browsed' => 'scorm_completed',
56
    ];
57
58
    public $attempt = 0; // The number for the current ID view.
59
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
60
    public $current; // Id of the current item the user is viewing.
61
    public $current_score; // The score of the current item.
62
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
63
    public $current_time_stop; // The time the user closed this resource.
64
    public $default_status = 'not attempted';
65
    public $encoding = 'UTF-8';
66
    public $error = '';
67
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
68
    public $index; // The index of the active learnpath_item in $ordered_items array.
69
    /** @var learnpathItem[] */
70
    public $items = [];
71
    public $last; // item_id of last item viewed in the learning path.
72
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
73
    public $license; // Which license this course has been given - not used yet on 20060522.
74
    public $lp_id; // DB iid for this learnpath.
75
    public $lp_view_id; // DB ID for lp_view
76
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
77
    public $message = '';
78
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
79
    public $name; // Learnpath name (they generally have one).
80
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
81
    public $path = ''; // Path inside the scorm directory (if scorm).
82
    public $theme; // The current theme of the learning path.
83
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
84
    public $accumulateWorkTime; // The min time of learnpath
85
86
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
87
    public $prevent_reinit = 1;
88
89
    // Describes the mode of progress bar display.
90
    public $seriousgame_mode = 0;
91
    public $progress_bar_mode = '%';
92
93
    // Percentage progress as saved in the db.
94
    public $progress_db = 0;
95
    public $proximity; // Wether the content is distant or local or unknown.
96
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
97
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
98
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
99
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
100
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
101
    public $user_id; //ID of the user that is viewing/using the course
102
    public $update_queue = [];
103
    public $scorm_debug = 0;
104
    public $arrMenu = []; // Array for the menu items.
105
    public $debug = 0; // Logging level.
106
    public $lp_session_id = 0;
107
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
108
    public $prerequisite = 0;
109
    public $use_max_score = 1; // 1 or 0
110
    public $subscribeUsers = 0; // Subscribe users or not
111
    public $created_on = '';
112
    public $modified_on = '';
113
    public $published_on = '';
114
    public $expired_on = '';
115
    public $ref;
116
    public $course_int_id;
117
    public $course_info;
118
    public $categoryId;
119
    public $scormUrl;
120
    public $entity;
121
122
    public function __construct(CLp $entity = null, $course_info, $user_id)
123
    {
124
        $debug = $this->debug;
125
        $user_id = (int) $user_id;
126
        $this->encoding = api_get_system_encoding();
127
        $lp_id = 0;
128
        if (null !== $entity) {
129
            $lp_id = $entity->getIid();
130
        }
131
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
132
        $course_id = (int) $course_info['real_id'];
133
        $this->course_info = $course_info;
134
        $this->set_course_int_id($course_id);
135
        if (empty($lp_id) || empty($course_id)) {
136
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
137
        } else {
138
            //$this->entity = $entity;
139
            $this->lp_id = $lp_id;
140
            $this->type = $entity->getLpType();
141
            $this->name = stripslashes($entity->getTitle());
142
            $this->proximity = $entity->getContentLocal();
143
            $this->theme = $entity->getTheme();
144
            $this->maker = $entity->getContentLocal();
145
            $this->prevent_reinit = $entity->getPreventReinit();
146
            $this->seriousgame_mode = $entity->getSeriousgameMode();
147
            $this->license = $entity->getContentLicense();
148
            $this->scorm_debug = $entity->getDebug();
149
            $this->js_lib = $entity->getJsLib();
150
            $this->path = $entity->getPath();
151
            $this->author = $entity->getAuthor();
152
            $this->hide_toc_frame = $entity->getHideTocFrame();
153
            //$this->lp_session_id = $entity->getSessionId();
154
            $this->use_max_score = $entity->getUseMaxScore();
155
            $this->subscribeUsers = $entity->getSubscribeUsers();
156
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
157
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
158
            $this->ref = $entity->getRef();
159
            $this->categoryId = 0;
160
            if ($entity->getCategory()) {
161
                $this->categoryId = $entity->getCategory()->getIid();
162
            }
163
164
            if ($entity->hasAsset()) {
165
                $asset = $entity->getAsset();
166
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/'.$entity->getPath().'/';
167
            }
168
169
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
170
171
            if (!empty($entity->getPublishedOn())) {
172
                $this->published_on = $entity->getPublishedOn()->format('Y-m-d H:i:s');
173
            }
174
175
            if (!empty($entity->getExpiredOn())) {
176
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
177
            }
178
            if (2 == $this->type) {
179
                if (1 == $entity->getForceCommit()) {
180
                    $this->force_commit = true;
181
                }
182
            }
183
            $this->mode = $entity->getDefaultViewMod();
184
185
            // Check user ID.
186
            if (empty($user_id)) {
187
                $this->error = 'User ID is empty';
188
            } else {
189
                $this->user_id = $user_id;
190
            }
191
192
            // End of variables checking.
193
            $session_id = api_get_session_id();
194
            //  Get the session condition for learning paths of the base + session.
195
            $session = api_get_session_condition($session_id);
196
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
197
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
198
199
            // Selecting by view_count descending allows to get the highest view_count first.
200
            $sql = "SELECT * FROM $lp_table
201
                    WHERE
202
                        c_id = $course_id AND
203
                        lp_id = $lp_id AND
204
                        user_id = $user_id
205
                        $session
206
                    ORDER BY view_count DESC";
207
            $res = Database::query($sql);
208
209
            if (Database::num_rows($res) > 0) {
210
                $row = Database::fetch_array($res);
211
                $this->attempt = $row['view_count'];
212
                $this->lp_view_id = $row['iid'];
213
                $this->last_item_seen = $row['last_item'];
214
                $this->progress_db = $row['progress'];
215
                $this->lp_view_session_id = $row['session_id'];
216
            } elseif (!api_is_invitee()) {
217
                $this->attempt = 1;
218
                $params = [
219
                    'c_id' => $course_id,
220
                    'lp_id' => $lp_id,
221
                    'user_id' => $user_id,
222
                    'view_count' => 1,
223
                    //'session_id' => $session_id,
224
                    'last_item' => 0,
225
                ];
226
                if (!empty($session_id)) {
227
                    $params['session_id'] = $session_id;
228
                }
229
                $this->last_item_seen = 0;
230
                $this->lp_view_session_id = $session_id;
231
                $this->lp_view_id = Database::insert($lp_table, $params);
232
            }
233
234
            $criteria = new Criteria();
235
            $criteria
236
                ->where($criteria->expr()->neq('path', 'root'))
237
                ->orderBy(
238
                    [
239
                        'parent' => Criteria::ASC,
240
                        'displayOrder' => Criteria::ASC,
241
                    ]
242
                );
243
            $items = $entity->getItems()->matching($criteria);
244
            $lp_item_id_list = [];
245
            foreach ($items as $item) {
246
                $itemId = $item->getIid();
247
                $lp_item_id_list[] = $itemId;
248
249
                switch ($this->type) {
250
                    case CLp::AICC_TYPE:
251
                        $oItem = new aiccItem('db', $itemId, $course_id);
252
                        if (is_object($oItem)) {
253
                            $oItem->set_lp_view($this->lp_view_id);
254
                            $oItem->set_prevent_reinit($this->prevent_reinit);
255
                            // Don't use reference here as the next loop will make the pointed object change.
256
                            $this->items[$itemId] = $oItem;
257
                            $this->refs_list[$oItem->ref] = $itemId;
258
                        }
259
                        break;
260
                    case CLp::SCORM_TYPE:
261
                        $oItem = new scormItem('db', $itemId);
262
                        if (is_object($oItem)) {
263
                            $oItem->set_lp_view($this->lp_view_id);
264
                            $oItem->set_prevent_reinit($this->prevent_reinit);
265
                            // Don't use reference here as the next loop will make the pointed object change.
266
                            $this->items[$itemId] = $oItem;
267
                            $this->refs_list[$oItem->ref] = $itemId;
268
                        }
269
                        break;
270
                    case CLp::LP_TYPE:
271
                    default:
272
                        $oItem = new learnpathItem(null, $item);
273
                        if (is_object($oItem)) {
274
                            // Moved down to when we are sure the item_view exists.
275
                            //$oItem->set_lp_view($this->lp_view_id);
276
                            $oItem->set_prevent_reinit($this->prevent_reinit);
277
                            // Don't use reference here as the next loop will make the pointed object change.
278
                            $this->items[$itemId] = $oItem;
279
                            $this->refs_list[$itemId] = $itemId;
280
                        }
281
                        break;
282
                }
283
284
                // Setting the object level with variable $this->items[$i][parent]
285
                foreach ($this->items as $itemLPObject) {
286
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
287
                    $itemLPObject->level = $level;
288
                }
289
290
                // Setting the view in the item object.
291
                if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
292
                    $this->items[$itemId]->set_lp_view($this->lp_view_id);
293
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
294
                        $this->items[$itemId]->current_start_time = 0;
295
                        $this->items[$itemId]->current_stop_time = 0;
296
                    }
297
                }
298
            }
299
300
            if (!empty($lp_item_id_list)) {
301
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
302
                if (!empty($lp_item_id_list_to_string)) {
303
                    // Get last viewing vars.
304
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
305
                    // This query should only return one or zero result.
306
                    $sql = "SELECT lp_item_id, status
307
                            FROM $itemViewTable
308
                            WHERE
309
                                lp_view_id = ".$this->get_view_id()." AND
310
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
311
                            ORDER BY view_count DESC ";
312
                    $status_list = [];
313
                    $res = Database::query($sql);
314
                    while ($row = Database:: fetch_array($res)) {
315
                        $status_list[$row['lp_item_id']] = $row['status'];
316
                    }
317
318
                    foreach ($lp_item_id_list as $item_id) {
319
                        if (isset($status_list[$item_id])) {
320
                            $status = $status_list[$item_id];
321
322
                            if (is_object($this->items[$item_id])) {
323
                                $this->items[$item_id]->set_status($status);
324
                                if (empty($status)) {
325
                                    $this->items[$item_id]->set_status(
326
                                        $this->default_status
327
                                    );
328
                                }
329
                            }
330
                        } else {
331
                            if (!api_is_invitee()) {
332
                                if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) {
333
                                    $this->items[$item_id]->set_status(
334
                                        $this->default_status
335
                                    );
336
                                }
337
338
                                if (!empty($this->lp_view_id)) {
339
                                    // Add that row to the lp_item_view table so that
340
                                    // we have something to show in the stats page.
341
                                    $params = [
342
                                        'lp_item_id' => $item_id,
343
                                        'lp_view_id' => $this->lp_view_id,
344
                                        'view_count' => 1,
345
                                        'status' => 'not attempted',
346
                                        'start_time' => time(),
347
                                        'total_time' => 0,
348
                                        'score' => 0,
349
                                    ];
350
                                    Database::insert($itemViewTable, $params);
351
352
                                    $this->items[$item_id]->set_lp_view(
353
                                        $this->lp_view_id
354
                                    );
355
                                }
356
                            }
357
                        }
358
                    }
359
                }
360
            }
361
362
            $this->ordered_items = self::get_flat_ordered_items_list($entity, null);
363
            $this->max_ordered_items = 0;
364
            foreach ($this->ordered_items as $index => $dummy) {
365
                if ($index > $this->max_ordered_items && !empty($dummy)) {
366
                    $this->max_ordered_items = $index;
367
                }
368
            }
369
            // TODO: Define the current item better.
370
            $this->first();
371
            if ($debug) {
372
                error_log('lp_view_session_id '.$this->lp_view_session_id);
373
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
374
            }
375
        }
376
    }
377
378
    /**
379
     * @return int
380
     */
381
    public function get_course_int_id()
382
    {
383
        return $this->course_int_id ?? api_get_course_int_id();
384
    }
385
386
    /**
387
     * @param $course_id
388
     *
389
     * @return int
390
     */
391
    public function set_course_int_id($course_id)
392
    {
393
        return $this->course_int_id = (int) $course_id;
394
    }
395
396
    /**
397
     * Function rewritten based on old_add_item() from Yannick Warnier.
398
     * Due the fact that users can decide where the item should come, I had to overlook this function and
399
     * I found it better to rewrite it. Old function is still available.
400
     * Added also the possibility to add a description.
401
     *
402
     * @param CLpItem $parent
403
     * @param int     $previousId
404
     * @param string  $type
405
     * @param int     $id resource ID (ref)
406
     * @param string  $title
407
     * @param string  $description
408
     * @param int     $prerequisites
409
     * @param int     $maxTimeAllowed
410
     * @param int     $userId
411
     *
412
     * @return int
413
     */
414
    public function add_item(
415
        ?CLpItem $parent,
416
        $previousId,
417
        $type,
418
        $id,
419
        $title,
420
        $description = '',
421
        $prerequisites = 0,
422
        $maxTimeAllowed = 0
423
    ) {
424
        $type = empty($type) ? 'dir' : $type;
425
        $course_id = $this->course_info['real_id'];
426
        if (empty($course_id)) {
427
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
428
            $this->course_info = api_get_course_info($this->cc);
429
            $course_id = $this->course_info['real_id'];
430
        }
431
        $id = (int) $id;
432
        $maxTimeAllowed = (int) $maxTimeAllowed;
433
        if (empty($maxTimeAllowed)) {
434
            $maxTimeAllowed = 0;
435
        }
436
        $maxScore = 100;
437
        if ('quiz' === $type && $id) {
438
            // Disabling the exercise if we add it inside a LP
439
            $exercise = new Exercise($course_id);
440
            $exercise->read($id);
441
            $maxScore = $exercise->getMaxScore();
442
443
            $exercise->disable();
444
            $exercise->save();
445
            $title = $exercise->get_formated_title();
446
        }
447
448
        $lpItem = (new CLpItem())
449
            ->setTitle($title)
450
            ->setDescription($description)
451
            ->setPath($id)
452
            ->setLp(api_get_lp_entity($this->get_id()))
453
            ->setItemType($type)
454
            ->setMaxScore($maxScore)
455
            ->setMaxTimeAllowed($maxTimeAllowed)
456
            ->setPrerequisite($prerequisites)
457
            //->setDisplayOrder($display_order + 1)
458
            //->setNextItemId((int) $next)
459
            //->setPreviousItemId($previous)
460
        ;
461
462
        if (!empty($parent))  {
463
            $lpItem->setParent($parent);
464
        }
465
        $em = Database::getManager();
466
        $em->persist($lpItem);
467
        $em->flush();
468
469
        $new_item_id = $lpItem->getIid();
470
        if ($new_item_id) {
471
            // @todo fix upload audio.
472
            // Upload audio.
473
            /*if (!empty($_FILES['mp3']['name'])) {
474
                // Create the audio folder if it does not exist yet.
475
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
476
                if (!is_dir($filepath.'audio')) {
477
                    mkdir(
478
                        $filepath.'audio',
479
                        api_get_permissions_for_new_directories()
480
                    );
481
                    DocumentManager::addDocument(
482
                        $_course,
483
                        '/audio',
484
                        'folder',
485
                        0,
486
                        'audio',
487
                        '',
488
                        0,
489
                        true,
490
                        null,
491
                        $sessionId,
492
                        $userId
493
                    );
494
                }
495
496
                $file_path = handle_uploaded_document(
497
                    $_course,
498
                    $_FILES['mp3'],
499
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
500
                    '/audio',
501
                    $userId,
502
                    '',
503
                    '',
504
                    '',
505
                    '',
506
                    false
507
                );
508
509
                // Getting the filename only.
510
                $file_components = explode('/', $file_path);
511
                $file = $file_components[count($file_components) - 1];
512
513
                // Store the mp3 file in the lp_item table.
514
                $sql = "UPDATE $tbl_lp_item SET
515
                          audio = '".Database::escape_string($file)."'
516
                        WHERE iid = '".intval($new_item_id)."'";
517
                Database::query($sql);
518
            }*/
519
        }
520
521
        return $new_item_id;
522
    }
523
524
    /**
525
     * Static admin function allowing addition of a learnpath to a course.
526
     *
527
     * @param string $courseCode
528
     * @param string $name
529
     * @param string $description
530
     * @param string $learnpath
531
     * @param string $origin
532
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
533
     * @param string $published_on
534
     * @param string $expired_on
535
     * @param int    $categoryId
536
     * @param int    $userId
537
     *
538
     * @return CLp
539
     */
540
    public static function add_lp(
541
        $courseCode,
542
        $name,
543
        $description = '',
544
        $learnpath = 'guess',
545
        $origin = 'zip',
546
        $zipname = '',
547
        $published_on = '',
548
        $expired_on = '',
549
        $categoryId = 0,
550
        $userId = 0
551
    ) {
552
        global $charset;
553
554
        if (!empty($courseCode)) {
555
            $courseInfo = api_get_course_info($courseCode);
556
            $course_id = $courseInfo['real_id'];
557
        } else {
558
            $course_id = api_get_course_int_id();
559
            $courseInfo = api_get_course_info();
560
        }
561
562
        $categoryId = (int) $categoryId;
563
564
        if (empty($published_on)) {
565
            $published_on = null;
566
        } else {
567
            $published_on = api_get_utc_datetime($published_on, true, true);
568
        }
569
570
        if (empty($expired_on)) {
571
            $expired_on = null;
572
        } else {
573
            $expired_on = api_get_utc_datetime($expired_on, true, true);
574
        }
575
576
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES));
577
        $type = 1;
578
        switch ($learnpath) {
579
            case 'guess':
580
            case 'aicc':
581
                break;
582
            case 'dokeos':
583
            case 'chamilo':
584
                $type = 1;
585
                break;
586
        }
587
588
        $sessionEntity = api_get_session_entity();
589
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
590
        $lp = null;
591
        switch ($origin) {
592
            case 'zip':
593
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
594
                break;
595
            case 'manual':
596
            default:
597
                /*$get_max = "SELECT MAX(display_order)
598
                            FROM $tbl_lp WHERE c_id = $course_id";
599
                $res_max = Database::query($get_max);
600
                if (Database::num_rows($res_max) < 1) {
601
                    $dsp = 1;
602
                } else {
603
                    $row = Database::fetch_array($res_max);
604
                    $dsp = $row[0] + 1;
605
                }*/
606
607
                $category = null;
608
                if (!empty($categoryId)) {
609
                    $category = Container::getLpCategoryRepository()->find($categoryId);
610
                }
611
612
                $lpRepo = Container::getLpRepository();
613
614
                $lp = (new CLp())
615
                    ->setLpType($type)
616
                    ->setTitle($name)
617
                    ->setDescription($description)
618
                    ->setCategory($category)
619
                    ->setPublishedOn($published_on)
620
                    ->setExpiredOn($expired_on)
621
                    ->setParent($courseEntity)
622
                    ->addCourseLink($courseEntity, $sessionEntity)
623
                ;
624
                $lpRepo->createLp($lp);
625
626
                break;
627
        }
628
629
        return $lp;
630
    }
631
632
    /**
633
     * Auto completes the parents of an item in case it's been completed or passed.
634
     *
635
     * @param int $item Optional ID of the item from which to look for parents
636
     */
637
    public function autocomplete_parents($item)
638
    {
639
        $debug = $this->debug;
640
641
        if (empty($item)) {
642
            $item = $this->current;
643
        }
644
645
        $currentItem = $this->getItem($item);
646
        if ($currentItem) {
647
            $parent_id = $currentItem->get_parent();
648
            $parent = $this->getItem($parent_id);
649
            if ($parent) {
650
                // if $item points to an object and there is a parent.
651
                if ($debug) {
652
                    error_log(
653
                        'Autocompleting parent of item '.$item.' '.
654
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
655
                        0
656
                    );
657
                }
658
659
                // New experiment including failed and browsed in completed status.
660
                //$current_status = $currentItem->get_status();
661
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
662
                // Fixes chapter auto complete
663
                if (true) {
664
                    // If the current item is completed or passes or succeeded.
665
                    $updateParentStatus = true;
666
                    if ($debug) {
667
                        error_log('Status of current item is alright');
668
                    }
669
670
                    foreach ($parent->get_children() as $childItemId) {
671
                        $childItem = $this->getItem($childItemId);
672
673
                        // If children was not set try to get the info
674
                        if (empty($childItem->db_item_view_id)) {
675
                            $childItem->set_lp_view($this->lp_view_id);
676
                        }
677
678
                        // Check all his brothers (parent's children) for completion status.
679
                        if ($childItemId != $item) {
680
                            if ($debug) {
681
                                error_log(
682
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
683
                                    0
684
                                );
685
                            }
686
                            // Trying completing parents of failed and browsed items as well.
687
                            if ($childItem->status_is(
688
                                [
689
                                    'completed',
690
                                    'passed',
691
                                    'succeeded',
692
                                    'browsed',
693
                                    'failed',
694
                                ]
695
                            )
696
                            ) {
697
                                // Keep completion status to true.
698
                                continue;
699
                            } else {
700
                                if ($debug > 2) {
701
                                    error_log(
702
                                        'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
703
                                        0
704
                                    );
705
                                }
706
                                $updateParentStatus = false;
707
                                break;
708
                            }
709
                        }
710
                    }
711
712
                    if ($updateParentStatus) {
713
                        // If all the children were completed:
714
                        $parent->set_status('completed');
715
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
716
                        // Force the status to "completed"
717
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
718
                        $this->update_queue[$parent->get_id()] = 'completed';
719
                        if ($debug) {
720
                            error_log(
721
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
722
                                print_r($this->update_queue, 1),
723
                                0
724
                            );
725
                        }
726
                        // Recursive call.
727
                        $this->autocomplete_parents($parent->get_id());
728
                    }
729
                }
730
            } else {
731
                if ($debug) {
732
                    error_log("Parent #$parent_id does not exists");
733
                }
734
            }
735
        } else {
736
            if ($debug) {
737
                error_log("#$item is an item that doesn't have parents");
738
            }
739
        }
740
    }
741
742
    /**
743
     * Closes the current resource.
744
     *
745
     * Stops the timer
746
     * Saves into the database if required
747
     * Clears the current resource data from this object
748
     *
749
     * @return bool True on success, false on failure
750
     */
751
    public function close()
752
    {
753
        if (empty($this->lp_id)) {
754
            $this->error = 'Trying to close this learnpath but no ID is set';
755
756
            return false;
757
        }
758
        $this->current_time_stop = time();
759
        $this->ordered_items = [];
760
        $this->index = 0;
761
        unset($this->lp_id);
762
        //unset other stuff
763
        return true;
764
    }
765
766
    /**
767
     * Static admin function allowing removal of a learnpath.
768
     *
769
     * @param array  $courseInfo
770
     * @param int    $id         Learnpath ID
771
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
772
     *
773
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
774
     */
775
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
776
    {
777
        $course_id = api_get_course_int_id();
778
        if (!empty($courseInfo)) {
779
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
780
        }
781
782
        // TODO: Implement a way of getting this to work when the current object is not set.
783
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
784
        // If an ID is specifically given and the current LP is not the same, prevent delete.
785
        if (!empty($id) && ($id != $this->lp_id)) {
786
            return false;
787
        }
788
789
        $course = api_get_course_entity();
790
        $session = api_get_session_entity();
791
792
        //$lp_item = Database::get_course_table(TABLE_LP_ITEM);
793
        //$lp_view = Database::get_course_table(TABLE_LP_VIEW);
794
        //$lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
795
796
        // Delete lp item id.
797
        //foreach ($this->items as $lpItemId => $dummy) {
798
        //    $sql = "DELETE FROM $lp_item_view
799
        //            WHERE lp_item_id = '".$lpItemId."'";
800
        //    Database::query($sql);
801
        //}
802
803
        // Proposed by Christophe (nickname: clefevre)
804
        //$sql = "DELETE FROM $lp_item
805
        //        WHERE lp_id = ".$this->lp_id;
806
        //Database::query($sql);
807
808
        //$sql = "DELETE FROM $lp_view
809
        //        WHERE lp_id = ".$this->lp_id;
810
        //Database::query($sql);
811
812
        //$table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
813
        //$sql = "DELETE FROM $table
814
        //        WHERE
815
        //            lp_id = {$this->lp_id}";
816
        //Database::query($sql);
817
818
        $lp = Container::getLpRepository()->find($this->lp_id);
819
820
        Database::getManager()
821
            ->getRepository(ResourceLink::class)
822
            ->removeByResourceInContext($lp, $course, $session);
823
824
        $link_info = GradebookUtils::isResourceInCourseGradebook(
825
            api_get_course_int_id(),
826
            4,
827
            $id,
828
            api_get_session_id()
829
        );
830
831
        if (false !== $link_info) {
832
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
833
        }
834
835
        //if ('true' === api_get_setting('search_enabled')) {
836
        //    delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
837
        //}
838
    }
839
840
    /**
841
     * Removes all the children of one item - dangerous!
842
     *
843
     * @param int $id Element ID of which children have to be removed
844
     *
845
     * @return int Total number of children removed
846
     */
847
    public function delete_children_items($id)
848
    {
849
        $course_id = $this->course_info['real_id'];
850
851
        $num = 0;
852
        $id = (int) $id;
853
        if (empty($id) || empty($course_id)) {
854
            return false;
855
        }
856
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
857
        $sql = "SELECT * FROM $lp_item
858
                WHERE parent_item_id = $id";
859
        $res = Database::query($sql);
860
        while ($row = Database::fetch_array($res)) {
861
            $num += $this->delete_children_items($row['iid']);
862
            $sql = "DELETE FROM $lp_item
863
                    WHERE iid = ".$row['iid'];
864
            Database::query($sql);
865
            $num++;
866
        }
867
868
        return $num;
869
    }
870
871
    /**
872
     * Removes an item from the current learnpath.
873
     *
874
     * @param int $id Elem ID (0 if first)
875
     *
876
     * @return int Number of elements moved
877
     *
878
     * @todo implement resource removal
879
     */
880
    public function delete_item($id)
881
    {
882
        $course_id = api_get_course_int_id();
883
        $id = (int) $id;
884
        // TODO: Implement the resource removal.
885
        if (empty($id) || empty($course_id)) {
886
            return false;
887
        }
888
889
        $repo = Container::getLpItemRepository();
890
        $item = $repo->find($id);
891
        if (null === $item) {
892
            return false;
893
        }
894
895
        $em = Database::getManager();
896
        $repo->removeFromTree($item);
897
        $em->flush();
898
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
899
900
        //Removing prerequisites since the item will not longer exist
901
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
902
                    WHERE prerequisite = '$id'";
903
        Database::query($sql_all);
904
905
        $sql = "UPDATE $lp_item
906
                SET previous_item_id = ".$this->getLastInFirstLevel()."
907
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
908
        Database::query($sql);
909
910
        // Remove from search engine if enabled.
911
        if ('true' === api_get_setting('search_enabled')) {
912
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
913
            $sql = 'SELECT * FROM %s
914
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
915
                    LIMIT 1';
916
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
917
            $res = Database::query($sql);
918
            if (Database::num_rows($res) > 0) {
919
                $row2 = Database::fetch_array($res);
920
                $di = new ChamiloIndexer();
921
                $di->remove_document($row2['search_did']);
922
            }
923
            $sql = 'DELETE FROM %s
924
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
925
                    LIMIT 1';
926
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
927
            Database::query($sql);
928
        }
929
    }
930
931
    /**
932
     * Updates an item's content in place.
933
     *
934
     * @param int    $id               Element ID
935
     * @param int    $parent           Parent item ID
936
     * @param int    $previous         Previous item ID
937
     * @param string $title            Item title
938
     * @param string $description      Item description
939
     * @param string $prerequisites    Prerequisites (optional)
940
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
941
     * @param int    $max_time_allowed
942
     * @param string $url
943
     *
944
     * @return bool True on success, false on error
945
     */
946
    public function edit_item(
947
        $id,
948
        $parent,
949
        $previous,
950
        $title,
951
        $description,
952
        $prerequisites = '0',
953
        $audio = [],
954
        $max_time_allowed = 0,
955
        $url = ''
956
    ) {
957
        $_course = api_get_course_info();
958
        $id = (int) $id;
959
960
        if (empty($id) || empty($_course)) {
961
            return false;
962
        }
963
        $repo = Container::getLpItemRepository();
964
        /** @var CLpItem $item */
965
        $item = $repo->find($id);
966
        if (null === $item) {
967
            return false;
968
        }
969
970
        $item
971
            ->setTitle($title)
972
            ->setDescription($description)
973
            ->setPrerequisite($prerequisites)
974
            ->setMaxTimeAllowed((int) $max_time_allowed)
975
        ;
976
977
        $em = Database::getManager();
978
        if (!empty($parent)) {
979
            $parent = $repo->find($parent);
980
            $item->setParent($parent);
981
        } else {
982
            $item->setParent(null);
983
        }
984
985
        if (!empty($previous)) {
986
            $previous = $repo->find($previous);
987
            $repo->persistAsNextSiblingOf( $item, $previous);
988
        } else {
989
            $em->persist($item);
990
        }
991
992
        $em->flush();
993
994
        if ('link' === $item->getItemType()) {
995
            $link = new Link();
996
            $linkId = $item->getPath();
997
            $link->updateLink($linkId, $url);
998
        }
999
    }
1000
1001
    /**
1002
     * Updates an item's prereq in place.
1003
     *
1004
     * @param int    $id              Element ID
1005
     * @param string $prerequisite_id Prerequisite Element ID
1006
     * @param int    $minScore        Prerequisite min score
1007
     * @param int    $maxScore        Prerequisite max score
1008
     *
1009
     * @return bool True on success, false on error
1010
     */
1011
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1012
    {
1013
        $id = (int) $id;
1014
1015
        if (empty($id)) {
1016
            return false;
1017
        }
1018
        $prerequisite_id = (int) $prerequisite_id;
1019
1020
        if (empty($minScore) || $minScore < 0) {
1021
            $minScore = 0;
1022
        }
1023
1024
        if (empty($maxScore) || $maxScore < 0) {
1025
            $maxScore = 100;
1026
        }
1027
1028
        $minScore = (float) $minScore;
1029
        $maxScore = (float) $maxScore;
1030
1031
        if (empty($prerequisite_id)) {
1032
            $prerequisite_id = 'NULL';
1033
            $minScore = 0;
1034
            $maxScore = 100;
1035
        }
1036
1037
        $table = Database::get_course_table(TABLE_LP_ITEM);
1038
        $sql = " UPDATE $table
1039
                 SET
1040
                    prerequisite = $prerequisite_id ,
1041
                    prerequisite_min_score = $minScore ,
1042
                    prerequisite_max_score = $maxScore
1043
                 WHERE iid = $id";
1044
        Database::query($sql);
1045
1046
        return true;
1047
    }
1048
1049
    /**
1050
     * Get the specific prefix index terms of this learning path.
1051
     *
1052
     * @param string $prefix
1053
     *
1054
     * @return array Array of terms
1055
     */
1056
    public function get_common_index_terms_by_prefix($prefix)
1057
    {
1058
        $terms = get_specific_field_values_list_by_prefix(
1059
            $prefix,
1060
            $this->cc,
1061
            TOOL_LEARNPATH,
1062
            $this->lp_id
1063
        );
1064
        $prefix_terms = [];
1065
        if (!empty($terms)) {
1066
            foreach ($terms as $term) {
1067
                $prefix_terms[] = $term['value'];
1068
            }
1069
        }
1070
1071
        return $prefix_terms;
1072
    }
1073
1074
    /**
1075
     * Gets the number of items currently completed.
1076
     *
1077
     * @param bool Flag to determine the failed status is not considered progressed
1078
     *
1079
     * @return int The number of items currently completed
1080
     */
1081
    public function get_complete_items_count(bool $failedStatusException = false): int
1082
    {
1083
        $i = 0;
1084
        $completedStatusList = [
1085
            'completed',
1086
            'passed',
1087
            'succeeded',
1088
            'browsed',
1089
        ];
1090
1091
        if (!$failedStatusException) {
1092
            $completedStatusList[] = 'failed';
1093
        }
1094
1095
        foreach ($this->items as $id => $dummy) {
1096
            // Trying failed and browsed considered "progressed" as well.
1097
            if ($this->items[$id]->status_is($completedStatusList) &&
1098
                'dir' !== $this->items[$id]->get_type()
1099
            ) {
1100
                $i++;
1101
            }
1102
        }
1103
1104
        return $i;
1105
    }
1106
1107
    /**
1108
     * Gets the current item ID.
1109
     *
1110
     * @return int The current learnpath item id
1111
     */
1112
    public function get_current_item_id()
1113
    {
1114
        $current = 0;
1115
        if (!empty($this->current)) {
1116
            $current = (int) $this->current;
1117
        }
1118
1119
        return $current;
1120
    }
1121
1122
    /**
1123
     * Force to get the first learnpath item id.
1124
     *
1125
     * @return int The current learnpath item id
1126
     */
1127
    public function get_first_item_id()
1128
    {
1129
        $current = 0;
1130
        if (is_array($this->ordered_items)) {
1131
            $current = $this->ordered_items[0];
1132
        }
1133
1134
        return $current;
1135
    }
1136
1137
    /**
1138
     * Gets the total number of items available for viewing in this SCORM.
1139
     *
1140
     * @return int The total number of items
1141
     */
1142
    public function get_total_items_count()
1143
    {
1144
        return count($this->items);
1145
    }
1146
1147
    /**
1148
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1149
     *
1150
     * @return int The total no-chapters number of items
1151
     */
1152
    public function getTotalItemsCountWithoutDirs()
1153
    {
1154
        $total = 0;
1155
        $typeListNotToCount = self::getChapterTypes();
1156
        foreach ($this->items as $temp2) {
1157
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1158
                $total++;
1159
            }
1160
        }
1161
1162
        return $total;
1163
    }
1164
1165
    /**
1166
     *  Sets the first element URL.
1167
     */
1168
    public function first()
1169
    {
1170
        if ($this->debug > 0) {
1171
            error_log('In learnpath::first()', 0);
1172
            error_log('$this->last_item_seen '.$this->last_item_seen);
1173
        }
1174
1175
        // Test if the last_item_seen exists and is not a dir.
1176
        if (0 == count($this->ordered_items)) {
1177
            $this->index = 0;
1178
        }
1179
1180
        if (!empty($this->last_item_seen) &&
1181
            !empty($this->items[$this->last_item_seen]) &&
1182
            'dir' !== $this->items[$this->last_item_seen]->get_type()
1183
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1184
            //&& !$this->items[$this->last_item_seen]->is_done()
1185
        ) {
1186
            if ($this->debug > 2) {
1187
                error_log(
1188
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1189
                    $this->items[$this->last_item_seen]->get_type()
1190
                );
1191
            }
1192
            $index = -1;
1193
            foreach ($this->ordered_items as $myindex => $item_id) {
1194
                if ($item_id == $this->last_item_seen) {
1195
                    $index = $myindex;
1196
                    break;
1197
                }
1198
            }
1199
            if (-1 == $index) {
1200
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1201
                if ($this->debug > 2) {
1202
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1203
                }
1204
1205
                return false;
1206
            } else {
1207
                $this->last = $this->last_item_seen;
1208
                $this->current = $this->last_item_seen;
1209
                $this->index = $index;
1210
            }
1211
        } else {
1212
            if ($this->debug > 2) {
1213
                error_log('In learnpath::first() - No last item seen', 0);
1214
            }
1215
            $index = 0;
1216
            // Loop through all ordered items and stop at the first item that is
1217
            // not a directory *and* that has not been completed yet.
1218
            while (!empty($this->ordered_items[$index]) &&
1219
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1220
                (
1221
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1222
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1223
                ) && $index < $this->max_ordered_items
1224
            ) {
1225
                $index++;
1226
            }
1227
1228
            $this->last = $this->current;
1229
            // current is
1230
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1231
            $this->index = $index;
1232
            if ($this->debug > 2) {
1233
                error_log('$index '.$index);
1234
                error_log('In learnpath::first() - No last item seen');
1235
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1236
            }
1237
        }
1238
        if ($this->debug > 2) {
1239
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1240
        }
1241
    }
1242
1243
    /**
1244
     * Gets the js library from the database.
1245
     *
1246
     * @return string The name of the javascript library to be used
1247
     */
1248
    public function get_js_lib()
1249
    {
1250
        $lib = '';
1251
        if (!empty($this->js_lib)) {
1252
            $lib = $this->js_lib;
1253
        }
1254
1255
        return $lib;
1256
    }
1257
1258
    /**
1259
     * Gets the learnpath database ID.
1260
     *
1261
     * @return int Learnpath ID in the lp table
1262
     */
1263
    public function get_id()
1264
    {
1265
        if (!empty($this->lp_id)) {
1266
            return (int) $this->lp_id;
1267
        }
1268
1269
        return 0;
1270
    }
1271
1272
    /**
1273
     * Gets the last element URL.
1274
     *
1275
     * @return string URL to load into the viewer
1276
     */
1277
    public function get_last()
1278
    {
1279
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1280
        if (count($this->ordered_items) > 0) {
1281
            $this->index = count($this->ordered_items) - 1;
1282
1283
            return $this->ordered_items[$this->index];
1284
        }
1285
1286
        return false;
1287
    }
1288
1289
    /**
1290
     * Get the last element in the first level.
1291
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1292
     *
1293
     * @return mixed
1294
     */
1295
    public function getLastInFirstLevel()
1296
    {
1297
        try {
1298
            $lastId = Database::getManager()
1299
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1300
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1301
                ->setMaxResults(1)
1302
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1303
                ->getSingleScalarResult();
1304
1305
            return $lastId;
1306
        } catch (Exception $exception) {
1307
            return 0;
1308
        }
1309
    }
1310
1311
    /**
1312
     * Gets the navigation bar for the learnpath display screen.
1313
     *
1314
     * @param string $barId
1315
     *
1316
     * @return string The HTML string to use as a navigation bar
1317
     */
1318
    public function get_navigation_bar($barId = '')
1319
    {
1320
        if (empty($barId)) {
1321
            $barId = 'control-top';
1322
        }
1323
        $lpId = $this->lp_id;
1324
        $mycurrentitemid = $this->get_current_item_id();
1325
        $reportingText = get_lang('Reporting');
1326
        $previousText = get_lang('Previous');
1327
        $nextText = get_lang('Next');
1328
        $fullScreenText = get_lang('Back to normal screen');
1329
1330
        $settings = api_get_setting('lp.lp_view_settings', true);
1331
        $display = $settings['display'] ?? false;
1332
        $icon = Display::getMdiIcon('information');
1333
1334
        $reportingIcon = '
1335
            <a class="icon-toolbar"
1336
                id="stats_link"
1337
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1338
                onclick="window.parent.API.save_asset(); return true;"
1339
                target="content_name" title="'.$reportingText.'">
1340
                '.$icon.'<span class="sr-only">'.$reportingText.'</span>
1341
            </a>';
1342
1343
        if (!empty($display)) {
1344
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1345
            if (false === $showReporting) {
1346
                $reportingIcon = '';
1347
            }
1348
        }
1349
1350
        $hideArrows = false;
1351
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1352
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1353
        }
1354
1355
        $previousIcon = '';
1356
        $nextIcon = '';
1357
        if (false === $hideArrows) {
1358
            $icon = Display::getMdiIcon('chevron-left');
1359
            $previousIcon = '
1360
                <button class="icon-toolbar" id="scorm-previous" type="button"
1361
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1362
                    '.$icon.'<span class="sr-only">'.$previousText.'</span>
1363
                </button>';
1364
1365
            $icon = Display::getMdiIcon('chevron-right');
1366
            $nextIcon = '
1367
                <button class="icon-toolbar" id="scorm-next" type="button"
1368
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1369
                    '.$icon.'<span class="sr-only">'.$nextText.'</span>
1370
                </button>';
1371
        }
1372
1373
        if ('fullscreen' === $this->mode) {
1374
            $icon = Display::getMdiIcon('view-column');
1375
            $navbar = '
1376
                  <span id="'.$barId.'" class="buttons">
1377
                    '.$reportingIcon.'
1378
                    '.$previousIcon.'
1379
                    '.$nextIcon.'
1380
                    <a class="icon-toolbar" id="view-embedded"
1381
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1382
                        '.$icon.'<span class="sr-only">'.$fullScreenText.'</span>
1383
                    </a>
1384
                  </span>';
1385
        } else {
1386
            $navbar = '
1387
                 <span id="'.$barId.'" class="buttons text-right">
1388
                    '.$reportingIcon.'
1389
                    '.$previousIcon.'
1390
                    '.$nextIcon.'
1391
                </span>';
1392
        }
1393
1394
        return $navbar;
1395
    }
1396
1397
    /**
1398
     * Gets the next resource in queue (url).
1399
     *
1400
     * @return string URL to load into the viewer
1401
     */
1402
    public function get_next_index()
1403
    {
1404
        // TODO
1405
        $index = $this->index;
1406
        $index++;
1407
        while (
1408
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1409
            $index < $this->max_ordered_items
1410
        ) {
1411
            $index++;
1412
            if ($index == $this->max_ordered_items) {
1413
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1414
                    return $this->index;
1415
                }
1416
1417
                return $index;
1418
            }
1419
        }
1420
        if (empty($this->ordered_items[$index])) {
1421
            return $this->index;
1422
        }
1423
1424
        return $index;
1425
    }
1426
1427
    /**
1428
     * Gets item_id for the next element.
1429
     *
1430
     * @return int Next item (DB) ID
1431
     */
1432
    public function get_next_item_id()
1433
    {
1434
        $new_index = $this->get_next_index();
1435
        if (!empty($new_index)) {
1436
            if (isset($this->ordered_items[$new_index])) {
1437
                return $this->ordered_items[$new_index];
1438
            }
1439
        }
1440
1441
        return 0;
1442
    }
1443
1444
    /**
1445
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1446
     *
1447
     * Generally, the package provided is in the form of a zip file, so the function
1448
     * has been written to test a zip file. If not a zip, the function will return the
1449
     * default return value: ''
1450
     *
1451
     * @param string $filePath the path to the file
1452
     * @param string $file_name the original name of the file
1453
     *
1454
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1455
     *                if the package is empty, or '' if the package cannot be recognized
1456
     */
1457
    public static function getPackageType($filePath, $file_name)
1458
    {
1459
        // Get name of the zip file without the extension.
1460
        $file_info = pathinfo($file_name);
1461
        $extension = $file_info['extension']; // Extension only.
1462
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1463
                'dll',
1464
                'exe',
1465
            ])) {
1466
            return 'oogie';
1467
        }
1468
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1469
                'dll',
1470
                'exe',
1471
            ])) {
1472
            return 'woogie';
1473
        }
1474
1475
        $zipFile = new ZipFile();
1476
        $zipFile->openFile($filePath);
1477
        $zipContentArray = $zipFile->getEntries();
1478
        $package_type = '';
1479
        $manifest = '';
1480
        $aicc_match_crs = 0;
1481
        $aicc_match_au = 0;
1482
        $aicc_match_des = 0;
1483
        $aicc_match_cst = 0;
1484
        $countItems = 0;
1485
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1486
        if ($zipContentArray) {
1487
            $countItems = count($zipContentArray);
1488
            if ($countItems > 0) {
1489
                foreach ($zipContentArray as $thisContent) {
1490
                    $fileName = basename($thisContent->getName());
1491
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1492
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1493
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1494
                        $manifest = $fileName; // Just the relative directory inside scorm/
1495
                        $package_type = 'scorm';
1496
                        break; // Exit the foreach loop.
1497
                    } elseif (
1498
                        preg_match('/aicc\//i', $fileName) ||
1499
                        in_array(
1500
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1501
                            ['crs', 'au', 'des', 'cst']
1502
                        )
1503
                    ) {
1504
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1505
                        switch ($ext) {
1506
                            case 'crs':
1507
                                $aicc_match_crs = 1;
1508
                                break;
1509
                            case 'au':
1510
                                $aicc_match_au = 1;
1511
                                break;
1512
                            case 'des':
1513
                                $aicc_match_des = 1;
1514
                                break;
1515
                            case 'cst':
1516
                                $aicc_match_cst = 1;
1517
                                break;
1518
                            default:
1519
                                break;
1520
                        }
1521
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1522
                    } else {
1523
                        $package_type = '';
1524
                    }
1525
                }
1526
            }
1527
        }
1528
1529
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1530
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1531
            $package_type = 'aicc';
1532
        }
1533
1534
        // Try with chamilo course builder
1535
        if (empty($package_type)) {
1536
            // Sometimes users will try to upload an empty zip, or a zip with
1537
            // only a folder. Catch that and make the calling function aware.
1538
            // If the single file was the imsmanifest.xml, then $package_type
1539
            // would be 'scorm' and we wouldn't be here.
1540
            if ($countItems < 2) {
1541
                return 'error-empty-package';
1542
            }
1543
            $package_type = 'chamilo';
1544
        }
1545
1546
        return $package_type;
1547
    }
1548
1549
    /**
1550
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1551
     *
1552
     * @return string URL to load into the viewer
1553
     */
1554
    public function get_previous_index()
1555
    {
1556
        $index = $this->index;
1557
        if (isset($this->ordered_items[$index - 1])) {
1558
            $index--;
1559
            while (isset($this->ordered_items[$index]) &&
1560
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1561
            ) {
1562
                $index--;
1563
                if ($index < 0) {
1564
                    return $this->index;
1565
                }
1566
            }
1567
        }
1568
1569
        return $index;
1570
    }
1571
1572
    /**
1573
     * Gets item_id for the next element.
1574
     *
1575
     * @return int Previous item (DB) ID
1576
     */
1577
    public function get_previous_item_id()
1578
    {
1579
        $index = $this->get_previous_index();
1580
1581
        return $this->ordered_items[$index];
1582
    }
1583
1584
    /**
1585
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1586
     *
1587
     * @param int    $lpItemId
1588
     * @param string $autostart
1589
     *
1590
     * @return string The mediaplayer HTML
1591
     */
1592
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1593
    {
1594
        $courseInfo = api_get_course_info();
1595
        $lpItemId = (int) $lpItemId;
1596
1597
        if (empty($courseInfo) || empty($lpItemId)) {
1598
            return '';
1599
        }
1600
        $item = $this->items[$lpItemId] ?? null;
1601
1602
        if (empty($item)) {
1603
            return '';
1604
        }
1605
1606
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1607
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1608
        $itemViewId = (int) $item->db_item_view_id;
1609
1610
        // Getting all the information about the item.
1611
        $sql = "SELECT lp_view.status
1612
                FROM $tbl_lp_item as lpi
1613
                INNER JOIN $tbl_lp_item_view as lp_view
1614
                ON (lpi.iid = lp_view.lp_item_id)
1615
                WHERE
1616
                    lp_view.iid = $itemViewId AND
1617
                    lpi.iid = $lpItemId
1618
                ";
1619
        $result = Database::query($sql);
1620
        $row = Database::fetch_assoc($result);
1621
        $output = '';
1622
        $audio = $item->audio;
1623
1624
        if (!empty($audio)) {
1625
            $list = $_SESSION['oLP']->get_toc();
1626
1627
            switch ($item->get_type()) {
1628
                case 'quiz':
1629
                    $type_quiz = false;
1630
                    foreach ($list as $toc) {
1631
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1632
                            $type_quiz = true;
1633
                        }
1634
                    }
1635
1636
                    if ($type_quiz) {
1637
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1638
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1639
                        } else {
1640
                            $autostart_audio = $autostart;
1641
                        }
1642
                    }
1643
                    break;
1644
                case TOOL_READOUT_TEXT:
1645
                    $autostart_audio = 'false';
1646
                    break;
1647
                default:
1648
                    $autostart_audio = 'true';
1649
            }
1650
1651
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1652
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1653
1654
            $player = Display::getMediaPlayer(
1655
                $file,
1656
                [
1657
                    'id' => 'lp_audio_media_player',
1658
                    'url' => $url,
1659
                    'autoplay' => $autostart_audio,
1660
                    'width' => '100%',
1661
                ]
1662
            );
1663
1664
            // The mp3 player.
1665
            $output = '<div id="container">';
1666
            $output .= $player;
1667
            $output .= '</div>';
1668
        }
1669
1670
        return $output;
1671
    }
1672
1673
    /**
1674
     * @param int    $studentId
1675
     * @param int    $prerequisite
1676
     * @param Course $course
1677
     * @param int    $sessionId
1678
     *
1679
     * @return bool
1680
     */
1681
    public static function isBlockedByPrerequisite(
1682
        $studentId,
1683
        $prerequisite,
1684
        Course $course,
1685
        $sessionId
1686
    ) {
1687
        $courseId = $course->getId();
1688
1689
        $allow = ('true' === api_get_setting('lp.allow_teachers_to_access_blocked_lp_by_prerequisite'));
1690
        if ($allow) {
1691
            if (api_is_allowed_to_edit() ||
1692
                api_is_platform_admin(true) ||
1693
                api_is_drh() ||
1694
                api_is_coach($sessionId, $courseId, false)
1695
            ) {
1696
                return false;
1697
            }
1698
        }
1699
1700
        $isBlocked = false;
1701
        if (!empty($prerequisite)) {
1702
            $progress = self::getProgress(
1703
                $prerequisite,
1704
                $studentId,
1705
                $courseId,
1706
                $sessionId
1707
            );
1708
            if ($progress < 100) {
1709
                $isBlocked = true;
1710
            }
1711
1712
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1713
                // Block if it does not exceed minimum time
1714
                // Minimum time (in minutes) to pass the learning path
1715
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1716
1717
                if ($accumulateWorkTime > 0) {
1718
                    // Total time in course (sum of times in learning paths from course)
1719
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1720
1721
                    // Connect with the plugin_licences_course_session table
1722
                    // which indicates what percentage of the time applies
1723
                    // Minimum connection percentage
1724
                    $perc = 100;
1725
                    // Time from the course
1726
                    $tc = $accumulateWorkTimeTotal;
1727
1728
                    // Percentage of the learning paths
1729
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1730
                    // Minimum time for each learning path
1731
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1732
1733
                    // Spent time (in seconds) so far in the learning path
1734
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1735
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1736
1737
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1738
                        $isBlocked = true;
1739
                    }
1740
                }
1741
            }
1742
        }
1743
1744
        return $isBlocked;
1745
    }
1746
1747
    /**
1748
     * Checks if the learning path is visible for student after the progress
1749
     * of its prerequisite is completed, considering the time availability and
1750
     * the LP visibility.
1751
     */
1752
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1753
    {
1754
        $sessionId = $session ? $session->getId() : 0;
1755
        $courseId = $course->getId();
1756
        $visibility = $lp->isVisible($course, $session);
1757
1758
        // If the item was deleted.
1759
        if (false === $visibility) {
1760
            return false;
1761
        }
1762
1763
        $now = time();
1764
        if ($lp->hasCategory()) {
1765
            $category = $lp->getCategory();
1766
1767
            if (false === self::categoryIsVisibleForStudent(
1768
                    $category,
1769
                    api_get_user_entity($student_id),
1770
                    $course,
1771
                    $session
1772
                )) {
1773
                return false;
1774
            }
1775
1776
            $prerequisite = $lp->getPrerequisite();
1777
            $is_visible = true;
1778
1779
            $isBlocked = self::isBlockedByPrerequisite(
1780
                $student_id,
1781
                $prerequisite,
1782
                $course,
1783
                $sessionId
1784
            );
1785
1786
            if ($isBlocked) {
1787
                $is_visible = false;
1788
            }
1789
1790
            // Also check the time availability of the LP
1791
            if ($is_visible) {
1792
                // Adding visibility restrictions
1793
                if (null !== $lp->getPublishedOn()) {
1794
                    if ($now < $lp->getPublishedOn()->getTimestamp()) {
1795
                        $is_visible = false;
1796
                    }
1797
                }
1798
                // Blocking empty start times see BT#2800
1799
                global $_custom;
1800
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1801
                    $_custom['lps_hidden_when_no_start_date']
1802
                ) {
1803
                    if (null !== $lp->getPublishedOn()) {
1804
                        $is_visible = false;
1805
                    }
1806
                }
1807
1808
                if (null !== $lp->getExpiredOn()) {
1809
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1810
                        $is_visible = false;
1811
                    }
1812
                }
1813
            }
1814
1815
            if ($is_visible) {
1816
                $subscriptionSettings = self::getSubscriptionSettings();
1817
1818
                // Check if the subscription users/group to a LP is ON
1819
                if (1 == $lp->getSubscribeUsers() &&
1820
                    true === $subscriptionSettings['allow_add_users_to_lp']
1821
                ) {
1822
                    // Try group
1823
                    $is_visible = false;
1824
                    // Checking only the user visibility
1825
                    $userVisibility = self::isUserSubscribedToLp($lp, $student_id, $course, $session);
1826
1827
                    if (true === $userVisibility) {
1828
                        return true;
1829
                    }
1830
1831
                    // Try with groups
1832
                    $groupVisibility = self::isGroupSubscribedToLp($lp, $student_id, $course, $session);
1833
                    if (true === $groupVisibility) {
1834
                        return true;
1835
                    }
1836
                }
1837
            }
1838
1839
            return $is_visible;
1840
        } else {
1841
1842
            $is_visible = true;
1843
            $subscriptionSettings = self::getSubscriptionSettings();
1844
            // Check if the subscription users/group to a LP is ON
1845
            if (1 == $lp->getSubscribeUsers() &&
1846
                true === $subscriptionSettings['allow_add_users_to_lp']
1847
            ) {
1848
                $is_visible = false;
1849
                $userVisibility = self::isUserSubscribedToLp($lp, $student_id, $course, $session);
1850
1851
                if (true === $userVisibility) {
1852
                    return true;
1853
                }
1854
1855
                // Try with groups
1856
                $groupVisibility = self::isGroupSubscribedToLp($lp, $student_id, $course, $session);
1857
                if (true === $groupVisibility) {
1858
                    return true;
1859
                }
1860
            }
1861
1862
            return $is_visible;
1863
        }
1864
1865
        return true;
0 ignored issues
show
Unused Code introduced by
return true is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1866
    }
1867
1868
    public static function isGroupSubscribedToLp(
1869
        CLp $lp,
1870
        int $studentId,
1871
        Course $course,
1872
        SessionEntity $session = null
1873
    ): bool {
1874
1875
        // Subscribed groups to a LP
1876
        $links = $lp->getResourceNode()->getResourceLinks();
1877
        $selectedChoices = [];
1878
        foreach ($links as $link) {
1879
            if (null !== $link->getGroup()) {
1880
                $selectedChoices[] = $link->getGroup()->getIid();
1881
            }
1882
        }
1883
1884
        $isVisible = false;
1885
        $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $course->getId());
1886
        if (!empty($userGroups)) {
1887
            foreach ($userGroups as $groupInfo) {
1888
                $groupId = $groupInfo['iid'];
1889
                if (in_array($groupId, $selectedChoices)) {
1890
                    $isVisible = true;
1891
                    break;
1892
                }
1893
            }
1894
        }
1895
1896
        return $isVisible;
1897
    }
1898
1899
    public static function isUserSubscribedToLp(
1900
        CLp $lp,
1901
        int $studentId,
1902
        Course $course,
1903
        SessionEntity $session = null
1904
    ): bool {
1905
1906
        $isVisible = true;
1907
        $em = Database::getManager();
1908
1909
        /** @var CLpRelUserRepository $cLpRelUserRepo */
1910
        $cLpRelUserRepo = $em->getRepository(CLpRelUser::class);
1911
1912
        // Getting subscribed users to a LP.
1913
        $subscribedUsersInLp = $cLpRelUserRepo->getUsersSubscribedToItem(
1914
            $lp,
1915
            $course,
1916
            $session
1917
        );
1918
1919
        $selectedChoices = [];
1920
        foreach ($subscribedUsersInLp as $users) {
1921
            /** @var \Chamilo\CourseBundle\Entity\CLpRelUser $users */
1922
            $selectedChoices[] = $users->getUser()->getId();
1923
        }
1924
1925
        if (!api_is_allowed_to_edit() && !in_array($studentId, $selectedChoices)) {
1926
            $isVisible = false;
1927
        }
1928
1929
        return $isVisible;
1930
    }
1931
1932
    /**
1933
     * @param int $lpId
1934
     * @param int $userId
1935
     * @param int $courseId
1936
     * @param int $sessionId
1937
     *
1938
     * @return int
1939
     */
1940
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1941
    {
1942
        $lpId = (int) $lpId;
1943
        $userId = (int) $userId;
1944
        $courseId = (int) $courseId;
1945
        $sessionId = (int) $sessionId;
1946
1947
        $sessionCondition = api_get_session_condition($sessionId);
1948
        $table = Database::get_course_table(TABLE_LP_VIEW);
1949
        $sql = "SELECT progress FROM $table
1950
                WHERE
1951
                    c_id = $courseId AND
1952
                    lp_id = $lpId AND
1953
                    user_id = $userId $sessionCondition ";
1954
        $res = Database::query($sql);
1955
1956
        $progress = 0;
1957
        if (Database::num_rows($res) > 0) {
1958
            $row = Database::fetch_array($res);
1959
            $progress = (int) $row['progress'];
1960
        }
1961
1962
        return $progress;
1963
    }
1964
1965
    /**
1966
     * @param array $lpList
1967
     * @param int   $userId
1968
     * @param int   $courseId
1969
     * @param int   $sessionId
1970
     *
1971
     * @return array
1972
     */
1973
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1974
    {
1975
        $lpList = array_map('intval', $lpList);
1976
        if (empty($lpList)) {
1977
            return [];
1978
        }
1979
1980
        $lpList = implode("','", $lpList);
1981
1982
        $userId = (int) $userId;
1983
        $courseId = (int) $courseId;
1984
        $sessionId = (int) $sessionId;
1985
1986
        $sessionCondition = api_get_session_condition($sessionId);
1987
        $table = Database::get_course_table(TABLE_LP_VIEW);
1988
        $sql = "SELECT lp_id, progress FROM $table
1989
                WHERE
1990
                    c_id = $courseId AND
1991
                    lp_id IN ('".$lpList."') AND
1992
                    user_id = $userId $sessionCondition ";
1993
        $res = Database::query($sql);
1994
1995
        if (Database::num_rows($res) > 0) {
1996
            $list = [];
1997
            while ($row = Database::fetch_array($res)) {
1998
                $list[$row['lp_id']] = $row['progress'];
1999
            }
2000
2001
            return $list;
2002
        }
2003
2004
        return [];
2005
    }
2006
2007
    /**
2008
     * Displays a progress bar
2009
     * completed so far.
2010
     *
2011
     * @param int    $percentage Progress value to display
2012
     * @param string $text_add   Text to display near the progress value
2013
     *
2014
     * @return string HTML string containing the progress bar
2015
     */
2016
    public static function get_progress_bar($percentage = -1, $text_add = '')
2017
    {
2018
        $text = $percentage.$text_add;
2019
2020
        return '<div class="p-progressbar p-progressbar-determinate"
2021
            role="progressbar" aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100">
2022
            <div id="progress_bar_value" class="p-progressbar-value" style="width: '.$text.';">
2023
                 <div class="p-progressbar-label">'.$text.'</div>
2024
            </div>
2025
        </div>';
2026
    }
2027
2028
    /**
2029
     * @param string $mode can be '%' or 'abs'
2030
     *                     otherwise this value will be used $this->progress_bar_mode
2031
     *
2032
     * @return string
2033
     */
2034
    public function getProgressBar($mode = null)
2035
    {
2036
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2037
2038
        return self::get_progress_bar($percentage, $text_add);
2039
    }
2040
2041
    /**
2042
     * Gets the progress bar info to display inside the progress bar.
2043
     * Also used by scorm_api.php.
2044
     *
2045
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2046
     *                     we display a number of completed elements per total elements
2047
     * @param int    $add  Additional steps to fake as completed
2048
     *
2049
     * @return array Percentage or number and symbol (% or /xx)
2050
     */
2051
    public function get_progress_bar_text($mode = '', $add = 0)
2052
    {
2053
        if (empty($mode)) {
2054
            $mode = $this->progress_bar_mode;
2055
        }
2056
        $text = '';
2057
        $percentage = 0;
2058
        // If the option to use the score as progress is set for this learning
2059
        // path, then the rules are completely different: we assume only one
2060
        // item exists and the progress of the LP depends on the score
2061
        $scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable'));
2062
        if (true === $scoreAsProgressSetting) {
2063
            $scoreAsProgress = $this->getUseScoreAsProgress();
2064
            if ($scoreAsProgress) {
2065
                // Get single item's score
2066
                $itemId = $this->get_current_item_id();
2067
                $item = $this->getItem($itemId);
2068
                $score = $item->get_score();
2069
                $maxScore = $item->get_max();
2070
                if ($mode = '%') {
2071
                    if (!empty($maxScore)) {
2072
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2073
                    }
2074
                    $percentage = number_format($percentage, 0);
2075
                    $text = '%';
2076
                } else {
2077
                    $percentage = $score;
2078
                    $text = '/'.$maxScore;
2079
                }
2080
2081
                return [$percentage, $text];
2082
            }
2083
        }
2084
        // otherwise just continue the normal processing of progress
2085
        $total_items = $this->getTotalItemsCountWithoutDirs();
2086
        $completeItems = $this->get_complete_items_count();
2087
        if (0 != $add) {
2088
            $completeItems += $add;
2089
        }
2090
        if ($completeItems > $total_items) {
2091
            $completeItems = $total_items;
2092
        }
2093
        if ('%' === $mode) {
2094
            if ($total_items > 0) {
2095
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2096
            }
2097
            $percentage = number_format($percentage, 0);
2098
            $text = '%';
2099
        } elseif ('abs' === $mode) {
2100
            $percentage = $completeItems;
2101
            $text = '/'.$total_items;
2102
        }
2103
2104
        return [
2105
            $percentage,
2106
            $text,
2107
        ];
2108
    }
2109
2110
    /**
2111
     * Gets the progress bar mode.
2112
     *
2113
     * @return string The progress bar mode attribute
2114
     */
2115
    public function get_progress_bar_mode()
2116
    {
2117
        if (!empty($this->progress_bar_mode)) {
2118
            return $this->progress_bar_mode;
2119
        }
2120
2121
        return '%';
2122
    }
2123
2124
    /**
2125
     * Gets the learnpath theme (remote or local).
2126
     *
2127
     * @return string Learnpath theme
2128
     */
2129
    public function get_theme()
2130
    {
2131
        if (!empty($this->theme)) {
2132
            return $this->theme;
2133
        }
2134
2135
        return '';
2136
    }
2137
2138
    /**
2139
     * Gets the learnpath session id.
2140
     *
2141
     * @return int
2142
     */
2143
    public function get_lp_session_id()
2144
    {
2145
        $lp = Container::getLpRepository()->find($this->lp_id);
2146
        if ($lp) {
2147
            /* @var ResourceNode $resourceNode */
2148
            $resourceNode = $lp->getResourceNode();
2149
            if ($resourceNode) {
0 ignored issues
show
introduced by
$resourceNode is of type \Chamilo\CoreBundle\Entity\ResourceNode, thus it always evaluated to true.
Loading history...
2150
                $link = $resourceNode->getResourceLinks()->first();
2151
                if ($link && $link->getSession()) {
2152
2153
                    return (int) $link->getSession()->getId();
2154
                }
2155
            }
2156
        }
2157
2158
        return 0;
2159
    }
2160
2161
    /**
2162
     * Generate a new prerequisites string for a given item. If this item was a sco and
2163
     * its prerequisites were strings (instead of IDs), then transform those strings into
2164
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2165
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2166
     * same rule as the scormExport() method.
2167
     *
2168
     * @param int $item_id Item ID
2169
     *
2170
     * @return string Prerequisites string ready for the export as SCORM
2171
     */
2172
    public function get_scorm_prereq_string($item_id)
2173
    {
2174
        if ($this->debug > 0) {
2175
            error_log('In learnpath::get_scorm_prereq_string()');
2176
        }
2177
        if (!is_object($this->items[$item_id])) {
2178
            return false;
2179
        }
2180
        /** @var learnpathItem $oItem */
2181
        $oItem = $this->items[$item_id];
2182
        $prereq = $oItem->get_prereq_string();
2183
2184
        if (empty($prereq)) {
2185
            return '';
2186
        }
2187
        if (preg_match('/^\d+$/', $prereq) &&
2188
            isset($this->items[$prereq]) &&
2189
            is_object($this->items[$prereq])
2190
        ) {
2191
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2192
            // then simply return it (with the ITEM_ prefix).
2193
            //return 'ITEM_' . $prereq;
2194
            return $this->items[$prereq]->ref;
2195
        } else {
2196
            if (isset($this->refs_list[$prereq])) {
2197
                // It's a simple string item from which the ID can be found in the refs list,
2198
                // so we can transform it directly to an ID for export.
2199
                return $this->items[$this->refs_list[$prereq]]->ref;
2200
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2201
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2202
            } else {
2203
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2204
                // and replace them, one by one, by the internal IDs (chamilo db)
2205
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2206
                // by a space as well.
2207
                $find = [
2208
                    '&',
2209
                    '|',
2210
                    '~',
2211
                    '=',
2212
                    '<>',
2213
                    '{',
2214
                    '}',
2215
                    '*',
2216
                    '(',
2217
                    ')',
2218
                ];
2219
                $replace = [
2220
                    ' ',
2221
                    ' ',
2222
                    ' ',
2223
                    ' ',
2224
                    ' ',
2225
                    ' ',
2226
                    ' ',
2227
                    ' ',
2228
                    ' ',
2229
                    ' ',
2230
                ];
2231
                $prereq_mod = str_replace($find, $replace, $prereq);
2232
                $ids = explode(' ', $prereq_mod);
2233
                foreach ($ids as $id) {
2234
                    $id = trim($id);
2235
                    if (isset($this->refs_list[$id])) {
2236
                        $prereq = preg_replace(
2237
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2238
                            'ITEM_'.$this->refs_list[$id],
2239
                            $prereq
2240
                        );
2241
                    }
2242
                }
2243
2244
                return $prereq;
2245
            }
2246
        }
2247
    }
2248
2249
    /**
2250
     * Returns the XML DOM document's node.
2251
     *
2252
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2253
     * @param string   $id       The identifier to look for
2254
     *
2255
     * @return mixed The reference to the element found with that identifier. False if not found
2256
     */
2257
    public function get_scorm_xml_node(&$children, $id)
2258
    {
2259
        for ($i = 0; $i < $children->length; $i++) {
2260
            $item_temp = $children->item($i);
2261
            if ('item' === $item_temp->nodeName) {
2262
                if ($item_temp->getAttribute('identifier') == $id) {
2263
                    return $item_temp;
2264
                }
2265
            }
2266
            $subchildren = $item_temp->childNodes;
2267
            if ($subchildren && $subchildren->length > 0) {
2268
                $val = $this->get_scorm_xml_node($subchildren, $id);
2269
                if (is_object($val)) {
2270
                    return $val;
2271
                }
2272
            }
2273
        }
2274
2275
        return false;
2276
    }
2277
2278
    /**
2279
     * Gets the status list for all LP's items.
2280
     *
2281
     * @return array Array of [index] => [item ID => current status]
2282
     */
2283
    public function get_items_status_list()
2284
    {
2285
        $list = [];
2286
        foreach ($this->ordered_items as $item_id) {
2287
            $list[] = [
2288
                $item_id => $this->items[$item_id]->get_status(),
2289
            ];
2290
        }
2291
2292
        return $list;
2293
    }
2294
2295
    /**
2296
     * Return the number of interactions for the given learnpath Item View ID.
2297
     * This method can be used as static.
2298
     *
2299
     * @param int $lp_iv_id  Item View ID
2300
     * @param int $course_id course id
2301
     *
2302
     * @return int
2303
     */
2304
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2305
    {
2306
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2307
        $lp_iv_id = (int) $lp_iv_id;
2308
        $course_id = (int) $course_id;
2309
2310
        $sql = "SELECT count(*) FROM $table
2311
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2312
        $res = Database::query($sql);
2313
        $num = 0;
2314
        if (Database::num_rows($res)) {
2315
            $row = Database::fetch_array($res);
2316
            $num = $row[0];
2317
        }
2318
2319
        return $num;
2320
    }
2321
2322
    /**
2323
     * Return the interactions as an array for the given lp_iv_id.
2324
     * This method can be used as static.
2325
     *
2326
     * @param int $lp_iv_id Learnpath Item View ID
2327
     *
2328
     * @return array
2329
     *
2330
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2331
     */
2332
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2333
    {
2334
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2335
        $list = [];
2336
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2337
        $lp_iv_id = (int) $lp_iv_id;
2338
2339
        if (empty($lp_iv_id) || empty($course_id)) {
2340
            return [];
2341
        }
2342
2343
        $sql = "SELECT * FROM $table
2344
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2345
                ORDER BY order_id ASC";
2346
        $res = Database::query($sql);
2347
        $num = Database::num_rows($res);
2348
        if ($num > 0) {
2349
            $list[] = [
2350
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2351
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2352
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2353
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2354
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2355
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2356
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2357
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2358
                'student_response_formatted' => '',
2359
            ];
2360
            while ($row = Database::fetch_array($res)) {
2361
                $studentResponseFormatted = urldecode($row['student_response']);
2362
                $content_student_response = explode('__|', $studentResponseFormatted);
2363
                if (count($content_student_response) > 0) {
2364
                    if (count($content_student_response) >= 3) {
2365
                        // Pop the element off the end of array.
2366
                        array_pop($content_student_response);
2367
                    }
2368
                    $studentResponseFormatted = implode(',', $content_student_response);
2369
                }
2370
2371
                $list[] = [
2372
                    'order_id' => $row['order_id'] + 1,
2373
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2374
                    'type' => $row['interaction_type'],
2375
                    'time' => $row['completion_time'],
2376
                    'correct_responses' => '', // Hide correct responses from students.
2377
                    'student_response' => $row['student_response'],
2378
                    'result' => $row['result'],
2379
                    'latency' => $row['latency'],
2380
                    'student_response_formatted' => $studentResponseFormatted,
2381
                ];
2382
            }
2383
        }
2384
2385
        return $list;
2386
    }
2387
2388
    /**
2389
     * Return the number of objectives for the given learnpath Item View ID.
2390
     * This method can be used as static.
2391
     *
2392
     * @param int $lp_iv_id  Item View ID
2393
     * @param int $course_id Course ID
2394
     *
2395
     * @return int Number of objectives
2396
     */
2397
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2398
    {
2399
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2400
        $course_id = (int) $course_id;
2401
        $lp_iv_id = (int) $lp_iv_id;
2402
        $sql = "SELECT count(*) FROM $table
2403
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2404
        //@todo seems that this always returns 0
2405
        $res = Database::query($sql);
2406
        $num = 0;
2407
        if (Database::num_rows($res)) {
2408
            $row = Database::fetch_array($res);
2409
            $num = $row[0];
2410
        }
2411
2412
        return $num;
2413
    }
2414
2415
    /**
2416
     * Return the objectives as an array for the given lp_iv_id.
2417
     * This method can be used as static.
2418
     *
2419
     * @param int $lpItemViewId Learnpath Item View ID
2420
     * @param int $course_id
2421
     *
2422
     * @return array
2423
     *
2424
     * @todo    Translate labels
2425
     */
2426
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2427
    {
2428
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2429
        $lpItemViewId = (int) $lpItemViewId;
2430
2431
        if (empty($course_id) || empty($lpItemViewId)) {
2432
            return [];
2433
        }
2434
2435
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2436
        $sql = "SELECT * FROM $table
2437
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2438
                ORDER BY order_id ASC";
2439
        $res = Database::query($sql);
2440
        $num = Database::num_rows($res);
2441
        $list = [];
2442
        if ($num > 0) {
2443
            $list[] = [
2444
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2445
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2446
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2447
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2448
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2449
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2450
            ];
2451
            while ($row = Database::fetch_array($res)) {
2452
                $list[] = [
2453
                    'order_id' => $row['order_id'] + 1,
2454
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2455
                    'score_raw' => $row['score_raw'],
2456
                    'score_max' => $row['score_max'],
2457
                    'score_min' => $row['score_min'],
2458
                    'status' => $row['status'],
2459
                ];
2460
            }
2461
        }
2462
2463
        return $list;
2464
    }
2465
2466
    /**
2467
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2468
     * used by get_html_toc() to be ready to display.
2469
     */
2470
    public function get_toc(): array
2471
    {
2472
        $toc = [];
2473
        foreach ($this->ordered_items as $item_id) {
2474
            // TODO: Change this link generation and use new function instead.
2475
            $toc[] = [
2476
                'id' => $item_id,
2477
                'title' => $this->items[$item_id]->get_title(),
2478
                'status' => $this->items[$item_id]->get_status(false),
2479
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status(false)),
2480
                'level' => $this->items[$item_id]->get_level(),
2481
                'type' => $this->items[$item_id]->get_type(),
2482
                'description' => $this->items[$item_id]->get_description(),
2483
                'path' => $this->items[$item_id]->get_path(),
2484
                'parent' => $this->items[$item_id]->get_parent(),
2485
            ];
2486
        }
2487
2488
        return $toc;
2489
    }
2490
2491
    /**
2492
     * Returns the CSS class name associated with a given item status.
2493
     *
2494
     * @param $status string an item status
2495
     *
2496
     * @return string CSS class name
2497
     */
2498
    public static function getStatusCSSClassName($status)
2499
    {
2500
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2501
            return self::STATUS_CSS_CLASS_NAME[$status];
2502
        }
2503
2504
        return '';
2505
    }
2506
2507
    /**
2508
     * Generate and return the table of contents for this learnpath. The JS
2509
     * table returned is used inside of scorm_api.php.
2510
     *
2511
     * @param string $varname
2512
     *
2513
     * @return string A JS array variable construction
2514
     */
2515
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2516
    {
2517
        $toc = $varname.' = new Array();';
2518
        foreach ($this->ordered_items as $item_id) {
2519
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2520
        }
2521
2522
        return $toc;
2523
    }
2524
2525
    /**
2526
     * Gets the learning path type.
2527
     *
2528
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2529
     *
2530
     * @return mixed Type ID or name, depending on the parameter
2531
     */
2532
    public function get_type($get_name = false)
2533
    {
2534
        $res = false;
2535
        if (!empty($this->type) && (!$get_name)) {
2536
            $res = $this->type;
2537
        }
2538
2539
        return $res;
2540
    }
2541
2542
    /**
2543
     * Gets the learning path type as static method.
2544
     *
2545
     * @param int $lp_id
2546
     *
2547
     * @return mixed Type ID or name, depending on the parameter
2548
     */
2549
    public static function get_type_static($lp_id = 0)
2550
    {
2551
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2552
        $lp_id = (int) $lp_id;
2553
        $sql = "SELECT lp_type FROM $tbl_lp
2554
                WHERE iid = $lp_id";
2555
        $res = Database::query($sql);
2556
        if (false === $res) {
2557
            return null;
2558
        }
2559
        if (Database::num_rows($res) <= 0) {
2560
            return null;
2561
        }
2562
        $row = Database::fetch_array($res);
2563
2564
        return $row['lp_type'];
2565
    }
2566
2567
    /**
2568
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2569
     * This method can be used as abstract and is recursive.
2570
     *
2571
     * @param CLp $lp
2572
     * @param int $parent    Parent ID of the items to look for
2573
     *
2574
     * @return array Ordered list of item IDs (empty array on error)
2575
     */
2576
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2577
    {
2578
        $parent = (int) $parent;
2579
        $lpItemRepo = Container::getLpItemRepository();
2580
        if (empty($parent)) {
2581
            $rootItem = $lpItemRepo->getRootItem($lp->getIid());
2582
            if (null !== $rootItem) {
2583
                $parent = $rootItem->getIid();
2584
            }
2585
        }
2586
2587
        if (empty($parent)) {
2588
            return [];
2589
        }
2590
2591
        $criteria = new Criteria();
2592
        $criteria
2593
            ->where($criteria->expr()->neq('path', 'root'))
2594
            ->orderBy(
2595
                [
2596
                    'displayOrder' => Criteria::ASC,
2597
                ]
2598
            );
2599
        $items = $lp->getItems()->matching($criteria);
2600
        $items = $items->filter(
2601
            function (CLpItem $element) use ($parent) {
2602
                if ('root' === $element->getPath()) {
2603
                    return false;
2604
                }
2605
2606
                if (null !== $element->getParent()) {
2607
                    return $element->getParent()->getIid() === $parent;
2608
                }
2609
                return false;
2610
2611
            }
2612
        );
2613
        $list = [];
2614
        foreach ($items as $item) {
2615
            $itemId = $item->getIid();
2616
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2617
            $list[] = $itemId;
2618
            foreach ($sublist as $subItem) {
2619
                $list[] = $subItem;
2620
            }
2621
        }
2622
2623
        return $list;
2624
    }
2625
2626
    public static function getChapterTypes(): array
2627
    {
2628
        return [
2629
            'dir',
2630
        ];
2631
    }
2632
2633
    /**
2634
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2635
     *
2636
     * @return array HTML TOC ready to display
2637
     */
2638
    public function getListArrayToc()
2639
    {
2640
        $lpItemRepo = Container::getLpItemRepository();
2641
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
2642
        $options = [
2643
            'decorate' => false,
2644
        ];
2645
2646
        return $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2647
    }
2648
2649
    /**
2650
     * Returns an HTML-formatted string ready to display with teacher buttons
2651
     * in LP view menu.
2652
     *
2653
     * @return string HTML TOC ready to display
2654
     */
2655
    public function get_teacher_toc_buttons()
2656
    {
2657
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2658
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2659
        $html = '';
2660
        if ($isAllow && false == $hideIcons) {
2661
            if ($this->get_lp_session_id() == api_get_session_id()) {
2662
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2663
                $html .= '<div class="flex flex-wrap gap-1 justify-center">';
2664
                $html .= "<a
2665
                    class='btn btn-sm btn--plain'
2666
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2667
                    target='_parent'>".
2668
                    Display::getMdiIcon('pencil').get_lang('Edit')."</a>";
2669
                $html .= '<a
2670
                    class="btn btn-sm btn--plain"
2671
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2672
                    Display::getMdiIcon('hammer-wrench').get_lang('Settings').'</a>';
2673
                $html .= '</div>';
2674
                $html .= '</div>';
2675
            }
2676
        }
2677
2678
        return $html;
2679
    }
2680
2681
    /**
2682
     * Gets the learnpath name/title.
2683
     *
2684
     * @return string Learnpath name/title
2685
     */
2686
    public function get_name()
2687
    {
2688
        if (!empty($this->name)) {
2689
            return $this->name;
2690
        }
2691
2692
        return 'N/A';
2693
    }
2694
2695
    /**
2696
     * @return string
2697
     */
2698
    public function getNameNoTags()
2699
    {
2700
        return strip_tags($this->get_name());
2701
    }
2702
2703
    /**
2704
     * Gets a link to the resource from the present location, depending on item ID.
2705
     *
2706
     * @param string $type         Type of link expected
2707
     * @param int    $item_id      Learnpath item ID
2708
     * @param bool   $provided_toc
2709
     *
2710
     * @return string $provided_toc Link to the lp_item resource
2711
     */
2712
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2713
    {
2714
        $course_id = $this->get_course_int_id();
2715
        $item_id = (int) $item_id;
2716
2717
        if (empty($item_id)) {
2718
            $item_id = $this->get_current_item_id();
2719
2720
            if (empty($item_id)) {
2721
                //still empty, this means there was no item_id given and we are not in an object context or
2722
                //the object property is empty, return empty link
2723
                $this->first();
2724
2725
                return '';
2726
            }
2727
        }
2728
2729
        $file = '';
2730
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2731
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2732
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2733
2734
        $sql = "SELECT
2735
                    l.lp_type as ltype,
2736
                    l.path as lpath,
2737
                    li.item_type as litype,
2738
                    li.path as lipath,
2739
                    li.parameters as liparams
2740
        		FROM $lp_table l
2741
                INNER JOIN $lp_item_table li
2742
                ON (li.lp_id = l.iid)
2743
        		WHERE
2744
        		    li.iid = $item_id
2745
        		";
2746
        $res = Database::query($sql);
2747
        if (Database::num_rows($res) > 0) {
2748
            $row = Database::fetch_array($res);
2749
            $lp_type = $row['ltype'];
2750
            $lp_path = $row['lpath'];
2751
            $lp_item_type = $row['litype'];
2752
            $lp_item_path = $row['lipath'];
2753
            $lp_item_params = $row['liparams'];
2754
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2755
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2756
            }
2757
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2758
            if ('http' === $type) {
2759
                //web path
2760
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2761
            } else {
2762
                //$course_path = $sys_course_path; //system path
2763
            }
2764
2765
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2766
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2767
            if (in_array(
2768
                $lp_item_type,
2769
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2770
            )
2771
            ) {
2772
                $lp_type = CLp::LP_TYPE;
2773
            }
2774
2775
            // Now go through the specific cases to get the end of the path
2776
            // @todo Use constants instead of int values.
2777
            switch ($lp_type) {
2778
                case CLp::LP_TYPE:
2779
                    $file = self::rl_get_resource_link_for_learnpath(
2780
                        $course_id,
2781
                        $this->get_id(),
2782
                        $item_id,
2783
                        $this->get_view_id()
2784
                    );
2785
                    switch ($lp_item_type) {
2786
                        case 'document':
2787
                            // Shows a button to download the file instead of just downloading the file directly.
2788
                            $documentPathInfo = pathinfo($file);
2789
                            if (isset($documentPathInfo['extension'])) {
2790
                                $parsed = parse_url($documentPathInfo['extension']);
2791
                                if (isset($parsed['path'])) {
2792
                                    $extension = $parsed['path'];
2793
                                    $extensionsToDownload = [
2794
                                        'zip',
2795
                                        'ppt',
2796
                                        'pptx',
2797
                                        'ods',
2798
                                        'xlsx',
2799
                                        'xls',
2800
                                        'csv',
2801
                                        'doc',
2802
                                        'docx',
2803
                                        'dot',
2804
                                    ];
2805
2806
                                    if (in_array($extension, $extensionsToDownload)) {
2807
                                        $file = api_get_path(WEB_CODE_PATH).
2808
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2809
                                    }
2810
                                }
2811
                            }
2812
                            break;
2813
                        case 'dir':
2814
                            $file = 'lp_content.php?type=dir';
2815
                            break;
2816
                        case 'link':
2817
                            if (Link::is_youtube_link($file)) {
2818
                                $src = Link::get_youtube_video_id($file);
2819
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2820
                            } elseif (Link::isVimeoLink($file)) {
2821
                                $src = Link::getVimeoLinkId($file);
2822
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2823
                            } else {
2824
                                // If the current site is HTTPS and the link is
2825
                                // HTTP, browsers will refuse opening the link
2826
                                $urlId = api_get_current_access_url_id();
2827
                                $url = api_get_access_url($urlId, false);
2828
                                $protocol = substr($url['url'], 0, 5);
2829
                                if ('https' === $protocol) {
2830
                                    $linkProtocol = substr($file, 0, 5);
2831
                                    if ('http:' === $linkProtocol) {
2832
                                        //this is the special intervention case
2833
                                        $file = api_get_path(WEB_CODE_PATH).
2834
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2835
                                    }
2836
                                }
2837
                            }
2838
                            break;
2839
                        case 'quiz':
2840
                            // Check how much attempts of a exercise exits in lp
2841
                            $lp_item_id = $this->get_current_item_id();
2842
                            $lp_view_id = $this->get_view_id();
2843
2844
                            $prevent_reinit = null;
2845
                            if (isset($this->items[$this->current])) {
2846
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2847
                            }
2848
2849
                            if (empty($provided_toc)) {
2850
                                $list = $this->get_toc();
2851
                            } else {
2852
                                $list = $provided_toc;
2853
                            }
2854
2855
                            $type_quiz = false;
2856
                            foreach ($list as $toc) {
2857
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2858
                                    $type_quiz = true;
2859
                                }
2860
                            }
2861
2862
                            if ($type_quiz) {
2863
                                $lp_item_id = (int) $lp_item_id;
2864
                                $lp_view_id = (int) $lp_view_id;
2865
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2866
                                        WHERE
2867
                                            lp_item_id='".$lp_item_id."' AND
2868
                                            lp_view_id ='".$lp_view_id."' AND
2869
                                            status='completed'";
2870
                                $result = Database::query($sql);
2871
                                $row_count = Database:: fetch_row($result);
2872
                                $count_item_view = (int) $row_count[0];
2873
                                $not_multiple_attempt = 0;
2874
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2875
                                    $not_multiple_attempt = 1;
2876
                                }
2877
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2878
                            }
2879
                            break;
2880
                    }
2881
2882
                    $tmp_array = explode('/', $file);
2883
                    $document_name = $tmp_array[count($tmp_array) - 1];
2884
                    if (strpos($document_name, '_DELETED_')) {
2885
                        $file = 'blank.php?error=document_deleted';
2886
                    }
2887
                    break;
2888
                case CLp::SCORM_TYPE:
2889
                    if ('dir' !== $lp_item_type) {
2890
                        // Quite complex here:
2891
                        // We want to make sure 'http://' (and similar) links can
2892
                        // be loaded as is (withouth the Chamilo path in front) but
2893
                        // some contents use this form: resource.htm?resource=http://blablabla
2894
                        // which means we have to find a protocol at the path's start, otherwise
2895
                        // it should not be considered as an external URL.
2896
                        // if ($this->prerequisites_match($item_id)) {
2897
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2898
                            if ($this->debug > 2) {
2899
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2900
                            }
2901
                            // Distant url, return as is.
2902
                            $file = $lp_item_path;
2903
                        } else {
2904
                            if ($this->debug > 2) {
2905
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2906
                            }
2907
                            // Prevent getting untranslatable urls.
2908
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2909
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2910
2911
                            /*$asset = $this->getEntity()->getAsset();
2912
                            $folder = Container::getAssetRepository()->getFolder($asset);
2913
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2914
                            $file = null;
2915
                            if ($hasFile) {
2916
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2917
                            }*/
2918
                            $file = $this->scormUrl.$lp_item_path;
2919
2920
                            // Prepare the path.
2921
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2922
                            // TODO: Fix this for urls with protocol header.
2923
                            $file = str_replace('//', '/', $file);
2924
                            $file = str_replace(':/', '://', $file);
2925
                            if ('/' === substr($lp_path, -1)) {
2926
                                $lp_path = substr($lp_path, 0, -1);
2927
                            }*/
2928
                            /*if (!$hasFile) {
2929
                                // if file not found.
2930
                                $decoded = html_entity_decode($lp_item_path);
2931
                                [$decoded] = explode('?', $decoded);
2932
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
2933
                                    $file = self::rl_get_resource_link_for_learnpath(
2934
                                        $course_id,
2935
                                        $this->get_id(),
2936
                                        $item_id,
2937
                                        $this->get_view_id()
2938
                                    );
2939
                                    if (empty($file)) {
2940
                                        $file = 'blank.php?error=document_not_found';
2941
                                    } else {
2942
                                        $tmp_array = explode('/', $file);
2943
                                        $document_name = $tmp_array[count($tmp_array) - 1];
2944
                                        if (strpos($document_name, '_DELETED_')) {
2945
                                            $file = 'blank.php?error=document_deleted';
2946
                                        } else {
2947
                                            $file = 'blank.php?error=document_not_found';
2948
                                        }
2949
                                    }
2950
                                } else {
2951
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
2952
                                }
2953
                            }*/
2954
                        }
2955
2956
                        // We want to use parameters if they were defined in the imsmanifest
2957
                        if (false === strpos($file, 'blank.php')) {
2958
                            $lp_item_params = ltrim($lp_item_params, '?');
2959
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
2960
                        }
2961
                    } else {
2962
                        $file = 'lp_content.php?type=dir';
2963
                    }
2964
                    break;
2965
                case CLp::AICC_TYPE:
2966
                    // Formatting AICC HACP append URL.
2967
                    $aicc_append = '?aicc_sid='.
2968
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
2969
                    if (!empty($lp_item_params)) {
2970
                        $aicc_append .= $lp_item_params.'&';
2971
                    }
2972
                    if ('dir' !== $lp_item_type) {
2973
                        // Quite complex here:
2974
                        // We want to make sure 'http://' (and similar) links can
2975
                        // be loaded as is (withouth the Chamilo path in front) but
2976
                        // some contents use this form: resource.htm?resource=http://blablabla
2977
                        // which means we have to find a protocol at the path's start, otherwise
2978
                        // it should not be considered as an external URL.
2979
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2980
                            if ($this->debug > 2) {
2981
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2982
                            }
2983
                            // Distant url, return as is.
2984
                            $file = $lp_item_path;
2985
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
2986
                            /*
2987
                            if (stristr($file,'<servername>') !== false) {
2988
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
2989
                            }
2990
                            */
2991
                            if (false !== stripos($file, '<servername>')) {
2992
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
2993
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
2994
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
2995
                            }
2996
2997
                            $file .= $aicc_append;
2998
                        } else {
2999
                            if ($this->debug > 2) {
3000
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3001
                            }
3002
                            // Prevent getting untranslatable urls.
3003
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3004
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3005
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3006
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3007
                            // TODO: Fix this for urls with protocol header.
3008
                            $file = str_replace('//', '/', $file);
3009
                            $file = str_replace(':/', '://', $file);
3010
                            $file .= $aicc_append;
3011
                        }
3012
                    } else {
3013
                        $file = 'lp_content.php?type=dir';
3014
                    }
3015
                    break;
3016
                case 4:
3017
                default:
3018
                    break;
3019
            }
3020
            // Replace &amp; by & because &amp; will break URL with params
3021
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3022
        }
3023
        if ($this->debug > 2) {
3024
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3025
        }
3026
3027
        return $file;
3028
    }
3029
3030
    /**
3031
     * Gets the latest usable view or generate a new one.
3032
     *
3033
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3034
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3035
     *
3036
     * @return int DB lp_view id
3037
     */
3038
    public function get_view($attempt_num = 0, $userId = null)
3039
    {
3040
        $search = '';
3041
        $attempt_num = (int) $attempt_num;
3042
        // Use $attempt_num to enable multi-views management (disabled so far).
3043
        if (!empty($attempt_num)) {
3044
            $search = 'AND view_count = '.$attempt_num;
3045
        }
3046
3047
        $course_id = api_get_course_int_id();
3048
        $sessionId = api_get_session_id();
3049
3050
        // Check user ID.
3051
        if (empty($userId)) {
3052
            if (empty($this->get_user_id())) {
3053
                $this->error = 'User ID is empty in learnpath::get_view()';
3054
3055
                return null;
3056
            } else {
3057
                $userId = $this->get_user_id();
3058
            }
3059
        }
3060
        $sessionCondition = api_get_session_condition($sessionId);
3061
3062
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3063
        $table = Database::get_course_table(TABLE_LP_VIEW);
3064
        $sql = "SELECT iid FROM $table
3065
        		WHERE
3066
        		    c_id = $course_id AND
3067
        		    lp_id = ".$this->get_id()." AND
3068
        		    user_id = ".$userId."
3069
        		    $sessionCondition
3070
        		    $search
3071
                ORDER BY view_count DESC";
3072
        $res = Database::query($sql);
3073
        if (Database::num_rows($res) > 0) {
3074
            $row = Database::fetch_array($res);
3075
            $this->lp_view_id = $row['iid'];
3076
        } elseif (!api_is_invitee()) {
3077
            $params = [
3078
                'c_id' => $course_id,
3079
                'lp_id' => $this->get_id(),
3080
                'user_id' => $this->get_user_id(),
3081
                'view_count' => 1,
3082
                'last_item' => 0,
3083
            ];
3084
            if (!empty($sessionId)) {
3085
                $params['session_id']  = $sessionId;
3086
            }
3087
            $this->lp_view_id = Database::insert($table, $params);
3088
        }
3089
3090
        return $this->lp_view_id;
3091
    }
3092
3093
    /**
3094
     * Gets the current view id.
3095
     *
3096
     * @return int View ID (from lp_view)
3097
     */
3098
    public function get_view_id()
3099
    {
3100
        if (!empty($this->lp_view_id)) {
3101
            return (int) $this->lp_view_id;
3102
        }
3103
3104
        return 0;
3105
    }
3106
3107
    /**
3108
     * Gets the update queue.
3109
     *
3110
     * @return array Array containing IDs of items to be updated by JavaScript
3111
     */
3112
    public function get_update_queue()
3113
    {
3114
        return $this->update_queue;
3115
    }
3116
3117
    /**
3118
     * Gets the user ID.
3119
     *
3120
     * @return int User ID
3121
     */
3122
    public function get_user_id()
3123
    {
3124
        if (!empty($this->user_id)) {
3125
            return (int) $this->user_id;
3126
        }
3127
3128
        return false;
3129
    }
3130
3131
    /**
3132
     * Checks if any of the items has an audio element attached.
3133
     *
3134
     * @return bool True or false
3135
     */
3136
    public function has_audio()
3137
    {
3138
        $has = false;
3139
        foreach ($this->items as $i => $item) {
3140
            if (!empty($this->items[$i]->audio)) {
3141
                $has = true;
3142
                break;
3143
            }
3144
        }
3145
3146
        return $has;
3147
    }
3148
3149
    /**
3150
     * Updates learnpath attributes to point to the next element
3151
     * The last part is similar to set_current_item but processing the other way around.
3152
     */
3153
    public function next()
3154
    {
3155
        if ($this->debug > 0) {
3156
            error_log('In learnpath::next()', 0);
3157
        }
3158
        $this->last = $this->get_current_item_id();
3159
        $this->items[$this->last]->save(
3160
            false,
3161
            $this->prerequisites_match($this->last)
3162
        );
3163
        $this->autocomplete_parents($this->last);
3164
        $new_index = $this->get_next_index();
3165
        if ($this->debug > 2) {
3166
            error_log('New index: '.$new_index, 0);
3167
        }
3168
        $this->index = $new_index;
3169
        if ($this->debug > 2) {
3170
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3171
        }
3172
        $this->current = $this->ordered_items[$new_index];
3173
        if ($this->debug > 2) {
3174
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3175
        }
3176
    }
3177
3178
    /**
3179
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3180
     * class, this might be redefined to allow several behaviours depending on the document type.
3181
     *
3182
     * @param int $id Resource ID
3183
     */
3184
    public function open($id)
3185
    {
3186
        // TODO:
3187
        // set the current resource attribute to this resource
3188
        // switch on element type (redefine in child class?)
3189
        // set status for this item to "opened"
3190
        // start timer
3191
        // initialise score
3192
        $this->index = 0; //or = the last item seen (see $this->last)
3193
    }
3194
3195
    /**
3196
     * Check that all prerequisites are fulfilled. Returns true and an
3197
     * empty string on success, returns false
3198
     * and the prerequisite string on error.
3199
     * This function is based on the rules for aicc_script language as
3200
     * described in the SCORM 1.2 CAM documentation page 108.
3201
     *
3202
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3203
     *
3204
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3205
     *              string otherwise
3206
     */
3207
    public function prerequisites_match($itemId = null)
3208
    {
3209
        $allow = ('true' === api_get_setting('lp.allow_teachers_to_access_blocked_lp_by_prerequisite'));
3210
        if ($allow) {
3211
            if (api_is_allowed_to_edit() ||
3212
                api_is_platform_admin(true) ||
3213
                api_is_drh() ||
3214
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3215
            ) {
3216
                return true;
3217
            }
3218
        }
3219
3220
        $debug = $this->debug;
3221
        if ($debug > 0) {
3222
            error_log('In learnpath::prerequisites_match()');
3223
        }
3224
3225
        if (empty($itemId)) {
3226
            $itemId = $this->current;
3227
        }
3228
3229
        $currentItem = $this->getItem($itemId);
3230
3231
        if ($currentItem) {
3232
            if (2 == $this->type) {
3233
                // Getting prereq from scorm
3234
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3235
            } else {
3236
                $prereq_string = $currentItem->get_prereq_string();
3237
            }
3238
3239
            if (empty($prereq_string)) {
3240
                if ($debug > 0) {
3241
                    error_log('Found prereq_string is empty return true');
3242
                }
3243
3244
                return true;
3245
            }
3246
3247
            // Clean spaces.
3248
            $prereq_string = str_replace(' ', '', $prereq_string);
3249
            if ($debug > 0) {
3250
                error_log('Found prereq_string: '.$prereq_string, 0);
3251
            }
3252
3253
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3254
            $result = $currentItem->parse_prereq(
3255
                $prereq_string,
3256
                $this->items,
3257
                $this->refs_list,
3258
                $this->get_user_id()
3259
            );
3260
3261
            if (false === $result) {
3262
                $this->set_error_msg($currentItem->prereq_alert);
3263
            }
3264
        } else {
3265
            $result = true;
3266
            if ($debug > 1) {
3267
                error_log('$this->items['.$itemId.'] was not an object', 0);
3268
            }
3269
        }
3270
3271
        if ($debug > 1) {
3272
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3273
        }
3274
3275
        return $result;
3276
    }
3277
3278
    /**
3279
     * Updates learnpath attributes to point to the previous element
3280
     * The last part is similar to set_current_item but processing the other way around.
3281
     */
3282
    public function previous()
3283
    {
3284
        $this->last = $this->get_current_item_id();
3285
        $this->items[$this->last]->save(
3286
            false,
3287
            $this->prerequisites_match($this->last)
3288
        );
3289
        $this->autocomplete_parents($this->last);
3290
        $new_index = $this->get_previous_index();
3291
        $this->index = $new_index;
3292
        $this->current = $this->ordered_items[$new_index];
3293
    }
3294
3295
    /**
3296
     * Publishes a learnpath. This basically means show or hide the learnpath
3297
     * to normal users.
3298
     * Can be used as abstract.
3299
     *
3300
     * @param int $id         Learnpath ID
3301
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3302
     *
3303
     * @return bool
3304
     */
3305
    public static function toggleVisibility($id, $visibility = 1)
3306
    {
3307
        $repo = Container::getLpRepository();
3308
        $lp = $repo->find($id);
3309
3310
        if (!$lp) {
3311
            return false;
3312
        }
3313
3314
        $visibility = (int) $visibility;
3315
3316
        if (1 === $visibility) {
3317
            $repo->setVisibilityPublished($lp);
3318
        } else {
3319
            $repo->setVisibilityDraft($lp);
3320
        }
3321
3322
        return true;
3323
    }
3324
3325
    /**
3326
     * Publishes a learnpath category.
3327
     * This basically means show or hide the learnpath category to normal users.
3328
     *
3329
     * @param int $id
3330
     * @param int $visibility
3331
     *
3332
     * @return bool
3333
     */
3334
    public static function toggleCategoryVisibility($id, $visibility = 1)
3335
    {
3336
        $repo = Container::getLpCategoryRepository();
3337
        $resource = $repo->find($id);
3338
3339
        if (!$resource) {
3340
            return false;
3341
        }
3342
3343
        $visibility = (int) $visibility;
3344
3345
        if (1 === $visibility) {
3346
            $repo->setVisibilityPublished($resource);
3347
        } else {
3348
            $repo->setVisibilityDraft($resource);
3349
            self::toggleCategoryPublish($id, 0);
3350
        }
3351
3352
        return false;
3353
    }
3354
3355
    /**
3356
     * Publishes a learnpath. This basically means show or hide the learnpath
3357
     * on the course homepage.
3358
     *
3359
     * @param int    $id            Learnpath id
3360
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3361
     *
3362
     * @return bool
3363
     */
3364
    public static function togglePublish($id, $setVisibility = 'v')
3365
    {
3366
        $addShortcut = false;
3367
        if ('v' === $setVisibility) {
3368
            $addShortcut = true;
3369
        }
3370
        $repo = Container::getLpRepository();
3371
        /** @var CLp|null $lp */
3372
        $lp = $repo->find($id);
3373
        if (null === $lp) {
3374
            return false;
3375
        }
3376
        $repoShortcut = Container::getShortcutRepository();
3377
        if ($addShortcut) {
3378
            $repoShortcut->addShortCut($lp, api_get_user_entity(), api_get_course_entity(), api_get_session_entity());
3379
        } else {
3380
            $repoShortcut->removeShortCut($lp);
3381
        }
3382
3383
        return true;
3384
    }
3385
3386
    /**
3387
     * Show or hide the learnpath category on the course homepage.
3388
     *
3389
     * @param int $id
3390
     * @param int $setVisibility
3391
     *
3392
     * @return bool
3393
     */
3394
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3395
    {
3396
        $setVisibility = (int) $setVisibility;
3397
        $addShortcut = false;
3398
        if (1 === $setVisibility) {
3399
            $addShortcut = true;
3400
        }
3401
3402
        $repo = Container::getLpCategoryRepository();
3403
        /** @var CLpCategory|null $lp */
3404
        $category = $repo->find($id);
3405
3406
        if (null === $category) {
3407
            return false;
3408
        }
3409
3410
        $repoShortcut = Container::getShortcutRepository();
3411
        if ($addShortcut) {
3412
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3413
            $repoShortcut->addShortCut($category, api_get_user_entity(), $courseEntity, api_get_session_entity());
3414
        } else {
3415
            $repoShortcut->removeShortCut($category);
3416
        }
3417
3418
        return true;
3419
    }
3420
3421
    /**
3422
     * Check if the learnpath category is visible for a user.
3423
     *
3424
     * @return bool
3425
     */
3426
    public static function categoryIsVisibleForStudent(
3427
        CLpCategory $category,
3428
        User $user,
3429
        Course $course,
3430
        SessionEntity $session = null
3431
    ) {
3432
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3433
3434
        if ($isAllowedToEdit) {
3435
            return true;
3436
        }
3437
3438
        $categoryVisibility = $category->isVisible($course, $session);
3439
3440
        if (!$categoryVisibility) {
3441
            return false;
3442
        }
3443
3444
        $subscriptionSettings = self::getSubscriptionSettings();
3445
3446
        if (false === $subscriptionSettings['allow_add_users_to_lp_category']) {
3447
            return true;
3448
        }
3449
3450
        $noUserSubscribed = false;
3451
        $noGroupSubscribed = true;
3452
        $users = $category->getUsers();
3453
        if (empty($users) || !$users->count()) {
3454
            $noUserSubscribed = true;
3455
        } elseif ($category->hasUserAdded($user)) {
3456
            return true;
3457
        }
3458
3459
        //$groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3460
3461
        return $noGroupSubscribed && $noUserSubscribed;
3462
    }
3463
3464
    /**
3465
     * Check if a learnpath category is published as course tool.
3466
     *
3467
     * @param int $courseId
3468
     *
3469
     * @return bool
3470
     */
3471
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3472
    {
3473
        return false;
3474
        $link = self::getCategoryLinkForTool($category->getId());
0 ignored issues
show
Unused Code introduced by
$link = self::getCategor...ool($category->getId()) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
3475
        $em = Database::getManager();
3476
3477
        $tools = $em
3478
            ->createQuery("
3479
                SELECT t FROM ChamiloCourseBundle:CTool t
3480
                WHERE t.course = :course AND
3481
                    t.name = :name AND
3482
                    t.image LIKE 'lp_category.%' AND
3483
                    t.link LIKE :link
3484
            ")
3485
            ->setParameters([
3486
                'course' => $courseId,
3487
                'name' => strip_tags($category->getTitle()),
3488
                'link' => "$link%",
3489
            ])
3490
            ->getResult();
3491
3492
        /** @var CTool $tool */
3493
        $tool = current($tools);
3494
3495
        return $tool ? $tool->getVisibility() : false;
3496
    }
3497
3498
    /**
3499
     * Restart the whole learnpath. Return the URL of the first element.
3500
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3501
     * To use a similar method  statically, use the create_new_attempt() method.
3502
     *
3503
     * @return bool
3504
     */
3505
    public function restart()
3506
    {
3507
        if ($this->debug > 0) {
3508
            error_log('In learnpath::restart()', 0);
3509
        }
3510
        // TODO
3511
        // Call autosave method to save the current progress.
3512
        //$this->index = 0;
3513
        if (api_is_invitee()) {
3514
            return false;
3515
        }
3516
        $session_id = api_get_session_id();
3517
        $course_id = api_get_course_int_id();
3518
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3519
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3520
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3521
        if ($this->debug > 2) {
3522
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3523
        }
3524
        Database::query($sql);
3525
        $view_id = Database::insert_id();
3526
3527
        if ($view_id) {
3528
            $this->lp_view_id = $view_id;
3529
            $this->attempt = $this->attempt + 1;
3530
        } else {
3531
            $this->error = 'Could not insert into item_view table...';
3532
3533
            return false;
3534
        }
3535
        $this->autocomplete_parents($this->current);
3536
        foreach ($this->items as $index => $dummy) {
3537
            $this->items[$index]->restart();
3538
            $this->items[$index]->set_lp_view($this->lp_view_id);
3539
        }
3540
        $this->first();
3541
3542
        return true;
3543
    }
3544
3545
    /**
3546
     * Saves the current item.
3547
     *
3548
     * @return bool
3549
     */
3550
    public function save_current()
3551
    {
3552
        $debug = $this->debug;
3553
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3554
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3555
        if ($debug) {
3556
            error_log('save_current() saving item '.$this->current, 0);
3557
            error_log(''.print_r($this->items, true), 0);
3558
        }
3559
        if (isset($this->items[$this->current]) &&
3560
            is_object($this->items[$this->current])
3561
        ) {
3562
            if ($debug) {
3563
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3564
            }
3565
3566
            $res = $this->items[$this->current]->save(
3567
                false,
3568
                $this->prerequisites_match($this->current)
3569
            );
3570
            $this->autocomplete_parents($this->current);
3571
            $status = $this->items[$this->current]->get_status();
3572
            $this->update_queue[$this->current] = $status;
3573
3574
            if ($debug) {
3575
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3576
            }
3577
3578
            return $res;
3579
        }
3580
3581
        return false;
3582
    }
3583
3584
    /**
3585
     * Saves the given item.
3586
     *
3587
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3588
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3589
     *
3590
     * @return bool
3591
     */
3592
    public function save_item($item_id = null, $from_outside = true)
3593
    {
3594
        $debug = $this->debug;
3595
        if ($debug) {
3596
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3597
        }
3598
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3599
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3600
        if (empty($item_id)) {
3601
            $item_id = (int) $_REQUEST['id'];
3602
        }
3603
3604
        if (empty($item_id)) {
3605
            $item_id = $this->get_current_item_id();
3606
        }
3607
        if (isset($this->items[$item_id]) &&
3608
            is_object($this->items[$item_id])
3609
        ) {
3610
            if ($debug) {
3611
                error_log('Object exists');
3612
            }
3613
3614
            // Saving the item.
3615
            $res = $this->items[$item_id]->save(
3616
                $from_outside,
3617
                $this->prerequisites_match($item_id)
3618
            );
3619
3620
            if ($debug) {
3621
                error_log('update_queue before:');
3622
                error_log(print_r($this->update_queue, 1));
3623
            }
3624
            $this->autocomplete_parents($item_id);
3625
3626
            $status = $this->items[$item_id]->get_status();
3627
            $this->update_queue[$item_id] = $status;
3628
3629
            if ($debug) {
3630
                error_log('get_status(): '.$status);
3631
                error_log('update_queue after:');
3632
                error_log(print_r($this->update_queue, 1));
3633
            }
3634
3635
            return $res;
3636
        }
3637
3638
        return false;
3639
    }
3640
3641
    /**
3642
     * Saves the last item seen's ID only in case.
3643
     */
3644
    public function save_last()
3645
    {
3646
        $course_id = api_get_course_int_id();
3647
        $debug = $this->debug;
3648
        if ($debug) {
3649
            error_log('In learnpath::save_last()', 0);
3650
        }
3651
        $session_condition = api_get_session_condition(
3652
            api_get_session_id(),
3653
            true,
3654
            false
3655
        );
3656
        $table = Database::get_course_table(TABLE_LP_VIEW);
3657
3658
        $userId = $this->get_user_id();
3659
        if (empty($userId)) {
3660
            $userId = api_get_user_id();
3661
            if ($debug) {
3662
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
3663
            }
3664
        }
3665
        if (isset($this->current) && !api_is_invitee()) {
3666
            if ($debug) {
3667
                error_log('Saving current item ('.$this->current.') for later review', 0);
3668
            }
3669
            $sql = "UPDATE $table SET
3670
                        last_item = ".$this->get_current_item_id()."
3671
                    WHERE
3672
                        c_id = $course_id AND
3673
                        lp_id = ".$this->get_id()." AND
3674
                        user_id = ".$userId." ".$session_condition;
3675
3676
            if ($debug) {
3677
                error_log('Saving last item seen : '.$sql, 0);
3678
            }
3679
            Database::query($sql);
3680
        }
3681
3682
        if (!api_is_invitee()) {
3683
            // Save progress.
3684
            [$progress] = $this->get_progress_bar_text('%');
3685
            $scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable'));
3686
            $scoreAsProgress = $this->getUseScoreAsProgress();
3687
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
3688
                if ($debug) {
3689
                    error_log("Return false: Dont save score: $score");
3690
                    error_log("progress: $progress");
3691
                }
3692
3693
                return false;
3694
            }
3695
3696
            if ($scoreAsProgress && $scoreAsProgressSetting) {
3697
                $storedProgress = self::getProgress(
3698
                    $this->get_id(),
3699
                    $userId,
3700
                    $course_id,
3701
                    $this->get_lp_session_id()
3702
                );
3703
3704
                // Check if the stored progress is higher than the new value
3705
                if ($storedProgress >= $progress) {
3706
                    if ($debug) {
3707
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
3708
                    }
3709
3710
                    return false;
3711
                }
3712
            }
3713
            if ($progress >= 0 && $progress <= 100) {
3714
                $progress = (int) $progress;
3715
                $sql = "UPDATE $table SET
3716
                            progress = $progress
3717
                        WHERE
3718
                            c_id = $course_id AND
3719
                            lp_id = ".$this->get_id()." AND
3720
                            user_id = ".$userId." ".$session_condition;
3721
                // Ignore errors as some tables might not have the progress field just yet.
3722
                Database::query($sql);
3723
                $this->progress_db = $progress;
3724
            }
3725
        }
3726
    }
3727
3728
    /**
3729
     * Sets the current item ID (checks if valid and authorized first).
3730
     *
3731
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
3732
     */
3733
    public function set_current_item($item_id = null)
3734
    {
3735
        $debug = $this->debug;
3736
        if ($debug) {
3737
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
3738
        }
3739
        if (empty($item_id)) {
3740
            if ($debug) {
3741
                error_log('No new current item given, ignore...', 0);
3742
            }
3743
            // Do nothing.
3744
        } else {
3745
            if ($debug) {
3746
                error_log('New current item given is '.$item_id.'...', 0);
3747
            }
3748
            if (is_numeric($item_id)) {
3749
                $item_id = (int) $item_id;
3750
                // TODO: Check in database here.
3751
                $this->last = $this->current;
3752
                $this->current = $item_id;
3753
                // TODO: Update $this->index as well.
3754
                foreach ($this->ordered_items as $index => $item) {
3755
                    if ($item == $this->current) {
3756
                        $this->index = $index;
3757
                        break;
3758
                    }
3759
                }
3760
                if ($debug) {
3761
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
3762
                }
3763
            } else {
3764
                if ($debug) {
3765
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
3766
                }
3767
            }
3768
        }
3769
    }
3770
3771
    /**
3772
     * Set index specified prefix terms for all items in this path.
3773
     *
3774
     * @param string $terms_string Comma-separated list of terms
3775
     * @param string $prefix       Xapian term prefix
3776
     *
3777
     * @return bool False on error, true otherwise
3778
     */
3779
    public function set_terms_by_prefix($terms_string, $prefix)
3780
    {
3781
        $course_id = api_get_course_int_id();
3782
        if ('true' !== api_get_setting('search_enabled')) {
3783
            return false;
3784
        }
3785
3786
        if (!extension_loaded('xapian')) {
3787
            return false;
3788
        }
3789
3790
        $terms_string = trim($terms_string);
3791
        $terms = explode(',', $terms_string);
3792
        array_walk($terms, 'trim_value');
3793
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
3794
3795
        // Don't do anything if no change, verify only at DB, not the search engine.
3796
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
3797
            return false;
3798
        }
3799
3800
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
3801
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
3802
3803
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
3804
        // TODO: Make query secure agains XSS : use member attr instead of post var.
3805
        $lp_id = (int) $_POST['lp_id'];
3806
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
3807
        $result = Database::query($sql);
3808
        $di = new ChamiloIndexer();
3809
3810
        while ($lp_item = Database::fetch_array($result)) {
3811
            // Get search_did.
3812
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3813
            $sql = 'SELECT * FROM %s
3814
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
3815
                    LIMIT 1';
3816
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
3817
3818
            //echo $sql; echo '<br>';
3819
            $res = Database::query($sql);
3820
            if (Database::num_rows($res) > 0) {
3821
                $se_ref = Database::fetch_array($res);
3822
                // Compare terms.
3823
                $doc = $di->get_document($se_ref['search_did']);
3824
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
3825
                $xterms = [];
3826
                foreach ($xapian_terms as $xapian_term) {
3827
                    $xterms[] = substr($xapian_term['name'], 1);
3828
                }
3829
3830
                $dterms = $terms;
3831
                $missing_terms = array_diff($dterms, $xterms);
3832
                $deprecated_terms = array_diff($xterms, $dterms);
3833
3834
                // Save it to search engine.
3835
                foreach ($missing_terms as $term) {
3836
                    $doc->add_term($prefix.$term, 1);
3837
                }
3838
                foreach ($deprecated_terms as $term) {
3839
                    $doc->remove_term($prefix.$term);
3840
                }
3841
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
3842
                $di->getDb()->flush();
3843
            }
3844
        }
3845
3846
        return true;
3847
    }
3848
3849
    /**
3850
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
3851
     *
3852
     * @param int $id DB ID of the item
3853
     */
3854
    public function set_previous_item($id)
3855
    {
3856
        if ($this->debug > 0) {
3857
            error_log('In learnpath::set_previous_item()', 0);
3858
        }
3859
        $this->last = $id;
3860
    }
3861
3862
    /**
3863
     * Sets and saves the expired_on date.
3864
     *
3865
     * @return bool Returns true if author's name is not empty
3866
     */
3867
    public function set_modified_on()
3868
    {
3869
        $this->modified_on = api_get_utc_datetime();
3870
        $table = Database::get_course_table(TABLE_LP_MAIN);
3871
        $lp_id = $this->get_id();
3872
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
3873
                WHERE iid = $lp_id";
3874
        Database::query($sql);
3875
3876
        return true;
3877
    }
3878
3879
    /**
3880
     * Sets the object's error message.
3881
     *
3882
     * @param string $error Error message. If empty, reinits the error string
3883
     */
3884
    public function set_error_msg($error = '')
3885
    {
3886
        if ($this->debug > 0) {
3887
            error_log('In learnpath::set_error_msg()', 0);
3888
        }
3889
        if (empty($error)) {
3890
            $this->error = '';
3891
        } else {
3892
            $this->error .= $error;
3893
        }
3894
    }
3895
3896
    /**
3897
     * Launches the current item if not 'sco'
3898
     * (starts timer and make sure there is a record ready in the DB).
3899
     *
3900
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
3901
     *
3902
     * @return bool
3903
     */
3904
    public function start_current_item($allow_new_attempt = false)
3905
    {
3906
        $debug = $this->debug;
3907
        if ($debug) {
3908
            error_log('In learnpath::start_current_item()');
3909
            error_log('current: '.$this->current);
3910
        }
3911
        if (0 != $this->current && isset($this->items[$this->current]) &&
3912
            is_object($this->items[$this->current])
3913
        ) {
3914
            $type = $this->get_type();
3915
            $item_type = $this->items[$this->current]->get_type();
3916
            if ($debug) {
3917
                error_log('item type: '.$item_type);
3918
                error_log('lp type: '.$type);
3919
            }
3920
            if ((2 == $type && 'sco' !== $item_type) ||
3921
                (3 == $type && 'au' !== $item_type) ||
3922
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
3923
            ) {
3924
                $this->items[$this->current]->open($allow_new_attempt);
3925
                $this->autocomplete_parents($this->current);
3926
                $prereq_check = $this->prerequisites_match($this->current);
3927
                if ($debug) {
3928
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
3929
                }
3930
                $this->items[$this->current]->save(false, $prereq_check);
3931
            }
3932
            // If sco, then it is supposed to have been updated by some other call.
3933
            if ('sco' === $item_type) {
3934
                $this->items[$this->current]->restart();
3935
            }
3936
        }
3937
        if ($debug) {
3938
            error_log('lp_view_session_id');
3939
            error_log($this->lp_view_session_id);
3940
            error_log('api session id');
3941
            error_log(api_get_session_id());
3942
            error_log('End of learnpath::start_current_item()');
3943
        }
3944
3945
        return true;
3946
    }
3947
3948
    /**
3949
     * Stops the processing and counters for the old item (as held in $this->last).
3950
     *
3951
     * @return bool True/False
3952
     */
3953
    public function stop_previous_item()
3954
    {
3955
        $debug = $this->debug;
3956
        if ($debug) {
3957
            error_log('In learnpath::stop_previous_item()', 0);
3958
        }
3959
3960
        if (0 != $this->last && $this->last != $this->current &&
3961
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
3962
        ) {
3963
            if ($debug) {
3964
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
3965
            }
3966
            switch ($this->get_type()) {
3967
                case '3':
3968
                    if ('au' != $this->items[$this->last]->get_type()) {
3969
                        if ($debug) {
3970
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
3971
                        }
3972
                        $this->items[$this->last]->close();
3973
                    } else {
3974
                        if ($debug) {
3975
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
3976
                        }
3977
                    }
3978
                    break;
3979
                case '2':
3980
                    if ('sco' != $this->items[$this->last]->get_type()) {
3981
                        if ($debug) {
3982
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
3983
                        }
3984
                        $this->items[$this->last]->close();
3985
                    } else {
3986
                        if ($debug) {
3987
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
3988
                        }
3989
                    }
3990
                    break;
3991
                case '1':
3992
                default:
3993
                    if ($debug) {
3994
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
3995
                    }
3996
                    $this->items[$this->last]->close();
3997
                    break;
3998
            }
3999
        } else {
4000
            if ($debug) {
4001
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4002
            }
4003
4004
            return false;
4005
        }
4006
4007
        return true;
4008
    }
4009
4010
    /**
4011
     * Updates the default view mode from fullscreen to embedded and inversely.
4012
     *
4013
     * @return string The current default view mode ('fullscreen' or 'embedded')
4014
     */
4015
    public function update_default_view_mode()
4016
    {
4017
        $table = Database::get_course_table(TABLE_LP_MAIN);
4018
        $sql = "SELECT * FROM $table
4019
                WHERE iid = ".$this->get_id();
4020
        $res = Database::query($sql);
4021
        if (Database::num_rows($res) > 0) {
4022
            $row = Database::fetch_array($res);
4023
            $default_view_mode = $row['default_view_mod'];
4024
            $view_mode = $default_view_mode;
4025
            switch ($default_view_mode) {
4026
                case 'fullscreen': // default with popup
4027
                    $view_mode = 'embedded';
4028
                    break;
4029
                case 'embedded': // default view with left menu
4030
                    $view_mode = 'embedframe';
4031
                    break;
4032
                case 'embedframe': //folded menu
4033
                    $view_mode = 'impress';
4034
                    break;
4035
                case 'impress':
4036
                    $view_mode = 'fullscreen';
4037
                    break;
4038
            }
4039
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4040
                    WHERE iid = ".$this->get_id();
4041
            Database::query($sql);
4042
            $this->mode = $view_mode;
4043
4044
            return $view_mode;
4045
        }
4046
4047
        return -1;
4048
    }
4049
4050
    /**
4051
     * Updates the default behaviour about auto-commiting SCORM updates.
4052
     *
4053
     * @return bool True if auto-commit has been set to 'on', false otherwise
4054
     */
4055
    public function update_default_scorm_commit()
4056
    {
4057
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4058
        $sql = "SELECT * FROM $lp_table
4059
                WHERE iid = ".$this->get_id();
4060
        $res = Database::query($sql);
4061
        if (Database::num_rows($res) > 0) {
4062
            $row = Database::fetch_array($res);
4063
            $force = $row['force_commit'];
4064
            if (1 == $force) {
4065
                $force = 0;
4066
                $force_return = false;
4067
            } elseif (0 == $force) {
4068
                $force = 1;
4069
                $force_return = true;
4070
            }
4071
            $sql = "UPDATE $lp_table SET force_commit = $force
4072
                    WHERE iid = ".$this->get_id();
4073
            Database::query($sql);
4074
            $this->force_commit = $force_return;
4075
4076
            return $force_return;
4077
        }
4078
4079
        return -1;
4080
    }
4081
4082
    /**
4083
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4084
     *
4085
     * @return bool True on success, false on failure
4086
     */
4087
    public function update_display_order()
4088
    {
4089
        return;
4090
        $course_id = api_get_course_int_id();
0 ignored issues
show
Unused Code introduced by
$course_id = api_get_course_int_id() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
4091
        $table = Database::get_course_table(TABLE_LP_MAIN);
4092
        $sql = "SELECT * FROM $table
4093
                WHERE c_id = $course_id
4094
                ORDER BY display_order";
4095
        $res = Database::query($sql);
4096
        if (false === $res) {
4097
            return false;
4098
        }
4099
4100
        $num = Database::num_rows($res);
4101
        // First check the order is correct, globally (might be wrong because
4102
        // of versions < 1.8.4).
4103
        if ($num > 0) {
4104
            $i = 1;
4105
            while ($row = Database::fetch_array($res)) {
4106
                if ($row['display_order'] != $i) {
4107
                    // If we find a gap in the order, we need to fix it.
4108
                    $sql = "UPDATE $table SET display_order = $i
4109
                            WHERE iid = ".$row['iid'];
4110
                    Database::query($sql);
4111
                }
4112
                $i++;
4113
            }
4114
        }
4115
4116
        return true;
4117
    }
4118
4119
    /**
4120
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4121
     *
4122
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4123
     */
4124
    public function update_reinit()
4125
    {
4126
        $force = $this->prevent_reinit;
4127
        if (1 == $force) {
4128
            $force = 0;
4129
        } elseif (0 == $force) {
4130
            $force = 1;
4131
        }
4132
4133
        $table = Database::get_course_table(TABLE_LP_MAIN);
4134
        $sql = "UPDATE $table SET prevent_reinit = $force
4135
                WHERE iid = ".$this->get_id();
4136
        Database::query($sql);
4137
        $this->prevent_reinit = $force;
4138
4139
        return $force;
4140
    }
4141
4142
    /**
4143
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4144
     *
4145
     * @return string 'single', 'multi' or 'seriousgame'
4146
     *
4147
     * @author ndiechburg <[email protected]>
4148
     */
4149
    public function get_attempt_mode()
4150
    {
4151
        //Set default value for seriousgame_mode
4152
        if (!isset($this->seriousgame_mode)) {
4153
            $this->seriousgame_mode = 0;
4154
        }
4155
        // Set default value for prevent_reinit
4156
        if (!isset($this->prevent_reinit)) {
4157
            $this->prevent_reinit = 1;
4158
        }
4159
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4160
            return 'seriousgame';
4161
        }
4162
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4163
            return 'single';
4164
        }
4165
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4166
            return 'multiple';
4167
        }
4168
4169
        return 'single';
4170
    }
4171
4172
    /**
4173
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4174
     *
4175
     * @param string 'seriousgame', 'single' or 'multiple'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'seriousgame', 'single' at position 0 could not be parsed: Unknown type name ''seriousgame'' at position 0 in 'seriousgame', 'single'.
Loading history...
4176
     *
4177
     * @return bool
4178
     *
4179
     * @author ndiechburg <[email protected]>
4180
     */
4181
    public function set_attempt_mode($mode)
4182
    {
4183
        switch ($mode) {
4184
            case 'seriousgame':
4185
                $sg_mode = 1;
4186
                $prevent_reinit = 1;
4187
                break;
4188
            case 'single':
4189
                $sg_mode = 0;
4190
                $prevent_reinit = 1;
4191
                break;
4192
            case 'multiple':
4193
                $sg_mode = 0;
4194
                $prevent_reinit = 0;
4195
                break;
4196
            default:
4197
                $sg_mode = 0;
4198
                $prevent_reinit = 0;
4199
                break;
4200
        }
4201
        $this->prevent_reinit = $prevent_reinit;
4202
        $this->seriousgame_mode = $sg_mode;
4203
        $table = Database::get_course_table(TABLE_LP_MAIN);
4204
        $sql = "UPDATE $table SET
4205
                prevent_reinit = $prevent_reinit ,
4206
                seriousgame_mode = $sg_mode
4207
                WHERE iid = ".$this->get_id();
4208
        $res = Database::query($sql);
4209
        if ($res) {
4210
            return true;
4211
        } else {
4212
            return false;
4213
        }
4214
    }
4215
4216
    /**
4217
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4218
     *
4219
     * @author ndiechburg <[email protected]>
4220
     */
4221
    public function switch_attempt_mode()
4222
    {
4223
        $mode = $this->get_attempt_mode();
4224
        switch ($mode) {
4225
            case 'single':
4226
                $next_mode = 'multiple';
4227
                break;
4228
            case 'multiple':
4229
                $next_mode = 'seriousgame';
4230
                break;
4231
            case 'seriousgame':
4232
            default:
4233
                $next_mode = 'single';
4234
                break;
4235
        }
4236
        $this->set_attempt_mode($next_mode);
4237
    }
4238
4239
    /**
4240
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4241
     * but possibility to do again a completed item.
4242
     *
4243
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4244
     *
4245
     * @author ndiechburg <[email protected]>
4246
     */
4247
    public function set_seriousgame_mode()
4248
    {
4249
        $table = Database::get_course_table(TABLE_LP_MAIN);
4250
        $force = $this->seriousgame_mode;
4251
        if (1 == $force) {
4252
            $force = 0;
4253
        } elseif (0 == $force) {
4254
            $force = 1;
4255
        }
4256
        $sql = "UPDATE $table SET seriousgame_mode = $force
4257
                WHERE iid = ".$this->get_id();
4258
        Database::query($sql);
4259
        $this->seriousgame_mode = $force;
4260
4261
        return $force;
4262
    }
4263
4264
    /**
4265
     * Updates the "scorm_debug" value that shows or hide the debug window.
4266
     *
4267
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4268
     */
4269
    public function update_scorm_debug()
4270
    {
4271
        $table = Database::get_course_table(TABLE_LP_MAIN);
4272
        $force = $this->scorm_debug;
4273
        if (1 == $force) {
4274
            $force = 0;
4275
        } elseif (0 == $force) {
4276
            $force = 1;
4277
        }
4278
        $sql = "UPDATE $table SET debug = $force
4279
                WHERE iid = ".$this->get_id();
4280
        Database::query($sql);
4281
        $this->scorm_debug = $force;
4282
4283
        return $force;
4284
    }
4285
4286
    /**
4287
     * Function that creates a html list of learning path items so that we can add audio files to them.
4288
     *
4289
     * @author Kevin Van Den Haute
4290
     *
4291
     * @return string
4292
     */
4293
    public function overview()
4294
    {
4295
        $return = '';
4296
        $update_audio = $_GET['updateaudio'] ?? null;
4297
4298
        // we need to start a form when we want to update all the mp3 files
4299
        if ('true' == $update_audio) {
4300
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4301
                    $_GET['updateaudio']
4302
                ).'&action='.Security::remove_XSS(
4303
                    $_GET['action']
4304
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4305
        }
4306
        $return .= '<div id="message"></div>';
4307
        if (0 == count($this->items)) {
4308
            $return .= Display::return_message(
4309
                get_lang(
4310
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4311
                ),
4312
                'normal'
4313
            );
4314
        } else {
4315
            $return_audio = '<table class="table table-hover table-striped data_table">';
4316
            $return_audio .= '<tr>';
4317
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4318
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4319
            $return_audio .= '</tr>';
4320
4321
            if ('true' != $update_audio) {
4322
                /*$return .= '<div class="col-md-12">';
4323
                $return .= self::return_new_tree($update_audio);
4324
                $return .= '</div>';*/
4325
                $return .= Display::div(
4326
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn--primary']),
4327
                    ['style' => 'float:left; margin-top:15px;width:100%']
4328
                );
4329
            } else {
4330
                //$return_audio .= self::return_new_tree($update_audio);
4331
                $return .= $return_audio.'</table>';
4332
            }
4333
4334
            // We need to close the form when we are updating the mp3 files.
4335
            if ('true' == $update_audio) {
4336
                $return .= '<div class="footer-audio">';
4337
                $return .= Display::button(
4338
                    'save_audio',
4339
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4340
                    ['class' => 'btn btn--primary', 'type' => 'submit']
4341
                );
4342
                $return .= '</div>';
4343
            }
4344
        }
4345
4346
        // We need to close the form when we are updating the mp3 files.
4347
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4348
            $return .= '</form>';
4349
        }
4350
4351
        return $return;
4352
    }
4353
4354
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4355
    {
4356
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4357
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4358
4359
        $content = '
4360
        <script>
4361
            /*
4362
            Script to manipulate Learning Path items with Drag and drop
4363
             */
4364
            $(function() {
4365
                function refreshTree() {
4366
                    var params = "&a=get_lp_item_tree";
4367
                    $.get(
4368
                        "'.$ajax_url.'",
4369
                        params,
4370
                        function(result) {
4371
                            serialized = [];
4372
                            $("#lp_item_list").html(result);
4373
                            nestedSortable();
4374
                        }
4375
                    );
4376
                }
4377
4378
                const nestedQuery = ".nested-sortable";
4379
                const identifier = "id";
4380
                const root = document.getElementById("lp_item_list");
4381
4382
                var serialized = [];
4383
                function serialize(sortable) {
4384
                  var children = [].slice.call(sortable.children);
4385
                  for (var i in children) {
4386
                    var nested = children[i].querySelector(nestedQuery);
4387
                    var parentId = $(children[i]).parent().parent().attr("id");
4388
                    var id = children[i].dataset[identifier];
4389
                    if (typeof id === "undefined") {
4390
                        return;
4391
                    }
4392
                    serialized.push({
4393
                      id: children[i].dataset[identifier],
4394
                      parent_id: parentId
4395
                    });
4396
4397
                    if (nested) {
4398
                        serialize(nested);
4399
                    }
4400
                  }
4401
4402
                  return serialized;
4403
                }
4404
4405
                function nestedSortable() {
4406
                    let left = document.getElementsByClassName("nested-sortable");
4407
                    Array.prototype.forEach.call(left, function(resource) {
4408
                        Sortable.create(resource, {
4409
                            group: "nested",
4410
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4411
                            animation: 150,
4412
                            //fallbackOnBody: true,
4413
                            swapThreshold: 0.65,
4414
                            dataIdAttr: "data-id",
4415
                            store: {
4416
                                set: function (sortable) {
4417
                                    var order = sortable.toArray();
4418
                                    console.log(order);
4419
                                }
4420
                            },
4421
                            onEnd: function(evt) {
4422
                                console.log("onEnd");
4423
                                let list = serialize(root);
4424
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4425
                                $.get(
4426
                                    "'.$ajax_url.'",
4427
                                    order,
4428
                                    function(reponse) {
4429
                                        $("#message").html(reponse);
4430
                                        refreshTree();
4431
                                    }
4432
                                );
4433
                            },
4434
                        });
4435
                    });
4436
                }
4437
4438
                nestedSortable();
4439
4440
                let resources = document.getElementsByClassName("lp_resource");
4441
                Array.prototype.forEach.call(resources, function(resource) {
4442
                    Sortable.create(resource, {
4443
                        group: "nested",
4444
                        put: ["nested-sortable"],
4445
                        filter: ".disable_drag",
4446
                        animation: 150,
4447
                        fallbackOnBody: true,
4448
                        swapThreshold: 0.65,
4449
                        dataIdAttr: "data-id",
4450
                        onRemove: function(evt) {
4451
                            console.log("onRemove");
4452
                            var itemEl = evt.item;
4453
                            var newIndex = evt.newIndex;
4454
                            var id = $(itemEl).attr("id");
4455
                            var parent_id = $(itemEl).parent().parent().attr("id");
4456
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4457
                            var title = $(itemEl).find(".link_with_id").text();
4458
4459
                            let previousId = 0;
4460
                            if (0 !== newIndex) {
4461
                                previousId = $(itemEl).prev().attr("id");
4462
                            }
4463
                            var params = {
4464
                                "a": "add_lp_item",
4465
                                "id": id,
4466
                                "parent_id": parent_id,
4467
                                "previous_id": previousId,
4468
                                "type": type,
4469
                                "title" : title
4470
                            };
4471
                            console.log(params);
4472
                            $.ajax({
4473
                                type: "GET",
4474
                                url: "'.$ajax_url.'",
4475
                                data: params,
4476
                                success: function(itemId) {
4477
                                    $(itemEl).attr("id", itemId);
4478
                                    $(itemEl).attr("data-id", itemId);
4479
                                    let list = serialize(root);
4480
                                    let listInString = JSON.stringify(list);
4481
                                    if (typeof listInString === "undefined") {
4482
                                        listInString = "";
4483
                                    }
4484
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4485
                                    $.get(
4486
                                        "'.$ajax_url.'",
4487
                                        order,
4488
                                        function(reponse) {
4489
                                            $("#message").html(reponse);
4490
                                            refreshTree();
4491
                                        }
4492
                                    );
4493
                                }
4494
                            });
4495
                        },
4496
                    });
4497
                });
4498
            });
4499
        </script>';
4500
4501
        $content .= "
4502
        <script>
4503
            function confirmation(name) {
4504
                if (confirm('$sureToDelete ' + name)) {
4505
                    return true;
4506
                } else {
4507
                    return false;
4508
                }
4509
            }
4510
            function refreshTree() {
4511
                var params = '&a=get_lp_item_tree';
4512
                $.get(
4513
                    '".$ajax_url."',
4514
                    params,
4515
                    function(result) {
4516
                        $('#lp_item_list').html(result);
4517
                    }
4518
                );
4519
            }
4520
4521
            $(function () {
4522
                //$('.scrollbar-inner').scrollbar();
4523
                /*$('#subtab').on('click', 'a:first', function() {
4524
                    window.location.reload();
4525
                });
4526
                $('#subtab ').on('click', 'a:first', function () {
4527
                    window.location.reload();
4528
                });*/
4529
4530
                expandColumnToggle('#hide_bar_template', {
4531
                    selector: '#lp_sidebar'
4532
                }, {
4533
                    selector: '#doc_form'
4534
                });
4535
4536
                $('.lp-btn-associate-forum').on('click', function (e) {
4537
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
4538
                    if (!associate) {
4539
                        e.preventDefault();
4540
                    }
4541
                });
4542
4543
                $('.lp-btn-dissociate-forum').on('click', function (e) {
4544
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
4545
                    if (!dissociate) {
4546
                        e.preventDefault();
4547
                    }
4548
                });
4549
4550
                // hide the current template list for new documment until it tab clicked
4551
                $('#frmModel').hide();
4552
            });
4553
4554
            // document template for new document tab handler
4555
            /*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
4556
                var id = e.target.id;
4557
                if (id == 'subtab2') {
4558
                    $('#frmModel').show();
4559
                } else {
4560
                    $('#frmModel').hide();
4561
                }
4562
            });*/
4563
4564
          function deleteItem(event) {
4565
            var id = $(event).attr('data-id');
4566
            var title = $(event).attr('data-title');
4567
            var params = '&a=delete_item&id=' + id;
4568
            if (confirmation(title)) {
4569
                $.get(
4570
                    '".$ajax_url."',
4571
                    params,
4572
                    function(result) {
4573
                        refreshTree();
4574
                    }
4575
                );
4576
            }
4577
        }
4578
        </script>";
4579
4580
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
4581
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
4582
4583
        $repo = Container::getDocumentRepository();
4584
        $document = $repo->find($documentId);
4585
        if ($document) {
4586
            // Show the template list
4587
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4588
        }
4589
4590
        // Show the template list.
4591
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
4592
            // Show the template list.
4593
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4594
        }
4595
4596
        return $content;
4597
    }
4598
4599
    /**
4600
     * @param bool  $updateAudio
4601
     * @param bool   $dropElement
4602
     *
4603
     * @return string
4604
     */
4605
    public function return_new_tree($updateAudio = false, $dropElement = false)
4606
    {
4607
        $list = $this->getBuildTree(false, $dropElement);
4608
        $return = Display::panelCollapse(
4609
            $this->name,
4610
            $list,
4611
            'scorm-list',
4612
            null,
4613
            'scorm-list-accordion',
4614
            'scorm-list-collapse'
4615
        );
4616
4617
        if ($updateAudio) {
4618
            //$return = $result['return_audio'];
4619
        }
4620
4621
        return $return;
4622
    }
4623
4624
    public function getBuildTree($noWrapper = false, $dropElement = false): string
4625
    {
4626
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
4627
        $upIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon', '', 16, get_lang('Up'));
4628
        $disableUpIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon-disabled', '', 16, get_lang('Up'));
4629
        $downIcon = Display::getMdiIcon('arrow-down-bold', 'ch-tool-icon', '', 16, get_lang('Down'));
4630
        $previewImage = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 16, get_lang('Preview'));
4631
4632
        $lpItemRepo = Container::getLpItemRepository();
4633
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
4634
4635
        $options = [
4636
            'decorate' => true,
4637
            'rootOpen' => function($tree) use ($noWrapper) {
4638
                if ($tree[0]['lvl'] === 1) {
4639
                    if ($noWrapper) {
4640
                        return '';
4641
                    }
4642
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
4643
                }
4644
4645
                return '<ul class="list-group nested-sortable">';
4646
            },
4647
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
4648
                if ($tree[0]['lvl'] === 1) {
4649
                    if ($dropElement) {
4650
                        //return Display::return_message(get_lang('Drag and drop an element here'));
4651
                        //return $this->getDropElementHtml();
4652
                    }
4653
                    if ($noWrapper) {
4654
                        return '';
4655
                    }
4656
                }
4657
4658
                return '</ul>';
4659
            },
4660
            'childOpen' => function($child) {
4661
                $id = $child['iid'];
4662
                return '<li
4663
                    id="'.$id.'"
4664
                    data-id="'.$id.'"
4665
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
4666
            },
4667
            'childClose' => '',
4668
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
4669
                $fullTitle = $node['title'];
4670
                //$title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
4671
                $title = $fullTitle;
4672
                $itemId = $node['iid'];
4673
                $type = $node['itemType'];
4674
                $lpId = $this->get_id();
4675
4676
                $moveIcon = '';
4677
                if (TOOL_LP_FINAL_ITEM !== $type) {
4678
                    $moveIcon .= '<a class="moved" href="#">';
4679
                    $moveIcon .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
4680
                    $moveIcon .= '</a>';
4681
                }
4682
4683
                $iconName = str_replace(' ', '', $type);
4684
                $icon = '';
4685
                switch ($iconName) {
4686
                    case 'category':
4687
                    case 'chapter':
4688
                    case 'folder':
4689
                    case 'dir':
4690
                        $icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY);
4691
                        break;
4692
                    default:
4693
                        $icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY);
4694
                        break;
4695
                }
4696
4697
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
4698
                $previewIcon = Display::url(
4699
                    $previewImage,
4700
                    $urlPreviewLink,
4701
                    [
4702
                        'target' => '_blank',
4703
                        'class' => 'btn btn--plain',
4704
                        'data-title' => $title,
4705
                        'title' => $title,
4706
                    ]
4707
                );
4708
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
4709
4710
                $preRequisitesIcon = Display::url(
4711
                    Display::getMdiIcon('graph', 'ch-tool-icon', '', 16, get_lang('Prerequisites')),
4712
                    $url.'&action=edit_item_prereq',
4713
                    ['class' => '']
4714
                );
4715
4716
                $editIcon = '<a
4717
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
4718
                    class=""
4719
                    >';
4720
                $editIcon .= Display::getMdiIcon('pencil', 'ch-tool-icon', '', 16, get_lang('Edit section description/name'));
4721
                $editIcon .= '</a>';
4722
                $orderIcons = '';
4723
                /*if ('final_item' !== $type) {
4724
                    $orderIcons = Display::url(
4725
                        $upIcon,
4726
                        'javascript:void(0)',
4727
                        ['class' => 'btn btn--plain order_items', 'data-dir' => 'up', 'data-id' => $itemId]
4728
                    );
4729
                    $orderIcons .= Display::url(
4730
                        $downIcon,
4731
                        'javascript:void(0)',
4732
                        ['class' => 'btn btn--plain order_items', 'data-dir' => 'down', 'data-id' => $itemId]
4733
                    );
4734
                }*/
4735
4736
                $deleteIcon = ' <a
4737
                    data-id = '.$itemId.'
4738
                    data-title = \''.addslashes($title).'\'
4739
                    href="javascript:void(0);"
4740
                    onclick="return deleteItem(this);"
4741
                    class="">';
4742
                $deleteIcon .= Display::getMdiIcon('delete', 'ch-tool-icon', '', 16, get_lang('Delete section'));
4743
                $deleteIcon .= '</a>';
4744
                $extra = '';
4745
4746
                if ('dir' === $type && empty($node['__children'])) {
4747
                    $level = $node['lvl'] + 1;
4748
                    $extra = '<ul class="list-group nested-sortable">
4749
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
4750
                              </ul>';
4751
                }
4752
4753
                $buttons = Display::tag(
4754
                    'div',
4755
                    "<div class=\"btn-group btn-group-sm\">
4756
                                $editIcon
4757
                                $preRequisitesIcon
4758
                                $orderIcons
4759
                                $deleteIcon
4760
                               </div>",
4761
                    ['class' => 'btn-toolbar button_actions']
4762
                );
4763
4764
                return
4765
                    "<div class='flex flex-row'> $moveIcon  $icon <span class='mx-1'>$title </span></div>
4766
                    $extra
4767
                    $buttons
4768
                    "
4769
                    ;
4770
            },
4771
        ];
4772
4773
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
4774
4775
        if (empty($tree) && $dropElement) {
4776
            return $this->getDropElementHtml($noWrapper);
4777
        }
4778
4779
        return $tree;
4780
    }
4781
4782
    public function getDropElementHtml($noWrapper = false)
4783
    {
4784
        $li = '<li class="list-group-item">'.
4785
            Display::return_message(get_lang('Drag and drop an element here')).
4786
            '</li>';
4787
        if ($noWrapper) {
4788
            return $li;
4789
        }
4790
4791
        return
4792
            '<ul id="lp_item_list" class="list-group nested-sortable">
4793
            '.$li.'
4794
            </ul>';
4795
    }
4796
4797
    /**
4798
     * This function builds the action menu.
4799
     *
4800
     * @param bool   $returnString           Optional
4801
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
4802
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
4803
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
4804
     * @param string $action
4805
     * @param array  $extraField
4806
     *
4807
     * @return string
4808
     */
4809
    public function build_action_menu(
4810
        $returnString = false,
4811
        $showRequirementButtons = true,
4812
        $isConfigPage = false,
4813
        $allowExpand = true,
4814
        $action = '',
4815
        $extraField = []
4816
    ) {
4817
        $actionsRight = '';
4818
        $lpId = $this->lp_id;
4819
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
4820
            $back = Display::url(
4821
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back to learning paths')),
4822
                'lp_controller.php?'.api_get_cidreq()
4823
            );
4824
        } else {
4825
            $back = Display::url(
4826
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back')),
4827
                $extraField['backTo']
4828
            );
4829
        }
4830
4831
        /*if ($backToBuild) {
4832
            $back = Display::url(
4833
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', null, 32, get_lang('GoBack')),
4834
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
4835
            );
4836
        }*/
4837
4838
        $actionsLeft = $back;
4839
4840
        $actionsLeft .= Display::url(
4841
            Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 32, get_lang('Preview')),
4842
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4843
                'action' => 'view',
4844
                'lp_id' => $lpId,
4845
                'isStudentView' => 'true',
4846
            ])
4847
        );
4848
4849
        /*$actionsLeft .= Display::url(
4850
            Display::getMdiIcon('music-note-plus', 'ch-tool-icon', null, 32, get_lang('Add audio')),
4851
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4852
                'action' => 'admin_view',
4853
                'lp_id' => $lpId,
4854
                'updateaudio' => 'true',
4855
            ])
4856
        );*/
4857
4858
        $subscriptionSettings = self::getSubscriptionSettings();
4859
4860
        $request = api_request_uri();
4861
        if (false === strpos($request, 'edit')) {
4862
            $actionsLeft .= Display::url(
4863
                Display::getMdiIcon('hammer-wrench', 'ch-tool-icon', '', 32, get_lang('Course settings')),
4864
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4865
                    'action' => 'edit',
4866
                    'lp_id' => $lpId,
4867
                ])
4868
            );
4869
        }
4870
4871
        if ((false === strpos($request, 'build') &&
4872
            false === strpos($request, 'add_item')) ||
4873
            in_array($action, ['add_audio'], true)
4874
        ) {
4875
            $actionsLeft .= Display::url(
4876
                Display::getMdiIcon('pencil', 'ch-tool-icon', '', 32, get_lang('Edit')),
4877
                'lp_controller.php?'.http_build_query([
4878
                    'action' => 'build',
4879
                    'lp_id' => $lpId,
4880
                ]).'&'.api_get_cidreq()
4881
            );
4882
        }
4883
4884
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
4885
            if (1 == $this->subscribeUsers &&
4886
                $subscriptionSettings['allow_add_users_to_lp']) {
4887
                $actionsLeft .= Display::url(
4888
                    Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Subscribe users to learning path')),
4889
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
4890
                );
4891
            }
4892
        }
4893
4894
        if ($allowExpand) {
4895
            /*$actionsLeft .= Display::url(
4896
                Display::getMdiIcon('arrow-expand-all', 'ch-tool-icon', null, 32, get_lang('Expand')).
4897
                Display::getMdiIcon('arrow-collapse-all', 'ch-tool-icon', null, 32, get_lang('Collapse')),
4898
                '#',
4899
                ['role' => 'button', 'id' => 'hide_bar_template']
4900
            );*/
4901
        }
4902
4903
        if ($showRequirementButtons) {
4904
            $buttons = [
4905
                [
4906
                    'title' => get_lang('Set previous step as prerequisite for each step'),
4907
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4908
                        'action' => 'set_previous_step_as_prerequisite',
4909
                        'lp_id' => $lpId,
4910
                    ]),
4911
                ],
4912
                [
4913
                    'title' => get_lang('Clear all prerequisites'),
4914
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4915
                        'action' => 'clear_prerequisites',
4916
                        'lp_id' => $lpId,
4917
                    ]),
4918
                ],
4919
            ];
4920
            $actionsRight = Display::groupButtonWithDropDown(
4921
                get_lang('Prerequisites options'),
4922
                $buttons,
4923
                true
4924
            );
4925
        }
4926
4927
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
4928
            $actionsLeft .= Display::url(
4929
                Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Author')),
4930
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4931
                    'action' => 'author_view',
4932
                    'lp_id' => $lpId,
4933
                ])
4934
            );
4935
        }
4936
4937
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
4938
4939
        if ($returnString) {
4940
            return $toolbar;
4941
        }
4942
4943
        echo $toolbar;
4944
    }
4945
4946
    /**
4947
     * Creates the default learning path folder.
4948
     *
4949
     * @param array $course
4950
     * @param int   $creatorId
4951
     *
4952
     * @return CDocument
4953
     */
4954
    public static function generate_learning_path_folder($course, $creatorId = 0)
4955
    {
4956
        // Creating learning_path folder
4957
        $dir = 'learning_path';
4958
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4959
4960
        return create_unexisting_directory(
4961
            $course,
4962
            $creatorId,
4963
            0,
4964
            null,
4965
            0,
4966
            '',
4967
            $dir,
4968
            get_lang('Learning paths'),
4969
            0
4970
        );
4971
    }
4972
4973
    /**
4974
     * @param array  $course
4975
     * @param string $lp_name
4976
     * @param int    $creatorId
4977
     *
4978
     * @return CDocument
4979
     */
4980
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
4981
    {
4982
        $filepath = '';
4983
        $dir = '/learning_path/';
4984
4985
        if (empty($lp_name)) {
4986
            $lp_name = $this->name;
4987
        }
4988
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4989
        $parent = self::generate_learning_path_folder($course, $creatorId);
4990
4991
        // Limits title size
4992
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
4993
        $dir = $dir.$title;
4994
4995
        // Creating LP folder
4996
        $folder = null;
4997
        if ($parent) {
4998
            $folder = create_unexisting_directory(
4999
                $course,
5000
                $creatorId,
5001
                0,
5002
                0,
5003
                0,
5004
                $filepath,
5005
                $dir,
5006
                $lp_name,
5007
                '',
5008
                false,
5009
                false,
5010
                $parent
5011
            );
5012
        }
5013
5014
        return $folder;
5015
    }
5016
5017
    /**
5018
     * Create a new document //still needs some finetuning.
5019
     *
5020
     * @param array  $courseInfo
5021
     * @param string $content
5022
     * @param string $title
5023
     * @param string $extension
5024
     * @param int    $parentId
5025
     * @param int    $creatorId  creator id
5026
     *
5027
     * @return int
5028
     */
5029
    public function create_document(
5030
        $courseInfo,
5031
        $content = '',
5032
        $title = '',
5033
        $extension = 'html',
5034
        $parentId = 0,
5035
        $creatorId = 0
5036
    ) {
5037
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5038
        $sessionId = api_get_session_id();
5039
5040
        // Generates folder
5041
        $this->generate_lp_folder($courseInfo);
5042
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
5043
        // is already escaped twice when it gets here.
5044
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5045
        if (!empty($title)) {
5046
            $title = api_replace_dangerous_char(stripslashes($title));
5047
        } else {
5048
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5049
        }
5050
5051
        $title = disable_dangerous_file($title);
5052
        $filename = $title;
5053
        $tmp_filename = $filename;
5054
        /*$i = 0;
5055
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
5056
            $tmp_filename = $filename.'_'.++$i;
5057
        }*/
5058
        $filename = $tmp_filename.'.'.$extension;
5059
5060
        if ('html' === $extension) {
5061
            $content = stripslashes($content);
5062
            $content = str_replace(
5063
                api_get_path(WEB_COURSE_PATH),
5064
                api_get_path(REL_PATH).'courses/',
5065
                $content
5066
            );
5067
5068
            // Change the path of mp3 to absolute.
5069
            // The first regexp deals with :// urls.
5070
            /*$content = preg_replace(
5071
                "|(flashvars=\"file=)([^:/]+)/|",
5072
                "$1".api_get_path(
5073
                    REL_COURSE_PATH
5074
                ).$courseInfo['path'].'/document/',
5075
                $content
5076
            );*/
5077
            // The second regexp deals with audio/ urls.
5078
            /*$content = preg_replace(
5079
                "|(flashvars=\"file=)([^/]+)/|",
5080
                "$1".api_get_path(
5081
                    REL_COURSE_PATH
5082
                ).$courseInfo['path'].'/document/$2/',
5083
                $content
5084
            );*/
5085
            // For flv player: To prevent edition problem with firefox,
5086
            // we have to use a strange tip (don't blame me please).
5087
            $content = str_replace(
5088
                '</body>',
5089
                '<style type="text/css">body{}</style></body>',
5090
                $content
5091
            );
5092
        }
5093
5094
        $document = DocumentManager::addDocument(
5095
            $courseInfo,
5096
            null,
5097
            'file',
5098
            '',
5099
            $tmp_filename,
5100
            '',
5101
            0, //readonly
5102
            true,
5103
            null,
5104
            $sessionId,
5105
            $creatorId,
5106
            false,
5107
            $content,
5108
            $parentId
5109
        );
5110
5111
        $document_id = $document->getIid();
5112
        if ($document_id) {
5113
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5114
            $new_title = $originalTitle;
5115
5116
            if ($new_comment || $new_title) {
5117
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
5118
                $ct = '';
5119
                if ($new_comment) {
5120
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
5121
                }
5122
                if ($new_title) {
5123
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
5124
                }
5125
5126
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
5127
                        WHERE iid = $document_id ";
5128
                Database::query($sql);
5129
            }
5130
        }
5131
5132
        return $document_id;
5133
    }
5134
5135
    /**
5136
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
5137
     */
5138
    public function edit_document()
5139
    {
5140
        $repo = Container::getDocumentRepository();
5141
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
5142
            $id = (int) $_REQUEST['document_id'];
5143
            /** @var CDocument $document */
5144
            $document = $repo->find($id);
5145
            if ($document->getResourceNode()->hasEditableTextContent()) {
5146
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
5147
            }
5148
            $document->setTitle($_REQUEST['title']);
5149
            $repo->update($document);
5150
        }
5151
    }
5152
5153
    /**
5154
     * Displays the selected item, with a panel for manipulating the item.
5155
     *
5156
     * @param CLpItem $lpItem
5157
     * @param string  $msg
5158
     * @param bool    $show_actions
5159
     *
5160
     * @return string
5161
     */
5162
    public function display_item($lpItem, $msg = null, $show_actions = true)
5163
    {
5164
        $course_id = api_get_course_int_id();
5165
        $return = '';
5166
5167
        if (null === $lpItem) {
5168
            return '';
5169
        }
5170
        $item_id = $lpItem->getIid();
5171
        $itemType = $lpItem->getItemType();
5172
        $lpId = $lpItem->getLp()->getIid();
5173
        $path = $lpItem->getPath();
5174
5175
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
5176
5177
        // Prevents wrong parent selection for document, see Bug#1251.
5178
        if ('dir' !== $itemType) {
5179
            Session::write('parent_item_id', $lpItem->getParentItemId());
5180
        }
5181
5182
        if ($show_actions) {
5183
            $return .= $this->displayItemMenu($lpItem);
5184
        }
5185
        $return .= '<div style="padding:10px;">';
5186
5187
        if ('' != $msg) {
5188
            $return .= $msg;
5189
        }
5190
5191
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
5192
5193
        switch ($itemType) {
5194
            case TOOL_THREAD:
5195
                $link = $this->rl_get_resource_link_for_learnpath(
5196
                    $course_id,
5197
                    $lpId,
5198
                    $item_id,
5199
                    0
5200
                );
5201
                $return .= Display::url(
5202
                    get_lang('Go to thread'),
5203
                    $link,
5204
                    ['class' => 'btn btn--primary']
5205
                );
5206
                break;
5207
            case TOOL_FORUM:
5208
                $return .= Display::url(
5209
                    get_lang('Go to the forum'),
5210
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
5211
                    ['class' => 'btn btn--primary']
5212
                );
5213
                break;
5214
            case TOOL_QUIZ:
5215
                if (!empty($path)) {
5216
                    $exercise = new Exercise();
5217
                    $exercise->read($path);
5218
                    $return .= $exercise->description.'<br />';
5219
                    $return .= Display::url(
5220
                        get_lang('Go to exercise'),
5221
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
5222
                        ['class' => 'btn btn--primary']
5223
                    );
5224
                }
5225
                break;
5226
            case TOOL_LP_FINAL_ITEM:
5227
                $return .= $this->getSavedFinalItem();
5228
                break;
5229
            case TOOL_DOCUMENT:
5230
            case TOOL_READOUT_TEXT:
5231
                $repo = Container::getDocumentRepository();
5232
                /** @var CDocument $document */
5233
                $document = $repo->find($lpItem->getPath());
5234
                $return .= $this->display_document($document, true, true);
5235
                break;
5236
        }
5237
        $return .= '</div>';
5238
5239
        return $return;
5240
    }
5241
5242
    /**
5243
     * Shows the needed forms for editing a specific item.
5244
     *
5245
     * @param CLpItem $lpItem
5246
     *
5247
     * @throws Exception
5248
     *
5249
     *
5250
     * @return string
5251
     */
5252
    public function display_edit_item($lpItem, $excludeExtraFields = [])
5253
    {
5254
        $return = '';
5255
        if (empty($lpItem)) {
5256
            return '';
5257
        }
5258
        $itemType = $lpItem->getItemType();
5259
        $path = $lpItem->getPath();
5260
5261
        switch ($itemType) {
5262
            case 'dir':
5263
            case 'asset':
5264
            case 'sco':
5265
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
5266
                    $return .= $this->displayItemMenu($lpItem);
5267
                    $return .= $this->display_item_form($lpItem, 'edit');
5268
                } else {
5269
                    $return .= $this->display_item_form($lpItem, 'edit_item');
5270
                }
5271
                break;
5272
            case TOOL_LP_FINAL_ITEM:
5273
            case TOOL_DOCUMENT:
5274
            case TOOL_READOUT_TEXT:
5275
                $return .= $this->displayItemMenu($lpItem);
5276
                $return .= $this->displayDocumentForm('edit', $lpItem);
5277
                break;
5278
            case TOOL_LINK:
5279
                $link = null;
5280
                if (!empty($path)) {
5281
                    $repo = Container::getLinkRepository();
5282
                    $link = $repo->find($path);
5283
                }
5284
                $return .= $this->displayItemMenu($lpItem);
5285
                $return .= $this->display_link_form('edit', $lpItem, $link);
5286
5287
                break;
5288
            case TOOL_QUIZ:
5289
                if (!empty($path)) {
5290
                    $repo = Container::getQuizRepository();
5291
                    $resource = $repo->find($path);
5292
                }
5293
                $return .= $this->displayItemMenu($lpItem);
5294
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
5295
                break;
5296
            case TOOL_STUDENTPUBLICATION:
5297
                if (!empty($path)) {
5298
                    $repo = Container::getStudentPublicationRepository();
5299
                    $resource = $repo->find($path);
5300
                }
5301
                $return .= $this->displayItemMenu($lpItem);
5302
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
5303
                break;
5304
            case TOOL_FORUM:
5305
                if (!empty($path)) {
5306
                    $repo = Container::getForumRepository();
5307
                    $resource = $repo->find($path);
5308
                }
5309
                $return .= $this->displayItemMenu($lpItem);
5310
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
5311
                break;
5312
            case TOOL_THREAD:
5313
                if (!empty($path)) {
5314
                    $repo = Container::getForumPostRepository();
5315
                    $resource = $repo->find($path);
5316
                }
5317
                $return .= $this->displayItemMenu($lpItem);
5318
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
5319
                break;
5320
        }
5321
5322
        return $return;
5323
    }
5324
5325
    /**
5326
     * Function that displays a list with al the resources that
5327
     * could be added to the learning path.
5328
     *
5329
     * @throws Exception
5330
     */
5331
    public function displayResources(): string
5332
    {
5333
        // Get all the docs.
5334
        $documents = $this->get_documents(true);
5335
5336
        // Get all the exercises.
5337
        $exercises = $this->get_exercises();
5338
5339
        // Get all the links.
5340
        $links = $this->get_links();
5341
5342
        // Get all the student publications.
5343
        $works = $this->get_student_publications();
5344
5345
        // Get all the forums.
5346
        $forums = $this->get_forums();
5347
5348
        // Get the final item form (see BT#11048) .
5349
        $finish = $this->getFinalItemForm();
5350
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5351
        $headers = [
5352
            Display::getMdiIcon('bookshelf', 'ch-tool-icon-gradient', '', 64, get_lang('Documents')),
5353
            Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon-gradient', '', 64, get_lang('Tests')),
5354
            Display::getMdiIcon('file-link', 'ch-tool-icon-gradient', '', 64, get_lang('Links')),
5355
            Display::getMdiIcon('inbox-full', 'ch-tool-icon-gradient', '', 64, get_lang('Assignments')),
5356
            Display::getMdiIcon('comment-quote', 'ch-tool-icon-gradient', '', 64, get_lang('Forums')),
5357
            Display::getMdiIcon('bookmark-multiple', 'ch-tool-icon-gradient', '', 64, get_lang('Add section')),
5358
            Display::getMdiIcon('certificate', 'ch-tool-icon-gradient', '', 64, get_lang('Certificate')),
5359
        ];
5360
        $content = '';
5361
        /*$content = Display::return_message(
5362
            get_lang('Click on the [Learner view] button to see your learning path'),
5363
            'normal'
5364
        );*/
5365
        $section = $this->displayNewSectionForm();
5366
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5367
5368
        return Display::tabs(
5369
            $headers,
5370
            [
5371
                $documents,
5372
                $exercises,
5373
                $links,
5374
                $works,
5375
                $forums,
5376
                $section,
5377
                $finish,
5378
            ],
5379
            'resource_tab',
5380
            [],
5381
            [],
5382
            $selected
5383
        );
5384
    }
5385
5386
    /**
5387
     * Returns the extension of a document.
5388
     *
5389
     * @param string $filename
5390
     *
5391
     * @return string Extension (part after the last dot)
5392
     */
5393
    public function get_extension($filename)
5394
    {
5395
        $explode = explode('.', $filename);
5396
5397
        return $explode[count($explode) - 1];
5398
    }
5399
5400
    /**
5401
     * @return string
5402
     */
5403
    public function getCurrentBuildingModeURL()
5404
    {
5405
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
5406
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
5407
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
5408
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
5409
5410
        $currentUrl = api_get_self().'?'.api_get_cidreq().
5411
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
5412
5413
        return $currentUrl;
5414
    }
5415
5416
    /**
5417
     * Displays a document by id.
5418
     *
5419
     * @param CDocument $document
5420
     * @param bool      $show_title
5421
     * @param bool      $iframe
5422
     * @param bool      $edit_link
5423
     *
5424
     * @return string
5425
     */
5426
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
5427
    {
5428
        $return = '';
5429
        if (!$document) {
5430
            return '';
5431
        }
5432
5433
        $repo = Container::getDocumentRepository();
5434
5435
        // TODO: Add a path filter.
5436
        if ($iframe) {
5437
            $url = $repo->getResourceFileUrl($document);
5438
5439
            $return .= '<iframe
5440
                id="learnpath_preview_frame"
5441
                frameborder="0"
5442
                height="400"
5443
                width="100%"
5444
                scrolling="auto"
5445
                src="'.$url.'"></iframe>';
5446
        } else {
5447
            $return = $repo->getResourceFileContent($document);
5448
        }
5449
5450
        return $return;
5451
    }
5452
5453
    /**
5454
     * Return HTML form to add/edit a link item.
5455
     *
5456
     * @param string  $action (add/edit)
5457
     * @param CLpItem $lpItem
5458
     * @param CLink   $link
5459
     *
5460
     * @throws Exception
5461
     *
5462
     *
5463
     * @return string HTML form
5464
     */
5465
    public function display_link_form($action, $lpItem, $link)
5466
    {
5467
        $item_url = '';
5468
        if ($link) {
5469
            $item_url = stripslashes($link->getUrl());
5470
        }
5471
        $form = new FormValidator(
5472
            'edit_link',
5473
            'POST',
5474
            $this->getCurrentBuildingModeURL()
5475
        );
5476
5477
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5478
5479
        $urlAttributes = ['class' => 'learnpath_item_form'];
5480
        $urlAttributes['disabled'] = 'disabled';
5481
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
5482
        $form->setDefault('url', $item_url);
5483
5484
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5485
5486
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5487
    }
5488
5489
    /**
5490
     * Return HTML form to add/edit a quiz.
5491
     *
5492
     * @param string  $action   Action (add/edit)
5493
     * @param CLpItem $lpItem   Item ID if already exists
5494
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
5495
     *
5496
     * @throws Exception
5497
     *
5498
     * @return string HTML form
5499
     */
5500
    public function display_quiz_form($action, $lpItem, $exercise)
5501
    {
5502
        $form = new FormValidator(
5503
            'quiz_form',
5504
            'POST',
5505
            $this->getCurrentBuildingModeURL()
5506
        );
5507
5508
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5509
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5510
5511
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5512
    }
5513
5514
    /**
5515
     * Return the form to display the forum edit/add option.
5516
     *
5517
     * @param CLpItem $lpItem
5518
     *
5519
     * @throws Exception
5520
     *
5521
     * @return string HTML form
5522
     */
5523
    public function display_forum_form($action, $lpItem, $resource)
5524
    {
5525
        $form = new FormValidator(
5526
            'forum_form',
5527
            'POST',
5528
            $this->getCurrentBuildingModeURL()
5529
        );
5530
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5531
5532
        if ('add' === $action) {
5533
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
5534
        } else {
5535
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
5536
        }
5537
5538
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5539
    }
5540
5541
    /**
5542
     * Return HTML form to add/edit forum threads.
5543
     *
5544
     * @param string  $action
5545
     * @param CLpItem $lpItem
5546
     * @param string  $resource
5547
     *
5548
     * @throws Exception
5549
     *
5550
     * @return string HTML form
5551
     */
5552
    public function display_thread_form($action, $lpItem, $resource)
5553
    {
5554
        $form = new FormValidator(
5555
            'thread_form',
5556
            'POST',
5557
            $this->getCurrentBuildingModeURL()
5558
        );
5559
5560
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5561
5562
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5563
5564
        return $form->returnForm();
5565
    }
5566
5567
    /**
5568
     * Return the HTML form to display an item (generally a dir item).
5569
     *
5570
     * @param CLpItem $lpItem
5571
     * @param string  $action
5572
     *
5573
     * @throws Exception
5574
     *
5575
     *
5576
     * @return string HTML form
5577
     */
5578
    public function display_item_form(
5579
        $lpItem,
5580
        $action = 'add_item'
5581
    ) {
5582
        $item_type = $lpItem->getItemType();
5583
5584
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5585
5586
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5587
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5588
5589
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5590
5591
        return $form->returnForm();
5592
    }
5593
5594
    /**
5595
     * Return HTML form to add/edit a student publication (work).
5596
     *
5597
     * @param string              $action
5598
     * @param CStudentPublication $resource
5599
     *
5600
     * @throws Exception
5601
     *
5602
     * @return string HTML form
5603
     */
5604
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
5605
    {
5606
        $form = new FormValidator('frm_student_publication', 'post', '#');
5607
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5608
5609
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5610
5611
        $return = '<div class="sectioncomment">';
5612
        $return .= $form->returnForm();
5613
        $return .= '</div>';
5614
5615
        return $return;
5616
    }
5617
5618
    public function displayNewSectionForm()
5619
    {
5620
        $action = 'add_item';
5621
        $item_type = 'dir';
5622
5623
        $lpItem = (new CLpItem())
5624
            ->setTitle('')
5625
            ->setItemType('dir')
5626
        ;
5627
5628
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5629
5630
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5631
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
5632
5633
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5634
        $form->addElement('hidden', 'type', 'dir');
5635
5636
        return $form->returnForm();
5637
    }
5638
5639
    /**
5640
     * Returns the form to update or create a document.
5641
     *
5642
     * @param string  $action (add/edit)
5643
     * @param CLpItem $lpItem
5644
     *
5645
     *
5646
     * @throws Exception
5647
     *
5648
     * @return string HTML form
5649
     */
5650
    public function displayDocumentForm($action = 'add', $lpItem = null)
5651
    {
5652
        $courseInfo = api_get_course_info();
5653
5654
        $form = new FormValidator(
5655
            'form',
5656
            'POST',
5657
            $this->getCurrentBuildingModeURL(),
5658
            '',
5659
            ['enctype' => 'multipart/form-data']
5660
        );
5661
5662
        $data = $this->generate_lp_folder($courseInfo);
5663
5664
        if (null !== $lpItem) {
5665
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5666
        }
5667
5668
        switch ($action) {
5669
            case 'add':
5670
                $folders = DocumentManager::get_all_document_folders(
5671
                    $courseInfo,
5672
                    0,
5673
                    true
5674
                );
5675
                DocumentManager::build_directory_selector(
5676
                    $folders,
5677
                    '',
5678
                    [],
5679
                    true,
5680
                    $form,
5681
                    'directory_parent_id'
5682
                );
5683
5684
                if ($data) {
5685
                    $defaults['directory_parent_id'] = $data->getIid();
5686
                }
5687
5688
                break;
5689
        }
5690
5691
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5692
5693
        return $form->returnForm();
5694
    }
5695
5696
    /**
5697
     * @param array  $courseInfo
5698
     * @param string $content
5699
     * @param string $title
5700
     * @param int    $parentId
5701
     *
5702
     * @return int
5703
     */
5704
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
5705
    {
5706
        $creatorId = api_get_user_id();
5707
        $sessionId = api_get_session_id();
5708
5709
        // Generates folder
5710
        $result = $this->generate_lp_folder($courseInfo);
5711
        $dir = $result['dir'];
5712
5713
        if (empty($parentId) || '/' === $parentId) {
5714
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
5715
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
5716
5717
            if ('/' === $parentId) {
5718
                $dir = '/';
5719
            }
5720
5721
            // Please, do not modify this dirname formatting.
5722
            if (strstr($dir, '..')) {
5723
                $dir = '/';
5724
            }
5725
5726
            if (!empty($dir[0]) && '.' == $dir[0]) {
5727
                $dir = substr($dir, 1);
5728
            }
5729
            if (!empty($dir[0]) && '/' != $dir[0]) {
5730
                $dir = '/'.$dir;
5731
            }
5732
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
5733
                $dir .= '/';
5734
            }
5735
        } else {
5736
            $parentInfo = DocumentManager::get_document_data_by_id(
5737
                $parentId,
5738
                $courseInfo['code']
5739
            );
5740
            if (!empty($parentInfo)) {
5741
                $dir = $parentInfo['path'].'/';
5742
            }
5743
        }
5744
5745
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5746
5747
        if (!is_dir($filepath)) {
5748
            $dir = '/';
5749
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5750
        }
5751
5752
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5753
5754
        if (!empty($title)) {
5755
            $title = api_replace_dangerous_char(stripslashes($title));
5756
        } else {
5757
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5758
        }
5759
5760
        $title = disable_dangerous_file($title);
5761
        $filename = $title;
5762
        $content = !empty($content) ? $content : $_POST['content_lp'];
5763
        $tmpFileName = $filename;
5764
5765
        $i = 0;
5766
        while (file_exists($filepath.$tmpFileName.'.html')) {
5767
            $tmpFileName = $filename.'_'.++$i;
5768
        }
5769
5770
        $filename = $tmpFileName.'.html';
5771
        $content = stripslashes($content);
5772
5773
        if (file_exists($filepath.$filename)) {
5774
            return 0;
5775
        }
5776
5777
        $putContent = file_put_contents($filepath.$filename, $content);
5778
5779
        if (false === $putContent) {
5780
            return 0;
5781
        }
5782
5783
        $fileSize = filesize($filepath.$filename);
5784
        $saveFilePath = $dir.$filename;
5785
5786
        $document = DocumentManager::addDocument(
5787
            $courseInfo,
5788
            $saveFilePath,
5789
            'file',
5790
            $fileSize,
5791
            $tmpFileName,
5792
            '',
5793
            0, //readonly
5794
            true,
5795
            null,
5796
            $sessionId,
5797
            $creatorId
5798
        );
5799
5800
        $documentId = $document->getIid();
5801
5802
        if (!$document) {
5803
            return 0;
5804
        }
5805
5806
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5807
        $newTitle = $originalTitle;
5808
5809
        if ($newComment || $newTitle) {
5810
            $em = Database::getManager();
5811
5812
            if ($newComment) {
5813
                $document->setComment($newComment);
5814
            }
5815
5816
            if ($newTitle) {
5817
                $document->setTitle($newTitle);
5818
            }
5819
5820
            $em->persist($document);
5821
            $em->flush();
5822
        }
5823
5824
        return $documentId;
5825
    }
5826
5827
    /**
5828
     * Displays the menu for manipulating a step.
5829
     *
5830
     * @return string
5831
     */
5832
    public function displayItemMenu(CLpItem $lpItem)
5833
    {
5834
        $item_id = $lpItem->getIid();
5835
        $audio = $lpItem->getAudio();
5836
        $itemType = $lpItem->getItemType();
5837
        $path = $lpItem->getPath();
5838
5839
        $return = '';
5840
        $audio_player = null;
5841
        // We display an audio player if needed.
5842
        if (!empty($audio)) {
5843
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
5844
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
5845
                .'<audio src="'.$webAudioPath.'" controls>'
5846
                .'</div><br>';*/
5847
        }
5848
5849
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
5850
5851
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
5852
            $return .= Display::url(
5853
                Display::getMdiIcon('pencil', 'ch-tool-icon', null, 22, get_lang('Edit')),
5854
                $url.'&action=edit_item&path_item='.$path
5855
            );
5856
5857
            /*$return .= Display::url(
5858
                Display::getMdiIcon('arrow-right-bold', 'ch-tool-icon', null, 22, get_lang('Move')),
5859
                $url.'&action=move_item'
5860
            );*/
5861
        }
5862
5863
        // Commented for now as prerequisites cannot be added to chapters.
5864
        if ('dir' !== $itemType) {
5865
            $return .= Display::url(
5866
                Display::getMdiIcon('graph', 'ch-tool-icon', null, 22, get_lang('Prerequisites')),
5867
                $url.'&action=edit_item_prereq'
5868
            );
5869
        }
5870
        $return .= Display::url(
5871
            Display::getMdiIcon('delete', 'ch-tool-icon', null, 22, get_lang('Delete')),
5872
            $url.'&action=delete_item'
5873
        );
5874
5875
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
5876
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
5877
            if (empty($documentData)) {
5878
                // Try with iid
5879
                $table = Database::get_course_table(TABLE_DOCUMENT);
5880
                $sql = "SELECT path FROM $table
5881
                        WHERE
5882
                              c_id = ".api_get_course_int_id()." AND
5883
                              iid = ".$path." AND
5884
                              path NOT LIKE '%_DELETED_%'";
5885
                $result = Database::query($sql);
5886
                $documentData = Database::fetch_array($result);
5887
                if ($documentData) {
5888
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
5889
                }
5890
            }
5891
            if (isset($documentData['absolute_path_from_document'])) {
5892
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
5893
            }
5894
        }*/
5895
5896
        if (!empty($audio_player)) {
5897
            $return .= $audio_player;
5898
        }
5899
5900
        return Display::toolbarAction('lp_item', [$return]);
5901
    }
5902
5903
    /**
5904
     * Creates the javascript needed for filling up the checkboxes without page reload.
5905
     *
5906
     * @return string
5907
     */
5908
    public function get_js_dropdown_array()
5909
    {
5910
        $return = 'var child_name = new Array();'."\n";
5911
        $return .= 'var child_value = new Array();'."\n\n";
5912
        $return .= 'child_name[0] = new Array();'."\n";
5913
        $return .= 'child_value[0] = new Array();'."\n\n";
5914
5915
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
5916
        $sql = "SELECT * FROM ".$tbl_lp_item."
5917
                WHERE
5918
                    lp_id = ".$this->lp_id." AND
5919
                    parent_item_id = 0
5920
                ORDER BY display_order ASC";
5921
        Database::query($sql);
5922
        $i = 0;
5923
5924
        $list = $this->getItemsForForm(true);
5925
5926
        foreach ($list as $row_zero) {
5927
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
5928
                if (TOOL_QUIZ == $row_zero['item_type']) {
5929
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
5930
                }
5931
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
5932
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
5933
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
5934
            }
5935
        }
5936
5937
        $return .= "\n";
5938
        $sql = "SELECT * FROM $tbl_lp_item
5939
                WHERE lp_id = ".$this->lp_id;
5940
        $res = Database::query($sql);
5941
        while ($row = Database::fetch_array($res)) {
5942
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
5943
                           WHERE
5944
                                parent_item_id = ".$row['iid']."
5945
                           ORDER BY display_order ASC";
5946
            $res_parent = Database::query($sql_parent);
5947
            $i = 0;
5948
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
5949
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
5950
5951
            while ($row_parent = Database::fetch_array($res_parent)) {
5952
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
5953
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
5954
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
5955
            }
5956
            $return .= "\n";
5957
        }
5958
5959
        $return .= "
5960
            function load_cbo(id) {
5961
                if (!id) {
5962
                    return false;
5963
                }
5964
5965
                var cbo = document.getElementById('previous');
5966
                if (cbo) {
5967
                    for(var i = cbo.length - 1; i > 0; i--) {
5968
                        cbo.options[i] = null;
5969
                    }
5970
                    var k=0;
5971
                    for (var i = 1; i <= child_name[id].length; i++){
5972
                        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
5973
                        option.style.paddingLeft = '40px';
5974
                        cbo.options[i] = option;
5975
                        k = i;
5976
                    }
5977
                    cbo.options[k].selected = true;
5978
                }
5979
5980
                //$('#previous').selectpicker('refresh');
5981
            }";
5982
5983
        return $return;
5984
    }
5985
5986
    /**
5987
     * Display the form to allow moving an item.
5988
     *
5989
     * @param CLpItem $lpItem
5990
     *
5991
     * @throws Exception
5992
     *
5993
     *
5994
     * @return string HTML form
5995
     */
5996
    public function display_move_item($lpItem)
5997
    {
5998
        $return = '';
5999
        $path = $lpItem->getPath();
6000
6001
        if ($lpItem) {
6002
            $itemType = $lpItem->getItemType();
6003
            switch ($itemType) {
6004
                case 'dir':
6005
                case 'asset':
6006
                    $return .= $this->displayItemMenu($lpItem);
6007
                    $return .= $this->display_item_form(
6008
                        $lpItem,
6009
                        get_lang('Move the current section'),
6010
                        'move',
6011
                        $row
6012
                    );
6013
                    break;
6014
                case TOOL_DOCUMENT:
6015
                    $return .= $this->displayItemMenu($lpItem);
6016
                    $return .= $this->displayDocumentForm('move', $lpItem);
6017
                    break;
6018
                case TOOL_LINK:
6019
                    $link = null;
6020
                    if (!empty($path)) {
6021
                        $repo = Container::getLinkRepository();
6022
                        $link = $repo->find($path);
6023
                    }
6024
                    $return .= $this->displayItemMenu($lpItem);
6025
                    $return .= $this->display_link_form('move', $lpItem, $link);
6026
                    break;
6027
                case TOOL_HOTPOTATOES:
6028
                    $return .= $this->displayItemMenu($lpItem);
6029
                    $return .= $this->display_link_form('move', $lpItem, $row);
6030
                    break;
6031
                case TOOL_QUIZ:
6032
                    $return .= $this->displayItemMenu($lpItem);
6033
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6034
                    break;
6035
                case TOOL_STUDENTPUBLICATION:
6036
                    $return .= $this->displayItemMenu($lpItem);
6037
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6038
                    break;
6039
                case TOOL_FORUM:
6040
                    $return .= $this->displayItemMenu($lpItem);
6041
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6042
                    break;
6043
                case TOOL_THREAD:
6044
                    $return .= $this->displayItemMenu($lpItem);
6045
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6046
                    break;
6047
            }
6048
        }
6049
6050
        return $return;
6051
    }
6052
6053
    /**
6054
     * Return HTML form to allow prerequisites selection.
6055
     *
6056
     * @todo use FormValidator
6057
     *
6058
     * @return string HTML form
6059
     */
6060
    public function displayItemPrerequisitesForm(CLpItem $lpItem)
6061
    {
6062
        $courseId = api_get_course_int_id();
6063
        $preRequisiteId = $lpItem->getPrerequisite();
6064
        $itemId = $lpItem->getIid();
6065
6066
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6067
6068
        $return .= '<form method="POST">';
6069
        $return .= '<div class="table-responsive">';
6070
        $return .= '<table class="table table-hover">';
6071
        $return .= '<thead>';
6072
        $return .= '<tr>';
6073
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6074
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6075
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6076
        $return .= '</tr>';
6077
        $return .= '</thead>';
6078
6079
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6080
        $return .= '<tbody>';
6081
        $return .= '<tr>';
6082
        $return .= '<td colspan="3">';
6083
        $return .= '<div class="radio learnpath"><label for="idnone">';
6084
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6085
        $return .= get_lang('none').'</label>';
6086
        $return .= '</div>';
6087
        $return .= '</tr>';
6088
6089
        // @todo use entitites
6090
        $tblLpItem = Database::get_course_table(TABLE_LP_ITEM);
6091
        $sql = "SELECT * FROM $tblLpItem
6092
                WHERE lp_id = ".$this->lp_id;
6093
        $result = Database::query($sql);
6094
6095
        $selectedMinScore = [];
6096
        $selectedMaxScore = [];
6097
        $masteryScore = [];
6098
        while ($row = Database::fetch_array($result)) {
6099
            if ($row['iid'] == $itemId) {
6100
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6101
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6102
            }
6103
            $masteryScore[$row['iid']] = $row['mastery_score'];
6104
        }
6105
6106
        $displayOrder = $lpItem->getDisplayOrder();
6107
        $lpItemRepo = Container::getLpItemRepository();
6108
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
6109
        $em = Database::getManager();
6110
6111
        $currentItemId = $itemId;
6112
        $options = [
6113
            'decorate' => true,
6114
            'rootOpen' => function () {
6115
                return '';
6116
            },
6117
            'rootClose' => function () {
6118
                return '';
6119
            },
6120
            'childOpen' => function () {
6121
                return '';
6122
            },
6123
            'childClose' => '',
6124
            'nodeDecorator' => function ($item) use (
6125
                $currentItemId,
6126
                $preRequisiteId,
6127
                $courseId,
6128
                $selectedMaxScore,
6129
                $selectedMinScore,
6130
                $displayOrder,
6131
                $lpItemRepo,
6132
                $em
6133
            ) {
6134
                $itemId = $item['iid'];
6135
                $type = $item['itemType'];
6136
                $iconName = str_replace(' ', '', $type);
6137
                switch ($iconName) {
6138
                    case 'category':
6139
                    case 'chapter':
6140
                    case 'folder':
6141
                    case 'dir':
6142
                        $icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY);
6143
                        break;
6144
                    default:
6145
                        $icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY);
6146
                        break;
6147
                }
6148
6149
                if ($itemId == $currentItemId) {
6150
                    return '';
6151
                }
6152
6153
                if ($displayOrder < $item['displayOrder']) {
6154
                    return '';
6155
                }
6156
6157
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6158
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6159
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6160
6161
                $return = '<tr>';
6162
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6163
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6164
                $return .= '<label for="id'.$itemId.'">';
6165
6166
                $checked = '';
6167
                if (null !== $preRequisiteId) {
6168
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6169
                }
6170
6171
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6172
6173
                $return .= '<input
6174
                    '.$checked.' '.$disabled.'
6175
                    id="id'.$itemId.'"
6176
                    name="prerequisites"
6177
                    type="radio"
6178
                    value="'.$itemId.'" />';
6179
6180
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6181
                $return .= '</div>';
6182
                $return .= '</td>';
6183
6184
                if (TOOL_QUIZ == $type) {
6185
                    // let's update max_score Tests information depending of the Tests Advanced properties
6186
                    $exercise = new Exercise($courseId);
6187
                    /** @var CLpItem $itemEntity */
6188
                    $itemEntity = $lpItemRepo->find($itemId);
6189
                    $exercise->read($item['path']);
6190
                    $itemEntity->setMaxScore($exercise->getMaxScore());
6191
                    $em->persist($itemEntity);
6192
                    $em->flush($itemEntity);
6193
6194
                    $item['maxScore'] = $exercise->getMaxScore();
6195
6196
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6197
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6198
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6199
                    }
6200
                    $return .= '<td>';
6201
                    $return .= '<input
6202
                        class="form-control"
6203
                        size="4" maxlength="3"
6204
                        name="min_'.$itemId.'"
6205
                        type="number"
6206
                        min="0"
6207
                        step="any"
6208
                        max="'.$item['maxScore'].'"
6209
                        value="'.$selectedMinScoreValue.'"
6210
                    />';
6211
                    $return .= '</td>';
6212
                    $return .= '<td>';
6213
                    $return .= '<input
6214
                        class="form-control"
6215
                        size="4"
6216
                        maxlength="3"
6217
                        name="max_'.$itemId.'"
6218
                        type="number"
6219
                        min="0"
6220
                        step="any"
6221
                        max="'.$item['maxScore'].'"
6222
                        value="'.$selectedMaxScoreValue.'"
6223
                    />';
6224
                        $return .= '</td>';
6225
                    }
6226
6227
                if (TOOL_HOTPOTATOES == $type) {
6228
                    $return .= '<td>';
6229
                    $return .= '<input
6230
                        size="4"
6231
                        maxlength="3"
6232
                        name="min_'.$itemId.'"
6233
                        type="number"
6234
                        min="0"
6235
                        step="any"
6236
                        max="'.$item['maxScore'].'"
6237
                        value="'.$selectedMinScoreValue.'"
6238
                    />';
6239
                        $return .= '</td>';
6240
                        $return .= '<td>';
6241
                        $return .= '<input
6242
                        size="4"
6243
                        maxlength="3"
6244
                        name="max_'.$itemId.'"
6245
                        type="number"
6246
                        min="0"
6247
                        step="any"
6248
                        max="'.$item['maxScore'].'"
6249
                        value="'.$selectedMaxScoreValue.'"
6250
                    />';
6251
                    $return .= '</td>';
6252
                }
6253
                $return .= '</tr>';
6254
6255
                return $return;
6256
            },
6257
        ];
6258
6259
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6260
        $return .= $tree;
6261
        $return .= '</tbody>';
6262
        $return .= '</table>';
6263
        $return .= '</div>';
6264
        $return .= '<div class="form-group">';
6265
        $return .= '<button class="btn btn--primary" name="submit_button" type="submit">'.
6266
            get_lang('Save prerequisites settings').'</button>';
6267
        $return .= '</form>';
6268
6269
        return $return;
6270
    }
6271
6272
    /**
6273
     * Return HTML list to allow prerequisites selection for lp.
6274
     */
6275
    public function display_lp_prerequisites_list(FormValidator $form)
6276
    {
6277
        $lp_id = $this->lp_id;
6278
        $lp = api_get_lp_entity($lp_id);
6279
        $prerequisiteId = $lp->getPrerequisite();
6280
6281
        $repo = Container::getLpRepository();
6282
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6283
        /** @var CLp[] $lps */
6284
        $lps = $qb->getQuery()->getResult();
6285
6286
        //$session_id = api_get_session_id();
6287
        /*$session_condition = api_get_session_condition($session_id, true, true);
6288
        $sql = "SELECT * FROM $tbl_lp
6289
                WHERE c_id = $course_id $session_condition
6290
                ORDER BY display_order ";
6291
        $rs = Database::query($sql);*/
6292
6293
        $items = [get_lang('none')];
6294
        foreach ($lps as $lp) {
6295
            $myLpId = $lp->getIid();
6296
            if ($myLpId == $lp_id) {
6297
                continue;
6298
            }
6299
            $items[$myLpId] = $lp->getTitle();
6300
            /*$return .= '<option
6301
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6302
                $lp->getName().
6303
                '</option>';*/
6304
        }
6305
6306
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6307
        $select->setSelected($prerequisiteId);
6308
    }
6309
6310
    /**
6311
     * Creates a list with all the documents in it.
6312
     *
6313
     * @param bool $showInvisibleFiles
6314
     *
6315
     * @throws Exception
6316
     *
6317
     *
6318
     * @return string
6319
     */
6320
    public function get_documents($showInvisibleFiles = false)
6321
    {
6322
        $sessionId = api_get_session_id();
6323
        $documentTree = DocumentManager::get_document_preview(
6324
            api_get_course_entity(),
6325
            $this->lp_id,
6326
            null,
6327
            $sessionId,
6328
            true,
6329
            null,
6330
            null,
6331
            $showInvisibleFiles,
6332
            true
6333
        );
6334
6335
        $form = new FormValidator(
6336
            'form_upload',
6337
            'POST',
6338
            $this->getCurrentBuildingModeURL(),
6339
            '',
6340
            ['enctype' => 'multipart/form-data']
6341
        );
6342
6343
        $folders = DocumentManager::get_all_document_folders(
6344
            api_get_course_info(),
6345
            0,
6346
            true
6347
        );
6348
6349
        $folder = $this->generate_lp_folder(api_get_course_info());
6350
6351
        DocumentManager::build_directory_selector(
6352
            $folders,
6353
            $folder->getIid(),
6354
            [],
6355
            true,
6356
            $form,
6357
            'directory_parent_id'
6358
        );
6359
6360
        $group = [
6361
            $form->createElement(
6362
                'radio',
6363
                'if_exists',
6364
                get_lang('If file exists:'),
6365
                get_lang('Do nothing'),
6366
                'nothing'
6367
            ),
6368
            $form->createElement(
6369
                'radio',
6370
                'if_exists',
6371
                null,
6372
                get_lang('Overwrite the existing file'),
6373
                'overwrite'
6374
            ),
6375
            $form->createElement(
6376
                'radio',
6377
                'if_exists',
6378
                null,
6379
                get_lang('Rename the uploaded file if it exists'),
6380
                'rename'
6381
            ),
6382
        ];
6383
        $form->addGroup($group, null, get_lang('If file exists:'));
6384
6385
        $fileExistsOption = api_get_setting('document.document_if_file_exists_option');
6386
        $defaultFileExistsOption = 'rename';
6387
        if (!empty($fileExistsOption)) {
6388
            $defaultFileExistsOption = $fileExistsOption;
6389
        }
6390
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6391
6392
        // Check box options
6393
        $form->addCheckBox(
6394
            'unzip',
6395
            get_lang('Options'),
6396
            get_lang('Uncompress zip')
6397
        );
6398
6399
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6400
        $form->addMultipleUpload($url);
6401
6402
        $lpItem = (new CLpItem())
6403
            ->setTitle('')
6404
            ->setItemType(TOOL_DOCUMENT)
6405
        ;
6406
        $new = $this->displayDocumentForm('add', $lpItem);
6407
6408
        /*$lpItem = new CLpItem();
6409
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6410
        $frmReadOutText = $this->displayDocumentForm('add');*/
6411
6412
        $headers = [
6413
            get_lang('Files'),
6414
            get_lang('Create a new document'),
6415
            //get_lang('Create read-out text'),
6416
            get_lang('Upload'),
6417
        ];
6418
6419
        return Display::tabs(
6420
            $headers,
6421
            [$documentTree, $new, $form->returnForm()],
6422
            'subtab',
6423
            ['class' => 'mt-2']
6424
        );
6425
    }
6426
6427
    /**
6428
     * Creates a list with all the exercises (quiz) in it.
6429
     *
6430
     * @return string
6431
     */
6432
    public function get_exercises()
6433
    {
6434
        $course_id = api_get_course_int_id();
6435
        $session_id = api_get_session_id();
6436
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6437
6438
        //$activeCondition = ' active <> -1 ';
6439
        $active = 2;
6440
        if ($setting) {
6441
            $active = 1;
6442
            //$activeCondition = ' active = 1 ';
6443
        }
6444
6445
        $categoryCondition = '';
6446
6447
        $keyword = $_REQUEST['keyword'] ?? null;
6448
        $categoryId = $_REQUEST['category_id'] ?? null;
6449
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6450
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6451
        }
6452
6453
        $keywordCondition = '';
6454
6455
        if (!empty($keyword)) {
6456
            $keyword = Database::escape_string($keyword);
6457
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6458
        }
6459
        */
6460
        $course = api_get_course_entity($course_id);
6461
        $session = api_get_session_entity($session_id);
6462
6463
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6464
        /** @var CQuiz[] $exercises */
6465
        $exercises = $qb->getQuery()->getResult();
6466
6467
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6468
                     WHERE
6469
                            c_id = $course_id AND
6470
                            $activeCondition
6471
                            $condition_session
6472
                            $categoryCondition
6473
                            $keywordCondition
6474
                     ORDER BY title ASC";
6475
        $res_quiz = Database::query($sql_quiz);*/
6476
6477
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6478
6479
        // Create a search-box
6480
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6481
        $form->addHidden('action', 'add_item');
6482
        $form->addHidden('type', 'step');
6483
        $form->addHidden('lp_id', $this->lp_id);
6484
        $form->addHidden('lp_build_selected', '2');
6485
6486
        $form->addCourseHiddenParams();
6487
        $form->addText(
6488
            'keyword',
6489
            get_lang('Search'),
6490
            false,
6491
            [
6492
                'aria-label' => get_lang('Search'),
6493
            ]
6494
        );
6495
6496
        if (api_get_configuration_value('allow_exercise_categories')) {
6497
            $manager = new ExerciseCategoryManager();
6498
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6499
            if (!empty($options)) {
6500
                $form->addSelect(
6501
                    'category_id',
6502
                    get_lang('Category'),
6503
                    $options,
6504
                    ['placeholder' => get_lang('Please select an option')]
6505
                );
6506
            }
6507
        }
6508
6509
        $form->addButtonSearch(get_lang('Search'));
6510
        $return = $form->returnForm();*/
6511
6512
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6513
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6514
        $return .= Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 32, get_lang('New test'));
6515
        $return .= '<a
6516
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6517
            get_lang('New test').'</a>';
6518
        $return .= '</li>';
6519
6520
        $previewIcon = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview'));
6521
        $quizIcon = Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 16, get_lang('Exercise'));
6522
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6523
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6524
        foreach ($exercises as $exercise) {
6525
            $exerciseId = $exercise->getIid();
6526
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6527
            $visibility = $exercise->isVisible($course, $session);
6528
6529
            $link = Display::url(
6530
                $previewIcon,
6531
                $exerciseUrl.'&exerciseId='.$exerciseId,
6532
                ['target' => '_blank']
6533
            );
6534
            $return .= '<li
6535
                class="list-group-item lp_resource_element"
6536
                id="'.$exerciseId.'"
6537
                data-id="'.$exerciseId.'"
6538
                title="'.$title.'">';
6539
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6540
            $return .= $quizIcon;
6541
            $sessionStar = '';
6542
            /*$sessionStar = api_get_session_image(
6543
                $row_quiz['session_id'],
6544
                $userInfo['status']
6545
            );*/
6546
            $return .= Display::url(
6547
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6548
                api_get_self().'?'.
6549
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6550
                [
6551
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6552
                    'data_type' => 'quiz',
6553
                    'data-id' => $exerciseId,
6554
                ]
6555
            );
6556
            $return .= '</li>';
6557
        }
6558
6559
        $return .= '</ul>';
6560
6561
        return $return;
6562
    }
6563
6564
    /**
6565
     * Creates a list with all the links in it.
6566
     *
6567
     * @return string
6568
     */
6569
    public function get_links()
6570
    {
6571
        $sessionId = api_get_session_id();
6572
        $repo = Container::getLinkRepository();
6573
6574
        $course = api_get_course_entity();
6575
        $session = api_get_session_entity($sessionId);
6576
        $qb = $repo->getResourcesByCourse($course, $session);
6577
        /** @var CLink[] $links */
6578
        $links = $qb->getQuery()->getResult();
6579
6580
        $selfUrl = api_get_self();
6581
        $courseIdReq = api_get_cidreq();
6582
        $userInfo = api_get_user_info();
6583
6584
        $moveEverywhereIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6585
6586
        $categorizedLinks = [];
6587
        $categories = [];
6588
6589
        foreach ($links as $link) {
6590
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6591
            if (empty($categoryId)) {
6592
                $categories[0] = get_lang('Uncategorized');
6593
            } else {
6594
                $category = $link->getCategory();
6595
                $categories[$categoryId] = $category->getTitle();
6596
            }
6597
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6598
        }
6599
6600
        $linksHtmlCode =
6601
            '<script>
6602
            function toggle_tool(tool, id) {
6603
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6604
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6605
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6606
                } else {
6607
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6608
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6609
                }
6610
            }
6611
        </script>
6612
6613
        <ul class="mt-2 bg-white list-group lp_resource">
6614
            <li class="list-group-item lp_resource_element disable_drag ">
6615
                '.Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL).'
6616
                <a
6617
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6618
                title="'.get_lang('Add a link').'">'.
6619
                get_lang('Add a link').'
6620
                </a>
6621
            </li>';
6622
        $linkIcon = Display::getMdiIcon('file-link', 'ch-tool-icon', null, 16, get_lang('Link'));
6623
        foreach ($categorizedLinks as $categoryId => $links) {
6624
            $linkNodes = null;
6625
            /** @var CLink $link */
6626
            foreach ($links as $key => $link) {
6627
                $title = $link->getTitle();
6628
                $id = $link->getIid();
6629
                $linkUrl = Display::url(
6630
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6631
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6632
                    ['target' => '_blank']
6633
                );
6634
6635
                if ($link->isVisible($course, $session)) {
6636
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6637
                    $sessionStar = '';
6638
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6639
                    $link = Display::url(
6640
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6641
                        $url,
6642
                        [
6643
                            'class' => 'moved link_with_id',
6644
                            'data-id' => $key,
6645
                            'data_type' => TOOL_LINK,
6646
                            'title' => $title,
6647
                        ]
6648
                    );
6649
                    $linkNodes .=
6650
                        "<li
6651
                            class='list-group-item lp_resource_element'
6652
                            id= $id
6653
                            data-id= $id
6654
                            >
6655
                         <a class='moved' href='#'>
6656
                            $moveEverywhereIcon
6657
                        </a>
6658
                        $linkIcon $link
6659
                        </li>";
6660
                }
6661
            }
6662
            $linksHtmlCode .=
6663
                '<li class="list-group-item disable_drag">
6664
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6665
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6666
                        align="absbottom" />
6667
                    </a>
6668
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6669
                </li>
6670
            '.
6671
                $linkNodes.
6672
            '';
6673
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6674
        }
6675
        $linksHtmlCode .= '</ul>';
6676
6677
        return $linksHtmlCode;
6678
    }
6679
6680
    /**
6681
     * Creates a list with all the student publications in it.
6682
     *
6683
     * @return string
6684
     */
6685
    public function get_student_publications()
6686
    {
6687
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6688
        $return .= '<li class="list-group-item lp_resource_element">';
6689
        $works = getWorkListTeacher(0, 100, null, null, null);
6690
        if (!empty($works)) {
6691
            $icon = Display::getMdiIcon('inbox-full', 'ch-tool-icon',null, 16, get_lang('Student publication'));
6692
            foreach ($works as $work) {
6693
                $workId = $work['iid'];
6694
                $link = Display::url(
6695
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6696
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6697
                    ['target' => '_blank']
6698
                );
6699
6700
                $return .= '<li
6701
                    class="list-group-item lp_resource_element"
6702
                    id="'.$workId.'"
6703
                    data-id="'.$workId.'"
6704
                    >';
6705
                $return .= '<a class="moved" href="#">';
6706
                $return .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6707
                $return .= '</a> ';
6708
6709
                $return .= $icon;
6710
                $return .= Display::url(
6711
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6712
                    api_get_self().'?'.
6713
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6714
                    [
6715
                        'class' => 'moved link_with_id',
6716
                        'data-id' => $work['iid'],
6717
                        'data_type' => TOOL_STUDENTPUBLICATION,
6718
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6719
                    ]
6720
                );
6721
                $return .= '</li>';
6722
            }
6723
        }
6724
6725
        $return .= '</ul>';
6726
6727
        return $return;
6728
    }
6729
6730
    /**
6731
     * Creates a list with all the forums in it.
6732
     *
6733
     * @return string
6734
     */
6735
    public function get_forums()
6736
    {
6737
        $forumCategories = get_forum_categories();
6738
        $forumsInNoCategory = get_forums_in_category(0);
6739
        if (!empty($forumsInNoCategory)) {
6740
            $forumCategories = array_merge(
6741
                $forumCategories,
6742
                [
6743
                    [
6744
                        'cat_id' => 0,
6745
                        'session_id' => 0,
6746
                        'visibility' => 1,
6747
                        'cat_comment' => null,
6748
                    ],
6749
                ]
6750
            );
6751
        }
6752
6753
        $a_forums = [];
6754
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6755
        $sessionEntity = api_get_session_entity(api_get_session_id());
6756
6757
        foreach ($forumCategories as $forumCategory) {
6758
            // The forums in this category.
6759
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6760
            if (!empty($forumsInCategory)) {
6761
                foreach ($forumsInCategory as $forum) {
6762
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6763
                        $a_forums[] = $forum;
6764
                    }
6765
                }
6766
            }
6767
        }
6768
6769
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6770
6771
        // First add link
6772
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6773
        $return .= Display::getMdiIcon('comment-quote	', 'ch-tool-icon', null, 32, get_lang('Create a new forum'));
6774
        $return .= Display::url(
6775
            get_lang('Create a new forum'),
6776
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6777
                'action' => 'add',
6778
                'content' => 'forum',
6779
                'lp_id' => $this->lp_id,
6780
            ]),
6781
            ['title' => get_lang('Create a new forum')]
6782
        );
6783
        $return .= '</li>';
6784
6785
        $return .= '<script>
6786
            function toggle_forum(forum_id) {
6787
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6788
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6789
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6790
                } else {
6791
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6792
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6793
                }
6794
            }
6795
        </script>';
6796
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6797
        $userRights = api_is_allowed_to_edit(false, true);
6798
        foreach ($a_forums as $forum) {
6799
            $forumSession = $forum->getFirstResourceLink()->getSession();
6800
            $isForumSession = (null !== $forumSession);
6801
            $forumId = $forum->getIid();
6802
            $title = Security::remove_XSS($forum->getTitle());
6803
            $link = Display::url(
6804
                Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6805
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6806
                ['target' => '_blank']
6807
            );
6808
6809
            $return .= '<li
6810
                    class="list-group-item lp_resource_element"
6811
                    id="'.$forumId.'"
6812
                    data-id="'.$forumId.'"
6813
                    >';
6814
            $return .= '<a class="moved" href="#">';
6815
            $return .= $moveIcon;
6816
            $return .= ' </a>';
6817
            $return .= Display::getMdiIcon('comment-quote', 'ch-tool-icon', null, 16, get_lang('Forum'));
6818
6819
            $moveLink = Display::url(
6820
                $title,
6821
                api_get_self().'?'.
6822
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6823
                [
6824
                    'class' => 'moved link_with_id',
6825
                    'data-id' => $forumId,
6826
                    'data_type' => TOOL_FORUM,
6827
                    'title' => $title,
6828
                    'style' => 'vertical-align:middle',
6829
                ]
6830
            );
6831
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
6832
                    <img
6833
                        src="'.Display::returnIconPath('add.png').'"
6834
                        id="forum_'.$forumId.'_opener" align="absbottom"
6835
                     />
6836
                </a>
6837
                '.$moveLink;
6838
            $return .= '</li>';
6839
6840
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
6841
            $threads = get_threads($forumId);
6842
            if (is_array($threads)) {
6843
                foreach ($threads as $thread) {
6844
                    $threadId = $thread->getIid();
6845
                    $link = Display::url(
6846
                        Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6847
                        api_get_path(WEB_CODE_PATH).
6848
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
6849
                        ['target' => '_blank']
6850
                    );
6851
6852
                    $return .= '<li
6853
                        class="list-group-item lp_resource_element"
6854
                      id="'.$threadId.'"
6855
                        data-id="'.$threadId.'"
6856
                    >';
6857
                    $return .= '&nbsp;<a class="moved" href="#">';
6858
                    $return .= $moveIcon;
6859
                    $return .= ' </a>';
6860
                    $return .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, 16, get_lang('Thread'));
6861
                    $return .= '<a
6862
                        class="moved link_with_id"
6863
                        data-id="'.$threadId.'"
6864
                        data_type="'.TOOL_THREAD.'"
6865
                        title="'.$thread->getTitle().'"
6866
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
6867
                        >'.
6868
                        Security::remove_XSS($thread->getTitle()).' '.$link.'</a>';
6869
                    $return .= '</li>';
6870
                }
6871
            }
6872
            $return .= '</div>';
6873
        }
6874
        $return .= '</ul>';
6875
6876
        return $return;
6877
    }
6878
6879
    /**
6880
     * Temp function to be moved in main_api or the best place around for this.
6881
     * Creates a file path if it doesn't exist.
6882
     *
6883
     * @param string $path
6884
     */
6885
    public function create_path($path)
6886
    {
6887
        $path_bits = explode('/', dirname($path));
6888
6889
        // IS_WINDOWS_OS has been defined in main_api.lib.php
6890
        $path_built = IS_WINDOWS_OS ? '' : '/';
6891
        foreach ($path_bits as $bit) {
6892
            if (!empty($bit)) {
6893
                $new_path = $path_built.$bit;
6894
                if (is_dir($new_path)) {
6895
                    $path_built = $new_path.'/';
6896
                } else {
6897
                    mkdir($new_path, api_get_permissions_for_new_directories());
6898
                    $path_built = $new_path.'/';
6899
                }
6900
            }
6901
        }
6902
    }
6903
6904
    /**
6905
     * @param int    $lp_id
6906
     * @param string $status
6907
     */
6908
    public function set_autolaunch($lp_id, $status)
6909
    {
6910
        $course_id = api_get_course_int_id();
6911
        $lp_id = (int) $lp_id;
6912
        $status = (int) $status;
6913
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6914
6915
        // Setting everything to autolaunch = 0
6916
        $attributes['autolaunch'] = 0;
6917
        $where = [
6918
            'session_id = ? AND c_id = ? ' => [
6919
                api_get_session_id(),
6920
                $course_id,
6921
            ],
6922
        ];
6923
        Database::update($lp_table, $attributes, $where);
6924
        if (1 == $status) {
6925
            //Setting my lp_id to autolaunch = 1
6926
            $attributes['autolaunch'] = 1;
6927
            $where = [
6928
                'iid = ? AND session_id = ? AND c_id = ?' => [
6929
                    $lp_id,
6930
                    api_get_session_id(),
6931
                    $course_id,
6932
                ],
6933
            ];
6934
            Database::update($lp_table, $attributes, $where);
6935
        }
6936
    }
6937
6938
    /**
6939
     * Gets previous_item_id for the next element of the lp_item table.
6940
     *
6941
     * @author Isaac flores paz
6942
     *
6943
     * @return int Previous item ID
6944
     */
6945
    public function select_previous_item_id()
6946
    {
6947
        $course_id = api_get_course_int_id();
6948
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6949
6950
        // Get the max order of the items
6951
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
6952
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6953
        $rs_max_order = Database::query($sql);
6954
        $row_max_order = Database::fetch_object($rs_max_order);
6955
        $max_order = $row_max_order->display_order;
6956
        // Get the previous item ID
6957
        $sql = "SELECT iid as previous FROM $table_lp_item
6958
                WHERE
6959
                    c_id = $course_id AND
6960
                    lp_id = ".$this->lp_id." AND
6961
                    display_order = '$max_order' ";
6962
        $rs_max = Database::query($sql);
6963
        $row_max = Database::fetch_object($rs_max);
6964
6965
        // Return the previous item ID
6966
        return $row_max->previous;
6967
    }
6968
6969
    /**
6970
     * Copies an LP.
6971
     */
6972
    public function copy()
6973
    {
6974
        // Course builder
6975
        $cb = new CourseBuilder();
6976
6977
        //Setting tools that will be copied
6978
        $cb->set_tools_to_build(['learnpaths']);
6979
6980
        //Setting elements that will be copied
6981
        $cb->set_tools_specific_id_list(
6982
            ['learnpaths' => [$this->lp_id]]
6983
        );
6984
6985
        $course = $cb->build();
6986
6987
        //Course restorer
6988
        $course_restorer = new CourseRestorer($course);
6989
        $course_restorer->set_add_text_in_items(true);
6990
        $course_restorer->set_tool_copy_settings(
6991
            ['learnpaths' => ['reset_dates' => true]]
6992
        );
6993
        $course_restorer->restore(
6994
            api_get_course_id(),
6995
            api_get_session_id(),
6996
            false,
6997
            false
6998
        );
6999
    }
7000
7001
    /**
7002
     * Verify document size.
7003
     *
7004
     * @param string $s
7005
     *
7006
     * @return bool
7007
     */
7008
    public static function verify_document_size($s)
7009
    {
7010
        $post_max = ini_get('post_max_size');
7011
        if ('M' == substr($post_max, -1, 1)) {
7012
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7013
        } elseif ('G' == substr($post_max, -1, 1)) {
7014
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7015
        }
7016
        $upl_max = ini_get('upload_max_filesize');
7017
        if ('M' == substr($upl_max, -1, 1)) {
7018
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7019
        } elseif ('G' == substr($upl_max, -1, 1)) {
7020
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7021
        }
7022
7023
        $repo = Container::getDocumentRepository();
7024
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7025
7026
        $course_max_space = DocumentManager::get_course_quota();
7027
        $total_size = filesize($s) + $documents_total_space;
7028
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7029
            return true;
7030
        }
7031
7032
        return false;
7033
    }
7034
7035
    /**
7036
     * Clear LP prerequisites.
7037
     */
7038
    public function clearPrerequisites()
7039
    {
7040
        $course_id = $this->get_course_int_id();
7041
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7042
        $lp_id = $this->get_id();
7043
        // Cleaning prerequisites
7044
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7045
                WHERE lp_id = $lp_id";
7046
        Database::query($sql);
7047
7048
        // Cleaning mastery score for exercises
7049
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7050
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7051
        Database::query($sql);
7052
    }
7053
7054
    public function set_previous_step_as_prerequisite_for_all_items()
7055
    {
7056
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7057
        $course_id = $this->get_course_int_id();
7058
        $lp_id = $this->get_id();
7059
7060
        if (!empty($this->items)) {
7061
            $previous_item_id = null;
7062
            $previous_item_max = 0;
7063
            $previous_item_type = null;
7064
            $last_item_not_dir = null;
7065
            $last_item_not_dir_type = null;
7066
            $last_item_not_dir_max = null;
7067
7068
            foreach ($this->ordered_items as $itemId) {
7069
                $item = $this->getItem($itemId);
7070
                // if there was a previous item... (otherwise jump to set it)
7071
                if (!empty($previous_item_id)) {
7072
                    $current_item_id = $item->get_id(); //save current id
7073
                    if ('dir' != $item->get_type()) {
7074
                        // Current item is not a folder, so it qualifies to get a prerequisites
7075
                        if ('quiz' == $last_item_not_dir_type) {
7076
                            // if previous is quiz, mark its max score as default score to be achieved
7077
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7078
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7079
                            Database::query($sql);
7080
                        }
7081
                        // now simply update the prerequisite to set it to the last non-chapter item
7082
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7083
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7084
                        Database::query($sql);
7085
                        // record item as 'non-chapter' reference
7086
                        $last_item_not_dir = $item->get_id();
7087
                        $last_item_not_dir_type = $item->get_type();
7088
                        $last_item_not_dir_max = $item->get_max();
7089
                    }
7090
                } else {
7091
                    if ('dir' != $item->get_type()) {
7092
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7093
                        $last_item_not_dir = $item->get_id();
7094
                        $last_item_not_dir_type = $item->get_type();
7095
                        $last_item_not_dir_max = $item->get_max();
7096
                    }
7097
                }
7098
                // Saving the item as "previous item" for the next loop
7099
                $previous_item_id = $item->get_id();
7100
                $previous_item_max = $item->get_max();
7101
                $previous_item_type = $item->get_type();
7102
            }
7103
        }
7104
    }
7105
7106
    /**
7107
     * @param array $params
7108
     *
7109
     * @return int
7110
     */
7111
    public static function createCategory($params)
7112
    {
7113
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7114
7115
        $item = new CLpCategory();
7116
        $item
7117
            ->setTitle($params['name'])
7118
            ->setParent($courseEntity)
7119
            ->addCourseLink($courseEntity, api_get_session_entity())
7120
        ;
7121
7122
        $repo = Container::getLpCategoryRepository();
7123
        $repo->create($item);
7124
7125
        return $item->getIid();
7126
    }
7127
7128
    /**
7129
     * @param array $params
7130
     */
7131
    public static function updateCategory($params)
7132
    {
7133
        $em = Database::getManager();
7134
        /** @var CLpCategory $item */
7135
        $item = $em->find(CLpCategory::class, $params['id']);
7136
        if ($item) {
7137
            $item->setTitle($params['name']);
7138
            $em->persist($item);
7139
            $em->flush();
7140
        }
7141
    }
7142
7143
    public static function moveUpCategory(int $id): void
7144
    {
7145
        $em = Database::getManager();
7146
        /** @var CLpCategory $item */
7147
        $item = $em->find(CLpCategory::class, $id);
7148
        if ($item) {
7149
            $course = api_get_course_entity();
7150
            $session = api_get_session_entity();
7151
7152
            $link = $item->resourceNode->getResourceLinkByContext($course, $session);
7153
7154
            if ($link) {
7155
                $link->moveUpPosition();
7156
7157
                $em->flush();
7158
            }
7159
        }
7160
    }
7161
7162
    public static function moveDownCategory(int $id): void
7163
    {
7164
        $em = Database::getManager();
7165
        /** @var CLpCategory $item */
7166
        $item = $em->find(CLpCategory::class, $id);
7167
        if ($item) {
7168
            $course = api_get_course_entity();
7169
            $session = api_get_session_entity();
7170
7171
            $link = $item->resourceNode->getResourceLinkByContext($course, $session);
7172
7173
            if ($link) {
7174
                $link->moveDownPosition();
7175
7176
                $em->flush();
7177
            }
7178
        }
7179
    }
7180
7181
    /**
7182
     * @param int $courseId
7183
     *
7184
     * @return int
7185
     */
7186
    public static function getCountCategories($courseId)
7187
    {
7188
        if (empty($courseId)) {
7189
            return 0;
7190
        }
7191
        $repo = Container::getLpCategoryRepository();
7192
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7193
        $qb->addSelect('count(resource)');
7194
7195
        return (int) $qb->getQuery()->getSingleScalarResult();
7196
    }
7197
7198
    /**
7199
     * @param int $courseId
7200
     *
7201
     * @return CLpCategory[]
7202
     */
7203
    public static function getCategories($courseId)
7204
    {
7205
        // Using doctrine extensions
7206
        $repo = Container::getLpCategoryRepository();
7207
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity(), null, null, true, true);
7208
7209
        return $qb->getQuery()->getResult();
7210
    }
7211
7212
    public static function getCategorySessionId($id)
7213
    {
7214
        if ('true' !== api_get_setting('lp.allow_session_lp_category')) {
7215
            return 0;
7216
        }
7217
7218
        $repo = Container::getLpCategoryRepository();
7219
        /** @var CLpCategory $category */
7220
        $category = $repo->find($id);
7221
7222
        $sessionId = 0;
7223
        $link = $category->getFirstResourceLink();
7224
        if ($link && $link->getSession()) {
7225
            $sessionId = (int) $link->getSession()->getId();
7226
        }
7227
7228
        return $sessionId;
7229
    }
7230
7231
    public static function deleteCategory(int $id): bool
7232
    {
7233
        $repo = Container::getLpCategoryRepository();
7234
        /** @var CLpCategory $category */
7235
        $category = $repo->find($id);
7236
        if ($category) {
7237
            $em = Database::getManager();
7238
            $lps = $category->getLps();
7239
7240
            foreach ($lps as $lp) {
7241
                $lp->setCategory(null);
7242
                $em->persist($lp);
7243
            }
7244
7245
            $course = api_get_course_entity();
7246
            $session = api_get_session_entity();
7247
7248
            $em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session);
7249
7250
            return true;
7251
        }
7252
7253
        return false;
7254
    }
7255
7256
    /**
7257
     * @param int  $courseId
7258
     * @param bool $addSelectOption
7259
     *
7260
     * @return array
7261
     */
7262
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7263
    {
7264
        $repo = Container::getLpCategoryRepository();
7265
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity());
7266
        $items = $qb->getQuery()->getResult();
7267
7268
        $cats = [];
7269
        if ($addSelectOption) {
7270
            $cats = [get_lang('Select a category')];
7271
        }
7272
7273
        if (!empty($items)) {
7274
            foreach ($items as $cat) {
7275
                $cats[$cat->getIid()] = $cat->getTitle();
7276
            }
7277
        }
7278
7279
        return $cats;
7280
    }
7281
7282
    /**
7283
     * @param int   $courseId
7284
     * @param int   $lpId
7285
     * @param int   $user_id
7286
     *
7287
     * @return learnpath
7288
     */
7289
    public static function getLpFromSession(int $courseId, int $lpId, int $user_id)
7290
    {
7291
        $debug = 0;
7292
        $learnPath = null;
7293
        $lpObject = Session::read('lpobject');
7294
7295
        $repo = Container::getLpRepository();
7296
        $lp = $repo->find($lpId);
7297
        if (null !== $lpObject) {
7298
            /** @var learnpath $learnPath */
7299
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7300
            $learnPath->entity = $lp;
7301
            if ($debug) {
7302
                error_log('getLpFromSession: unserialize');
7303
                error_log('------getLpFromSession------');
7304
                error_log('------unserialize------');
7305
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7306
                error_log("api_get_sessionid: ".api_get_session_id());
7307
            }
7308
        }
7309
7310
        if (!is_object($learnPath)) {
7311
            $learnPath = new learnpath($lp, api_get_course_info_by_id($courseId), $user_id);
7312
            if ($debug) {
7313
                error_log('------getLpFromSession------');
7314
                error_log('getLpFromSession: create new learnpath');
7315
                error_log("create new LP with $courseId - $lpId - $user_id");
7316
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7317
                error_log("api_get_sessionid: ".api_get_session_id());
7318
            }
7319
        }
7320
7321
        return $learnPath;
7322
    }
7323
7324
    /**
7325
     * @param int $itemId
7326
     *
7327
     * @return learnpathItem|false
7328
     */
7329
    public function getItem($itemId)
7330
    {
7331
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7332
            return $this->items[$itemId];
7333
        }
7334
7335
        return false;
7336
    }
7337
7338
    /**
7339
     * @return int
7340
     */
7341
    public function getCurrentAttempt()
7342
    {
7343
        $attempt = $this->getItem($this->get_current_item_id());
7344
        if ($attempt) {
7345
            return $attempt->get_attempt_id();
7346
        }
7347
7348
        return 0;
7349
    }
7350
7351
    /**
7352
     * @return int
7353
     */
7354
    public function getCategoryId()
7355
    {
7356
        return (int) $this->categoryId;
7357
    }
7358
7359
    /**
7360
     * Get whether this is a learning path with the possibility to subscribe
7361
     * users or not.
7362
     *
7363
     * @return int
7364
     */
7365
    public function getSubscribeUsers()
7366
    {
7367
        return $this->subscribeUsers;
7368
    }
7369
7370
    /**
7371
     * Calculate the count of stars for a user in this LP
7372
     * This calculation is based on the following rules:
7373
     * - the student gets one star when he gets to 50% of the learning path
7374
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7375
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7376
     * - the student gets the final star when the score for the *last* test is >= 80%.
7377
     *
7378
     * @param int $sessionId Optional. The session ID
7379
     *
7380
     * @return int The count of stars
7381
     */
7382
    public function getCalculateStars($sessionId = 0)
7383
    {
7384
        $stars = 0;
7385
        $progress = self::getProgress(
7386
            $this->lp_id,
7387
            $this->user_id,
7388
            $this->course_int_id,
7389
            $sessionId
7390
        );
7391
7392
        if ($progress >= 50) {
7393
            $stars++;
7394
        }
7395
7396
        // Calculate stars chapters evaluation
7397
        $exercisesItems = $this->getExercisesItems();
7398
7399
        if (!empty($exercisesItems)) {
7400
            $totalResult = 0;
7401
7402
            foreach ($exercisesItems as $exerciseItem) {
7403
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7404
                    $this->user_id,
7405
                    $exerciseItem->path,
7406
                    $this->course_int_id,
7407
                    $sessionId,
7408
                    $this->lp_id,
7409
                    $exerciseItem->db_id
7410
                );
7411
7412
                $exerciseResultInfo = end($exerciseResultInfo);
7413
7414
                if (!$exerciseResultInfo) {
7415
                    continue;
7416
                }
7417
7418
                if (!empty($exerciseResultInfo['max_score'])) {
7419
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7420
                } else {
7421
                    $exerciseResult = 0;
7422
                }
7423
                $totalResult += $exerciseResult;
7424
            }
7425
7426
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7427
7428
            if ($totalExerciseAverage >= 50) {
7429
                $stars++;
7430
            }
7431
7432
            if ($totalExerciseAverage >= 80) {
7433
                $stars++;
7434
            }
7435
        }
7436
7437
        // Calculate star for final evaluation
7438
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7439
7440
        if (!empty($finalEvaluationItem)) {
7441
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7442
                $this->user_id,
7443
                $finalEvaluationItem->path,
7444
                $this->course_int_id,
7445
                $sessionId,
7446
                $this->lp_id,
7447
                $finalEvaluationItem->db_id
7448
            );
7449
7450
            $evaluationResultInfo = end($evaluationResultInfo);
7451
7452
            if ($evaluationResultInfo) {
7453
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7454
                if ($evaluationResult >= 80) {
7455
                    $stars++;
7456
                }
7457
            }
7458
        }
7459
7460
        return $stars;
7461
    }
7462
7463
    /**
7464
     * Get the items of exercise type.
7465
     *
7466
     * @return array The items. Otherwise return false
7467
     */
7468
    public function getExercisesItems()
7469
    {
7470
        $exercises = [];
7471
        foreach ($this->items as $item) {
7472
            if ('quiz' !== $item->type) {
7473
                continue;
7474
            }
7475
            $exercises[] = $item;
7476
        }
7477
7478
        array_pop($exercises);
7479
7480
        return $exercises;
7481
    }
7482
7483
    /**
7484
     * Get the item of exercise type (evaluation type).
7485
     *
7486
     * @return array The final evaluation. Otherwise return false
7487
     */
7488
    public function getFinalEvaluationItem()
7489
    {
7490
        $exercises = [];
7491
        foreach ($this->items as $item) {
7492
            if (TOOL_QUIZ !== $item->type) {
7493
                continue;
7494
            }
7495
7496
            $exercises[] = $item;
7497
        }
7498
7499
        return array_pop($exercises);
7500
    }
7501
7502
    /**
7503
     * Calculate the total points achieved for the current user in this learning path.
7504
     *
7505
     * @param int $sessionId Optional. The session Id
7506
     *
7507
     * @return int
7508
     */
7509
    public function getCalculateScore($sessionId = 0)
7510
    {
7511
        // Calculate stars chapters evaluation
7512
        $exercisesItems = $this->getExercisesItems();
7513
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7514
        $totalExercisesResult = 0;
7515
        $totalEvaluationResult = 0;
7516
7517
        if (false !== $exercisesItems) {
7518
            foreach ($exercisesItems as $exerciseItem) {
7519
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7520
                    $this->user_id,
7521
                    $exerciseItem->path,
7522
                    $this->course_int_id,
7523
                    $sessionId,
7524
                    $this->lp_id,
7525
                    $exerciseItem->db_id
7526
                );
7527
7528
                $exerciseResultInfo = end($exerciseResultInfo);
7529
7530
                if (!$exerciseResultInfo) {
7531
                    continue;
7532
                }
7533
7534
                $totalExercisesResult += $exerciseResultInfo['score'];
7535
            }
7536
        }
7537
7538
        if (!empty($finalEvaluationItem)) {
7539
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7540
                $this->user_id,
7541
                $finalEvaluationItem->path,
7542
                $this->course_int_id,
7543
                $sessionId,
7544
                $this->lp_id,
7545
                $finalEvaluationItem->db_id
7546
            );
7547
7548
            $evaluationResultInfo = end($evaluationResultInfo);
7549
7550
            if ($evaluationResultInfo) {
7551
                $totalEvaluationResult += $evaluationResultInfo['score'];
7552
            }
7553
        }
7554
7555
        return $totalExercisesResult + $totalEvaluationResult;
7556
    }
7557
7558
    /**
7559
     * Check if URL is not allowed to be show in a iframe.
7560
     *
7561
     * @param string $src
7562
     *
7563
     * @return string
7564
     */
7565
    public function fixBlockedLinks($src)
7566
    {
7567
        $urlInfo = parse_url($src);
7568
7569
        $platformProtocol = 'https';
7570
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7571
            $platformProtocol = 'http';
7572
        }
7573
7574
        $protocolFixApplied = false;
7575
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7576
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7577
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7578
7579
        if ($platformProtocol != $scheme) {
7580
            Session::write('x_frame_source', $src);
7581
            $src = 'blank.php?error=x_frames_options';
7582
            $protocolFixApplied = true;
7583
        }
7584
7585
        if (false == $protocolFixApplied) {
7586
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7587
                // Check X-Frame-Options
7588
                $ch = curl_init();
7589
                $options = [
7590
                    CURLOPT_URL => $src,
7591
                    CURLOPT_RETURNTRANSFER => true,
7592
                    CURLOPT_HEADER => true,
7593
                    CURLOPT_FOLLOWLOCATION => true,
7594
                    CURLOPT_ENCODING => "",
7595
                    CURLOPT_AUTOREFERER => true,
7596
                    CURLOPT_CONNECTTIMEOUT => 120,
7597
                    CURLOPT_TIMEOUT => 120,
7598
                    CURLOPT_MAXREDIRS => 10,
7599
                ];
7600
7601
                $proxySettings = api_get_setting('platform.proxy_settings', true);
7602
                if (!empty($proxySettings) &&
7603
                    isset($proxySettings['curl_setopt_array'])
7604
                ) {
7605
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7606
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7607
                }
7608
7609
                curl_setopt_array($ch, $options);
7610
                $response = curl_exec($ch);
7611
                $httpCode = curl_getinfo($ch);
7612
                $headers = substr($response, 0, $httpCode['header_size']);
7613
7614
                $error = false;
7615
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7616
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7617
                ) {
7618
                    $error = true;
7619
                }
7620
7621
                if ($error) {
7622
                    Session::write('x_frame_source', $src);
7623
                    $src = 'blank.php?error=x_frames_options';
7624
                }
7625
            }
7626
        }
7627
7628
        return $src;
7629
    }
7630
7631
    /**
7632
     * Check if this LP has a created forum in the basis course.
7633
     *
7634
     * @deprecated
7635
     *
7636
     * @return bool
7637
     */
7638
    public function lpHasForum()
7639
    {
7640
        $forumTable = Database::get_course_table(TABLE_FORUM);
7641
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7642
7643
        $fakeFrom = "
7644
            $forumTable f
7645
            INNER JOIN $itemProperty ip
7646
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7647
        ";
7648
7649
        $resultData = Database::select(
7650
            'COUNT(f.iid) AS qty',
7651
            $fakeFrom,
7652
            [
7653
                'where' => [
7654
                    'ip.visibility != ? AND ' => 2,
7655
                    'ip.tool = ? AND ' => TOOL_FORUM,
7656
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7657
                    'f.lp_id = ?' => intval($this->lp_id),
7658
                ],
7659
            ],
7660
            'first'
7661
        );
7662
7663
        return $resultData['qty'] > 0;
7664
    }
7665
7666
    /**
7667
     * Get the forum for this learning path.
7668
     *
7669
     * @param int $sessionId
7670
     *
7671
     * @return array
7672
     */
7673
    public function getForum($sessionId = 0)
7674
    {
7675
        $repo = Container::getForumRepository();
7676
7677
        $course = api_get_course_entity();
7678
        $session = api_get_session_entity($sessionId);
7679
        $qb = $repo->getResourcesByCourse($course, $session);
7680
7681
        return $qb->getQuery()->getResult();
7682
    }
7683
7684
    /**
7685
     * Get the LP Final Item form.
7686
     *
7687
     * @throws Exception
7688
     *
7689
     *
7690
     * @return string
7691
     */
7692
    public function getFinalItemForm()
7693
    {
7694
        $finalItem = $this->getFinalItem();
7695
        $title = '';
7696
7697
        if ($finalItem) {
7698
            $title = $finalItem->get_title();
7699
            $buttonText = get_lang('Save');
7700
            $content = $this->getSavedFinalItem();
7701
        } else {
7702
            $buttonText = get_lang('Add this document to the course');
7703
            $content = $this->getFinalItemTemplate();
7704
        }
7705
7706
        $editorConfig = [
7707
            'ToolbarSet' => 'LearningPathDocuments',
7708
            'Width' => '100%',
7709
            'Height' => '500',
7710
            'FullPage' => true,
7711
        ];
7712
7713
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7714
            'type' => 'document',
7715
            'lp_id' => $this->lp_id,
7716
        ]);
7717
7718
        $form = new FormValidator('final_item', 'POST', $url);
7719
        $form->addText('title', get_lang('Title'));
7720
        $form->addButtonSave($buttonText);
7721
        $form->addHtml(
7722
            Display::return_message(
7723
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7724
                'normal',
7725
                false
7726
            )
7727
        );
7728
7729
        $renderer = $form->defaultRenderer();
7730
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7731
7732
        $form->addHtmlEditor(
7733
            'content_lp_certificate',
7734
            null,
7735
            true,
7736
            false,
7737
            $editorConfig
7738
        );
7739
        $form->addHidden('action', 'add_final_item');
7740
        $form->addHidden('path', Session::read('pathItem'));
7741
        $form->addHidden('previous', $this->get_last());
7742
        $form->setDefaults(
7743
            ['title' => $title, 'content_lp_certificate' => $content]
7744
        );
7745
7746
        if ($form->validate()) {
7747
            $values = $form->exportValues();
7748
            $lastItemId = $this->getLastInFirstLevel();
7749
7750
            if (!$finalItem) {
7751
                $documentId = $this->create_document(
7752
                    $this->course_info,
7753
                    $values['content_lp_certificate'],
7754
                    $values['title']
7755
                );
7756
                $this->add_item(
7757
                    null,
7758
                    $lastItemId,
7759
                    'final_item',
7760
                    $documentId,
7761
                    $values['title'],
7762
                );
7763
7764
                Display::addFlash(
7765
                    Display::return_message(get_lang('Added'))
7766
                );
7767
            } else {
7768
                $this->edit_document();
7769
            }
7770
        }
7771
7772
        return $form->returnForm();
7773
    }
7774
7775
    /**
7776
     * Check if the current lp item is first, both, last or none from lp list.
7777
     *
7778
     * @param int $currentItemId
7779
     *
7780
     * @return string
7781
     */
7782
    public function isFirstOrLastItem($currentItemId)
7783
    {
7784
        $lpItemId = [];
7785
        $typeListNotToVerify = self::getChapterTypes();
7786
7787
        // Using get_toc() function instead $this->items because returns the correct order of the items
7788
        foreach ($this->get_toc() as $item) {
7789
            if (!in_array($item['type'], $typeListNotToVerify)) {
7790
                $lpItemId[] = $item['id'];
7791
            }
7792
        }
7793
7794
        $lastLpItemIndex = count($lpItemId) - 1;
7795
        $position = array_search($currentItemId, $lpItemId);
7796
7797
        switch ($position) {
7798
            case 0:
7799
                if (!$lastLpItemIndex) {
7800
                    $answer = 'both';
7801
                    break;
7802
                }
7803
7804
                $answer = 'first';
7805
                break;
7806
            case $lastLpItemIndex:
7807
                $answer = 'last';
7808
                break;
7809
            default:
7810
                $answer = 'none';
7811
        }
7812
7813
        return $answer;
7814
    }
7815
7816
    /**
7817
     * Get whether this is a learning path with the accumulated SCORM time or not.
7818
     *
7819
     * @return int
7820
     */
7821
    public function getAccumulateScormTime()
7822
    {
7823
        return $this->accumulateScormTime;
7824
    }
7825
7826
    /**
7827
     * Returns an HTML-formatted link to a resource, to incorporate directly into
7828
     * the new learning path tool.
7829
     *
7830
     * The function is a big switch on tool type.
7831
     * In each case, we query the corresponding table for information and build the link
7832
     * with that information.
7833
     *
7834
     * @author Yannick Warnier <[email protected]> - rebranding based on
7835
     * previous work (display_addedresource_link_in_learnpath())
7836
     *
7837
     * @param int $course_id      Course code
7838
     * @param int $learningPathId The learning path ID (in lp table)
7839
     * @param int $id_in_path     the unique index in the items table
7840
     * @param int $lpViewId
7841
     *
7842
     * @return string
7843
     */
7844
    public static function rl_get_resource_link_for_learnpath(
7845
        $course_id,
7846
        $learningPathId,
7847
        $id_in_path,
7848
        $lpViewId
7849
    ) {
7850
        $session_id = api_get_session_id();
7851
7852
        $learningPathId = (int) $learningPathId;
7853
        $id_in_path = (int) $id_in_path;
7854
        $lpViewId = (int) $lpViewId;
7855
7856
        $em = Database::getManager();
7857
        $lpItemRepo = $em->getRepository(CLpItem::class);
7858
7859
        /** @var CLpItem $rowItem */
7860
        $rowItem = $lpItemRepo->findOneBy([
7861
            'lp' => $learningPathId,
7862
            'iid' => $id_in_path,
7863
        ]);
7864
        $type = $rowItem->getItemType();
7865
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
7866
        $main_dir_path = api_get_path(WEB_CODE_PATH);
7867
        $link = '';
7868
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
7869
7870
        switch ($type) {
7871
            case 'dir':
7872
                return $main_dir_path.'lp/blank.php';
7873
            case TOOL_CALENDAR_EVENT:
7874
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
7875
            case TOOL_ANNOUNCEMENT:
7876
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
7877
            case TOOL_LINK:
7878
                $linkInfo = Link::getLinkInfo($id);
7879
                if (isset($linkInfo['url'])) {
7880
                    return $linkInfo['url'];
7881
                }
7882
7883
                return '';
7884
            case TOOL_QUIZ:
7885
                if (empty($id)) {
7886
                    return '';
7887
                }
7888
7889
                // Get the lp_item_view with the highest view_count.
7890
                $learnpathItemViewResult = $em
7891
                    ->getRepository(CLpItemView::class)
7892
                    ->findBy(
7893
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
7894
                        ['viewCount' => 'DESC'],
7895
                        1
7896
                    );
7897
                /** @var CLpItemView $learnpathItemViewData */
7898
                $learnpathItemViewData = current($learnpathItemViewResult);
7899
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
7900
7901
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
7902
                    .http_build_query([
7903
                        'lp_init' => 1,
7904
                        'learnpath_item_view_id' => $learnpathItemViewId,
7905
                        'learnpath_id' => $learningPathId,
7906
                        'learnpath_item_id' => $id_in_path,
7907
                        'exerciseId' => $id,
7908
                    ]);
7909
            case TOOL_HOTPOTATOES:
7910
                return '';
7911
            case TOOL_FORUM:
7912
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
7913
            case TOOL_THREAD:
7914
                // forum post
7915
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
7916
                if (empty($id)) {
7917
                    return '';
7918
                }
7919
                $sql = "SELECT * FROM $tbl_topics WHERE iid=$id";
7920
                $result = Database::query($sql);
7921
                $row = Database::fetch_array($result);
7922
7923
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$row['forum_id'].'&lp=true&'
7924
                    .$extraParams;
7925
            case TOOL_POST:
7926
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
7927
                $result = Database::query("SELECT * FROM $tbl_post WHERE post_id=$id");
7928
                $row = Database::fetch_array($result);
7929
7930
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$row['thread_id'].'&forum='
7931
                    .$row['forum_id'].'&lp=true&'.$extraParams;
7932
            case TOOL_READOUT_TEXT:
7933
                return api_get_path(WEB_CODE_PATH).
7934
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
7935
            case TOOL_DOCUMENT:
7936
                $repo = Container::getDocumentRepository();
7937
                $document = $repo->find($rowItem->getPath());
7938
                if ($document) {
7939
                    $params = [
7940
                        'cid' => $course_id,
7941
                        'sid' => $session_id,
7942
                    ];
7943
7944
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
7945
                }
7946
7947
                return null;
7948
            case TOOL_LP_FINAL_ITEM:
7949
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
7950
                    .$extraParams;
7951
            case 'assignments':
7952
                return $main_dir_path.'work/work.php?'.$extraParams;
7953
            case TOOL_DROPBOX:
7954
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
7955
            case 'introduction_text': //DEPRECATED
7956
                return '';
7957
            case TOOL_COURSE_DESCRIPTION:
7958
                return $main_dir_path.'course_description?'.$extraParams;
7959
            case TOOL_GROUP:
7960
                return $main_dir_path.'group/group.php?'.$extraParams;
7961
            case TOOL_USER:
7962
                return $main_dir_path.'user/user.php?'.$extraParams;
7963
            case TOOL_STUDENTPUBLICATION:
7964
                if (!empty($rowItem->getPath())) {
7965
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
7966
                }
7967
7968
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
7969
        }
7970
7971
        return $link;
7972
    }
7973
7974
    /**
7975
     * Checks if any forum items in a given learning path are from the base course.
7976
     */
7977
    public static function isForumFromBaseCourse(int $learningPathId): bool
7978
    {
7979
        $itemRepository = Container::getLpItemRepository();
7980
        $forumRepository = Container::getForumRepository();
7981
        $forums = $itemRepository->findItemsByLearningPathAndType($learningPathId, 'forum');
7982
7983
        /* @var CLpItem $forumItem */
7984
        foreach ($forums as $forumItem) {
7985
            $forumId = (int) $forumItem->getPath();
7986
            $forum = $forumRepository->find($forumId);
7987
7988
            if ($forum !== null) {
7989
                $forumSession = $forum->getFirstResourceLink()->getSession();
7990
                if ($forumSession === null) {
7991
                    return true;
7992
                }
7993
            }
7994
        }
7995
7996
        return false;
7997
    }
7998
7999
    /**
8000
     * Gets the name of a resource (generally used in learnpath when no name is provided).
8001
     *
8002
     * @author Yannick Warnier <[email protected]>
8003
     *
8004
     * @param string $course_code    Course code
8005
     * @param int    $learningPathId
8006
     * @param int    $id_in_path     The resource ID
8007
     *
8008
     * @return string
8009
     */
8010
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
8011
    {
8012
        $_course = api_get_course_info($course_code);
8013
        if (empty($_course)) {
8014
            return '';
8015
        }
8016
        $course_id = $_course['real_id'];
8017
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8018
        $learningPathId = (int) $learningPathId;
8019
        $id_in_path = (int) $id_in_path;
8020
8021
        $sql = "SELECT item_type, title, ref
8022
                FROM $tbl_lp_item
8023
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8024
        $res_item = Database::query($sql);
8025
8026
        if (Database::num_rows($res_item) < 1) {
8027
            return '';
8028
        }
8029
        $row_item = Database::fetch_array($res_item);
8030
        $type = strtolower($row_item['item_type']);
8031
        $id = $row_item['ref'];
8032
        $output = '';
8033
8034
        switch ($type) {
8035
            case TOOL_CALENDAR_EVENT:
8036
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8037
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8038
                $myrow = Database::fetch_array($result);
8039
                $output = $myrow['title'];
8040
                break;
8041
            case TOOL_ANNOUNCEMENT:
8042
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8043
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8044
                $myrow = Database::fetch_array($result);
8045
                $output = $myrow['title'];
8046
                break;
8047
            case TOOL_LINK:
8048
                // Doesn't take $target into account.
8049
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8050
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8051
                $myrow = Database::fetch_array($result);
8052
                $output = $myrow['title'];
8053
                break;
8054
            case TOOL_QUIZ:
8055
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8056
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8057
                $myrow = Database::fetch_array($result);
8058
                $output = $myrow['title'];
8059
                break;
8060
            case TOOL_FORUM:
8061
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8062
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8063
                $myrow = Database::fetch_array($result);
8064
                $output = $myrow['title'];
8065
                break;
8066
            case TOOL_THREAD:
8067
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8068
                // Grabbing the title of the post.
8069
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8070
                $result_title = Database::query($sql_title);
8071
                $myrow_title = Database::fetch_array($result_title);
8072
                $output = $myrow_title['title'];
8073
                break;
8074
            case TOOL_POST:
8075
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8076
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8077
                $result = Database::query($sql);
8078
                $post = Database::fetch_array($result);
8079
                $output = $post['title'];
8080
                break;
8081
            case 'dir':
8082
            case TOOL_DOCUMENT:
8083
                $title = $row_item['title'];
8084
                $output = '-';
8085
                if (!empty($title)) {
8086
                    $output = $title;
8087
                }
8088
                break;
8089
            case 'hotpotatoes':
8090
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8091
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8092
                $myrow = Database::fetch_array($result);
8093
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8094
                $last = count($pathname) - 1; // Making a correct name for the link.
8095
                $filename = $pathname[$last]; // Making a correct name for the link.
8096
                $myrow['path'] = rawurlencode($myrow['path']);
8097
                $output = $filename;
8098
                break;
8099
        }
8100
8101
        return stripslashes($output);
8102
    }
8103
8104
    /**
8105
     * Get the parent names for the current item.
8106
     *
8107
     * @param int $newItemId Optional. The item ID
8108
     */
8109
    public function getCurrentItemParentNames($newItemId = 0): array
8110
    {
8111
        $newItemId = $newItemId ?: $this->get_current_item_id();
8112
        $return = [];
8113
        $item = $this->getItem($newItemId);
8114
8115
        $parent = null;
8116
        if ($item) {
8117
            $parent = $this->getItem($item->get_parent());
8118
        }
8119
8120
        while ($parent) {
8121
            $return[] = $parent->get_title();
8122
            $parent = $this->getItem($parent->get_parent());
8123
        }
8124
8125
        return array_reverse($return);
8126
    }
8127
8128
    /**
8129
     * Reads and process "lp_subscription_settings" setting.
8130
     *
8131
     * @return array
8132
     */
8133
    public static function getSubscriptionSettings()
8134
    {
8135
        $subscriptionSettings = api_get_setting('lp.lp_subscription_settings', true);
8136
        if (!is_array($subscriptionSettings)) {
8137
            // By default, allow both settings
8138
            $subscriptionSettings = [
8139
                'allow_add_users_to_lp' => true,
8140
                'allow_add_users_to_lp_category' => true,
8141
            ];
8142
        } else {
8143
            $subscriptionSettings = $subscriptionSettings['options'];
8144
        }
8145
8146
        return $subscriptionSettings;
8147
    }
8148
8149
    /**
8150
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8151
     */
8152
    public function exportToCourseBuildFormat()
8153
    {
8154
        if (!api_is_allowed_to_edit()) {
8155
            return false;
8156
        }
8157
8158
        $courseBuilder = new CourseBuilder();
8159
        $itemList = [];
8160
        /** @var learnpathItem $item */
8161
        foreach ($this->items as $item) {
8162
            $itemList[$item->get_type()][] = $item->get_path();
8163
        }
8164
8165
        if (empty($itemList)) {
8166
            return false;
8167
        }
8168
8169
        if (isset($itemList['document'])) {
8170
            // Get parents
8171
            foreach ($itemList['document'] as $documentId) {
8172
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8173
                if (!empty($documentInfo['parents'])) {
8174
                    foreach ($documentInfo['parents'] as $parentInfo) {
8175
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8176
                            continue;
8177
                        }
8178
                        $itemList['document'][] = $parentInfo['iid'];
8179
                    }
8180
                }
8181
            }
8182
8183
            $courseInfo = api_get_course_info();
8184
            foreach ($itemList['document'] as $documentId) {
8185
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8186
                $items = DocumentManager::get_resources_from_source_html(
8187
                    $documentInfo['absolute_path'],
8188
                    true,
8189
                    TOOL_DOCUMENT
8190
                );
8191
8192
                if (!empty($items)) {
8193
                    foreach ($items as $item) {
8194
                        // Get information about source url
8195
                        $url = $item[0]; // url
8196
                        $scope = $item[1]; // scope (local, remote)
8197
                        $type = $item[2]; // type (rel, abs, url)
8198
8199
                        $origParseUrl = parse_url($url);
8200
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8201
8202
                        if ('local' === $scope) {
8203
                            if ('abs' === $type || 'rel' === $type) {
8204
                                $documentFile = strstr($realOrigPath, 'document');
8205
                                if (false !== strpos($realOrigPath, $documentFile)) {
8206
                                    $documentFile = str_replace('document', '', $documentFile);
8207
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8208
                                    // Document found! Add it to the list
8209
                                    if ($itemDocumentId) {
8210
                                        $itemList['document'][] = $itemDocumentId;
8211
                                    }
8212
                                }
8213
                            }
8214
                        }
8215
                    }
8216
                }
8217
            }
8218
8219
            $courseBuilder->build_documents(
8220
                api_get_session_id(),
8221
                $this->get_course_int_id(),
8222
                true,
8223
                $itemList['document']
8224
            );
8225
        }
8226
8227
        if (isset($itemList['quiz'])) {
8228
            $courseBuilder->build_quizzes(
8229
                api_get_session_id(),
8230
                $this->get_course_int_id(),
8231
                true,
8232
                $itemList['quiz']
8233
            );
8234
        }
8235
8236
        if (!empty($itemList['thread'])) {
8237
            $threadList = [];
8238
            $repo = Container::getForumThreadRepository();
8239
            foreach ($itemList['thread'] as $threadId) {
8240
                /** @var CForumThread $thread */
8241
                $thread = $repo->find($threadId);
8242
                if ($thread) {
8243
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8244
                    $threadList[] = $thread->getIid();
8245
                }
8246
            }
8247
8248
            if (!empty($threadList)) {
8249
                $courseBuilder->build_forum_topics(
8250
                    api_get_session_id(),
8251
                    $this->get_course_int_id(),
8252
                    null,
8253
                    $threadList
8254
                );
8255
            }
8256
        }
8257
8258
        $forumCategoryList = [];
8259
        if (isset($itemList['forum'])) {
8260
            foreach ($itemList['forum'] as $forumId) {
8261
                $forumInfo = get_forums($forumId);
8262
                $forumCategoryList[] = $forumInfo['forum_category'];
8263
            }
8264
        }
8265
8266
        if (!empty($forumCategoryList)) {
8267
            $courseBuilder->build_forum_category(
8268
                api_get_session_id(),
8269
                $this->get_course_int_id(),
8270
                true,
8271
                $forumCategoryList
8272
            );
8273
        }
8274
8275
        if (!empty($itemList['forum'])) {
8276
            $courseBuilder->build_forums(
8277
                api_get_session_id(),
8278
                $this->get_course_int_id(),
8279
                true,
8280
                $itemList['forum']
8281
            );
8282
        }
8283
8284
        if (isset($itemList['link'])) {
8285
            $courseBuilder->build_links(
8286
                api_get_session_id(),
8287
                $this->get_course_int_id(),
8288
                true,
8289
                $itemList['link']
8290
            );
8291
        }
8292
8293
        if (!empty($itemList['student_publication'])) {
8294
            $courseBuilder->build_works(
8295
                api_get_session_id(),
8296
                $this->get_course_int_id(),
8297
                true,
8298
                $itemList['student_publication']
8299
            );
8300
        }
8301
8302
        $courseBuilder->build_learnpaths(
8303
            api_get_session_id(),
8304
            $this->get_course_int_id(),
8305
            true,
8306
            [$this->get_id()],
8307
            false
8308
        );
8309
8310
        $courseBuilder->restoreDocumentsFromList();
8311
8312
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8313
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8314
        $result = DocumentManager::file_send_for_download(
8315
            $zipPath,
8316
            true,
8317
            $this->get_name().'.zip'
8318
        );
8319
8320
        if ($result) {
8321
            api_not_allowed();
8322
        }
8323
8324
        return true;
8325
    }
8326
8327
    /**
8328
     * Get whether this is a learning path with the accumulated work time or not.
8329
     *
8330
     * @return int
8331
     */
8332
    public function getAccumulateWorkTime()
8333
    {
8334
        return (int) $this->accumulateWorkTime;
8335
    }
8336
8337
    /**
8338
     * Get whether this is a learning path with the accumulated work time or not.
8339
     *
8340
     * @return int
8341
     */
8342
    public function getAccumulateWorkTimeTotalCourse()
8343
    {
8344
        $table = Database::get_course_table(TABLE_LP_MAIN);
8345
        $sql = "SELECT SUM(accumulate_work_time) AS total
8346
                FROM $table
8347
                WHERE c_id = ".$this->course_int_id;
8348
        $result = Database::query($sql);
8349
        $row = Database::fetch_array($result);
8350
8351
        return (int) $row['total'];
8352
    }
8353
8354
    /**
8355
     * @param int $lpId
8356
     * @param int $courseId
8357
     *
8358
     * @return mixed
8359
     */
8360
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8361
    {
8362
        $lpId = (int) $lpId;
8363
        $table = Database::get_course_table(TABLE_LP_MAIN);
8364
        $sql = "SELECT accumulate_work_time
8365
                FROM $table
8366
                WHERE iid = $lpId";
8367
        $result = Database::query($sql);
8368
        $row = Database::fetch_array($result);
8369
8370
        return $row['accumulate_work_time'];
8371
    }
8372
8373
    /**
8374
     * @param int $courseId
8375
     *
8376
     * @return int
8377
     */
8378
    public static function getAccumulateWorkTimeTotal($courseId)
8379
    {
8380
        $table = Database::get_course_table(TABLE_LP_MAIN);
8381
        $courseId = (int) $courseId;
8382
        $sql = "SELECT SUM(accumulate_work_time) AS total
8383
                FROM $table
8384
                WHERE c_id = $courseId";
8385
        $result = Database::query($sql);
8386
        $row = Database::fetch_array($result);
8387
8388
        return (int) $row['total'];
8389
    }
8390
8391
    /**
8392
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8393
     * and put the images in.
8394
     *
8395
     * @return array
8396
     */
8397
    public static function getIconSelect()
8398
    {
8399
        $theme = api_get_visual_theme();
8400
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8401
        $icons = ['' => get_lang('Please select an option')];
8402
8403
        if (is_dir($path)) {
8404
            $finder = new Finder();
8405
            $finder->files()->in($path);
8406
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8407
            /** @var SplFileInfo $file */
8408
            foreach ($finder as $file) {
8409
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8410
                    $icons[$file->getFilename()] = $file->getFilename();
8411
                }
8412
            }
8413
        }
8414
8415
        return $icons;
8416
    }
8417
8418
    /**
8419
     * @param int $lpId
8420
     *
8421
     * @return string
8422
     */
8423
    public static function getSelectedIcon($lpId)
8424
    {
8425
        $extraFieldValue = new ExtraFieldValue('lp');
8426
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8427
        $icon = '';
8428
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8429
            $icon = $lpIcon['value'];
8430
        }
8431
8432
        return $icon;
8433
    }
8434
8435
    /**
8436
     * @param int $lpId
8437
     *
8438
     * @return string
8439
     */
8440
    public static function getSelectedIconHtml($lpId)
8441
    {
8442
        $icon = self::getSelectedIcon($lpId);
8443
8444
        if (empty($icon)) {
8445
            return '';
8446
        }
8447
8448
        $theme = api_get_visual_theme();
8449
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8450
8451
        return Display::img($path);
8452
    }
8453
8454
    /**
8455
     * @param string $value
8456
     *
8457
     * @return string
8458
     */
8459
    public function cleanItemTitle($value)
8460
    {
8461
        $value = Security::remove_XSS(strip_tags($value));
8462
8463
        return $value;
8464
    }
8465
8466
    public function setItemTitle(FormValidator $form)
8467
    {
8468
        if ('true' === api_get_setting('editor.save_titles_as_html')) {
8469
            $form->addHtmlEditor(
8470
                'title',
8471
                get_lang('Title'),
8472
                true,
8473
                false,
8474
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8475
            );
8476
        } else {
8477
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8478
            $form->applyFilter('title', 'trim');
8479
            $form->applyFilter('title', 'html_filter');
8480
        }
8481
    }
8482
8483
    /**
8484
     * @return array
8485
     */
8486
    public function getItemsForForm($addParentCondition = false)
8487
    {
8488
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8489
8490
        $sql = "SELECT * FROM $tbl_lp_item
8491
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8492
8493
        if ($addParentCondition) {
8494
            $sql .= ' AND parent_item_id IS NULL ';
8495
        }
8496
        $sql .= ' ORDER BY display_order ASC';
8497
8498
        $result = Database::query($sql);
8499
        $arrLP = [];
8500
        while ($row = Database::fetch_array($result)) {
8501
            $arrLP[] = [
8502
                'iid' => $row['iid'],
8503
                'id' => $row['iid'],
8504
                'item_type' => $row['item_type'],
8505
                'title' => $this->cleanItemTitle($row['title']),
8506
                'title_raw' => $row['title'],
8507
                'path' => $row['path'],
8508
                'description' => Security::remove_XSS($row['description']),
8509
                'parent_item_id' => $row['parent_item_id'],
8510
                'previous_item_id' => $row['previous_item_id'],
8511
                'next_item_id' => $row['next_item_id'],
8512
                'display_order' => $row['display_order'],
8513
                'max_score' => $row['max_score'],
8514
                'min_score' => $row['min_score'],
8515
                'mastery_score' => $row['mastery_score'],
8516
                'prerequisite' => $row['prerequisite'],
8517
                'max_time_allowed' => $row['max_time_allowed'],
8518
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8519
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8520
            ];
8521
        }
8522
8523
        return $arrLP;
8524
    }
8525
8526
    /**
8527
     * Gets whether this SCORM learning path has been marked to use the score
8528
     * as progress. Takes into account whether the learnpath matches (SCORM
8529
     * content + less than 2 items).
8530
     *
8531
     * @return bool True if the score should be used as progress, false otherwise
8532
     */
8533
    public function getUseScoreAsProgress()
8534
    {
8535
        // If not a SCORM, we don't care about the setting
8536
        if (2 != $this->get_type()) {
8537
            return false;
8538
        }
8539
        // If more than one step in the SCORM, we don't care about the setting
8540
        if ($this->get_total_items_count() > 1) {
8541
            return false;
8542
        }
8543
        $extraFieldValue = new ExtraFieldValue('lp');
8544
        $doUseScore = false;
8545
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8546
            $this->get_id(),
8547
            'use_score_as_progress'
8548
        );
8549
        if (!empty($useScore) && isset($useScore['value'])) {
8550
            $doUseScore = $useScore['value'];
8551
        }
8552
8553
        return $doUseScore;
8554
    }
8555
8556
    /**
8557
     * Get the user identifier (user_id or username
8558
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8559
     *
8560
     * @return string User ID or username, depending on configuration setting
8561
     */
8562
    public static function getUserIdentifierForExternalServices()
8563
    {
8564
        $scormApiExtraFieldUseStudentId = api_get_setting('lp.scorm_api_extrafield_to_use_as_student_id');
8565
        $extraFieldValue = new ExtraFieldValue('user');
8566
        $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8567
            api_get_user_id(),
8568
            $scormApiExtraFieldUseStudentId
8569
        );
8570
        if (is_array($extrafield) && isset($extrafield['value'])) {
8571
            return $extrafield['value'];
8572
        } else {
8573
            if ('true' === $scormApiExtraFieldUseStudentId) {
8574
                return api_get_user_info(api_get_user_id())['username'];
8575
            } else {
8576
                return api_get_user_id();
8577
            }
8578
        }
8579
    }
8580
8581
    /**
8582
     * Save the new order for learning path items.
8583
     *
8584
     * @param array $orderList A associative array with id and parent_id keys.
8585
     */
8586
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true, $lpItemRepo = null, $em = null)
8587
    {
8588
        if (empty($orderList)) {
8589
            return true;
8590
        }
8591
        if (!isset($lpItemRepo)) {
8592
            $lpItemRepo = Container::getLpItemRepository();
8593
        }
8594
        if (!isset($em)) {
8595
            $em = Database::getManager();
8596
        }
8597
        $counter = 2;
8598
        $rootItem->setDisplayOrder(1);
8599
        $rootItem->setPreviousItemId(null);
8600
        $em->persist($rootItem);
8601
        if ($flush) {
8602
            $em->flush();
8603
        }
8604
8605
        foreach ($orderList as $item) {
8606
            $itemId = $item->id ?? 0;
8607
            if (empty($itemId)) {
8608
                continue;
8609
            }
8610
            $parentId = $item->parent_id ?? 0;
8611
            $parent = $rootItem;
8612
            if (!empty($parentId)) {
8613
                $parentExists = $lpItemRepo->find($parentId);
8614
                if (null !== $parentExists) {
8615
                    $parent = $parentExists;
8616
                }
8617
            }
8618
8619
            /** @var CLpItem $itemEntity */
8620
            $itemEntity = $lpItemRepo->find($itemId);
8621
            $itemEntity->setParent($parent);
8622
            $itemEntity->setPreviousItemId(null);
8623
            $itemEntity->setNextItemId(null);
8624
            $itemEntity->setDisplayOrder($counter);
8625
8626
            $em->persist($itemEntity);
8627
            if ($flush) {
8628
                $em->flush();
8629
            }
8630
            $counter++;
8631
        }
8632
8633
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8634
        $em->persist($rootItem);
8635
        if ($flush) {
8636
            $em->flush();
8637
        }
8638
8639
        return true;
8640
    }
8641
8642
    public static function move(int $lpId, string $direction)
8643
    {
8644
        $em = Database::getManager();
8645
        /** @var CLp $lp */
8646
        $lp = Container::getLpRepository()->find($lpId);
8647
        if ($lp) {
8648
            $course = api_get_course_entity();
8649
            $session = api_get_session_entity();
8650
            $group = api_get_group_entity();
8651
8652
            $link = $lp->getResourceNode()->getResourceLinkByContext($course, $session, $group);
8653
8654
            if ($link) {
8655
                if ('down' === $direction) {
8656
                    $link->moveDownPosition();
8657
                }
8658
                if ('up' === $direction) {
8659
                    $link->moveUpPosition();
8660
                }
8661
8662
                $em->flush();
8663
            }
8664
        }
8665
    }
8666
8667
    /**
8668
     * Get the depth level of LP item.
8669
     *
8670
     * @param array $items
8671
     * @param int   $currentItemId
8672
     *
8673
     * @return int
8674
     */
8675
    private static function get_level_for_item($items, $currentItemId)
8676
    {
8677
        $parentItemId = 0;
8678
        if (isset($items[$currentItemId])) {
8679
            $parentItemId = $items[$currentItemId]->parent;
8680
        }
8681
8682
        if (0 == $parentItemId) {
8683
            return 0;
8684
        }
8685
8686
        return self::get_level_for_item($items, $parentItemId) + 1;
8687
    }
8688
8689
    /**
8690
     * Generate the link for a learnpath category as course tool.
8691
     *
8692
     * @param int $categoryId
8693
     *
8694
     * @return string
8695
     */
8696
    private static function getCategoryLinkForTool($categoryId)
8697
    {
8698
        $categoryId = (int) $categoryId;
8699
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8700
            .http_build_query(
8701
                [
8702
                    'action' => 'view_category',
8703
                    'id' => $categoryId,
8704
                ]
8705
            );
8706
    }
8707
8708
    /**
8709
     * Check and obtain the lp final item if exist.
8710
     *
8711
     * @return learnpathItem
8712
     */
8713
    private function getFinalItem()
8714
    {
8715
        if (empty($this->items)) {
8716
            return null;
8717
        }
8718
8719
        foreach ($this->items as $item) {
8720
            if ('final_item' !== $item->type) {
8721
                continue;
8722
            }
8723
8724
            return $item;
8725
        }
8726
    }
8727
8728
    /**
8729
     * Get the LP Final Item Template.
8730
     *
8731
     * @return string
8732
     */
8733
    private function getFinalItemTemplate()
8734
    {
8735
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8736
    }
8737
8738
    /**
8739
     * Get the LP Final Item Url.
8740
     *
8741
     * @return string
8742
     */
8743
    private function getSavedFinalItem()
8744
    {
8745
        $finalItem = $this->getFinalItem();
8746
8747
        $repo = Container::getDocumentRepository();
8748
        /** @var CDocument $document */
8749
        $document = $repo->find($finalItem->path);
8750
8751
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8752
            return $repo->getResourceFileContent($document);
8753
        }
8754
8755
        return '';
8756
    }
8757
}
8758