Passed
Push — master ( e1f1cb...e0d9bc )
by Yannick
06:49
created

learnpath::get_view()   B

Complexity

Conditions 7
Paths 18

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 35
nc 18
nop 2
dl 0
loc 53
rs 8.4266
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\User;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CourseBundle\Entity\CLpRelUser;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
11
use Chamilo\CourseBundle\Repository\CLpRelUserRepository;
12
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
13
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
14
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
15
use Chamilo\CourseBundle\Entity\CDocument;
16
use Chamilo\CourseBundle\Entity\CForumCategory;
17
use Chamilo\CourseBundle\Entity\CForumThread;
18
use Chamilo\CourseBundle\Entity\CLink;
19
use Chamilo\CourseBundle\Entity\CLp;
20
use Chamilo\CourseBundle\Entity\CLpCategory;
21
use Chamilo\CourseBundle\Entity\CLpItem;
22
use Chamilo\CourseBundle\Entity\CLpItemView;
23
use Chamilo\CourseBundle\Entity\CQuiz;
24
use Chamilo\CourseBundle\Entity\CStudentPublication;
25
use Chamilo\CourseBundle\Entity\CTool;
26
use \Chamilo\CoreBundle\Entity\ResourceNode;
27
use ChamiloSession as Session;
28
use Doctrine\Common\Collections\Criteria;
29
use PhpZip\ZipFile;
30
use Symfony\Component\Finder\Finder;
31
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
32
use Chamilo\CoreBundle\Component\Utils\ObjectIcon;
33
34
/**
35
 * Class learnpath
36
 * This class defines the parent attributes and methods for Chamilo learnpaths
37
 * and SCORM learnpaths. It is used by the scorm class.
38
 *
39
 * @todo decouple class
40
 *
41
 * @author  Yannick Warnier <[email protected]>
42
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
43
 */
44
class learnpath
45
{
46
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
47
    public const STATUS_CSS_CLASS_NAME = [
48
        'not attempted' => 'scorm_not_attempted',
49
        'incomplete' => 'scorm_not_attempted',
50
        'failed' => 'scorm_failed',
51
        'completed' => 'scorm_completed',
52
        'passed' => 'scorm_completed',
53
        'succeeded' => 'scorm_completed',
54
        'browsed' => 'scorm_completed',
55
    ];
56
57
    public $attempt = 0; // The number for the current ID view.
58
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
59
    public $current; // Id of the current item the user is viewing.
60
    public $current_score; // The score of the current item.
61
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
62
    public $current_time_stop; // The time the user closed this resource.
63
    public $default_status = 'not attempted';
64
    public $encoding = 'UTF-8';
65
    public $error = '';
66
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
67
    public $index; // The index of the active learnpath_item in $ordered_items array.
68
    /** @var learnpathItem[] */
69
    public $items = [];
70
    public $last; // item_id of last item viewed in the learning path.
71
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
72
    public $license; // Which license this course has been given - not used yet on 20060522.
73
    public $lp_id; // DB iid for this learnpath.
74
    public $lp_view_id; // DB ID for lp_view
75
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
76
    public $message = '';
77
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
78
    public $name; // Learnpath name (they generally have one).
79
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
80
    public $path = ''; // Path inside the scorm directory (if scorm).
81
    public $theme; // The current theme of the learning path.
82
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
83
    public $accumulateWorkTime; // The min time of learnpath
84
85
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
86
    public $prevent_reinit = 1;
87
88
    // Describes the mode of progress bar display.
89
    public $seriousgame_mode = 0;
90
    public $progress_bar_mode = '%';
91
92
    // Percentage progress as saved in the db.
93
    public $progress_db = 0;
94
    public $proximity; // Wether the content is distant or local or unknown.
95
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
96
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
97
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
98
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
99
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
100
    public $user_id; //ID of the user that is viewing/using the course
101
    public $update_queue = [];
102
    public $scorm_debug = 0;
103
    public $arrMenu = []; // Array for the menu items.
104
    public $debug = 0; // Logging level.
105
    public $lp_session_id = 0;
106
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
107
    public $prerequisite = 0;
108
    public $use_max_score = 1; // 1 or 0
109
    public $subscribeUsers = 0; // Subscribe users or not
110
    public $created_on = '';
111
    public $modified_on = '';
112
    public $published_on = '';
113
    public $expired_on = '';
114
    public $ref;
115
    public $course_int_id;
116
    public $course_info;
117
    public $categoryId;
118
    public $scormUrl;
119
    public $entity;
120
121
    public function __construct(CLp $entity = null, $course_info, $user_id)
122
    {
123
        $debug = $this->debug;
124
        $user_id = (int) $user_id;
125
        $this->encoding = api_get_system_encoding();
126
        $lp_id = 0;
127
        if (null !== $entity) {
128
            $lp_id = $entity->getIid();
129
        }
130
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
131
        $course_id = (int) $course_info['real_id'];
132
        $this->course_info = $course_info;
133
        $this->set_course_int_id($course_id);
134
        if (empty($lp_id) || empty($course_id)) {
135
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
136
        } else {
137
            //$this->entity = $entity;
138
            $this->lp_id = $lp_id;
139
            $this->type = $entity->getLpType();
140
            $this->name = stripslashes($entity->getName());
141
            $this->proximity = $entity->getContentLocal();
142
            $this->theme = $entity->getTheme();
143
            $this->maker = $entity->getContentLocal();
144
            $this->prevent_reinit = $entity->getPreventReinit();
145
            $this->seriousgame_mode = $entity->getSeriousgameMode();
146
            $this->license = $entity->getContentLicense();
147
            $this->scorm_debug = $entity->getDebug();
148
            $this->js_lib = $entity->getJsLib();
149
            $this->path = $entity->getPath();
150
            $this->author = $entity->getAuthor();
151
            $this->hide_toc_frame = $entity->getHideTocFrame();
152
            //$this->lp_session_id = $entity->getSessionId();
153
            $this->use_max_score = $entity->getUseMaxScore();
154
            $this->subscribeUsers = $entity->getSubscribeUsers();
155
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
156
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
157
            $this->ref = $entity->getRef();
158
            $this->categoryId = 0;
159
            if ($entity->getCategory()) {
160
                $this->categoryId = $entity->getCategory()->getIid();
161
            }
162
163
            if ($entity->hasAsset()) {
164
                $asset = $entity->getAsset();
165
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/'.$entity->getPath().'/';
166
            }
167
168
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
169
170
            if (!empty($entity->getPublishedOn())) {
171
                $this->published_on = $entity->getPublishedOn()->format('Y-m-d H:i:s');
172
            }
173
174
            if (!empty($entity->getExpiredOn())) {
175
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
176
            }
177
            if (2 == $this->type) {
178
                if (1 == $entity->getForceCommit()) {
179
                    $this->force_commit = true;
180
                }
181
            }
182
            $this->mode = $entity->getDefaultViewMod();
183
184
            // Check user ID.
185
            if (empty($user_id)) {
186
                $this->error = 'User ID is empty';
187
            } else {
188
                $this->user_id = $user_id;
189
            }
190
191
            // End of variables checking.
192
            $session_id = api_get_session_id();
193
            //  Get the session condition for learning paths of the base + session.
194
            $session = api_get_session_condition($session_id);
195
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
196
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
197
198
            // Selecting by view_count descending allows to get the highest view_count first.
199
            $sql = "SELECT * FROM $lp_table
200
                    WHERE
201
                        c_id = $course_id AND
202
                        lp_id = $lp_id AND
203
                        user_id = $user_id
204
                        $session
205
                    ORDER BY view_count DESC";
206
            $res = Database::query($sql);
207
208
            if (Database::num_rows($res) > 0) {
209
                $row = Database::fetch_array($res);
210
                $this->attempt = $row['view_count'];
211
                $this->lp_view_id = $row['iid'];
212
                $this->last_item_seen = $row['last_item'];
213
                $this->progress_db = $row['progress'];
214
                $this->lp_view_session_id = $row['session_id'];
215
            } elseif (!api_is_invitee()) {
216
                $this->attempt = 1;
217
                $params = [
218
                    'c_id' => $course_id,
219
                    'lp_id' => $lp_id,
220
                    'user_id' => $user_id,
221
                    'view_count' => 1,
222
                    //'session_id' => $session_id,
223
                    'last_item' => 0,
224
                ];
225
                if (!empty($session_id)) {
226
                    $params['session_id'] = $session_id;
227
                }
228
                $this->last_item_seen = 0;
229
                $this->lp_view_session_id = $session_id;
230
                $this->lp_view_id = Database::insert($lp_table, $params);
231
            }
232
233
            $criteria = new Criteria();
234
            $criteria
235
                ->where($criteria->expr()->neq('path', 'root'))
236
                ->orderBy(
237
                    [
238
                        'parent' => Criteria::ASC,
239
                        'displayOrder' => Criteria::ASC,
240
                    ]
241
                );
242
            $items = $entity->getItems()->matching($criteria);
243
            $lp_item_id_list = [];
244
            foreach ($items as $item) {
245
                $itemId = $item->getIid();
246
                $lp_item_id_list[] = $itemId;
247
248
                switch ($this->type) {
249
                    case CLp::AICC_TYPE:
250
                        $oItem = new aiccItem('db', $itemId, $course_id);
251
                        if (is_object($oItem)) {
252
                            $oItem->set_lp_view($this->lp_view_id);
253
                            $oItem->set_prevent_reinit($this->prevent_reinit);
254
                            // Don't use reference here as the next loop will make the pointed object change.
255
                            $this->items[$itemId] = $oItem;
256
                            $this->refs_list[$oItem->ref] = $itemId;
257
                        }
258
                        break;
259
                    case CLp::SCORM_TYPE:
260
                        $oItem = new scormItem('db', $itemId);
261
                        if (is_object($oItem)) {
262
                            $oItem->set_lp_view($this->lp_view_id);
263
                            $oItem->set_prevent_reinit($this->prevent_reinit);
264
                            // Don't use reference here as the next loop will make the pointed object change.
265
                            $this->items[$itemId] = $oItem;
266
                            $this->refs_list[$oItem->ref] = $itemId;
267
                        }
268
                        break;
269
                    case CLp::LP_TYPE:
270
                    default:
271
                        $oItem = new learnpathItem(null, $item);
272
                        if (is_object($oItem)) {
273
                            // Moved down to when we are sure the item_view exists.
274
                            //$oItem->set_lp_view($this->lp_view_id);
275
                            $oItem->set_prevent_reinit($this->prevent_reinit);
276
                            // Don't use reference here as the next loop will make the pointed object change.
277
                            $this->items[$itemId] = $oItem;
278
                            $this->refs_list[$itemId] = $itemId;
279
                        }
280
                        break;
281
                }
282
283
                // Setting the object level with variable $this->items[$i][parent]
284
                foreach ($this->items as $itemLPObject) {
285
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
286
                    $itemLPObject->level = $level;
287
                }
288
289
                // Setting the view in the item object.
290
                if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
291
                    $this->items[$itemId]->set_lp_view($this->lp_view_id);
292
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
293
                        $this->items[$itemId]->current_start_time = 0;
294
                        $this->items[$itemId]->current_stop_time = 0;
295
                    }
296
                }
297
            }
298
299
            if (!empty($lp_item_id_list)) {
300
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
301
                if (!empty($lp_item_id_list_to_string)) {
302
                    // Get last viewing vars.
303
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
304
                    // This query should only return one or zero result.
305
                    $sql = "SELECT lp_item_id, status
306
                            FROM $itemViewTable
307
                            WHERE
308
                                lp_view_id = ".$this->get_view_id()." AND
309
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
310
                            ORDER BY view_count DESC ";
311
                    $status_list = [];
312
                    $res = Database::query($sql);
313
                    while ($row = Database:: fetch_array($res)) {
314
                        $status_list[$row['lp_item_id']] = $row['status'];
315
                    }
316
317
                    foreach ($lp_item_id_list as $item_id) {
318
                        if (isset($status_list[$item_id])) {
319
                            $status = $status_list[$item_id];
320
321
                            if (is_object($this->items[$item_id])) {
322
                                $this->items[$item_id]->set_status($status);
323
                                if (empty($status)) {
324
                                    $this->items[$item_id]->set_status(
325
                                        $this->default_status
326
                                    );
327
                                }
328
                            }
329
                        } else {
330
                            if (!api_is_invitee()) {
331
                                if (isset($this->items[$item_id]) && is_object($this->items[$item_id])) {
332
                                    $this->items[$item_id]->set_status(
333
                                        $this->default_status
334
                                    );
335
                                }
336
337
                                if (!empty($this->lp_view_id)) {
338
                                    // Add that row to the lp_item_view table so that
339
                                    // we have something to show in the stats page.
340
                                    $params = [
341
                                        'lp_item_id' => $item_id,
342
                                        'lp_view_id' => $this->lp_view_id,
343
                                        'view_count' => 1,
344
                                        'status' => 'not attempted',
345
                                        'start_time' => time(),
346
                                        'total_time' => 0,
347
                                        'score' => 0,
348
                                    ];
349
                                    Database::insert($itemViewTable, $params);
350
351
                                    $this->items[$item_id]->set_lp_view(
352
                                        $this->lp_view_id
353
                                    );
354
                                }
355
                            }
356
                        }
357
                    }
358
                }
359
            }
360
361
            $this->ordered_items = self::get_flat_ordered_items_list($entity, null);
362
            $this->max_ordered_items = 0;
363
            foreach ($this->ordered_items as $index => $dummy) {
364
                if ($index > $this->max_ordered_items && !empty($dummy)) {
365
                    $this->max_ordered_items = $index;
366
                }
367
            }
368
            // TODO: Define the current item better.
369
            $this->first();
370
            if ($debug) {
371
                error_log('lp_view_session_id '.$this->lp_view_session_id);
372
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
373
            }
374
        }
375
    }
376
377
    /**
378
     * @return int
379
     */
380
    public function get_course_int_id()
381
    {
382
        return $this->course_int_id ?? api_get_course_int_id();
383
    }
384
385
    /**
386
     * @param $course_id
387
     *
388
     * @return int
389
     */
390
    public function set_course_int_id($course_id)
391
    {
392
        return $this->course_int_id = (int) $course_id;
393
    }
394
395
    /**
396
     * Function rewritten based on old_add_item() from Yannick Warnier.
397
     * Due the fact that users can decide where the item should come, I had to overlook this function and
398
     * I found it better to rewrite it. Old function is still available.
399
     * Added also the possibility to add a description.
400
     *
401
     * @param CLpItem $parent
402
     * @param int     $previousId
403
     * @param string  $type
404
     * @param int     $id resource ID (ref)
405
     * @param string  $title
406
     * @param string  $description
407
     * @param int     $prerequisites
408
     * @param int     $maxTimeAllowed
409
     * @param int     $userId
410
     *
411
     * @return int
412
     */
413
    public function add_item(
414
        ?CLpItem $parent,
415
        $previousId,
416
        $type,
417
        $id,
418
        $title,
419
        $description = '',
420
        $prerequisites = 0,
421
        $maxTimeAllowed = 0
422
    ) {
423
        $type = empty($type) ? 'dir' : $type;
424
        $course_id = $this->course_info['real_id'];
425
        if (empty($course_id)) {
426
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
427
            $this->course_info = api_get_course_info($this->cc);
428
            $course_id = $this->course_info['real_id'];
429
        }
430
        $id = (int) $id;
431
        $maxTimeAllowed = (int) $maxTimeAllowed;
432
        if (empty($maxTimeAllowed)) {
433
            $maxTimeAllowed = 0;
434
        }
435
        $maxScore = 100;
436
        if ('quiz' === $type && $id) {
437
            // Disabling the exercise if we add it inside a LP
438
            $exercise = new Exercise($course_id);
439
            $exercise->read($id);
440
            $maxScore = $exercise->getMaxScore();
441
442
            $exercise->disable();
443
            $exercise->save();
444
            $title = $exercise->get_formated_title();
445
        }
446
447
        $lpItem = (new CLpItem())
448
            ->setTitle($title)
449
            ->setDescription($description)
450
            ->setPath($id)
451
            ->setLp(api_get_lp_entity($this->get_id()))
452
            ->setItemType($type)
453
            ->setMaxScore($maxScore)
454
            ->setMaxTimeAllowed($maxTimeAllowed)
455
            ->setPrerequisite($prerequisites)
456
            //->setDisplayOrder($display_order + 1)
457
            //->setNextItemId((int) $next)
458
            //->setPreviousItemId($previous)
459
        ;
460
461
        if (!empty($parent))  {
462
            $lpItem->setParent($parent);
463
        }
464
        $em = Database::getManager();
465
        $em->persist($lpItem);
466
        $em->flush();
467
468
        $new_item_id = $lpItem->getIid();
469
        if ($new_item_id) {
470
            // @todo fix upload audio.
471
            // Upload audio.
472
            /*if (!empty($_FILES['mp3']['name'])) {
473
                // Create the audio folder if it does not exist yet.
474
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
475
                if (!is_dir($filepath.'audio')) {
476
                    mkdir(
477
                        $filepath.'audio',
478
                        api_get_permissions_for_new_directories()
479
                    );
480
                    DocumentManager::addDocument(
481
                        $_course,
482
                        '/audio',
483
                        'folder',
484
                        0,
485
                        'audio',
486
                        '',
487
                        0,
488
                        true,
489
                        null,
490
                        $sessionId,
491
                        $userId
492
                    );
493
                }
494
495
                $file_path = handle_uploaded_document(
496
                    $_course,
497
                    $_FILES['mp3'],
498
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
499
                    '/audio',
500
                    $userId,
501
                    '',
502
                    '',
503
                    '',
504
                    '',
505
                    false
506
                );
507
508
                // Getting the filename only.
509
                $file_components = explode('/', $file_path);
510
                $file = $file_components[count($file_components) - 1];
511
512
                // Store the mp3 file in the lp_item table.
513
                $sql = "UPDATE $tbl_lp_item SET
514
                          audio = '".Database::escape_string($file)."'
515
                        WHERE iid = '".intval($new_item_id)."'";
516
                Database::query($sql);
517
            }*/
518
        }
519
520
        return $new_item_id;
521
    }
522
523
    /**
524
     * Static admin function allowing addition of a learnpath to a course.
525
     *
526
     * @param string $courseCode
527
     * @param string $name
528
     * @param string $description
529
     * @param string $learnpath
530
     * @param string $origin
531
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
532
     * @param string $published_on
533
     * @param string $expired_on
534
     * @param int    $categoryId
535
     * @param int    $userId
536
     *
537
     * @return CLp
538
     */
539
    public static function add_lp(
540
        $courseCode,
541
        $name,
542
        $description = '',
543
        $learnpath = 'guess',
544
        $origin = 'zip',
545
        $zipname = '',
546
        $published_on = '',
547
        $expired_on = '',
548
        $categoryId = 0,
549
        $userId = 0
550
    ) {
551
        global $charset;
552
553
        if (!empty($courseCode)) {
554
            $courseInfo = api_get_course_info($courseCode);
555
            $course_id = $courseInfo['real_id'];
556
        } else {
557
            $course_id = api_get_course_int_id();
558
            $courseInfo = api_get_course_info();
559
        }
560
561
        $categoryId = (int) $categoryId;
562
563
        if (empty($published_on)) {
564
            $published_on = null;
565
        } else {
566
            $published_on = api_get_utc_datetime($published_on, true, true);
567
        }
568
569
        if (empty($expired_on)) {
570
            $expired_on = null;
571
        } else {
572
            $expired_on = api_get_utc_datetime($expired_on, true, true);
573
        }
574
575
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES));
576
        $type = 1;
577
        switch ($learnpath) {
578
            case 'guess':
579
            case 'aicc':
580
                break;
581
            case 'dokeos':
582
            case 'chamilo':
583
                $type = 1;
584
                break;
585
        }
586
587
        $sessionEntity = api_get_session_entity();
588
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
589
        $lp = null;
590
        switch ($origin) {
591
            case 'zip':
592
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
593
                break;
594
            case 'manual':
595
            default:
596
                /*$get_max = "SELECT MAX(display_order)
597
                            FROM $tbl_lp WHERE c_id = $course_id";
598
                $res_max = Database::query($get_max);
599
                if (Database::num_rows($res_max) < 1) {
600
                    $dsp = 1;
601
                } else {
602
                    $row = Database::fetch_array($res_max);
603
                    $dsp = $row[0] + 1;
604
                }*/
605
606
                $dsp = 1;
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
                    ->setName($name)
617
                    ->setDescription($description)
618
                    ->setDisplayOrder($dsp)
619
                    ->setCategory($category)
620
                    ->setPublishedOn($published_on)
621
                    ->setExpiredOn($expired_on)
622
                    ->setParent($courseEntity)
623
                    ->addCourseLink($courseEntity, $sessionEntity)
624
                ;
625
                $lpRepo->createLp($lp);
626
627
                break;
628
        }
629
630
        return $lp;
631
    }
632
633
    /**
634
     * Auto completes the parents of an item in case it's been completed or passed.
635
     *
636
     * @param int $item Optional ID of the item from which to look for parents
637
     */
638
    public function autocomplete_parents($item)
639
    {
640
        $debug = $this->debug;
641
642
        if (empty($item)) {
643
            $item = $this->current;
644
        }
645
646
        $currentItem = $this->getItem($item);
647
        if ($currentItem) {
648
            $parent_id = $currentItem->get_parent();
649
            $parent = $this->getItem($parent_id);
650
            if ($parent) {
651
                // if $item points to an object and there is a parent.
652
                if ($debug) {
653
                    error_log(
654
                        'Autocompleting parent of item '.$item.' '.
655
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
656
                        0
657
                    );
658
                }
659
660
                // New experiment including failed and browsed in completed status.
661
                //$current_status = $currentItem->get_status();
662
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
663
                // Fixes chapter auto complete
664
                if (true) {
665
                    // If the current item is completed or passes or succeeded.
666
                    $updateParentStatus = true;
667
                    if ($debug) {
668
                        error_log('Status of current item is alright');
669
                    }
670
671
                    foreach ($parent->get_children() as $childItemId) {
672
                        $childItem = $this->getItem($childItemId);
673
674
                        // If children was not set try to get the info
675
                        if (empty($childItem->db_item_view_id)) {
676
                            $childItem->set_lp_view($this->lp_view_id);
677
                        }
678
679
                        // Check all his brothers (parent's children) for completion status.
680
                        if ($childItemId != $item) {
681
                            if ($debug) {
682
                                error_log(
683
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
684
                                    0
685
                                );
686
                            }
687
                            // Trying completing parents of failed and browsed items as well.
688
                            if ($childItem->status_is(
689
                                [
690
                                    'completed',
691
                                    'passed',
692
                                    'succeeded',
693
                                    'browsed',
694
                                    'failed',
695
                                ]
696
                            )
697
                            ) {
698
                                // Keep completion status to true.
699
                                continue;
700
                            } else {
701
                                if ($debug > 2) {
702
                                    error_log(
703
                                        '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,
704
                                        0
705
                                    );
706
                                }
707
                                $updateParentStatus = false;
708
                                break;
709
                            }
710
                        }
711
                    }
712
713
                    if ($updateParentStatus) {
714
                        // If all the children were completed:
715
                        $parent->set_status('completed');
716
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
717
                        // Force the status to "completed"
718
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
719
                        $this->update_queue[$parent->get_id()] = 'completed';
720
                        if ($debug) {
721
                            error_log(
722
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
723
                                print_r($this->update_queue, 1),
724
                                0
725
                            );
726
                        }
727
                        // Recursive call.
728
                        $this->autocomplete_parents($parent->get_id());
729
                    }
730
                }
731
            } else {
732
                if ($debug) {
733
                    error_log("Parent #$parent_id does not exists");
734
                }
735
            }
736
        } else {
737
            if ($debug) {
738
                error_log("#$item is an item that doesn't have parents");
739
            }
740
        }
741
    }
742
743
    /**
744
     * Closes the current resource.
745
     *
746
     * Stops the timer
747
     * Saves into the database if required
748
     * Clears the current resource data from this object
749
     *
750
     * @return bool True on success, false on failure
751
     */
752
    public function close()
753
    {
754
        if (empty($this->lp_id)) {
755
            $this->error = 'Trying to close this learnpath but no ID is set';
756
757
            return false;
758
        }
759
        $this->current_time_stop = time();
760
        $this->ordered_items = [];
761
        $this->index = 0;
762
        unset($this->lp_id);
763
        //unset other stuff
764
        return true;
765
    }
766
767
    /**
768
     * Static admin function allowing removal of a learnpath.
769
     *
770
     * @param array  $courseInfo
771
     * @param int    $id         Learnpath ID
772
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
773
     *
774
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
775
     */
776
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
777
    {
778
        $course_id = api_get_course_int_id();
779
        if (!empty($courseInfo)) {
780
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
781
        }
782
783
        // TODO: Implement a way of getting this to work when the current object is not set.
784
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
785
        // If an ID is specifically given and the current LP is not the same, prevent delete.
786
        if (!empty($id) && ($id != $this->lp_id)) {
787
            return false;
788
        }
789
790
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
791
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
792
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
793
794
        // Delete lp item id.
795
        foreach ($this->items as $lpItemId => $dummy) {
796
            $sql = "DELETE FROM $lp_item_view
797
                    WHERE lp_item_id = '".$lpItemId."'";
798
            Database::query($sql);
799
        }
800
801
        // Proposed by Christophe (nickname: clefevre)
802
        $sql = "DELETE FROM $lp_item
803
                WHERE lp_id = ".$this->lp_id;
804
        Database::query($sql);
805
806
        $sql = "DELETE FROM $lp_view
807
                WHERE lp_id = ".$this->lp_id;
808
        Database::query($sql);
809
810
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
811
        $sql = "DELETE FROM $table
812
                WHERE
813
                    lp_id = {$this->lp_id}";
814
        Database::query($sql);
815
816
        $repo = Container::getLpRepository();
817
        $lp = $repo->find($this->lp_id);
818
        Database::getManager()->remove($lp);
819
        Database::getManager()->flush();
820
821
        // Updates the display order of all lps.
822
        $this->update_display_order();
823
824
        $link_info = GradebookUtils::isResourceInCourseGradebook(
825
            api_get_course_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
                <a class="icon-toolbar" id="scorm-previous" href="#"
1361
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1362
                    '.$icon.'<span class="sr-only">'.$previousText.'</span>
1363
                </a>';
1364
1365
            $icon = Display::getMdiIcon('chevron-right');
1366
            $nextIcon = '
1367
                <a class="icon-toolbar" id="scorm-next" href="#"
1368
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1369
                    '.$icon.'<span class="sr-only">'.$nextText.'</span>
1370
                </a>';
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="progress">
2021
            <div id="progress_bar_value"
2022
                class="progress-bar progress-bar-success" role="progressbar"
2023
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2024
            '.$text.'
2025
            </div>
2026
        </div>';
2027
    }
2028
2029
    /**
2030
     * @param string $mode can be '%' or 'abs'
2031
     *                     otherwise this value will be used $this->progress_bar_mode
2032
     *
2033
     * @return string
2034
     */
2035
    public function getProgressBar($mode = null)
2036
    {
2037
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2038
2039
        return self::get_progress_bar($percentage, $text_add);
2040
    }
2041
2042
    /**
2043
     * Gets the progress bar info to display inside the progress bar.
2044
     * Also used by scorm_api.php.
2045
     *
2046
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2047
     *                     we display a number of completed elements per total elements
2048
     * @param int    $add  Additional steps to fake as completed
2049
     *
2050
     * @return array Percentage or number and symbol (% or /xx)
2051
     */
2052
    public function get_progress_bar_text($mode = '', $add = 0)
2053
    {
2054
        if (empty($mode)) {
2055
            $mode = $this->progress_bar_mode;
2056
        }
2057
        $text = '';
2058
        $percentage = 0;
2059
        // If the option to use the score as progress is set for this learning
2060
        // path, then the rules are completely different: we assume only one
2061
        // item exists and the progress of the LP depends on the score
2062
        $scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable'));
2063
        if (true === $scoreAsProgressSetting) {
2064
            $scoreAsProgress = $this->getUseScoreAsProgress();
2065
            if ($scoreAsProgress) {
2066
                // Get single item's score
2067
                $itemId = $this->get_current_item_id();
2068
                $item = $this->getItem($itemId);
2069
                $score = $item->get_score();
2070
                $maxScore = $item->get_max();
2071
                if ($mode = '%') {
2072
                    if (!empty($maxScore)) {
2073
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2074
                    }
2075
                    $percentage = number_format($percentage, 0);
2076
                    $text = '%';
2077
                } else {
2078
                    $percentage = $score;
2079
                    $text = '/'.$maxScore;
2080
                }
2081
2082
                return [$percentage, $text];
2083
            }
2084
        }
2085
        // otherwise just continue the normal processing of progress
2086
        $total_items = $this->getTotalItemsCountWithoutDirs();
2087
        $completeItems = $this->get_complete_items_count();
2088
        if (0 != $add) {
2089
            $completeItems += $add;
2090
        }
2091
        if ($completeItems > $total_items) {
2092
            $completeItems = $total_items;
2093
        }
2094
        if ('%' === $mode) {
2095
            if ($total_items > 0) {
2096
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2097
            }
2098
            $percentage = number_format($percentage, 0);
2099
            $text = '%';
2100
        } elseif ('abs' === $mode) {
2101
            $percentage = $completeItems;
2102
            $text = '/'.$total_items;
2103
        }
2104
2105
        return [
2106
            $percentage,
2107
            $text,
2108
        ];
2109
    }
2110
2111
    /**
2112
     * Gets the progress bar mode.
2113
     *
2114
     * @return string The progress bar mode attribute
2115
     */
2116
    public function get_progress_bar_mode()
2117
    {
2118
        if (!empty($this->progress_bar_mode)) {
2119
            return $this->progress_bar_mode;
2120
        }
2121
2122
        return '%';
2123
    }
2124
2125
    /**
2126
     * Gets the learnpath theme (remote or local).
2127
     *
2128
     * @return string Learnpath theme
2129
     */
2130
    public function get_theme()
2131
    {
2132
        if (!empty($this->theme)) {
2133
            return $this->theme;
2134
        }
2135
2136
        return '';
2137
    }
2138
2139
    /**
2140
     * Gets the learnpath session id.
2141
     *
2142
     * @return int
2143
     */
2144
    public function get_lp_session_id()
2145
    {
2146
        if (!empty($this->lp_session_id)) {
2147
            return (int) $this->lp_session_id;
2148
        }
2149
2150
        return 0;
2151
    }
2152
2153
    /**
2154
     * Generate a new prerequisites string for a given item. If this item was a sco and
2155
     * its prerequisites were strings (instead of IDs), then transform those strings into
2156
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2157
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2158
     * same rule as the scormExport() method.
2159
     *
2160
     * @param int $item_id Item ID
2161
     *
2162
     * @return string Prerequisites string ready for the export as SCORM
2163
     */
2164
    public function get_scorm_prereq_string($item_id)
2165
    {
2166
        if ($this->debug > 0) {
2167
            error_log('In learnpath::get_scorm_prereq_string()');
2168
        }
2169
        if (!is_object($this->items[$item_id])) {
2170
            return false;
2171
        }
2172
        /** @var learnpathItem $oItem */
2173
        $oItem = $this->items[$item_id];
2174
        $prereq = $oItem->get_prereq_string();
2175
2176
        if (empty($prereq)) {
2177
            return '';
2178
        }
2179
        if (preg_match('/^\d+$/', $prereq) &&
2180
            isset($this->items[$prereq]) &&
2181
            is_object($this->items[$prereq])
2182
        ) {
2183
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2184
            // then simply return it (with the ITEM_ prefix).
2185
            //return 'ITEM_' . $prereq;
2186
            return $this->items[$prereq]->ref;
2187
        } else {
2188
            if (isset($this->refs_list[$prereq])) {
2189
                // It's a simple string item from which the ID can be found in the refs list,
2190
                // so we can transform it directly to an ID for export.
2191
                return $this->items[$this->refs_list[$prereq]]->ref;
2192
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2193
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2194
            } else {
2195
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2196
                // and replace them, one by one, by the internal IDs (chamilo db)
2197
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2198
                // by a space as well.
2199
                $find = [
2200
                    '&',
2201
                    '|',
2202
                    '~',
2203
                    '=',
2204
                    '<>',
2205
                    '{',
2206
                    '}',
2207
                    '*',
2208
                    '(',
2209
                    ')',
2210
                ];
2211
                $replace = [
2212
                    ' ',
2213
                    ' ',
2214
                    ' ',
2215
                    ' ',
2216
                    ' ',
2217
                    ' ',
2218
                    ' ',
2219
                    ' ',
2220
                    ' ',
2221
                    ' ',
2222
                ];
2223
                $prereq_mod = str_replace($find, $replace, $prereq);
2224
                $ids = explode(' ', $prereq_mod);
2225
                foreach ($ids as $id) {
2226
                    $id = trim($id);
2227
                    if (isset($this->refs_list[$id])) {
2228
                        $prereq = preg_replace(
2229
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2230
                            'ITEM_'.$this->refs_list[$id],
2231
                            $prereq
2232
                        );
2233
                    }
2234
                }
2235
2236
                return $prereq;
2237
            }
2238
        }
2239
    }
2240
2241
    /**
2242
     * Returns the XML DOM document's node.
2243
     *
2244
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2245
     * @param string   $id       The identifier to look for
2246
     *
2247
     * @return mixed The reference to the element found with that identifier. False if not found
2248
     */
2249
    public function get_scorm_xml_node(&$children, $id)
2250
    {
2251
        for ($i = 0; $i < $children->length; $i++) {
2252
            $item_temp = $children->item($i);
2253
            if ('item' === $item_temp->nodeName) {
2254
                if ($item_temp->getAttribute('identifier') == $id) {
2255
                    return $item_temp;
2256
                }
2257
            }
2258
            $subchildren = $item_temp->childNodes;
2259
            if ($subchildren && $subchildren->length > 0) {
2260
                $val = $this->get_scorm_xml_node($subchildren, $id);
2261
                if (is_object($val)) {
2262
                    return $val;
2263
                }
2264
            }
2265
        }
2266
2267
        return false;
2268
    }
2269
2270
    /**
2271
     * Gets the status list for all LP's items.
2272
     *
2273
     * @return array Array of [index] => [item ID => current status]
2274
     */
2275
    public function get_items_status_list()
2276
    {
2277
        $list = [];
2278
        foreach ($this->ordered_items as $item_id) {
2279
            $list[] = [
2280
                $item_id => $this->items[$item_id]->get_status(),
2281
            ];
2282
        }
2283
2284
        return $list;
2285
    }
2286
2287
    /**
2288
     * Return the number of interactions for the given learnpath Item View ID.
2289
     * This method can be used as static.
2290
     *
2291
     * @param int $lp_iv_id  Item View ID
2292
     * @param int $course_id course id
2293
     *
2294
     * @return int
2295
     */
2296
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2297
    {
2298
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2299
        $lp_iv_id = (int) $lp_iv_id;
2300
        $course_id = (int) $course_id;
2301
2302
        $sql = "SELECT count(*) FROM $table
2303
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2304
        $res = Database::query($sql);
2305
        $num = 0;
2306
        if (Database::num_rows($res)) {
2307
            $row = Database::fetch_array($res);
2308
            $num = $row[0];
2309
        }
2310
2311
        return $num;
2312
    }
2313
2314
    /**
2315
     * Return the interactions as an array for the given lp_iv_id.
2316
     * This method can be used as static.
2317
     *
2318
     * @param int $lp_iv_id Learnpath Item View ID
2319
     *
2320
     * @return array
2321
     *
2322
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2323
     */
2324
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2325
    {
2326
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2327
        $list = [];
2328
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2329
        $lp_iv_id = (int) $lp_iv_id;
2330
2331
        if (empty($lp_iv_id) || empty($course_id)) {
2332
            return [];
2333
        }
2334
2335
        $sql = "SELECT * FROM $table
2336
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2337
                ORDER BY order_id ASC";
2338
        $res = Database::query($sql);
2339
        $num = Database::num_rows($res);
2340
        if ($num > 0) {
2341
            $list[] = [
2342
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2343
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2344
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2345
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2346
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2347
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2348
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2349
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2350
                'student_response_formatted' => '',
2351
            ];
2352
            while ($row = Database::fetch_array($res)) {
2353
                $studentResponseFormatted = urldecode($row['student_response']);
2354
                $content_student_response = explode('__|', $studentResponseFormatted);
2355
                if (count($content_student_response) > 0) {
2356
                    if (count($content_student_response) >= 3) {
2357
                        // Pop the element off the end of array.
2358
                        array_pop($content_student_response);
2359
                    }
2360
                    $studentResponseFormatted = implode(',', $content_student_response);
2361
                }
2362
2363
                $list[] = [
2364
                    'order_id' => $row['order_id'] + 1,
2365
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2366
                    'type' => $row['interaction_type'],
2367
                    'time' => $row['completion_time'],
2368
                    'correct_responses' => '', // Hide correct responses from students.
2369
                    'student_response' => $row['student_response'],
2370
                    'result' => $row['result'],
2371
                    'latency' => $row['latency'],
2372
                    'student_response_formatted' => $studentResponseFormatted,
2373
                ];
2374
            }
2375
        }
2376
2377
        return $list;
2378
    }
2379
2380
    /**
2381
     * Return the number of objectives for the given learnpath Item View ID.
2382
     * This method can be used as static.
2383
     *
2384
     * @param int $lp_iv_id  Item View ID
2385
     * @param int $course_id Course ID
2386
     *
2387
     * @return int Number of objectives
2388
     */
2389
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2390
    {
2391
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2392
        $course_id = (int) $course_id;
2393
        $lp_iv_id = (int) $lp_iv_id;
2394
        $sql = "SELECT count(*) FROM $table
2395
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2396
        //@todo seems that this always returns 0
2397
        $res = Database::query($sql);
2398
        $num = 0;
2399
        if (Database::num_rows($res)) {
2400
            $row = Database::fetch_array($res);
2401
            $num = $row[0];
2402
        }
2403
2404
        return $num;
2405
    }
2406
2407
    /**
2408
     * Return the objectives as an array for the given lp_iv_id.
2409
     * This method can be used as static.
2410
     *
2411
     * @param int $lpItemViewId Learnpath Item View ID
2412
     * @param int $course_id
2413
     *
2414
     * @return array
2415
     *
2416
     * @todo    Translate labels
2417
     */
2418
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2419
    {
2420
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2421
        $lpItemViewId = (int) $lpItemViewId;
2422
2423
        if (empty($course_id) || empty($lpItemViewId)) {
2424
            return [];
2425
        }
2426
2427
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2428
        $sql = "SELECT * FROM $table
2429
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2430
                ORDER BY order_id ASC";
2431
        $res = Database::query($sql);
2432
        $num = Database::num_rows($res);
2433
        $list = [];
2434
        if ($num > 0) {
2435
            $list[] = [
2436
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2437
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2438
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2439
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2440
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2441
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2442
            ];
2443
            while ($row = Database::fetch_array($res)) {
2444
                $list[] = [
2445
                    'order_id' => $row['order_id'] + 1,
2446
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2447
                    'score_raw' => $row['score_raw'],
2448
                    'score_max' => $row['score_max'],
2449
                    'score_min' => $row['score_min'],
2450
                    'status' => $row['status'],
2451
                ];
2452
            }
2453
        }
2454
2455
        return $list;
2456
    }
2457
2458
    /**
2459
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2460
     * used by get_html_toc() to be ready to display.
2461
     */
2462
    public function get_toc(): array
2463
    {
2464
        $toc = [];
2465
        foreach ($this->ordered_items as $item_id) {
2466
            // TODO: Change this link generation and use new function instead.
2467
            $toc[] = [
2468
                'id' => $item_id,
2469
                'title' => $this->items[$item_id]->get_title(),
2470
                'status' => $this->items[$item_id]->get_status(false),
2471
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status(false)),
2472
                'level' => $this->items[$item_id]->get_level(),
2473
                'type' => $this->items[$item_id]->get_type(),
2474
                'description' => $this->items[$item_id]->get_description(),
2475
                'path' => $this->items[$item_id]->get_path(),
2476
                'parent' => $this->items[$item_id]->get_parent(),
2477
            ];
2478
        }
2479
2480
        return $toc;
2481
    }
2482
2483
    /**
2484
     * Returns the CSS class name associated with a given item status.
2485
     *
2486
     * @param $status string an item status
2487
     *
2488
     * @return string CSS class name
2489
     */
2490
    public static function getStatusCSSClassName($status)
2491
    {
2492
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2493
            return self::STATUS_CSS_CLASS_NAME[$status];
2494
        }
2495
2496
        return '';
2497
    }
2498
2499
    /**
2500
     * Generate and return the table of contents for this learnpath. The JS
2501
     * table returned is used inside of scorm_api.php.
2502
     *
2503
     * @param string $varname
2504
     *
2505
     * @return string A JS array variable construction
2506
     */
2507
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2508
    {
2509
        $toc = $varname.' = new Array();';
2510
        foreach ($this->ordered_items as $item_id) {
2511
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2512
        }
2513
2514
        return $toc;
2515
    }
2516
2517
    /**
2518
     * Gets the learning path type.
2519
     *
2520
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2521
     *
2522
     * @return mixed Type ID or name, depending on the parameter
2523
     */
2524
    public function get_type($get_name = false)
2525
    {
2526
        $res = false;
2527
        if (!empty($this->type) && (!$get_name)) {
2528
            $res = $this->type;
2529
        }
2530
2531
        return $res;
2532
    }
2533
2534
    /**
2535
     * Gets the learning path type as static method.
2536
     *
2537
     * @param int $lp_id
2538
     *
2539
     * @return mixed Type ID or name, depending on the parameter
2540
     */
2541
    public static function get_type_static($lp_id = 0)
2542
    {
2543
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2544
        $lp_id = (int) $lp_id;
2545
        $sql = "SELECT lp_type FROM $tbl_lp
2546
                WHERE iid = $lp_id";
2547
        $res = Database::query($sql);
2548
        if (false === $res) {
2549
            return null;
2550
        }
2551
        if (Database::num_rows($res) <= 0) {
2552
            return null;
2553
        }
2554
        $row = Database::fetch_array($res);
2555
2556
        return $row['lp_type'];
2557
    }
2558
2559
    /**
2560
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2561
     * This method can be used as abstract and is recursive.
2562
     *
2563
     * @param CLp $lp
2564
     * @param int $parent    Parent ID of the items to look for
2565
     *
2566
     * @return array Ordered list of item IDs (empty array on error)
2567
     */
2568
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2569
    {
2570
        $parent = (int) $parent;
2571
        $lpItemRepo = Container::getLpItemRepository();
2572
        if (empty($parent)) {
2573
            $rootItem = $lpItemRepo->getRootItem($lp->getIid());
2574
            if (null !== $rootItem) {
2575
                $parent = $rootItem->getIid();
2576
            }
2577
        }
2578
2579
        if (empty($parent)) {
2580
            return [];
2581
        }
2582
2583
        $criteria = new Criteria();
2584
        $criteria
2585
            ->where($criteria->expr()->neq('path', 'root'))
2586
            ->orderBy(
2587
                [
2588
                    'displayOrder' => Criteria::ASC,
2589
                ]
2590
            );
2591
        $items = $lp->getItems()->matching($criteria);
2592
        $items = $items->filter(
2593
            function (CLpItem $element) use ($parent) {
2594
                if ('root' === $element->getPath()) {
2595
                    return false;
2596
                }
2597
2598
                if (null !== $element->getParent()) {
2599
                    return $element->getParent()->getIid() === $parent;
2600
                }
2601
                return false;
2602
2603
            }
2604
        );
2605
        $list = [];
2606
        foreach ($items as $item) {
2607
            $itemId = $item->getIid();
2608
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2609
            $list[] = $itemId;
2610
            foreach ($sublist as $subItem) {
2611
                $list[] = $subItem;
2612
            }
2613
        }
2614
2615
        return $list;
2616
    }
2617
2618
    public static function getChapterTypes(): array
2619
    {
2620
        return [
2621
            'dir',
2622
        ];
2623
    }
2624
2625
    /**
2626
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2627
     *
2628
     * @return array HTML TOC ready to display
2629
     */
2630
    public function getListArrayToc()
2631
    {
2632
        $lpItemRepo = Container::getLpItemRepository();
2633
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
2634
        $options = [
2635
            'decorate' => false,
2636
        ];
2637
2638
        return $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2639
    }
2640
2641
    /**
2642
     * Returns an HTML-formatted string ready to display with teacher buttons
2643
     * in LP view menu.
2644
     *
2645
     * @return string HTML TOC ready to display
2646
     */
2647
    public function get_teacher_toc_buttons()
2648
    {
2649
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2650
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2651
        $html = '';
2652
        if ($isAllow && false == $hideIcons) {
2653
            if ($this->get_lp_session_id() == api_get_session_id()) {
2654
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2655
                $html .= '<div class="flex flex-row justify-center mb-2">';
2656
                $html .= "<a
2657
                    class='btn btn-sm btn--plain mx-1'
2658
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2659
                    target='_parent'>".
2660
                    Display::getMdiIcon('pencil').get_lang('Edit')."</a>";
2661
                $html .= '<a
2662
                    class="btn btn-sm btn--plain mx-1"
2663
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2664
                    Display::getMdiIcon('hammer-wrench').get_lang('Settings').'</a>';
2665
                $html .= '</div>';
2666
                $html .= '</div>';
2667
            }
2668
        }
2669
2670
        return $html;
2671
    }
2672
2673
    /**
2674
     * Gets the learnpath name/title.
2675
     *
2676
     * @return string Learnpath name/title
2677
     */
2678
    public function get_name()
2679
    {
2680
        if (!empty($this->name)) {
2681
            return $this->name;
2682
        }
2683
2684
        return 'N/A';
2685
    }
2686
2687
    /**
2688
     * @return string
2689
     */
2690
    public function getNameNoTags()
2691
    {
2692
        return strip_tags($this->get_name());
2693
    }
2694
2695
    /**
2696
     * Gets a link to the resource from the present location, depending on item ID.
2697
     *
2698
     * @param string $type         Type of link expected
2699
     * @param int    $item_id      Learnpath item ID
2700
     * @param bool   $provided_toc
2701
     *
2702
     * @return string $provided_toc Link to the lp_item resource
2703
     */
2704
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2705
    {
2706
        $course_id = $this->get_course_int_id();
2707
        $item_id = (int) $item_id;
2708
2709
        if (empty($item_id)) {
2710
            $item_id = $this->get_current_item_id();
2711
2712
            if (empty($item_id)) {
2713
                //still empty, this means there was no item_id given and we are not in an object context or
2714
                //the object property is empty, return empty link
2715
                $this->first();
2716
2717
                return '';
2718
            }
2719
        }
2720
2721
        $file = '';
2722
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2723
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2724
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2725
2726
        $sql = "SELECT
2727
                    l.lp_type as ltype,
2728
                    l.path as lpath,
2729
                    li.item_type as litype,
2730
                    li.path as lipath,
2731
                    li.parameters as liparams
2732
        		FROM $lp_table l
2733
                INNER JOIN $lp_item_table li
2734
                ON (li.lp_id = l.iid)
2735
        		WHERE
2736
        		    li.iid = $item_id
2737
        		";
2738
        $res = Database::query($sql);
2739
        if (Database::num_rows($res) > 0) {
2740
            $row = Database::fetch_array($res);
2741
            $lp_type = $row['ltype'];
2742
            $lp_path = $row['lpath'];
2743
            $lp_item_type = $row['litype'];
2744
            $lp_item_path = $row['lipath'];
2745
            $lp_item_params = $row['liparams'];
2746
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2747
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2748
            }
2749
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2750
            if ('http' === $type) {
2751
                //web path
2752
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2753
            } else {
2754
                //$course_path = $sys_course_path; //system path
2755
            }
2756
2757
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2758
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2759
            if (in_array(
2760
                $lp_item_type,
2761
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2762
            )
2763
            ) {
2764
                $lp_type = CLp::LP_TYPE;
2765
            }
2766
2767
            // Now go through the specific cases to get the end of the path
2768
            // @todo Use constants instead of int values.
2769
            switch ($lp_type) {
2770
                case CLp::LP_TYPE:
2771
                    $file = self::rl_get_resource_link_for_learnpath(
2772
                        $course_id,
2773
                        $this->get_id(),
2774
                        $item_id,
2775
                        $this->get_view_id()
2776
                    );
2777
                    switch ($lp_item_type) {
2778
                        case 'document':
2779
                            // Shows a button to download the file instead of just downloading the file directly.
2780
                            $documentPathInfo = pathinfo($file);
2781
                            if (isset($documentPathInfo['extension'])) {
2782
                                $parsed = parse_url($documentPathInfo['extension']);
2783
                                if (isset($parsed['path'])) {
2784
                                    $extension = $parsed['path'];
2785
                                    $extensionsToDownload = [
2786
                                        'zip',
2787
                                        'ppt',
2788
                                        'pptx',
2789
                                        'ods',
2790
                                        'xlsx',
2791
                                        'xls',
2792
                                        'csv',
2793
                                        'doc',
2794
                                        'docx',
2795
                                        'dot',
2796
                                    ];
2797
2798
                                    if (in_array($extension, $extensionsToDownload)) {
2799
                                        $file = api_get_path(WEB_CODE_PATH).
2800
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2801
                                    }
2802
                                }
2803
                            }
2804
                            break;
2805
                        case 'dir':
2806
                            $file = 'lp_content.php?type=dir';
2807
                            break;
2808
                        case 'link':
2809
                            if (Link::is_youtube_link($file)) {
2810
                                $src = Link::get_youtube_video_id($file);
2811
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2812
                            } elseif (Link::isVimeoLink($file)) {
2813
                                $src = Link::getVimeoLinkId($file);
2814
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2815
                            } else {
2816
                                // If the current site is HTTPS and the link is
2817
                                // HTTP, browsers will refuse opening the link
2818
                                $urlId = api_get_current_access_url_id();
2819
                                $url = api_get_access_url($urlId, false);
2820
                                $protocol = substr($url['url'], 0, 5);
2821
                                if ('https' === $protocol) {
2822
                                    $linkProtocol = substr($file, 0, 5);
2823
                                    if ('http:' === $linkProtocol) {
2824
                                        //this is the special intervention case
2825
                                        $file = api_get_path(WEB_CODE_PATH).
2826
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2827
                                    }
2828
                                }
2829
                            }
2830
                            break;
2831
                        case 'quiz':
2832
                            // Check how much attempts of a exercise exits in lp
2833
                            $lp_item_id = $this->get_current_item_id();
2834
                            $lp_view_id = $this->get_view_id();
2835
2836
                            $prevent_reinit = null;
2837
                            if (isset($this->items[$this->current])) {
2838
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2839
                            }
2840
2841
                            if (empty($provided_toc)) {
2842
                                $list = $this->get_toc();
2843
                            } else {
2844
                                $list = $provided_toc;
2845
                            }
2846
2847
                            $type_quiz = false;
2848
                            foreach ($list as $toc) {
2849
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2850
                                    $type_quiz = true;
2851
                                }
2852
                            }
2853
2854
                            if ($type_quiz) {
2855
                                $lp_item_id = (int) $lp_item_id;
2856
                                $lp_view_id = (int) $lp_view_id;
2857
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2858
                                        WHERE
2859
                                            lp_item_id='".$lp_item_id."' AND
2860
                                            lp_view_id ='".$lp_view_id."' AND
2861
                                            status='completed'";
2862
                                $result = Database::query($sql);
2863
                                $row_count = Database:: fetch_row($result);
2864
                                $count_item_view = (int) $row_count[0];
2865
                                $not_multiple_attempt = 0;
2866
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2867
                                    $not_multiple_attempt = 1;
2868
                                }
2869
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2870
                            }
2871
                            break;
2872
                    }
2873
2874
                    $tmp_array = explode('/', $file);
2875
                    $document_name = $tmp_array[count($tmp_array) - 1];
2876
                    if (strpos($document_name, '_DELETED_')) {
2877
                        $file = 'blank.php?error=document_deleted';
2878
                    }
2879
                    break;
2880
                case CLp::SCORM_TYPE:
2881
                    if ('dir' !== $lp_item_type) {
2882
                        // Quite complex here:
2883
                        // We want to make sure 'http://' (and similar) links can
2884
                        // be loaded as is (withouth the Chamilo path in front) but
2885
                        // some contents use this form: resource.htm?resource=http://blablabla
2886
                        // which means we have to find a protocol at the path's start, otherwise
2887
                        // it should not be considered as an external URL.
2888
                        // if ($this->prerequisites_match($item_id)) {
2889
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2890
                            if ($this->debug > 2) {
2891
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2892
                            }
2893
                            // Distant url, return as is.
2894
                            $file = $lp_item_path;
2895
                        } else {
2896
                            if ($this->debug > 2) {
2897
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2898
                            }
2899
                            // Prevent getting untranslatable urls.
2900
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2901
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2902
2903
                            /*$asset = $this->getEntity()->getAsset();
2904
                            $folder = Container::getAssetRepository()->getFolder($asset);
2905
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2906
                            $file = null;
2907
                            if ($hasFile) {
2908
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2909
                            }*/
2910
                            $file = $this->scormUrl.$lp_item_path;
2911
2912
                            // Prepare the path.
2913
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2914
                            // TODO: Fix this for urls with protocol header.
2915
                            $file = str_replace('//', '/', $file);
2916
                            $file = str_replace(':/', '://', $file);
2917
                            if ('/' === substr($lp_path, -1)) {
2918
                                $lp_path = substr($lp_path, 0, -1);
2919
                            }*/
2920
                            /*if (!$hasFile) {
2921
                                // if file not found.
2922
                                $decoded = html_entity_decode($lp_item_path);
2923
                                [$decoded] = explode('?', $decoded);
2924
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
2925
                                    $file = self::rl_get_resource_link_for_learnpath(
2926
                                        $course_id,
2927
                                        $this->get_id(),
2928
                                        $item_id,
2929
                                        $this->get_view_id()
2930
                                    );
2931
                                    if (empty($file)) {
2932
                                        $file = 'blank.php?error=document_not_found';
2933
                                    } else {
2934
                                        $tmp_array = explode('/', $file);
2935
                                        $document_name = $tmp_array[count($tmp_array) - 1];
2936
                                        if (strpos($document_name, '_DELETED_')) {
2937
                                            $file = 'blank.php?error=document_deleted';
2938
                                        } else {
2939
                                            $file = 'blank.php?error=document_not_found';
2940
                                        }
2941
                                    }
2942
                                } else {
2943
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
2944
                                }
2945
                            }*/
2946
                        }
2947
2948
                        // We want to use parameters if they were defined in the imsmanifest
2949
                        if (false === strpos($file, 'blank.php')) {
2950
                            $lp_item_params = ltrim($lp_item_params, '?');
2951
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
2952
                        }
2953
                    } else {
2954
                        $file = 'lp_content.php?type=dir';
2955
                    }
2956
                    break;
2957
                case CLp::AICC_TYPE:
2958
                    // Formatting AICC HACP append URL.
2959
                    $aicc_append = '?aicc_sid='.
2960
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
2961
                    if (!empty($lp_item_params)) {
2962
                        $aicc_append .= $lp_item_params.'&';
2963
                    }
2964
                    if ('dir' !== $lp_item_type) {
2965
                        // Quite complex here:
2966
                        // We want to make sure 'http://' (and similar) links can
2967
                        // be loaded as is (withouth the Chamilo path in front) but
2968
                        // some contents use this form: resource.htm?resource=http://blablabla
2969
                        // which means we have to find a protocol at the path's start, otherwise
2970
                        // it should not be considered as an external URL.
2971
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2972
                            if ($this->debug > 2) {
2973
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2974
                            }
2975
                            // Distant url, return as is.
2976
                            $file = $lp_item_path;
2977
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
2978
                            /*
2979
                            if (stristr($file,'<servername>') !== false) {
2980
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
2981
                            }
2982
                            */
2983
                            if (false !== stripos($file, '<servername>')) {
2984
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
2985
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
2986
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
2987
                            }
2988
2989
                            $file .= $aicc_append;
2990
                        } else {
2991
                            if ($this->debug > 2) {
2992
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
2993
                            }
2994
                            // Prevent getting untranslatable urls.
2995
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2996
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2997
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
2998
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
2999
                            // TODO: Fix this for urls with protocol header.
3000
                            $file = str_replace('//', '/', $file);
3001
                            $file = str_replace(':/', '://', $file);
3002
                            $file .= $aicc_append;
3003
                        }
3004
                    } else {
3005
                        $file = 'lp_content.php?type=dir';
3006
                    }
3007
                    break;
3008
                case 4:
3009
                default:
3010
                    break;
3011
            }
3012
            // Replace &amp; by & because &amp; will break URL with params
3013
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3014
        }
3015
        if ($this->debug > 2) {
3016
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3017
        }
3018
3019
        return $file;
3020
    }
3021
3022
    /**
3023
     * Gets the latest usable view or generate a new one.
3024
     *
3025
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3026
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3027
     *
3028
     * @return int DB lp_view id
3029
     */
3030
    public function get_view($attempt_num = 0, $userId = null)
3031
    {
3032
        $search = '';
3033
        $attempt_num = (int) $attempt_num;
3034
        // Use $attempt_num to enable multi-views management (disabled so far).
3035
        if (!empty($attempt_num)) {
3036
            $search = 'AND view_count = '.$attempt_num;
3037
        }
3038
3039
        $course_id = api_get_course_int_id();
3040
        $sessionId = api_get_session_id();
3041
3042
        // Check user ID.
3043
        if (empty($userId)) {
3044
            if (empty($this->get_user_id())) {
3045
                $this->error = 'User ID is empty in learnpath::get_view()';
3046
3047
                return null;
3048
            } else {
3049
                $userId = $this->get_user_id();
3050
            }
3051
        }
3052
        $sessionCondition = api_get_session_condition($sessionId);
3053
3054
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3055
        $table = Database::get_course_table(TABLE_LP_VIEW);
3056
        $sql = "SELECT iid FROM $table
3057
        		WHERE
3058
        		    c_id = $course_id AND
3059
        		    lp_id = ".$this->get_id()." AND
3060
        		    user_id = ".$userId."
3061
        		    $sessionCondition
3062
        		    $search
3063
                ORDER BY view_count DESC";
3064
        $res = Database::query($sql);
3065
        if (Database::num_rows($res) > 0) {
3066
            $row = Database::fetch_array($res);
3067
            $this->lp_view_id = $row['iid'];
3068
        } elseif (!api_is_invitee()) {
3069
            $params = [
3070
                'c_id' => $course_id,
3071
                'lp_id' => $this->get_id(),
3072
                'user_id' => $this->get_user_id(),
3073
                'view_count' => 1,
3074
                'last_item' => 0,
3075
            ];
3076
            if (!empty($sessionId)) {
3077
                $params['session_id']  = $sessionId;
3078
            }
3079
            $this->lp_view_id = Database::insert($table, $params);
3080
        }
3081
3082
        return $this->lp_view_id;
3083
    }
3084
3085
    /**
3086
     * Gets the current view id.
3087
     *
3088
     * @return int View ID (from lp_view)
3089
     */
3090
    public function get_view_id()
3091
    {
3092
        if (!empty($this->lp_view_id)) {
3093
            return (int) $this->lp_view_id;
3094
        }
3095
3096
        return 0;
3097
    }
3098
3099
    /**
3100
     * Gets the update queue.
3101
     *
3102
     * @return array Array containing IDs of items to be updated by JavaScript
3103
     */
3104
    public function get_update_queue()
3105
    {
3106
        return $this->update_queue;
3107
    }
3108
3109
    /**
3110
     * Gets the user ID.
3111
     *
3112
     * @return int User ID
3113
     */
3114
    public function get_user_id()
3115
    {
3116
        if (!empty($this->user_id)) {
3117
            return (int) $this->user_id;
3118
        }
3119
3120
        return false;
3121
    }
3122
3123
    /**
3124
     * Checks if any of the items has an audio element attached.
3125
     *
3126
     * @return bool True or false
3127
     */
3128
    public function has_audio()
3129
    {
3130
        $has = false;
3131
        foreach ($this->items as $i => $item) {
3132
            if (!empty($this->items[$i]->audio)) {
3133
                $has = true;
3134
                break;
3135
            }
3136
        }
3137
3138
        return $has;
3139
    }
3140
3141
    /**
3142
     * Updates learnpath attributes to point to the next element
3143
     * The last part is similar to set_current_item but processing the other way around.
3144
     */
3145
    public function next()
3146
    {
3147
        if ($this->debug > 0) {
3148
            error_log('In learnpath::next()', 0);
3149
        }
3150
        $this->last = $this->get_current_item_id();
3151
        $this->items[$this->last]->save(
3152
            false,
3153
            $this->prerequisites_match($this->last)
3154
        );
3155
        $this->autocomplete_parents($this->last);
3156
        $new_index = $this->get_next_index();
3157
        if ($this->debug > 2) {
3158
            error_log('New index: '.$new_index, 0);
3159
        }
3160
        $this->index = $new_index;
3161
        if ($this->debug > 2) {
3162
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3163
        }
3164
        $this->current = $this->ordered_items[$new_index];
3165
        if ($this->debug > 2) {
3166
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3167
        }
3168
    }
3169
3170
    /**
3171
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3172
     * class, this might be redefined to allow several behaviours depending on the document type.
3173
     *
3174
     * @param int $id Resource ID
3175
     */
3176
    public function open($id)
3177
    {
3178
        // TODO:
3179
        // set the current resource attribute to this resource
3180
        // switch on element type (redefine in child class?)
3181
        // set status for this item to "opened"
3182
        // start timer
3183
        // initialise score
3184
        $this->index = 0; //or = the last item seen (see $this->last)
3185
    }
3186
3187
    /**
3188
     * Check that all prerequisites are fulfilled. Returns true and an
3189
     * empty string on success, returns false
3190
     * and the prerequisite string on error.
3191
     * This function is based on the rules for aicc_script language as
3192
     * described in the SCORM 1.2 CAM documentation page 108.
3193
     *
3194
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3195
     *
3196
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3197
     *              string otherwise
3198
     */
3199
    public function prerequisites_match($itemId = null)
3200
    {
3201
        $allow = ('true' === api_get_setting('lp.allow_teachers_to_access_blocked_lp_by_prerequisite'));
3202
        if ($allow) {
3203
            if (api_is_allowed_to_edit() ||
3204
                api_is_platform_admin(true) ||
3205
                api_is_drh() ||
3206
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3207
            ) {
3208
                return true;
3209
            }
3210
        }
3211
3212
        $debug = $this->debug;
3213
        if ($debug > 0) {
3214
            error_log('In learnpath::prerequisites_match()');
3215
        }
3216
3217
        if (empty($itemId)) {
3218
            $itemId = $this->current;
3219
        }
3220
3221
        $currentItem = $this->getItem($itemId);
3222
3223
        if ($currentItem) {
3224
            if (2 == $this->type) {
3225
                // Getting prereq from scorm
3226
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3227
            } else {
3228
                $prereq_string = $currentItem->get_prereq_string();
3229
            }
3230
3231
            if (empty($prereq_string)) {
3232
                if ($debug > 0) {
3233
                    error_log('Found prereq_string is empty return true');
3234
                }
3235
3236
                return true;
3237
            }
3238
3239
            // Clean spaces.
3240
            $prereq_string = str_replace(' ', '', $prereq_string);
3241
            if ($debug > 0) {
3242
                error_log('Found prereq_string: '.$prereq_string, 0);
3243
            }
3244
3245
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3246
            $result = $currentItem->parse_prereq(
3247
                $prereq_string,
3248
                $this->items,
3249
                $this->refs_list,
3250
                $this->get_user_id()
3251
            );
3252
3253
            if (false === $result) {
3254
                $this->set_error_msg($currentItem->prereq_alert);
3255
            }
3256
        } else {
3257
            $result = true;
3258
            if ($debug > 1) {
3259
                error_log('$this->items['.$itemId.'] was not an object', 0);
3260
            }
3261
        }
3262
3263
        if ($debug > 1) {
3264
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3265
        }
3266
3267
        return $result;
3268
    }
3269
3270
    /**
3271
     * Updates learnpath attributes to point to the previous element
3272
     * The last part is similar to set_current_item but processing the other way around.
3273
     */
3274
    public function previous()
3275
    {
3276
        $this->last = $this->get_current_item_id();
3277
        $this->items[$this->last]->save(
3278
            false,
3279
            $this->prerequisites_match($this->last)
3280
        );
3281
        $this->autocomplete_parents($this->last);
3282
        $new_index = $this->get_previous_index();
3283
        $this->index = $new_index;
3284
        $this->current = $this->ordered_items[$new_index];
3285
    }
3286
3287
    /**
3288
     * Publishes a learnpath. This basically means show or hide the learnpath
3289
     * to normal users.
3290
     * Can be used as abstract.
3291
     *
3292
     * @param int $id         Learnpath ID
3293
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3294
     *
3295
     * @return bool
3296
     */
3297
    public static function toggleVisibility($id, $visibility = 1)
3298
    {
3299
        $repo = Container::getLpRepository();
3300
        $lp = $repo->find($id);
3301
3302
        if (!$lp) {
3303
            return false;
3304
        }
3305
3306
        $visibility = (int) $visibility;
3307
3308
        if (1 === $visibility) {
3309
            $repo->setVisibilityPublished($lp);
3310
        } else {
3311
            $repo->setVisibilityDraft($lp);
3312
        }
3313
3314
        return true;
3315
    }
3316
3317
    /**
3318
     * Publishes a learnpath category.
3319
     * This basically means show or hide the learnpath category to normal users.
3320
     *
3321
     * @param int $id
3322
     * @param int $visibility
3323
     *
3324
     * @return bool
3325
     */
3326
    public static function toggleCategoryVisibility($id, $visibility = 1)
3327
    {
3328
        $repo = Container::getLpCategoryRepository();
3329
        $resource = $repo->find($id);
3330
3331
        if (!$resource) {
3332
            return false;
3333
        }
3334
3335
        $visibility = (int) $visibility;
3336
3337
        if (1 === $visibility) {
3338
            $repo->setVisibilityPublished($resource);
3339
        } else {
3340
            $repo->setVisibilityDraft($resource);
3341
            self::toggleCategoryPublish($id, 0);
3342
        }
3343
3344
        return false;
3345
    }
3346
3347
    /**
3348
     * Publishes a learnpath. This basically means show or hide the learnpath
3349
     * on the course homepage.
3350
     *
3351
     * @param int    $id            Learnpath id
3352
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3353
     *
3354
     * @return bool
3355
     */
3356
    public static function togglePublish($id, $setVisibility = 'v')
3357
    {
3358
        $addShortcut = false;
3359
        if ('v' === $setVisibility) {
3360
            $addShortcut = true;
3361
        }
3362
        $repo = Container::getLpRepository();
3363
        /** @var CLp|null $lp */
3364
        $lp = $repo->find($id);
3365
        if (null === $lp) {
3366
            return false;
3367
        }
3368
        $repoShortcut = Container::getShortcutRepository();
3369
        if ($addShortcut) {
3370
            $repoShortcut->addShortCut($lp, api_get_user_entity(), api_get_course_entity(), api_get_session_entity());
3371
        } else {
3372
            $repoShortcut->removeShortCut($lp);
3373
        }
3374
3375
        return true;
3376
    }
3377
3378
    /**
3379
     * Show or hide the learnpath category on the course homepage.
3380
     *
3381
     * @param int $id
3382
     * @param int $setVisibility
3383
     *
3384
     * @return bool
3385
     */
3386
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3387
    {
3388
        $setVisibility = (int) $setVisibility;
3389
        $addShortcut = false;
3390
        if (1 === $setVisibility) {
3391
            $addShortcut = true;
3392
        }
3393
3394
        $repo = Container::getLpCategoryRepository();
3395
        /** @var CLpCategory|null $lp */
3396
        $category = $repo->find($id);
3397
3398
        if (null === $category) {
3399
            return false;
3400
        }
3401
3402
        $repoShortcut = Container::getShortcutRepository();
3403
        if ($addShortcut) {
3404
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3405
            $repoShortcut->addShortCut($category, api_get_user_entity(), $courseEntity, api_get_session_entity());
3406
        } else {
3407
            $repoShortcut->removeShortCut($category);
3408
        }
3409
3410
        return true;
3411
    }
3412
3413
    /**
3414
     * Check if the learnpath category is visible for a user.
3415
     *
3416
     * @return bool
3417
     */
3418
    public static function categoryIsVisibleForStudent(
3419
        CLpCategory $category,
3420
        User $user,
3421
        Course $course,
3422
        SessionEntity $session = null
3423
    ) {
3424
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3425
3426
        if ($isAllowedToEdit) {
3427
            return true;
3428
        }
3429
3430
        $categoryVisibility = $category->isVisible($course, $session);
3431
3432
        if (!$categoryVisibility) {
3433
            return false;
3434
        }
3435
3436
        $subscriptionSettings = self::getSubscriptionSettings();
3437
3438
        if (false === $subscriptionSettings['allow_add_users_to_lp_category']) {
3439
            return true;
3440
        }
3441
3442
        $noUserSubscribed = false;
3443
        $noGroupSubscribed = true;
3444
        $users = $category->getUsers();
3445
        if (empty($users) || !$users->count()) {
3446
            $noUserSubscribed = true;
3447
        } elseif ($category->hasUserAdded($user)) {
3448
            return true;
3449
        }
3450
3451
        //$groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3452
3453
        return $noGroupSubscribed && $noUserSubscribed;
3454
    }
3455
3456
    /**
3457
     * Check if a learnpath category is published as course tool.
3458
     *
3459
     * @param int $courseId
3460
     *
3461
     * @return bool
3462
     */
3463
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3464
    {
3465
        return false;
3466
        $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...
3467
        $em = Database::getManager();
3468
3469
        $tools = $em
3470
            ->createQuery("
3471
                SELECT t FROM ChamiloCourseBundle:CTool t
3472
                WHERE t.course = :course AND
3473
                    t.name = :name AND
3474
                    t.image LIKE 'lp_category.%' AND
3475
                    t.link LIKE :link
3476
            ")
3477
            ->setParameters([
3478
                'course' => $courseId,
3479
                'name' => strip_tags($category->getName()),
3480
                'link' => "$link%",
3481
            ])
3482
            ->getResult();
3483
3484
        /** @var CTool $tool */
3485
        $tool = current($tools);
3486
3487
        return $tool ? $tool->getVisibility() : false;
3488
    }
3489
3490
    /**
3491
     * Restart the whole learnpath. Return the URL of the first element.
3492
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3493
     * To use a similar method  statically, use the create_new_attempt() method.
3494
     *
3495
     * @return bool
3496
     */
3497
    public function restart()
3498
    {
3499
        if ($this->debug > 0) {
3500
            error_log('In learnpath::restart()', 0);
3501
        }
3502
        // TODO
3503
        // Call autosave method to save the current progress.
3504
        //$this->index = 0;
3505
        if (api_is_invitee()) {
3506
            return false;
3507
        }
3508
        $session_id = api_get_session_id();
3509
        $course_id = api_get_course_int_id();
3510
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3511
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3512
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3513
        if ($this->debug > 2) {
3514
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3515
        }
3516
        Database::query($sql);
3517
        $view_id = Database::insert_id();
3518
3519
        if ($view_id) {
3520
            $this->lp_view_id = $view_id;
3521
            $this->attempt = $this->attempt + 1;
3522
        } else {
3523
            $this->error = 'Could not insert into item_view table...';
3524
3525
            return false;
3526
        }
3527
        $this->autocomplete_parents($this->current);
3528
        foreach ($this->items as $index => $dummy) {
3529
            $this->items[$index]->restart();
3530
            $this->items[$index]->set_lp_view($this->lp_view_id);
3531
        }
3532
        $this->first();
3533
3534
        return true;
3535
    }
3536
3537
    /**
3538
     * Saves the current item.
3539
     *
3540
     * @return bool
3541
     */
3542
    public function save_current()
3543
    {
3544
        $debug = $this->debug;
3545
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3546
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3547
        if ($debug) {
3548
            error_log('save_current() saving item '.$this->current, 0);
3549
            error_log(''.print_r($this->items, true), 0);
3550
        }
3551
        if (isset($this->items[$this->current]) &&
3552
            is_object($this->items[$this->current])
3553
        ) {
3554
            if ($debug) {
3555
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3556
            }
3557
3558
            $res = $this->items[$this->current]->save(
3559
                false,
3560
                $this->prerequisites_match($this->current)
3561
            );
3562
            $this->autocomplete_parents($this->current);
3563
            $status = $this->items[$this->current]->get_status();
3564
            $this->update_queue[$this->current] = $status;
3565
3566
            if ($debug) {
3567
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3568
            }
3569
3570
            return $res;
3571
        }
3572
3573
        return false;
3574
    }
3575
3576
    /**
3577
     * Saves the given item.
3578
     *
3579
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3580
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3581
     *
3582
     * @return bool
3583
     */
3584
    public function save_item($item_id = null, $from_outside = true)
3585
    {
3586
        $debug = $this->debug;
3587
        if ($debug) {
3588
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3589
        }
3590
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3591
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3592
        if (empty($item_id)) {
3593
            $item_id = (int) $_REQUEST['id'];
3594
        }
3595
3596
        if (empty($item_id)) {
3597
            $item_id = $this->get_current_item_id();
3598
        }
3599
        if (isset($this->items[$item_id]) &&
3600
            is_object($this->items[$item_id])
3601
        ) {
3602
            if ($debug) {
3603
                error_log('Object exists');
3604
            }
3605
3606
            // Saving the item.
3607
            $res = $this->items[$item_id]->save(
3608
                $from_outside,
3609
                $this->prerequisites_match($item_id)
3610
            );
3611
3612
            if ($debug) {
3613
                error_log('update_queue before:');
3614
                error_log(print_r($this->update_queue, 1));
3615
            }
3616
            $this->autocomplete_parents($item_id);
3617
3618
            $status = $this->items[$item_id]->get_status();
3619
            $this->update_queue[$item_id] = $status;
3620
3621
            if ($debug) {
3622
                error_log('get_status(): '.$status);
3623
                error_log('update_queue after:');
3624
                error_log(print_r($this->update_queue, 1));
3625
            }
3626
3627
            return $res;
3628
        }
3629
3630
        return false;
3631
    }
3632
3633
    /**
3634
     * Saves the last item seen's ID only in case.
3635
     */
3636
    public function save_last()
3637
    {
3638
        $course_id = api_get_course_int_id();
3639
        $debug = $this->debug;
3640
        if ($debug) {
3641
            error_log('In learnpath::save_last()', 0);
3642
        }
3643
        $session_condition = api_get_session_condition(
3644
            api_get_session_id(),
3645
            true,
3646
            false
3647
        );
3648
        $table = Database::get_course_table(TABLE_LP_VIEW);
3649
3650
        $userId = $this->get_user_id();
3651
        if (empty($userId)) {
3652
            $userId = api_get_user_id();
3653
            if ($debug) {
3654
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
3655
            }
3656
        }
3657
        if (isset($this->current) && !api_is_invitee()) {
3658
            if ($debug) {
3659
                error_log('Saving current item ('.$this->current.') for later review', 0);
3660
            }
3661
            $sql = "UPDATE $table SET
3662
                        last_item = ".$this->get_current_item_id()."
3663
                    WHERE
3664
                        c_id = $course_id AND
3665
                        lp_id = ".$this->get_id()." AND
3666
                        user_id = ".$userId." ".$session_condition;
3667
3668
            if ($debug) {
3669
                error_log('Saving last item seen : '.$sql, 0);
3670
            }
3671
            Database::query($sql);
3672
        }
3673
3674
        if (!api_is_invitee()) {
3675
            // Save progress.
3676
            [$progress] = $this->get_progress_bar_text('%');
3677
            $scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable'));
3678
            $scoreAsProgress = $this->getUseScoreAsProgress();
3679
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
3680
                if ($debug) {
3681
                    error_log("Return false: Dont save score: $score");
3682
                    error_log("progress: $progress");
3683
                }
3684
3685
                return false;
3686
            }
3687
3688
            if ($scoreAsProgress && $scoreAsProgressSetting) {
3689
                $storedProgress = self::getProgress(
3690
                    $this->get_id(),
3691
                    $userId,
3692
                    $course_id,
3693
                    $this->get_lp_session_id()
3694
                );
3695
3696
                // Check if the stored progress is higher than the new value
3697
                if ($storedProgress >= $progress) {
3698
                    if ($debug) {
3699
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
3700
                    }
3701
3702
                    return false;
3703
                }
3704
            }
3705
            if ($progress >= 0 && $progress <= 100) {
3706
                $progress = (int) $progress;
3707
                $sql = "UPDATE $table SET
3708
                            progress = $progress
3709
                        WHERE
3710
                            c_id = $course_id AND
3711
                            lp_id = ".$this->get_id()." AND
3712
                            user_id = ".$userId." ".$session_condition;
3713
                // Ignore errors as some tables might not have the progress field just yet.
3714
                Database::query($sql);
3715
                $this->progress_db = $progress;
3716
            }
3717
        }
3718
    }
3719
3720
    /**
3721
     * Sets the current item ID (checks if valid and authorized first).
3722
     *
3723
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
3724
     */
3725
    public function set_current_item($item_id = null)
3726
    {
3727
        $debug = $this->debug;
3728
        if ($debug) {
3729
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
3730
        }
3731
        if (empty($item_id)) {
3732
            if ($debug) {
3733
                error_log('No new current item given, ignore...', 0);
3734
            }
3735
            // Do nothing.
3736
        } else {
3737
            if ($debug) {
3738
                error_log('New current item given is '.$item_id.'...', 0);
3739
            }
3740
            if (is_numeric($item_id)) {
3741
                $item_id = (int) $item_id;
3742
                // TODO: Check in database here.
3743
                $this->last = $this->current;
3744
                $this->current = $item_id;
3745
                // TODO: Update $this->index as well.
3746
                foreach ($this->ordered_items as $index => $item) {
3747
                    if ($item == $this->current) {
3748
                        $this->index = $index;
3749
                        break;
3750
                    }
3751
                }
3752
                if ($debug) {
3753
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
3754
                }
3755
            } else {
3756
                if ($debug) {
3757
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
3758
                }
3759
            }
3760
        }
3761
    }
3762
3763
    /**
3764
     * Set index specified prefix terms for all items in this path.
3765
     *
3766
     * @param string $terms_string Comma-separated list of terms
3767
     * @param string $prefix       Xapian term prefix
3768
     *
3769
     * @return bool False on error, true otherwise
3770
     */
3771
    public function set_terms_by_prefix($terms_string, $prefix)
3772
    {
3773
        $course_id = api_get_course_int_id();
3774
        if ('true' !== api_get_setting('search_enabled')) {
3775
            return false;
3776
        }
3777
3778
        if (!extension_loaded('xapian')) {
3779
            return false;
3780
        }
3781
3782
        $terms_string = trim($terms_string);
3783
        $terms = explode(',', $terms_string);
3784
        array_walk($terms, 'trim_value');
3785
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
3786
3787
        // Don't do anything if no change, verify only at DB, not the search engine.
3788
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
3789
            return false;
3790
        }
3791
3792
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
3793
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
3794
3795
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
3796
        // TODO: Make query secure agains XSS : use member attr instead of post var.
3797
        $lp_id = (int) $_POST['lp_id'];
3798
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
3799
        $result = Database::query($sql);
3800
        $di = new ChamiloIndexer();
3801
3802
        while ($lp_item = Database::fetch_array($result)) {
3803
            // Get search_did.
3804
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
3805
            $sql = 'SELECT * FROM %s
3806
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
3807
                    LIMIT 1';
3808
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
3809
3810
            //echo $sql; echo '<br>';
3811
            $res = Database::query($sql);
3812
            if (Database::num_rows($res) > 0) {
3813
                $se_ref = Database::fetch_array($res);
3814
                // Compare terms.
3815
                $doc = $di->get_document($se_ref['search_did']);
3816
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
3817
                $xterms = [];
3818
                foreach ($xapian_terms as $xapian_term) {
3819
                    $xterms[] = substr($xapian_term['name'], 1);
3820
                }
3821
3822
                $dterms = $terms;
3823
                $missing_terms = array_diff($dterms, $xterms);
3824
                $deprecated_terms = array_diff($xterms, $dterms);
3825
3826
                // Save it to search engine.
3827
                foreach ($missing_terms as $term) {
3828
                    $doc->add_term($prefix.$term, 1);
3829
                }
3830
                foreach ($deprecated_terms as $term) {
3831
                    $doc->remove_term($prefix.$term);
3832
                }
3833
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
3834
                $di->getDb()->flush();
3835
            }
3836
        }
3837
3838
        return true;
3839
    }
3840
3841
    /**
3842
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
3843
     *
3844
     * @param int $id DB ID of the item
3845
     */
3846
    public function set_previous_item($id)
3847
    {
3848
        if ($this->debug > 0) {
3849
            error_log('In learnpath::set_previous_item()', 0);
3850
        }
3851
        $this->last = $id;
3852
    }
3853
3854
    /**
3855
     * Sets and saves the expired_on date.
3856
     *
3857
     * @return bool Returns true if author's name is not empty
3858
     */
3859
    public function set_modified_on()
3860
    {
3861
        $this->modified_on = api_get_utc_datetime();
3862
        $table = Database::get_course_table(TABLE_LP_MAIN);
3863
        $lp_id = $this->get_id();
3864
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
3865
                WHERE iid = $lp_id";
3866
        Database::query($sql);
3867
3868
        return true;
3869
    }
3870
3871
    /**
3872
     * Sets the object's error message.
3873
     *
3874
     * @param string $error Error message. If empty, reinits the error string
3875
     */
3876
    public function set_error_msg($error = '')
3877
    {
3878
        if ($this->debug > 0) {
3879
            error_log('In learnpath::set_error_msg()', 0);
3880
        }
3881
        if (empty($error)) {
3882
            $this->error = '';
3883
        } else {
3884
            $this->error .= $error;
3885
        }
3886
    }
3887
3888
    /**
3889
     * Launches the current item if not 'sco'
3890
     * (starts timer and make sure there is a record ready in the DB).
3891
     *
3892
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
3893
     *
3894
     * @return bool
3895
     */
3896
    public function start_current_item($allow_new_attempt = false)
3897
    {
3898
        $debug = $this->debug;
3899
        if ($debug) {
3900
            error_log('In learnpath::start_current_item()');
3901
            error_log('current: '.$this->current);
3902
        }
3903
        if (0 != $this->current && isset($this->items[$this->current]) &&
3904
            is_object($this->items[$this->current])
3905
        ) {
3906
            $type = $this->get_type();
3907
            $item_type = $this->items[$this->current]->get_type();
3908
            if ($debug) {
3909
                error_log('item type: '.$item_type);
3910
                error_log('lp type: '.$type);
3911
            }
3912
            if ((2 == $type && 'sco' !== $item_type) ||
3913
                (3 == $type && 'au' !== $item_type) ||
3914
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
3915
            ) {
3916
                $this->items[$this->current]->open($allow_new_attempt);
3917
                $this->autocomplete_parents($this->current);
3918
                $prereq_check = $this->prerequisites_match($this->current);
3919
                if ($debug) {
3920
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
3921
                }
3922
                $this->items[$this->current]->save(false, $prereq_check);
3923
            }
3924
            // If sco, then it is supposed to have been updated by some other call.
3925
            if ('sco' === $item_type) {
3926
                $this->items[$this->current]->restart();
3927
            }
3928
        }
3929
        if ($debug) {
3930
            error_log('lp_view_session_id');
3931
            error_log($this->lp_view_session_id);
3932
            error_log('api session id');
3933
            error_log(api_get_session_id());
3934
            error_log('End of learnpath::start_current_item()');
3935
        }
3936
3937
        return true;
3938
    }
3939
3940
    /**
3941
     * Stops the processing and counters for the old item (as held in $this->last).
3942
     *
3943
     * @return bool True/False
3944
     */
3945
    public function stop_previous_item()
3946
    {
3947
        $debug = $this->debug;
3948
        if ($debug) {
3949
            error_log('In learnpath::stop_previous_item()', 0);
3950
        }
3951
3952
        if (0 != $this->last && $this->last != $this->current &&
3953
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
3954
        ) {
3955
            if ($debug) {
3956
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
3957
            }
3958
            switch ($this->get_type()) {
3959
                case '3':
3960
                    if ('au' != $this->items[$this->last]->get_type()) {
3961
                        if ($debug) {
3962
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
3963
                        }
3964
                        $this->items[$this->last]->close();
3965
                    } else {
3966
                        if ($debug) {
3967
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
3968
                        }
3969
                    }
3970
                    break;
3971
                case '2':
3972
                    if ('sco' != $this->items[$this->last]->get_type()) {
3973
                        if ($debug) {
3974
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
3975
                        }
3976
                        $this->items[$this->last]->close();
3977
                    } else {
3978
                        if ($debug) {
3979
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
3980
                        }
3981
                    }
3982
                    break;
3983
                case '1':
3984
                default:
3985
                    if ($debug) {
3986
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
3987
                    }
3988
                    $this->items[$this->last]->close();
3989
                    break;
3990
            }
3991
        } else {
3992
            if ($debug) {
3993
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
3994
            }
3995
3996
            return false;
3997
        }
3998
3999
        return true;
4000
    }
4001
4002
    /**
4003
     * Updates the default view mode from fullscreen to embedded and inversely.
4004
     *
4005
     * @return string The current default view mode ('fullscreen' or 'embedded')
4006
     */
4007
    public function update_default_view_mode()
4008
    {
4009
        $table = Database::get_course_table(TABLE_LP_MAIN);
4010
        $sql = "SELECT * FROM $table
4011
                WHERE iid = ".$this->get_id();
4012
        $res = Database::query($sql);
4013
        if (Database::num_rows($res) > 0) {
4014
            $row = Database::fetch_array($res);
4015
            $default_view_mode = $row['default_view_mod'];
4016
            $view_mode = $default_view_mode;
4017
            switch ($default_view_mode) {
4018
                case 'fullscreen': // default with popup
4019
                    $view_mode = 'embedded';
4020
                    break;
4021
                case 'embedded': // default view with left menu
4022
                    $view_mode = 'embedframe';
4023
                    break;
4024
                case 'embedframe': //folded menu
4025
                    $view_mode = 'impress';
4026
                    break;
4027
                case 'impress':
4028
                    $view_mode = 'fullscreen';
4029
                    break;
4030
            }
4031
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4032
                    WHERE iid = ".$this->get_id();
4033
            Database::query($sql);
4034
            $this->mode = $view_mode;
4035
4036
            return $view_mode;
4037
        }
4038
4039
        return -1;
4040
    }
4041
4042
    /**
4043
     * Updates the default behaviour about auto-commiting SCORM updates.
4044
     *
4045
     * @return bool True if auto-commit has been set to 'on', false otherwise
4046
     */
4047
    public function update_default_scorm_commit()
4048
    {
4049
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4050
        $sql = "SELECT * FROM $lp_table
4051
                WHERE iid = ".$this->get_id();
4052
        $res = Database::query($sql);
4053
        if (Database::num_rows($res) > 0) {
4054
            $row = Database::fetch_array($res);
4055
            $force = $row['force_commit'];
4056
            if (1 == $force) {
4057
                $force = 0;
4058
                $force_return = false;
4059
            } elseif (0 == $force) {
4060
                $force = 1;
4061
                $force_return = true;
4062
            }
4063
            $sql = "UPDATE $lp_table SET force_commit = $force
4064
                    WHERE iid = ".$this->get_id();
4065
            Database::query($sql);
4066
            $this->force_commit = $force_return;
4067
4068
            return $force_return;
4069
        }
4070
4071
        return -1;
4072
    }
4073
4074
    /**
4075
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4076
     *
4077
     * @return bool True on success, false on failure
4078
     */
4079
    public function update_display_order()
4080
    {
4081
        return;
4082
        $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...
4083
        $table = Database::get_course_table(TABLE_LP_MAIN);
4084
        $sql = "SELECT * FROM $table
4085
                WHERE c_id = $course_id
4086
                ORDER BY display_order";
4087
        $res = Database::query($sql);
4088
        if (false === $res) {
4089
            return false;
4090
        }
4091
4092
        $num = Database::num_rows($res);
4093
        // First check the order is correct, globally (might be wrong because
4094
        // of versions < 1.8.4).
4095
        if ($num > 0) {
4096
            $i = 1;
4097
            while ($row = Database::fetch_array($res)) {
4098
                if ($row['display_order'] != $i) {
4099
                    // If we find a gap in the order, we need to fix it.
4100
                    $sql = "UPDATE $table SET display_order = $i
4101
                            WHERE iid = ".$row['iid'];
4102
                    Database::query($sql);
4103
                }
4104
                $i++;
4105
            }
4106
        }
4107
4108
        return true;
4109
    }
4110
4111
    /**
4112
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4113
     *
4114
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4115
     */
4116
    public function update_reinit()
4117
    {
4118
        $force = $this->prevent_reinit;
4119
        if (1 == $force) {
4120
            $force = 0;
4121
        } elseif (0 == $force) {
4122
            $force = 1;
4123
        }
4124
4125
        $table = Database::get_course_table(TABLE_LP_MAIN);
4126
        $sql = "UPDATE $table SET prevent_reinit = $force
4127
                WHERE iid = ".$this->get_id();
4128
        Database::query($sql);
4129
        $this->prevent_reinit = $force;
4130
4131
        return $force;
4132
    }
4133
4134
    /**
4135
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4136
     *
4137
     * @return string 'single', 'multi' or 'seriousgame'
4138
     *
4139
     * @author ndiechburg <[email protected]>
4140
     */
4141
    public function get_attempt_mode()
4142
    {
4143
        //Set default value for seriousgame_mode
4144
        if (!isset($this->seriousgame_mode)) {
4145
            $this->seriousgame_mode = 0;
4146
        }
4147
        // Set default value for prevent_reinit
4148
        if (!isset($this->prevent_reinit)) {
4149
            $this->prevent_reinit = 1;
4150
        }
4151
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4152
            return 'seriousgame';
4153
        }
4154
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4155
            return 'single';
4156
        }
4157
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4158
            return 'multiple';
4159
        }
4160
4161
        return 'single';
4162
    }
4163
4164
    /**
4165
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4166
     *
4167
     * @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...
4168
     *
4169
     * @return bool
4170
     *
4171
     * @author ndiechburg <[email protected]>
4172
     */
4173
    public function set_attempt_mode($mode)
4174
    {
4175
        switch ($mode) {
4176
            case 'seriousgame':
4177
                $sg_mode = 1;
4178
                $prevent_reinit = 1;
4179
                break;
4180
            case 'single':
4181
                $sg_mode = 0;
4182
                $prevent_reinit = 1;
4183
                break;
4184
            case 'multiple':
4185
                $sg_mode = 0;
4186
                $prevent_reinit = 0;
4187
                break;
4188
            default:
4189
                $sg_mode = 0;
4190
                $prevent_reinit = 0;
4191
                break;
4192
        }
4193
        $this->prevent_reinit = $prevent_reinit;
4194
        $this->seriousgame_mode = $sg_mode;
4195
        $table = Database::get_course_table(TABLE_LP_MAIN);
4196
        $sql = "UPDATE $table SET
4197
                prevent_reinit = $prevent_reinit ,
4198
                seriousgame_mode = $sg_mode
4199
                WHERE iid = ".$this->get_id();
4200
        $res = Database::query($sql);
4201
        if ($res) {
4202
            return true;
4203
        } else {
4204
            return false;
4205
        }
4206
    }
4207
4208
    /**
4209
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4210
     *
4211
     * @author ndiechburg <[email protected]>
4212
     */
4213
    public function switch_attempt_mode()
4214
    {
4215
        $mode = $this->get_attempt_mode();
4216
        switch ($mode) {
4217
            case 'single':
4218
                $next_mode = 'multiple';
4219
                break;
4220
            case 'multiple':
4221
                $next_mode = 'seriousgame';
4222
                break;
4223
            case 'seriousgame':
4224
            default:
4225
                $next_mode = 'single';
4226
                break;
4227
        }
4228
        $this->set_attempt_mode($next_mode);
4229
    }
4230
4231
    /**
4232
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4233
     * but possibility to do again a completed item.
4234
     *
4235
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4236
     *
4237
     * @author ndiechburg <[email protected]>
4238
     */
4239
    public function set_seriousgame_mode()
4240
    {
4241
        $table = Database::get_course_table(TABLE_LP_MAIN);
4242
        $force = $this->seriousgame_mode;
4243
        if (1 == $force) {
4244
            $force = 0;
4245
        } elseif (0 == $force) {
4246
            $force = 1;
4247
        }
4248
        $sql = "UPDATE $table SET seriousgame_mode = $force
4249
                WHERE iid = ".$this->get_id();
4250
        Database::query($sql);
4251
        $this->seriousgame_mode = $force;
4252
4253
        return $force;
4254
    }
4255
4256
    /**
4257
     * Updates the "scorm_debug" value that shows or hide the debug window.
4258
     *
4259
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4260
     */
4261
    public function update_scorm_debug()
4262
    {
4263
        $table = Database::get_course_table(TABLE_LP_MAIN);
4264
        $force = $this->scorm_debug;
4265
        if (1 == $force) {
4266
            $force = 0;
4267
        } elseif (0 == $force) {
4268
            $force = 1;
4269
        }
4270
        $sql = "UPDATE $table SET debug = $force
4271
                WHERE iid = ".$this->get_id();
4272
        Database::query($sql);
4273
        $this->scorm_debug = $force;
4274
4275
        return $force;
4276
    }
4277
4278
    /**
4279
     * Function that creates a html list of learning path items so that we can add audio files to them.
4280
     *
4281
     * @author Kevin Van Den Haute
4282
     *
4283
     * @return string
4284
     */
4285
    public function overview()
4286
    {
4287
        $return = '';
4288
        $update_audio = $_GET['updateaudio'] ?? null;
4289
4290
        // we need to start a form when we want to update all the mp3 files
4291
        if ('true' == $update_audio) {
4292
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4293
                    $_GET['updateaudio']
4294
                ).'&action='.Security::remove_XSS(
4295
                    $_GET['action']
4296
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4297
        }
4298
        $return .= '<div id="message"></div>';
4299
        if (0 == count($this->items)) {
4300
            $return .= Display::return_message(
4301
                get_lang(
4302
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4303
                ),
4304
                'normal'
4305
            );
4306
        } else {
4307
            $return_audio = '<table class="table table-hover table-striped data_table">';
4308
            $return_audio .= '<tr>';
4309
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4310
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4311
            $return_audio .= '</tr>';
4312
4313
            if ('true' != $update_audio) {
4314
                /*$return .= '<div class="col-md-12">';
4315
                $return .= self::return_new_tree($update_audio);
4316
                $return .= '</div>';*/
4317
                $return .= Display::div(
4318
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn--primary']),
4319
                    ['style' => 'float:left; margin-top:15px;width:100%']
4320
                );
4321
            } else {
4322
                //$return_audio .= self::return_new_tree($update_audio);
4323
                $return .= $return_audio.'</table>';
4324
            }
4325
4326
            // We need to close the form when we are updating the mp3 files.
4327
            if ('true' == $update_audio) {
4328
                $return .= '<div class="footer-audio">';
4329
                $return .= Display::button(
4330
                    'save_audio',
4331
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4332
                    ['class' => 'btn btn--primary', 'type' => 'submit']
4333
                );
4334
                $return .= '</div>';
4335
            }
4336
        }
4337
4338
        // We need to close the form when we are updating the mp3 files.
4339
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4340
            $return .= '</form>';
4341
        }
4342
4343
        return $return;
4344
    }
4345
4346
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4347
    {
4348
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4349
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4350
4351
        $content = '
4352
        <script>
4353
            /*
4354
            Script to manipulate Learning Path items with Drag and drop
4355
             */
4356
            $(function() {
4357
                function refreshTree() {
4358
                    var params = "&a=get_lp_item_tree";
4359
                    $.get(
4360
                        "'.$ajax_url.'",
4361
                        params,
4362
                        function(result) {
4363
                            serialized = [];
4364
                            $("#lp_item_list").html(result);
4365
                            nestedSortable();
4366
                        }
4367
                    );
4368
                }
4369
4370
                const nestedQuery = ".nested-sortable";
4371
                const identifier = "id";
4372
                const root = document.getElementById("lp_item_list");
4373
4374
                var serialized = [];
4375
                function serialize(sortable) {
4376
                  var children = [].slice.call(sortable.children);
4377
                  for (var i in children) {
4378
                    var nested = children[i].querySelector(nestedQuery);
4379
                    var parentId = $(children[i]).parent().parent().attr("id");
4380
                    var id = children[i].dataset[identifier];
4381
                    if (typeof id === "undefined") {
4382
                        return;
4383
                    }
4384
                    serialized.push({
4385
                      id: children[i].dataset[identifier],
4386
                      parent_id: parentId
4387
                    });
4388
4389
                    if (nested) {
4390
                        serialize(nested);
4391
                    }
4392
                  }
4393
4394
                  return serialized;
4395
                }
4396
4397
                function nestedSortable() {
4398
                    let left = document.getElementsByClassName("nested-sortable");
4399
                    Array.prototype.forEach.call(left, function(resource) {
4400
                        Sortable.create(resource, {
4401
                            group: "nested",
4402
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4403
                            animation: 150,
4404
                            //fallbackOnBody: true,
4405
                            swapThreshold: 0.65,
4406
                            dataIdAttr: "data-id",
4407
                            store: {
4408
                                set: function (sortable) {
4409
                                    var order = sortable.toArray();
4410
                                    console.log(order);
4411
                                }
4412
                            },
4413
                            onEnd: function(evt) {
4414
                                console.log("onEnd");
4415
                                let list = serialize(root);
4416
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4417
                                $.get(
4418
                                    "'.$ajax_url.'",
4419
                                    order,
4420
                                    function(reponse) {
4421
                                        $("#message").html(reponse);
4422
                                        refreshTree();
4423
                                    }
4424
                                );
4425
                            },
4426
                        });
4427
                    });
4428
                }
4429
4430
                nestedSortable();
4431
4432
                let resources = document.getElementsByClassName("lp_resource");
4433
                Array.prototype.forEach.call(resources, function(resource) {
4434
                    Sortable.create(resource, {
4435
                        group: "nested",
4436
                        put: ["nested-sortable"],
4437
                        filter: ".disable_drag",
4438
                        animation: 150,
4439
                        fallbackOnBody: true,
4440
                        swapThreshold: 0.65,
4441
                        dataIdAttr: "data-id",
4442
                        onRemove: function(evt) {
4443
                            console.log("onRemove");
4444
                            var itemEl = evt.item;
4445
                            var newIndex = evt.newIndex;
4446
                            var id = $(itemEl).attr("id");
4447
                            var parent_id = $(itemEl).parent().parent().attr("id");
4448
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4449
                            var title = $(itemEl).find(".link_with_id").text();
4450
4451
                            let previousId = 0;
4452
                            if (0 !== newIndex) {
4453
                                previousId = $(itemEl).prev().attr("id");
4454
                            }
4455
                            var params = {
4456
                                "a": "add_lp_item",
4457
                                "id": id,
4458
                                "parent_id": parent_id,
4459
                                "previous_id": previousId,
4460
                                "type": type,
4461
                                "title" : title
4462
                            };
4463
                            console.log(params);
4464
                            $.ajax({
4465
                                type: "GET",
4466
                                url: "'.$ajax_url.'",
4467
                                data: params,
4468
                                success: function(itemId) {
4469
                                    $(itemEl).attr("id", itemId);
4470
                                    $(itemEl).attr("data-id", itemId);
4471
                                    let list = serialize(root);
4472
                                    let listInString = JSON.stringify(list);
4473
                                    if (typeof listInString === "undefined") {
4474
                                        listInString = "";
4475
                                    }
4476
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4477
                                    $.get(
4478
                                        "'.$ajax_url.'",
4479
                                        order,
4480
                                        function(reponse) {
4481
                                            $("#message").html(reponse);
4482
                                            refreshTree();
4483
                                        }
4484
                                    );
4485
                                }
4486
                            });
4487
                        },
4488
                    });
4489
                });
4490
            });
4491
        </script>';
4492
4493
        $content .= "
4494
        <script>
4495
            function confirmation(name) {
4496
                if (confirm('$sureToDelete ' + name)) {
4497
                    return true;
4498
                } else {
4499
                    return false;
4500
                }
4501
            }
4502
            function refreshTree() {
4503
                var params = '&a=get_lp_item_tree';
4504
                $.get(
4505
                    '".$ajax_url."',
4506
                    params,
4507
                    function(result) {
4508
                        $('#lp_item_list').html(result);
4509
                    }
4510
                );
4511
            }
4512
4513
            $(function () {
4514
                //$('.scrollbar-inner').scrollbar();
4515
                /*$('#subtab').on('click', 'a:first', function() {
4516
                    window.location.reload();
4517
                });
4518
                $('#subtab ').on('click', 'a:first', function () {
4519
                    window.location.reload();
4520
                });*/
4521
4522
                expandColumnToggle('#hide_bar_template', {
4523
                    selector: '#lp_sidebar'
4524
                }, {
4525
                    selector: '#doc_form'
4526
                });
4527
4528
                $('.lp-btn-associate-forum').on('click', function (e) {
4529
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
4530
                    if (!associate) {
4531
                        e.preventDefault();
4532
                    }
4533
                });
4534
4535
                $('.lp-btn-dissociate-forum').on('click', function (e) {
4536
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
4537
                    if (!dissociate) {
4538
                        e.preventDefault();
4539
                    }
4540
                });
4541
4542
                // hide the current template list for new documment until it tab clicked
4543
                $('#frmModel').hide();
4544
            });
4545
4546
            // document template for new document tab handler
4547
            /*$(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
4548
                var id = e.target.id;
4549
                if (id == 'subtab2') {
4550
                    $('#frmModel').show();
4551
                } else {
4552
                    $('#frmModel').hide();
4553
                }
4554
            });*/
4555
4556
          function deleteItem(event) {
4557
            var id = $(event).attr('data-id');
4558
            var title = $(event).attr('data-title');
4559
            var params = '&a=delete_item&id=' + id;
4560
            if (confirmation(title)) {
4561
                $.get(
4562
                    '".$ajax_url."',
4563
                    params,
4564
                    function(result) {
4565
                        refreshTree();
4566
                    }
4567
                );
4568
            }
4569
        }
4570
        </script>";
4571
4572
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
4573
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
4574
4575
        $repo = Container::getDocumentRepository();
4576
        $document = $repo->find($documentId);
4577
        if ($document) {
4578
            // Show the template list
4579
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4580
        }
4581
4582
        // Show the template list.
4583
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
4584
            // Show the template list.
4585
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
4586
        }
4587
4588
        return $content;
4589
    }
4590
4591
    /**
4592
     * @param bool  $updateAudio
4593
     * @param bool   $dropElement
4594
     *
4595
     * @return string
4596
     */
4597
    public function return_new_tree($updateAudio = false, $dropElement = false)
4598
    {
4599
        $list = $this->getBuildTree(false, $dropElement);
4600
        $return = Display::panelCollapse(
4601
            $this->name,
4602
            $list,
4603
            'scorm-list',
4604
            null,
4605
            'scorm-list-accordion',
4606
            'scorm-list-collapse'
4607
        );
4608
4609
        if ($updateAudio) {
4610
            //$return = $result['return_audio'];
4611
        }
4612
4613
        return $return;
4614
    }
4615
4616
    public function getBuildTree($noWrapper = false, $dropElement = false): string
4617
    {
4618
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
4619
        $upIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon', '', 16, get_lang('Up'));
4620
        $disableUpIcon = Display::getMdiIcon('arrow-up-bold', 'ch-tool-icon-disabled', '', 16, get_lang('Up'));
4621
        $downIcon = Display::getMdiIcon('arrow-down-bold', 'ch-tool-icon', '', 16, get_lang('Down'));
4622
        $previewImage = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 16, get_lang('Preview'));
4623
4624
        $lpItemRepo = Container::getLpItemRepository();
4625
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
4626
4627
        $options = [
4628
            'decorate' => true,
4629
            'rootOpen' => function($tree) use ($noWrapper) {
4630
                if ($tree[0]['lvl'] === 1) {
4631
                    if ($noWrapper) {
4632
                        return '';
4633
                    }
4634
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
4635
                }
4636
4637
                return '<ul class="list-group nested-sortable">';
4638
            },
4639
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
4640
                if ($tree[0]['lvl'] === 1) {
4641
                    if ($dropElement) {
4642
                        //return Display::return_message(get_lang('Drag and drop an element here'));
4643
                        //return $this->getDropElementHtml();
4644
                    }
4645
                    if ($noWrapper) {
4646
                        return '';
4647
                    }
4648
                }
4649
4650
                return '</ul>';
4651
            },
4652
            'childOpen' => function($child) {
4653
                $id = $child['iid'];
4654
                return '<li
4655
                    id="'.$id.'"
4656
                    data-id="'.$id.'"
4657
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
4658
            },
4659
            'childClose' => '',
4660
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
4661
                $fullTitle = $node['title'];
4662
                //$title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
4663
                $title = $fullTitle;
4664
                $itemId = $node['iid'];
4665
                $type = $node['itemType'];
4666
                $lpId = $this->get_id();
4667
4668
                $moveIcon = '';
4669
                if (TOOL_LP_FINAL_ITEM !== $type) {
4670
                    $moveIcon .= '<a class="moved" href="#">';
4671
                    $moveIcon .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
4672
                    $moveIcon .= '</a>';
4673
                }
4674
4675
                $iconName = str_replace(' ', '', $type);
4676
                $icon = '';
4677
                switch ($iconName) {
4678
                    case 'category':
4679
                    case 'chapter':
4680
                    case 'folder':
4681
                    case 'dir':
4682
                        $icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY);
4683
                        break;
4684
                    default:
4685
                        $icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY);
4686
                        break;
4687
                }
4688
4689
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
4690
                $previewIcon = Display::url(
4691
                    $previewImage,
4692
                    $urlPreviewLink,
4693
                    [
4694
                        'target' => '_blank',
4695
                        'class' => 'btn btn--plain',
4696
                        'data-title' => $title,
4697
                        'title' => $title,
4698
                    ]
4699
                );
4700
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
4701
4702
                $preRequisitesIcon = Display::url(
4703
                    Display::getMdiIcon('graph', 'ch-tool-icon', '', 16, get_lang('Prerequisites')),
4704
                    $url.'&action=edit_item_prereq',
4705
                    ['class' => '']
4706
                );
4707
4708
                $editIcon = '<a
4709
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
4710
                    class=""
4711
                    >';
4712
                $editIcon .= Display::getMdiIcon('pencil', 'ch-tool-icon', '', 16, get_lang('Edit section description/name'));
4713
                $editIcon .= '</a>';
4714
                $orderIcons = '';
4715
                /*if ('final_item' !== $type) {
4716
                    $orderIcons = Display::url(
4717
                        $upIcon,
4718
                        'javascript:void(0)',
4719
                        ['class' => 'btn btn--plain order_items', 'data-dir' => 'up', 'data-id' => $itemId]
4720
                    );
4721
                    $orderIcons .= Display::url(
4722
                        $downIcon,
4723
                        'javascript:void(0)',
4724
                        ['class' => 'btn btn--plain order_items', 'data-dir' => 'down', 'data-id' => $itemId]
4725
                    );
4726
                }*/
4727
4728
                $deleteIcon = ' <a
4729
                    data-id = '.$itemId.'
4730
                    data-title = \''.addslashes($title).'\'
4731
                    href="javascript:void(0);"
4732
                    onclick="return deleteItem(this);"
4733
                    class="">';
4734
                $deleteIcon .= Display::getMdiIcon('delete', 'ch-tool-icon', '', 16, get_lang('Delete section'));
4735
                $deleteIcon .= '</a>';
4736
                $extra = '';
4737
4738
                if ('dir' === $type && empty($node['__children'])) {
4739
                    $level = $node['lvl'] + 1;
4740
                    $extra = '<ul class="list-group nested-sortable">
4741
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
4742
                              </ul>';
4743
                }
4744
4745
                $buttons = Display::tag(
4746
                    'div',
4747
                    "<div class=\"btn-group btn-group-sm\">
4748
                                $editIcon
4749
                                $preRequisitesIcon
4750
                                $orderIcons
4751
                                $deleteIcon
4752
                               </div>",
4753
                    ['class' => 'btn-toolbar button_actions']
4754
                );
4755
4756
                return
4757
                    "<div class='flex flex-row'> $moveIcon  $icon <span class='mx-1'>$title </span></div>
4758
                    $extra
4759
                    $buttons
4760
                    "
4761
                    ;
4762
            },
4763
        ];
4764
4765
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
4766
4767
        if (empty($tree) && $dropElement) {
4768
            return $this->getDropElementHtml($noWrapper);
4769
        }
4770
4771
        return $tree;
4772
    }
4773
4774
    public function getDropElementHtml($noWrapper = false)
4775
    {
4776
        $li = '<li class="list-group-item">'.
4777
            Display::return_message(get_lang('Drag and drop an element here')).
4778
            '</li>';
4779
        if ($noWrapper) {
4780
            return $li;
4781
        }
4782
4783
        return
4784
            '<ul id="lp_item_list" class="list-group nested-sortable">
4785
            '.$li.'
4786
            </ul>';
4787
    }
4788
4789
    /**
4790
     * This function builds the action menu.
4791
     *
4792
     * @param bool   $returnString           Optional
4793
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
4794
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
4795
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
4796
     * @param string $action
4797
     * @param array  $extraField
4798
     *
4799
     * @return string
4800
     */
4801
    public function build_action_menu(
4802
        $returnString = false,
4803
        $showRequirementButtons = true,
4804
        $isConfigPage = false,
4805
        $allowExpand = true,
4806
        $action = '',
4807
        $extraField = []
4808
    ) {
4809
        $actionsRight = '';
4810
        $lpId = $this->lp_id;
4811
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
4812
            $back = Display::url(
4813
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back to learning paths')),
4814
                'lp_controller.php?'.api_get_cidreq()
4815
            );
4816
        } else {
4817
            $back = Display::url(
4818
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', '', 32, get_lang('Back')),
4819
                $extraField['backTo']
4820
            );
4821
        }
4822
4823
        /*if ($backToBuild) {
4824
            $back = Display::url(
4825
                Display::getMdiIcon('arrow-left-bold-box', 'ch-tool-icon', null, 32, get_lang('GoBack')),
4826
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
4827
            );
4828
        }*/
4829
4830
        $actionsLeft = $back;
4831
4832
        $actionsLeft .= Display::url(
4833
            Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', '', 32, get_lang('Preview')),
4834
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4835
                'action' => 'view',
4836
                'lp_id' => $lpId,
4837
                'isStudentView' => 'true',
4838
            ])
4839
        );
4840
4841
        /*$actionsLeft .= Display::url(
4842
            Display::getMdiIcon('music-note-plus', 'ch-tool-icon', null, 32, get_lang('Add audio')),
4843
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4844
                'action' => 'admin_view',
4845
                'lp_id' => $lpId,
4846
                'updateaudio' => 'true',
4847
            ])
4848
        );*/
4849
4850
        $subscriptionSettings = self::getSubscriptionSettings();
4851
4852
        $request = api_request_uri();
4853
        if (false === strpos($request, 'edit')) {
4854
            $actionsLeft .= Display::url(
4855
                Display::getMdiIcon('hammer-wrench', 'ch-tool-icon', '', 32, get_lang('Course settings')),
4856
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4857
                    'action' => 'edit',
4858
                    'lp_id' => $lpId,
4859
                ])
4860
            );
4861
        }
4862
4863
        if ((false === strpos($request, 'build') &&
4864
            false === strpos($request, 'add_item')) ||
4865
            in_array($action, ['add_audio'], true)
4866
        ) {
4867
            $actionsLeft .= Display::url(
4868
                Display::getMdiIcon('pencil', 'ch-tool-icon', '', 32, get_lang('Edit')),
4869
                'lp_controller.php?'.http_build_query([
4870
                    'action' => 'build',
4871
                    'lp_id' => $lpId,
4872
                ]).'&'.api_get_cidreq()
4873
            );
4874
        }
4875
4876
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
4877
            if (1 == $this->subscribeUsers &&
4878
                $subscriptionSettings['allow_add_users_to_lp']) {
4879
                $actionsLeft .= Display::url(
4880
                    Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Subscribe users to learning path')),
4881
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
4882
                );
4883
            }
4884
        }
4885
4886
        if ($allowExpand) {
4887
            /*$actionsLeft .= Display::url(
4888
                Display::getMdiIcon('arrow-expand-all', 'ch-tool-icon', null, 32, get_lang('Expand')).
4889
                Display::getMdiIcon('arrow-collapse-all', 'ch-tool-icon', null, 32, get_lang('Collapse')),
4890
                '#',
4891
                ['role' => 'button', 'id' => 'hide_bar_template']
4892
            );*/
4893
        }
4894
4895
        if ($showRequirementButtons) {
4896
            $buttons = [
4897
                [
4898
                    'title' => get_lang('Set previous step as prerequisite for each step'),
4899
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4900
                        'action' => 'set_previous_step_as_prerequisite',
4901
                        'lp_id' => $lpId,
4902
                    ]),
4903
                ],
4904
                [
4905
                    'title' => get_lang('Clear all prerequisites'),
4906
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4907
                        'action' => 'clear_prerequisites',
4908
                        'lp_id' => $lpId,
4909
                    ]),
4910
                ],
4911
            ];
4912
            $actionsRight = Display::groupButtonWithDropDown(
4913
                get_lang('Prerequisites options'),
4914
                $buttons,
4915
                true
4916
            );
4917
        }
4918
4919
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
4920
            $actionsLeft .= Display::url(
4921
                Display::getMdiIcon('account-multiple-plus', 'ch-tool-icon', '', 32, get_lang('Author')),
4922
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
4923
                    'action' => 'author_view',
4924
                    'lp_id' => $lpId,
4925
                ])
4926
            );
4927
        }
4928
4929
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
4930
4931
        if ($returnString) {
4932
            return $toolbar;
4933
        }
4934
4935
        echo $toolbar;
4936
    }
4937
4938
    /**
4939
     * Creates the default learning path folder.
4940
     *
4941
     * @param array $course
4942
     * @param int   $creatorId
4943
     *
4944
     * @return CDocument
4945
     */
4946
    public static function generate_learning_path_folder($course, $creatorId = 0)
4947
    {
4948
        // Creating learning_path folder
4949
        $dir = 'learning_path';
4950
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4951
4952
        return create_unexisting_directory(
4953
            $course,
4954
            $creatorId,
4955
            0,
4956
            null,
4957
            0,
4958
            '',
4959
            $dir,
4960
            get_lang('Learning paths'),
4961
            0
4962
        );
4963
    }
4964
4965
    /**
4966
     * @param array  $course
4967
     * @param string $lp_name
4968
     * @param int    $creatorId
4969
     *
4970
     * @return CDocument
4971
     */
4972
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
4973
    {
4974
        $filepath = '';
4975
        $dir = '/learning_path/';
4976
4977
        if (empty($lp_name)) {
4978
            $lp_name = $this->name;
4979
        }
4980
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
4981
        $parent = self::generate_learning_path_folder($course, $creatorId);
4982
4983
        // Limits title size
4984
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
4985
        $dir = $dir.$title;
4986
4987
        // Creating LP folder
4988
        $folder = null;
4989
        if ($parent) {
4990
            $folder = create_unexisting_directory(
4991
                $course,
4992
                $creatorId,
4993
                0,
4994
                0,
4995
                0,
4996
                $filepath,
4997
                $dir,
4998
                $lp_name,
4999
                '',
5000
                false,
5001
                false,
5002
                $parent
5003
            );
5004
        }
5005
5006
        return $folder;
5007
    }
5008
5009
    /**
5010
     * Create a new document //still needs some finetuning.
5011
     *
5012
     * @param array  $courseInfo
5013
     * @param string $content
5014
     * @param string $title
5015
     * @param string $extension
5016
     * @param int    $parentId
5017
     * @param int    $creatorId  creator id
5018
     *
5019
     * @return int
5020
     */
5021
    public function create_document(
5022
        $courseInfo,
5023
        $content = '',
5024
        $title = '',
5025
        $extension = 'html',
5026
        $parentId = 0,
5027
        $creatorId = 0
5028
    ) {
5029
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5030
        $sessionId = api_get_session_id();
5031
5032
        // Generates folder
5033
        $this->generate_lp_folder($courseInfo);
5034
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
5035
        // is already escaped twice when it gets here.
5036
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5037
        if (!empty($title)) {
5038
            $title = api_replace_dangerous_char(stripslashes($title));
5039
        } else {
5040
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5041
        }
5042
5043
        $title = disable_dangerous_file($title);
5044
        $filename = $title;
5045
        $tmp_filename = $filename;
5046
        /*$i = 0;
5047
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
5048
            $tmp_filename = $filename.'_'.++$i;
5049
        }*/
5050
        $filename = $tmp_filename.'.'.$extension;
5051
5052
        if ('html' === $extension) {
5053
            $content = stripslashes($content);
5054
            $content = str_replace(
5055
                api_get_path(WEB_COURSE_PATH),
5056
                api_get_path(REL_PATH).'courses/',
5057
                $content
5058
            );
5059
5060
            // Change the path of mp3 to absolute.
5061
            // The first regexp deals with :// urls.
5062
            /*$content = preg_replace(
5063
                "|(flashvars=\"file=)([^:/]+)/|",
5064
                "$1".api_get_path(
5065
                    REL_COURSE_PATH
5066
                ).$courseInfo['path'].'/document/',
5067
                $content
5068
            );*/
5069
            // The second regexp deals with audio/ urls.
5070
            /*$content = preg_replace(
5071
                "|(flashvars=\"file=)([^/]+)/|",
5072
                "$1".api_get_path(
5073
                    REL_COURSE_PATH
5074
                ).$courseInfo['path'].'/document/$2/',
5075
                $content
5076
            );*/
5077
            // For flv player: To prevent edition problem with firefox,
5078
            // we have to use a strange tip (don't blame me please).
5079
            $content = str_replace(
5080
                '</body>',
5081
                '<style type="text/css">body{}</style></body>',
5082
                $content
5083
            );
5084
        }
5085
5086
        $document = DocumentManager::addDocument(
5087
            $courseInfo,
5088
            null,
5089
            'file',
5090
            '',
5091
            $tmp_filename,
5092
            '',
5093
            0, //readonly
5094
            true,
5095
            null,
5096
            $sessionId,
5097
            $creatorId,
5098
            false,
5099
            $content,
5100
            $parentId
5101
        );
5102
5103
        $document_id = $document->getIid();
5104
        if ($document_id) {
5105
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5106
            $new_title = $originalTitle;
5107
5108
            if ($new_comment || $new_title) {
5109
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
5110
                $ct = '';
5111
                if ($new_comment) {
5112
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
5113
                }
5114
                if ($new_title) {
5115
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
5116
                }
5117
5118
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
5119
                        WHERE iid = $document_id ";
5120
                Database::query($sql);
5121
            }
5122
        }
5123
5124
        return $document_id;
5125
    }
5126
5127
    /**
5128
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
5129
     */
5130
    public function edit_document()
5131
    {
5132
        $repo = Container::getDocumentRepository();
5133
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
5134
            $id = (int) $_REQUEST['document_id'];
5135
            /** @var CDocument $document */
5136
            $document = $repo->find($id);
5137
            if ($document->getResourceNode()->hasEditableTextContent()) {
5138
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
5139
            }
5140
            $document->setTitle($_REQUEST['title']);
5141
            $repo->update($document);
5142
        }
5143
    }
5144
5145
    /**
5146
     * Displays the selected item, with a panel for manipulating the item.
5147
     *
5148
     * @param CLpItem $lpItem
5149
     * @param string  $msg
5150
     * @param bool    $show_actions
5151
     *
5152
     * @return string
5153
     */
5154
    public function display_item($lpItem, $msg = null, $show_actions = true)
5155
    {
5156
        $course_id = api_get_course_int_id();
5157
        $return = '';
5158
5159
        if (null === $lpItem) {
5160
            return '';
5161
        }
5162
        $item_id = $lpItem->getIid();
5163
        $itemType = $lpItem->getItemType();
5164
        $lpId = $lpItem->getLp()->getIid();
5165
        $path = $lpItem->getPath();
5166
5167
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
5168
5169
        // Prevents wrong parent selection for document, see Bug#1251.
5170
        if ('dir' !== $itemType) {
5171
            Session::write('parent_item_id', $lpItem->getParentItemId());
5172
        }
5173
5174
        if ($show_actions) {
5175
            $return .= $this->displayItemMenu($lpItem);
5176
        }
5177
        $return .= '<div style="padding:10px;">';
5178
5179
        if ('' != $msg) {
5180
            $return .= $msg;
5181
        }
5182
5183
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
5184
5185
        switch ($itemType) {
5186
            case TOOL_THREAD:
5187
                $link = $this->rl_get_resource_link_for_learnpath(
5188
                    $course_id,
5189
                    $lpId,
5190
                    $item_id,
5191
                    0
5192
                );
5193
                $return .= Display::url(
5194
                    get_lang('Go to thread'),
5195
                    $link,
5196
                    ['class' => 'btn btn--primary']
5197
                );
5198
                break;
5199
            case TOOL_FORUM:
5200
                $return .= Display::url(
5201
                    get_lang('Go to the forum'),
5202
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
5203
                    ['class' => 'btn btn--primary']
5204
                );
5205
                break;
5206
            case TOOL_QUIZ:
5207
                if (!empty($path)) {
5208
                    $exercise = new Exercise();
5209
                    $exercise->read($path);
5210
                    $return .= $exercise->description.'<br />';
5211
                    $return .= Display::url(
5212
                        get_lang('Go to exercise'),
5213
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
5214
                        ['class' => 'btn btn--primary']
5215
                    );
5216
                }
5217
                break;
5218
            case TOOL_LP_FINAL_ITEM:
5219
                $return .= $this->getSavedFinalItem();
5220
                break;
5221
            case TOOL_DOCUMENT:
5222
            case TOOL_READOUT_TEXT:
5223
                $repo = Container::getDocumentRepository();
5224
                /** @var CDocument $document */
5225
                $document = $repo->find($lpItem->getPath());
5226
                $return .= $this->display_document($document, true, true);
5227
                break;
5228
        }
5229
        $return .= '</div>';
5230
5231
        return $return;
5232
    }
5233
5234
    /**
5235
     * Shows the needed forms for editing a specific item.
5236
     *
5237
     * @param CLpItem $lpItem
5238
     *
5239
     * @throws Exception
5240
     *
5241
     *
5242
     * @return string
5243
     */
5244
    public function display_edit_item($lpItem, $excludeExtraFields = [])
5245
    {
5246
        $return = '';
5247
        if (empty($lpItem)) {
5248
            return '';
5249
        }
5250
        $itemType = $lpItem->getItemType();
5251
        $path = $lpItem->getPath();
5252
5253
        switch ($itemType) {
5254
            case 'dir':
5255
            case 'asset':
5256
            case 'sco':
5257
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
5258
                    $return .= $this->displayItemMenu($lpItem);
5259
                    $return .= $this->display_item_form($lpItem, 'edit');
5260
                } else {
5261
                    $return .= $this->display_item_form($lpItem, 'edit_item');
5262
                }
5263
                break;
5264
            case TOOL_LP_FINAL_ITEM:
5265
            case TOOL_DOCUMENT:
5266
            case TOOL_READOUT_TEXT:
5267
                $return .= $this->displayItemMenu($lpItem);
5268
                $return .= $this->displayDocumentForm('edit', $lpItem);
5269
                break;
5270
            case TOOL_LINK:
5271
                $link = null;
5272
                if (!empty($path)) {
5273
                    $repo = Container::getLinkRepository();
5274
                    $link = $repo->find($path);
5275
                }
5276
                $return .= $this->displayItemMenu($lpItem);
5277
                $return .= $this->display_link_form('edit', $lpItem, $link);
5278
5279
                break;
5280
            case TOOL_QUIZ:
5281
                if (!empty($path)) {
5282
                    $repo = Container::getQuizRepository();
5283
                    $resource = $repo->find($path);
5284
                }
5285
                $return .= $this->displayItemMenu($lpItem);
5286
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
5287
                break;
5288
            case TOOL_STUDENTPUBLICATION:
5289
                if (!empty($path)) {
5290
                    $repo = Container::getStudentPublicationRepository();
5291
                    $resource = $repo->find($path);
5292
                }
5293
                $return .= $this->displayItemMenu($lpItem);
5294
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
5295
                break;
5296
            case TOOL_FORUM:
5297
                if (!empty($path)) {
5298
                    $repo = Container::getForumRepository();
5299
                    $resource = $repo->find($path);
5300
                }
5301
                $return .= $this->displayItemMenu($lpItem);
5302
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
5303
                break;
5304
            case TOOL_THREAD:
5305
                if (!empty($path)) {
5306
                    $repo = Container::getForumPostRepository();
5307
                    $resource = $repo->find($path);
5308
                }
5309
                $return .= $this->displayItemMenu($lpItem);
5310
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
5311
                break;
5312
        }
5313
5314
        return $return;
5315
    }
5316
5317
    /**
5318
     * Function that displays a list with al the resources that
5319
     * could be added to the learning path.
5320
     *
5321
     * @throws Exception
5322
     */
5323
    public function displayResources(): string
5324
    {
5325
        // Get all the docs.
5326
        $documents = $this->get_documents(true);
5327
5328
        // Get all the exercises.
5329
        $exercises = $this->get_exercises();
5330
5331
        // Get all the links.
5332
        $links = $this->get_links();
5333
5334
        // Get all the student publications.
5335
        $works = $this->get_student_publications();
5336
5337
        // Get all the forums.
5338
        $forums = $this->get_forums();
5339
5340
        // Get the final item form (see BT#11048) .
5341
        $finish = $this->getFinalItemForm();
5342
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5343
        $headers = [
5344
            Display::getMdiIcon('bookshelf', 'ch-tool-icon-gradient', '', 64, get_lang('Documents')),
5345
            Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon-gradient', '', 64, get_lang('Tests')),
5346
            Display::getMdiIcon('file-link', 'ch-tool-icon-gradient', '', 64, get_lang('Links')),
5347
            Display::getMdiIcon('inbox-full', 'ch-tool-icon-gradient', '', 64, get_lang('Assignments')),
5348
            Display::getMdiIcon('comment-quote', 'ch-tool-icon-gradient', '', 64, get_lang('Forums')),
5349
            Display::getMdiIcon('bookmark-multiple', 'ch-tool-icon-gradient', '', 64, get_lang('Add section')),
5350
            Display::getMdiIcon('certificate', 'ch-tool-icon-gradient', '', 64, get_lang('Certificate')),
5351
        ];
5352
        $content = '';
5353
        /*$content = Display::return_message(
5354
            get_lang('Click on the [Learner view] button to see your learning path'),
5355
            'normal'
5356
        );*/
5357
        $section = $this->displayNewSectionForm();
5358
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5359
5360
        return Display::tabs(
5361
            $headers,
5362
            [
5363
                $documents,
5364
                $exercises,
5365
                $links,
5366
                $works,
5367
                $forums,
5368
                $section,
5369
                $finish,
5370
            ],
5371
            'resource_tab',
5372
            [],
5373
            [],
5374
            $selected
5375
        );
5376
    }
5377
5378
    /**
5379
     * Returns the extension of a document.
5380
     *
5381
     * @param string $filename
5382
     *
5383
     * @return string Extension (part after the last dot)
5384
     */
5385
    public function get_extension($filename)
5386
    {
5387
        $explode = explode('.', $filename);
5388
5389
        return $explode[count($explode) - 1];
5390
    }
5391
5392
    /**
5393
     * @return string
5394
     */
5395
    public function getCurrentBuildingModeURL()
5396
    {
5397
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
5398
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
5399
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
5400
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
5401
5402
        $currentUrl = api_get_self().'?'.api_get_cidreq().
5403
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
5404
5405
        return $currentUrl;
5406
    }
5407
5408
    /**
5409
     * Displays a document by id.
5410
     *
5411
     * @param CDocument $document
5412
     * @param bool      $show_title
5413
     * @param bool      $iframe
5414
     * @param bool      $edit_link
5415
     *
5416
     * @return string
5417
     */
5418
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
5419
    {
5420
        $return = '';
5421
        if (!$document) {
5422
            return '';
5423
        }
5424
5425
        $repo = Container::getDocumentRepository();
5426
5427
        // TODO: Add a path filter.
5428
        if ($iframe) {
5429
            $url = $repo->getResourceFileUrl($document);
5430
5431
            $return .= '<iframe
5432
                id="learnpath_preview_frame"
5433
                frameborder="0"
5434
                height="400"
5435
                width="100%"
5436
                scrolling="auto"
5437
                src="'.$url.'"></iframe>';
5438
        } else {
5439
            $return = $repo->getResourceFileContent($document);
5440
        }
5441
5442
        return $return;
5443
    }
5444
5445
    /**
5446
     * Return HTML form to add/edit a link item.
5447
     *
5448
     * @param string  $action (add/edit)
5449
     * @param CLpItem $lpItem
5450
     * @param CLink   $link
5451
     *
5452
     * @throws Exception
5453
     *
5454
     *
5455
     * @return string HTML form
5456
     */
5457
    public function display_link_form($action, $lpItem, $link)
5458
    {
5459
        $item_url = '';
5460
        if ($link) {
5461
            $item_url = stripslashes($link->getUrl());
5462
        }
5463
        $form = new FormValidator(
5464
            'edit_link',
5465
            'POST',
5466
            $this->getCurrentBuildingModeURL()
5467
        );
5468
5469
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5470
5471
        $urlAttributes = ['class' => 'learnpath_item_form'];
5472
        $urlAttributes['disabled'] = 'disabled';
5473
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
5474
        $form->setDefault('url', $item_url);
5475
5476
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5477
5478
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5479
    }
5480
5481
    /**
5482
     * Return HTML form to add/edit a quiz.
5483
     *
5484
     * @param string  $action   Action (add/edit)
5485
     * @param CLpItem $lpItem   Item ID if already exists
5486
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
5487
     *
5488
     * @throws Exception
5489
     *
5490
     * @return string HTML form
5491
     */
5492
    public function display_quiz_form($action, $lpItem, $exercise)
5493
    {
5494
        $form = new FormValidator(
5495
            'quiz_form',
5496
            'POST',
5497
            $this->getCurrentBuildingModeURL()
5498
        );
5499
5500
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5501
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5502
5503
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5504
    }
5505
5506
    /**
5507
     * Return the form to display the forum edit/add option.
5508
     *
5509
     * @param CLpItem $lpItem
5510
     *
5511
     * @throws Exception
5512
     *
5513
     * @return string HTML form
5514
     */
5515
    public function display_forum_form($action, $lpItem, $resource)
5516
    {
5517
        $form = new FormValidator(
5518
            'forum_form',
5519
            'POST',
5520
            $this->getCurrentBuildingModeURL()
5521
        );
5522
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5523
5524
        if ('add' === $action) {
5525
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
5526
        } else {
5527
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
5528
        }
5529
5530
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5531
    }
5532
5533
    /**
5534
     * Return HTML form to add/edit forum threads.
5535
     *
5536
     * @param string  $action
5537
     * @param CLpItem $lpItem
5538
     * @param string  $resource
5539
     *
5540
     * @throws Exception
5541
     *
5542
     * @return string HTML form
5543
     */
5544
    public function display_thread_form($action, $lpItem, $resource)
5545
    {
5546
        $form = new FormValidator(
5547
            'thread_form',
5548
            'POST',
5549
            $this->getCurrentBuildingModeURL()
5550
        );
5551
5552
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5553
5554
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5555
5556
        return $form->returnForm();
5557
    }
5558
5559
    /**
5560
     * Return the HTML form to display an item (generally a dir item).
5561
     *
5562
     * @param CLpItem $lpItem
5563
     * @param string  $action
5564
     *
5565
     * @throws Exception
5566
     *
5567
     *
5568
     * @return string HTML form
5569
     */
5570
    public function display_item_form(
5571
        $lpItem,
5572
        $action = 'add_item'
5573
    ) {
5574
        $item_type = $lpItem->getItemType();
5575
5576
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5577
5578
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5579
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5580
5581
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5582
5583
        return $form->returnForm();
5584
    }
5585
5586
    /**
5587
     * Return HTML form to add/edit a student publication (work).
5588
     *
5589
     * @param string              $action
5590
     * @param CStudentPublication $resource
5591
     *
5592
     * @throws Exception
5593
     *
5594
     * @return string HTML form
5595
     */
5596
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
5597
    {
5598
        $form = new FormValidator('frm_student_publication', 'post', '#');
5599
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5600
5601
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5602
5603
        $return = '<div class="sectioncomment">';
5604
        $return .= $form->returnForm();
5605
        $return .= '</div>';
5606
5607
        return $return;
5608
    }
5609
5610
    public function displayNewSectionForm()
5611
    {
5612
        $action = 'add_item';
5613
        $item_type = 'dir';
5614
5615
        $lpItem = (new CLpItem())
5616
            ->setTitle('')
5617
            ->setItemType('dir')
5618
        ;
5619
5620
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5621
5622
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5623
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
5624
5625
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5626
        $form->addElement('hidden', 'type', 'dir');
5627
5628
        return $form->returnForm();
5629
    }
5630
5631
    /**
5632
     * Returns the form to update or create a document.
5633
     *
5634
     * @param string  $action (add/edit)
5635
     * @param CLpItem $lpItem
5636
     *
5637
     *
5638
     * @throws Exception
5639
     *
5640
     * @return string HTML form
5641
     */
5642
    public function displayDocumentForm($action = 'add', $lpItem = null)
5643
    {
5644
        $courseInfo = api_get_course_info();
5645
5646
        $form = new FormValidator(
5647
            'form',
5648
            'POST',
5649
            $this->getCurrentBuildingModeURL(),
5650
            '',
5651
            ['enctype' => 'multipart/form-data']
5652
        );
5653
5654
        $data = $this->generate_lp_folder($courseInfo);
5655
5656
        if (null !== $lpItem) {
5657
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5658
        }
5659
5660
        switch ($action) {
5661
            case 'add':
5662
                $folders = DocumentManager::get_all_document_folders(
5663
                    $courseInfo,
5664
                    0,
5665
                    true
5666
                );
5667
                DocumentManager::build_directory_selector(
5668
                    $folders,
5669
                    '',
5670
                    [],
5671
                    true,
5672
                    $form,
5673
                    'directory_parent_id'
5674
                );
5675
5676
                if ($data) {
5677
                    $defaults['directory_parent_id'] = $data->getIid();
5678
                }
5679
5680
                break;
5681
        }
5682
5683
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5684
5685
        return $form->returnForm();
5686
    }
5687
5688
    /**
5689
     * @param array  $courseInfo
5690
     * @param string $content
5691
     * @param string $title
5692
     * @param int    $parentId
5693
     *
5694
     * @return int
5695
     */
5696
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
5697
    {
5698
        $creatorId = api_get_user_id();
5699
        $sessionId = api_get_session_id();
5700
5701
        // Generates folder
5702
        $result = $this->generate_lp_folder($courseInfo);
5703
        $dir = $result['dir'];
5704
5705
        if (empty($parentId) || '/' === $parentId) {
5706
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
5707
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
5708
5709
            if ('/' === $parentId) {
5710
                $dir = '/';
5711
            }
5712
5713
            // Please, do not modify this dirname formatting.
5714
            if (strstr($dir, '..')) {
5715
                $dir = '/';
5716
            }
5717
5718
            if (!empty($dir[0]) && '.' == $dir[0]) {
5719
                $dir = substr($dir, 1);
5720
            }
5721
            if (!empty($dir[0]) && '/' != $dir[0]) {
5722
                $dir = '/'.$dir;
5723
            }
5724
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
5725
                $dir .= '/';
5726
            }
5727
        } else {
5728
            $parentInfo = DocumentManager::get_document_data_by_id(
5729
                $parentId,
5730
                $courseInfo['code']
5731
            );
5732
            if (!empty($parentInfo)) {
5733
                $dir = $parentInfo['path'].'/';
5734
            }
5735
        }
5736
5737
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5738
5739
        if (!is_dir($filepath)) {
5740
            $dir = '/';
5741
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5742
        }
5743
5744
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5745
5746
        if (!empty($title)) {
5747
            $title = api_replace_dangerous_char(stripslashes($title));
5748
        } else {
5749
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5750
        }
5751
5752
        $title = disable_dangerous_file($title);
5753
        $filename = $title;
5754
        $content = !empty($content) ? $content : $_POST['content_lp'];
5755
        $tmpFileName = $filename;
5756
5757
        $i = 0;
5758
        while (file_exists($filepath.$tmpFileName.'.html')) {
5759
            $tmpFileName = $filename.'_'.++$i;
5760
        }
5761
5762
        $filename = $tmpFileName.'.html';
5763
        $content = stripslashes($content);
5764
5765
        if (file_exists($filepath.$filename)) {
5766
            return 0;
5767
        }
5768
5769
        $putContent = file_put_contents($filepath.$filename, $content);
5770
5771
        if (false === $putContent) {
5772
            return 0;
5773
        }
5774
5775
        $fileSize = filesize($filepath.$filename);
5776
        $saveFilePath = $dir.$filename;
5777
5778
        $document = DocumentManager::addDocument(
5779
            $courseInfo,
5780
            $saveFilePath,
5781
            'file',
5782
            $fileSize,
5783
            $tmpFileName,
5784
            '',
5785
            0, //readonly
5786
            true,
5787
            null,
5788
            $sessionId,
5789
            $creatorId
5790
        );
5791
5792
        $documentId = $document->getIid();
5793
5794
        if (!$document) {
5795
            return 0;
5796
        }
5797
5798
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5799
        $newTitle = $originalTitle;
5800
5801
        if ($newComment || $newTitle) {
5802
            $em = Database::getManager();
5803
5804
            if ($newComment) {
5805
                $document->setComment($newComment);
5806
            }
5807
5808
            if ($newTitle) {
5809
                $document->setTitle($newTitle);
5810
            }
5811
5812
            $em->persist($document);
5813
            $em->flush();
5814
        }
5815
5816
        return $documentId;
5817
    }
5818
5819
    /**
5820
     * Displays the menu for manipulating a step.
5821
     *
5822
     * @return string
5823
     */
5824
    public function displayItemMenu(CLpItem $lpItem)
5825
    {
5826
        $item_id = $lpItem->getIid();
5827
        $audio = $lpItem->getAudio();
5828
        $itemType = $lpItem->getItemType();
5829
        $path = $lpItem->getPath();
5830
5831
        $return = '';
5832
        $audio_player = null;
5833
        // We display an audio player if needed.
5834
        if (!empty($audio)) {
5835
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
5836
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
5837
                .'<audio src="'.$webAudioPath.'" controls>'
5838
                .'</div><br>';*/
5839
        }
5840
5841
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
5842
5843
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
5844
            $return .= Display::url(
5845
                Display::getMdiIcon('pencil', 'ch-tool-icon', null, 22, get_lang('Edit')),
5846
                $url.'&action=edit_item&path_item='.$path
5847
            );
5848
5849
            /*$return .= Display::url(
5850
                Display::getMdiIcon('arrow-right-bold', 'ch-tool-icon', null, 22, get_lang('Move')),
5851
                $url.'&action=move_item'
5852
            );*/
5853
        }
5854
5855
        // Commented for now as prerequisites cannot be added to chapters.
5856
        if ('dir' !== $itemType) {
5857
            $return .= Display::url(
5858
                Display::getMdiIcon('graph', 'ch-tool-icon', null, 22, get_lang('Prerequisites')),
5859
                $url.'&action=edit_item_prereq'
5860
            );
5861
        }
5862
        $return .= Display::url(
5863
            Display::getMdiIcon('delete', 'ch-tool-icon', null, 22, get_lang('Delete')),
5864
            $url.'&action=delete_item'
5865
        );
5866
5867
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
5868
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
5869
            if (empty($documentData)) {
5870
                // Try with iid
5871
                $table = Database::get_course_table(TABLE_DOCUMENT);
5872
                $sql = "SELECT path FROM $table
5873
                        WHERE
5874
                              c_id = ".api_get_course_int_id()." AND
5875
                              iid = ".$path." AND
5876
                              path NOT LIKE '%_DELETED_%'";
5877
                $result = Database::query($sql);
5878
                $documentData = Database::fetch_array($result);
5879
                if ($documentData) {
5880
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
5881
                }
5882
            }
5883
            if (isset($documentData['absolute_path_from_document'])) {
5884
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
5885
            }
5886
        }*/
5887
5888
        if (!empty($audio_player)) {
5889
            $return .= $audio_player;
5890
        }
5891
5892
        return Display::toolbarAction('lp_item', [$return]);
5893
    }
5894
5895
    /**
5896
     * Creates the javascript needed for filling up the checkboxes without page reload.
5897
     *
5898
     * @return string
5899
     */
5900
    public function get_js_dropdown_array()
5901
    {
5902
        $return = 'var child_name = new Array();'."\n";
5903
        $return .= 'var child_value = new Array();'."\n\n";
5904
        $return .= 'child_name[0] = new Array();'."\n";
5905
        $return .= 'child_value[0] = new Array();'."\n\n";
5906
5907
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
5908
        $sql = "SELECT * FROM ".$tbl_lp_item."
5909
                WHERE
5910
                    lp_id = ".$this->lp_id." AND
5911
                    parent_item_id = 0
5912
                ORDER BY display_order ASC";
5913
        Database::query($sql);
5914
        $i = 0;
5915
5916
        $list = $this->getItemsForForm(true);
5917
5918
        foreach ($list as $row_zero) {
5919
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
5920
                if (TOOL_QUIZ == $row_zero['item_type']) {
5921
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
5922
                }
5923
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
5924
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
5925
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
5926
            }
5927
        }
5928
5929
        $return .= "\n";
5930
        $sql = "SELECT * FROM $tbl_lp_item
5931
                WHERE lp_id = ".$this->lp_id;
5932
        $res = Database::query($sql);
5933
        while ($row = Database::fetch_array($res)) {
5934
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
5935
                           WHERE
5936
                                parent_item_id = ".$row['iid']."
5937
                           ORDER BY display_order ASC";
5938
            $res_parent = Database::query($sql_parent);
5939
            $i = 0;
5940
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
5941
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
5942
5943
            while ($row_parent = Database::fetch_array($res_parent)) {
5944
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
5945
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
5946
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
5947
            }
5948
            $return .= "\n";
5949
        }
5950
5951
        $return .= "
5952
            function load_cbo(id) {
5953
                if (!id) {
5954
                    return false;
5955
                }
5956
5957
                var cbo = document.getElementById('previous');
5958
                if (cbo) {
5959
                    for(var i = cbo.length - 1; i > 0; i--) {
5960
                        cbo.options[i] = null;
5961
                    }
5962
                    var k=0;
5963
                    for (var i = 1; i <= child_name[id].length; i++){
5964
                        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
5965
                        option.style.paddingLeft = '40px';
5966
                        cbo.options[i] = option;
5967
                        k = i;
5968
                    }
5969
                    cbo.options[k].selected = true;
5970
                }
5971
5972
                //$('#previous').selectpicker('refresh');
5973
            }";
5974
5975
        return $return;
5976
    }
5977
5978
    /**
5979
     * Display the form to allow moving an item.
5980
     *
5981
     * @param CLpItem $lpItem
5982
     *
5983
     * @throws Exception
5984
     *
5985
     *
5986
     * @return string HTML form
5987
     */
5988
    public function display_move_item($lpItem)
5989
    {
5990
        $return = '';
5991
        $path = $lpItem->getPath();
5992
5993
        if ($lpItem) {
5994
            $itemType = $lpItem->getItemType();
5995
            switch ($itemType) {
5996
                case 'dir':
5997
                case 'asset':
5998
                    $return .= $this->displayItemMenu($lpItem);
5999
                    $return .= $this->display_item_form(
6000
                        $lpItem,
6001
                        get_lang('Move the current section'),
6002
                        'move',
6003
                        $row
6004
                    );
6005
                    break;
6006
                case TOOL_DOCUMENT:
6007
                    $return .= $this->displayItemMenu($lpItem);
6008
                    $return .= $this->displayDocumentForm('move', $lpItem);
6009
                    break;
6010
                case TOOL_LINK:
6011
                    $link = null;
6012
                    if (!empty($path)) {
6013
                        $repo = Container::getLinkRepository();
6014
                        $link = $repo->find($path);
6015
                    }
6016
                    $return .= $this->displayItemMenu($lpItem);
6017
                    $return .= $this->display_link_form('move', $lpItem, $link);
6018
                    break;
6019
                case TOOL_HOTPOTATOES:
6020
                    $return .= $this->displayItemMenu($lpItem);
6021
                    $return .= $this->display_link_form('move', $lpItem, $row);
6022
                    break;
6023
                case TOOL_QUIZ:
6024
                    $return .= $this->displayItemMenu($lpItem);
6025
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6026
                    break;
6027
                case TOOL_STUDENTPUBLICATION:
6028
                    $return .= $this->displayItemMenu($lpItem);
6029
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6030
                    break;
6031
                case TOOL_FORUM:
6032
                    $return .= $this->displayItemMenu($lpItem);
6033
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6034
                    break;
6035
                case TOOL_THREAD:
6036
                    $return .= $this->displayItemMenu($lpItem);
6037
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6038
                    break;
6039
            }
6040
        }
6041
6042
        return $return;
6043
    }
6044
6045
    /**
6046
     * Return HTML form to allow prerequisites selection.
6047
     *
6048
     * @todo use FormValidator
6049
     *
6050
     * @return string HTML form
6051
     */
6052
    public function displayItemPrerequisitesForm(CLpItem $lpItem)
6053
    {
6054
        $courseId = api_get_course_int_id();
6055
        $preRequisiteId = $lpItem->getPrerequisite();
6056
        $itemId = $lpItem->getIid();
6057
6058
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6059
6060
        $return .= '<form method="POST">';
6061
        $return .= '<div class="table-responsive">';
6062
        $return .= '<table class="table table-hover">';
6063
        $return .= '<thead>';
6064
        $return .= '<tr>';
6065
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6066
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6067
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6068
        $return .= '</tr>';
6069
        $return .= '</thead>';
6070
6071
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6072
        $return .= '<tbody>';
6073
        $return .= '<tr>';
6074
        $return .= '<td colspan="3">';
6075
        $return .= '<div class="radio learnpath"><label for="idnone">';
6076
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6077
        $return .= get_lang('none').'</label>';
6078
        $return .= '</div>';
6079
        $return .= '</tr>';
6080
6081
        // @todo use entitites
6082
        $tblLpItem = Database::get_course_table(TABLE_LP_ITEM);
6083
        $sql = "SELECT * FROM $tblLpItem
6084
                WHERE lp_id = ".$this->lp_id;
6085
        $result = Database::query($sql);
6086
6087
        $selectedMinScore = [];
6088
        $selectedMaxScore = [];
6089
        $masteryScore = [];
6090
        while ($row = Database::fetch_array($result)) {
6091
            if ($row['iid'] == $itemId) {
6092
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6093
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6094
            }
6095
            $masteryScore[$row['iid']] = $row['mastery_score'];
6096
        }
6097
6098
        $displayOrder = $lpItem->getDisplayOrder();
6099
        $lpItemRepo = Container::getLpItemRepository();
6100
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
6101
        $em = Database::getManager();
6102
6103
        $currentItemId = $itemId;
6104
        $options = [
6105
            'decorate' => true,
6106
            'rootOpen' => function () {
6107
                return '';
6108
            },
6109
            'rootClose' => function () {
6110
                return '';
6111
            },
6112
            'childOpen' => function () {
6113
                return '';
6114
            },
6115
            'childClose' => '',
6116
            'nodeDecorator' => function ($item) use (
6117
                $currentItemId,
6118
                $preRequisiteId,
6119
                $courseId,
6120
                $selectedMaxScore,
6121
                $selectedMinScore,
6122
                $displayOrder,
6123
                $lpItemRepo,
6124
                $em
6125
            ) {
6126
                $itemId = $item['iid'];
6127
                $type = $item['itemType'];
6128
                $iconName = str_replace(' ', '', $type);
6129
                switch ($iconName) {
6130
                    case 'category':
6131
                    case 'chapter':
6132
                    case 'folder':
6133
                    case 'dir':
6134
                        $icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY);
6135
                        break;
6136
                    default:
6137
                        $icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY);
6138
                        break;
6139
                }
6140
6141
                if ($itemId == $currentItemId) {
6142
                    return '';
6143
                }
6144
6145
                if ($displayOrder < $item['displayOrder']) {
6146
                    return '';
6147
                }
6148
6149
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6150
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6151
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6152
6153
                $return = '<tr>';
6154
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6155
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6156
                $return .= '<label for="id'.$itemId.'">';
6157
6158
                $checked = '';
6159
                if (null !== $preRequisiteId) {
6160
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6161
                }
6162
6163
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6164
6165
                $return .= '<input
6166
                    '.$checked.' '.$disabled.'
6167
                    id="id'.$itemId.'"
6168
                    name="prerequisites"
6169
                    type="radio"
6170
                    value="'.$itemId.'" />';
6171
6172
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6173
                $return .= '</div>';
6174
                $return .= '</td>';
6175
6176
                if (TOOL_QUIZ == $type) {
6177
                    // let's update max_score Tests information depending of the Tests Advanced properties
6178
                    $exercise = new Exercise($courseId);
6179
                    /** @var CLpItem $itemEntity */
6180
                    $itemEntity = $lpItemRepo->find($itemId);
6181
                    $exercise->read($item['path']);
6182
                    $itemEntity->setMaxScore($exercise->getMaxScore());
6183
                    $em->persist($itemEntity);
6184
                    $em->flush($itemEntity);
6185
6186
                    $item['maxScore'] = $exercise->getMaxScore();
6187
6188
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6189
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6190
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6191
                    }
6192
                    $return .= '<td>';
6193
                    $return .= '<input
6194
                        class="form-control"
6195
                        size="4" maxlength="3"
6196
                        name="min_'.$itemId.'"
6197
                        type="number"
6198
                        min="0"
6199
                        step="any"
6200
                        max="'.$item['maxScore'].'"
6201
                        value="'.$selectedMinScoreValue.'"
6202
                    />';
6203
                    $return .= '</td>';
6204
                    $return .= '<td>';
6205
                    $return .= '<input
6206
                        class="form-control"
6207
                        size="4"
6208
                        maxlength="3"
6209
                        name="max_'.$itemId.'"
6210
                        type="number"
6211
                        min="0"
6212
                        step="any"
6213
                        max="'.$item['maxScore'].'"
6214
                        value="'.$selectedMaxScoreValue.'"
6215
                    />';
6216
                        $return .= '</td>';
6217
                    }
6218
6219
                if (TOOL_HOTPOTATOES == $type) {
6220
                    $return .= '<td>';
6221
                    $return .= '<input
6222
                        size="4"
6223
                        maxlength="3"
6224
                        name="min_'.$itemId.'"
6225
                        type="number"
6226
                        min="0"
6227
                        step="any"
6228
                        max="'.$item['maxScore'].'"
6229
                        value="'.$selectedMinScoreValue.'"
6230
                    />';
6231
                        $return .= '</td>';
6232
                        $return .= '<td>';
6233
                        $return .= '<input
6234
                        size="4"
6235
                        maxlength="3"
6236
                        name="max_'.$itemId.'"
6237
                        type="number"
6238
                        min="0"
6239
                        step="any"
6240
                        max="'.$item['maxScore'].'"
6241
                        value="'.$selectedMaxScoreValue.'"
6242
                    />';
6243
                    $return .= '</td>';
6244
                }
6245
                $return .= '</tr>';
6246
6247
                return $return;
6248
            },
6249
        ];
6250
6251
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6252
        $return .= $tree;
6253
        $return .= '</tbody>';
6254
        $return .= '</table>';
6255
        $return .= '</div>';
6256
        $return .= '<div class="form-group">';
6257
        $return .= '<button class="btn btn--primary" name="submit_button" type="submit">'.
6258
            get_lang('Save prerequisites settings').'</button>';
6259
        $return .= '</form>';
6260
6261
        return $return;
6262
    }
6263
6264
    /**
6265
     * Return HTML list to allow prerequisites selection for lp.
6266
     */
6267
    public function display_lp_prerequisites_list(FormValidator $form)
6268
    {
6269
        $lp_id = $this->lp_id;
6270
        $lp = api_get_lp_entity($lp_id);
6271
        $prerequisiteId = $lp->getPrerequisite();
6272
6273
        $repo = Container::getLpRepository();
6274
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6275
        /** @var CLp[] $lps */
6276
        $lps = $qb->getQuery()->getResult();
6277
6278
        //$session_id = api_get_session_id();
6279
        /*$session_condition = api_get_session_condition($session_id, true, true);
6280
        $sql = "SELECT * FROM $tbl_lp
6281
                WHERE c_id = $course_id $session_condition
6282
                ORDER BY display_order ";
6283
        $rs = Database::query($sql);*/
6284
6285
        $items = [get_lang('none')];
6286
        foreach ($lps as $lp) {
6287
            $myLpId = $lp->getIid();
6288
            if ($myLpId == $lp_id) {
6289
                continue;
6290
            }
6291
            $items[$myLpId] = $lp->getName();
6292
            /*$return .= '<option
6293
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6294
                $lp->getName().
6295
                '</option>';*/
6296
        }
6297
6298
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6299
        $select->setSelected($prerequisiteId);
6300
    }
6301
6302
    /**
6303
     * Creates a list with all the documents in it.
6304
     *
6305
     * @param bool $showInvisibleFiles
6306
     *
6307
     * @throws Exception
6308
     *
6309
     *
6310
     * @return string
6311
     */
6312
    public function get_documents($showInvisibleFiles = false)
6313
    {
6314
        $sessionId = api_get_session_id();
6315
        $documentTree = DocumentManager::get_document_preview(
6316
            api_get_course_entity(),
6317
            $this->lp_id,
6318
            null,
6319
            $sessionId,
6320
            true,
6321
            null,
6322
            null,
6323
            $showInvisibleFiles,
6324
            true
6325
        );
6326
6327
        $form = new FormValidator(
6328
            'form_upload',
6329
            'POST',
6330
            $this->getCurrentBuildingModeURL(),
6331
            '',
6332
            ['enctype' => 'multipart/form-data']
6333
        );
6334
6335
        $folders = DocumentManager::get_all_document_folders(
6336
            api_get_course_info(),
6337
            0,
6338
            true
6339
        );
6340
6341
        $folder = $this->generate_lp_folder(api_get_course_info());
6342
6343
        DocumentManager::build_directory_selector(
6344
            $folders,
6345
            $folder->getIid(),
6346
            [],
6347
            true,
6348
            $form,
6349
            'directory_parent_id'
6350
        );
6351
6352
        $group = [
6353
            $form->createElement(
6354
                'radio',
6355
                'if_exists',
6356
                get_lang('If file exists:'),
6357
                get_lang('Do nothing'),
6358
                'nothing'
6359
            ),
6360
            $form->createElement(
6361
                'radio',
6362
                'if_exists',
6363
                null,
6364
                get_lang('Overwrite the existing file'),
6365
                'overwrite'
6366
            ),
6367
            $form->createElement(
6368
                'radio',
6369
                'if_exists',
6370
                null,
6371
                get_lang('Rename the uploaded file if it exists'),
6372
                'rename'
6373
            ),
6374
        ];
6375
        $form->addGroup($group, null, get_lang('If file exists:'));
6376
6377
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
6378
        $defaultFileExistsOption = 'rename';
6379
        if (!empty($fileExistsOption)) {
6380
            $defaultFileExistsOption = $fileExistsOption;
6381
        }
6382
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6383
6384
        // Check box options
6385
        $form->addCheckBox(
6386
            'unzip',
6387
            get_lang('Options'),
6388
            get_lang('Uncompress zip')
6389
        );
6390
6391
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6392
        $form->addMultipleUpload($url);
6393
6394
        $lpItem = (new CLpItem())
6395
            ->setTitle('')
6396
            ->setItemType(TOOL_DOCUMENT)
6397
        ;
6398
        $new = $this->displayDocumentForm('add', $lpItem);
6399
6400
        /*$lpItem = new CLpItem();
6401
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6402
        $frmReadOutText = $this->displayDocumentForm('add');*/
6403
6404
        $headers = [
6405
            get_lang('Files'),
6406
            get_lang('Create a new document'),
6407
            //get_lang('Create read-out text'),
6408
            get_lang('Upload'),
6409
        ];
6410
6411
        return Display::tabs(
6412
            $headers,
6413
            [$documentTree, $new, $form->returnForm()],
6414
            'subtab',
6415
            ['class' => 'mt-2']
6416
        );
6417
    }
6418
6419
    /**
6420
     * Creates a list with all the exercises (quiz) in it.
6421
     *
6422
     * @return string
6423
     */
6424
    public function get_exercises()
6425
    {
6426
        $course_id = api_get_course_int_id();
6427
        $session_id = api_get_session_id();
6428
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6429
6430
        //$activeCondition = ' active <> -1 ';
6431
        $active = 2;
6432
        if ($setting) {
6433
            $active = 1;
6434
            //$activeCondition = ' active = 1 ';
6435
        }
6436
6437
        $categoryCondition = '';
6438
6439
        $keyword = $_REQUEST['keyword'] ?? null;
6440
        $categoryId = $_REQUEST['category_id'] ?? null;
6441
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6442
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6443
        }
6444
6445
        $keywordCondition = '';
6446
6447
        if (!empty($keyword)) {
6448
            $keyword = Database::escape_string($keyword);
6449
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6450
        }
6451
        */
6452
        $course = api_get_course_entity($course_id);
6453
        $session = api_get_session_entity($session_id);
6454
6455
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6456
        /** @var CQuiz[] $exercises */
6457
        $exercises = $qb->getQuery()->getResult();
6458
6459
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6460
                     WHERE
6461
                            c_id = $course_id AND
6462
                            $activeCondition
6463
                            $condition_session
6464
                            $categoryCondition
6465
                            $keywordCondition
6466
                     ORDER BY title ASC";
6467
        $res_quiz = Database::query($sql_quiz);*/
6468
6469
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6470
6471
        // Create a search-box
6472
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6473
        $form->addHidden('action', 'add_item');
6474
        $form->addHidden('type', 'step');
6475
        $form->addHidden('lp_id', $this->lp_id);
6476
        $form->addHidden('lp_build_selected', '2');
6477
6478
        $form->addCourseHiddenParams();
6479
        $form->addText(
6480
            'keyword',
6481
            get_lang('Search'),
6482
            false,
6483
            [
6484
                'aria-label' => get_lang('Search'),
6485
            ]
6486
        );
6487
6488
        if (api_get_configuration_value('allow_exercise_categories')) {
6489
            $manager = new ExerciseCategoryManager();
6490
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6491
            if (!empty($options)) {
6492
                $form->addSelect(
6493
                    'category_id',
6494
                    get_lang('Category'),
6495
                    $options,
6496
                    ['placeholder' => get_lang('Please select an option')]
6497
                );
6498
            }
6499
        }
6500
6501
        $form->addButtonSearch(get_lang('Search'));
6502
        $return = $form->returnForm();*/
6503
6504
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6505
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6506
        $return .= Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 32, get_lang('New test'));
6507
        $return .= '<a
6508
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6509
            get_lang('New test').'</a>';
6510
        $return .= '</li>';
6511
6512
        $previewIcon = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview'));
6513
        $quizIcon = Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 16, get_lang('Exercise'));
6514
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6515
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6516
        foreach ($exercises as $exercise) {
6517
            $exerciseId = $exercise->getIid();
6518
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6519
            $visibility = $exercise->isVisible($course, $session);
6520
6521
            $link = Display::url(
6522
                $previewIcon,
6523
                $exerciseUrl.'&exerciseId='.$exerciseId,
6524
                ['target' => '_blank']
6525
            );
6526
            $return .= '<li
6527
                class="list-group-item lp_resource_element"
6528
                id="'.$exerciseId.'"
6529
                data-id="'.$exerciseId.'"
6530
                title="'.$title.'">';
6531
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6532
            $return .= $quizIcon;
6533
            $sessionStar = '';
6534
            /*$sessionStar = api_get_session_image(
6535
                $row_quiz['session_id'],
6536
                $userInfo['status']
6537
            );*/
6538
            $return .= Display::url(
6539
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6540
                api_get_self().'?'.
6541
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6542
                [
6543
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6544
                    'data_type' => 'quiz',
6545
                    'data-id' => $exerciseId,
6546
                ]
6547
            );
6548
            $return .= '</li>';
6549
        }
6550
6551
        $return .= '</ul>';
6552
6553
        return $return;
6554
    }
6555
6556
    /**
6557
     * Creates a list with all the links in it.
6558
     *
6559
     * @return string
6560
     */
6561
    public function get_links()
6562
    {
6563
        $sessionId = api_get_session_id();
6564
        $repo = Container::getLinkRepository();
6565
6566
        $course = api_get_course_entity();
6567
        $session = api_get_session_entity($sessionId);
6568
        $qb = $repo->getResourcesByCourse($course, $session);
6569
        /** @var CLink[] $links */
6570
        $links = $qb->getQuery()->getResult();
6571
6572
        $selfUrl = api_get_self();
6573
        $courseIdReq = api_get_cidreq();
6574
        $userInfo = api_get_user_info();
6575
6576
        $moveEverywhereIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6577
6578
        $categorizedLinks = [];
6579
        $categories = [];
6580
6581
        foreach ($links as $link) {
6582
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6583
            if (empty($categoryId)) {
6584
                $categories[0] = get_lang('Uncategorized');
6585
            } else {
6586
                $category = $link->getCategory();
6587
                $categories[$categoryId] = $category->getCategoryTitle();
6588
            }
6589
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6590
        }
6591
6592
        $linksHtmlCode =
6593
            '<script>
6594
            function toggle_tool(tool, id) {
6595
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6596
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6597
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6598
                } else {
6599
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6600
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6601
                }
6602
            }
6603
        </script>
6604
6605
        <ul class="mt-2 bg-white list-group lp_resource">
6606
            <li class="list-group-item lp_resource_element disable_drag ">
6607
                '.Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL).'
6608
                <a
6609
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6610
                title="'.get_lang('Add a link').'">'.
6611
                get_lang('Add a link').'
6612
                </a>
6613
            </li>';
6614
        $linkIcon = Display::getMdiIcon('file-link', 'ch-tool-icon', null, 16, get_lang('Link'));
6615
        foreach ($categorizedLinks as $categoryId => $links) {
6616
            $linkNodes = null;
6617
            /** @var CLink $link */
6618
            foreach ($links as $key => $link) {
6619
                $title = $link->getTitle();
6620
                $id = $link->getIid();
6621
                $linkUrl = Display::url(
6622
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6623
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6624
                    ['target' => '_blank']
6625
                );
6626
6627
                if ($link->isVisible($course, $session)) {
6628
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6629
                    $sessionStar = '';
6630
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6631
                    $link = Display::url(
6632
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6633
                        $url,
6634
                        [
6635
                            'class' => 'moved link_with_id',
6636
                            'data-id' => $key,
6637
                            'data_type' => TOOL_LINK,
6638
                            'title' => $title,
6639
                        ]
6640
                    );
6641
                    $linkNodes .=
6642
                        "<li
6643
                            class='list-group-item lp_resource_element'
6644
                            id= $id
6645
                            data-id= $id
6646
                            >
6647
                         <a class='moved' href='#'>
6648
                            $moveEverywhereIcon
6649
                        </a>
6650
                        $linkIcon $link
6651
                        </li>";
6652
                }
6653
            }
6654
            $linksHtmlCode .=
6655
                '<li class="list-group-item disable_drag">
6656
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6657
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6658
                        align="absbottom" />
6659
                    </a>
6660
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6661
                </li>
6662
            '.
6663
                $linkNodes.
6664
            '';
6665
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6666
        }
6667
        $linksHtmlCode .= '</ul>';
6668
6669
        return $linksHtmlCode;
6670
    }
6671
6672
    /**
6673
     * Creates a list with all the student publications in it.
6674
     *
6675
     * @return string
6676
     */
6677
    public function get_student_publications()
6678
    {
6679
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6680
        $return .= '<li class="list-group-item lp_resource_element">';
6681
        $works = getWorkListTeacher(0, 100, null, null, null);
6682
        if (!empty($works)) {
6683
            $icon = Display::getMdiIcon('inbox-full', 'ch-tool-icon',null, 16, get_lang('Student publication'));
6684
            foreach ($works as $work) {
6685
                $workId = $work['iid'];
6686
                $link = Display::url(
6687
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6688
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6689
                    ['target' => '_blank']
6690
                );
6691
6692
                $return .= '<li
6693
                    class="list-group-item lp_resource_element"
6694
                    id="'.$workId.'"
6695
                    data-id="'.$workId.'"
6696
                    >';
6697
                $return .= '<a class="moved" href="#">';
6698
                $return .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6699
                $return .= '</a> ';
6700
6701
                $return .= $icon;
6702
                $return .= Display::url(
6703
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6704
                    api_get_self().'?'.
6705
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6706
                    [
6707
                        'class' => 'moved link_with_id',
6708
                        'data-id' => $work['iid'],
6709
                        'data_type' => TOOL_STUDENTPUBLICATION,
6710
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6711
                    ]
6712
                );
6713
                $return .= '</li>';
6714
            }
6715
        }
6716
6717
        $return .= '</ul>';
6718
6719
        return $return;
6720
    }
6721
6722
    /**
6723
     * Creates a list with all the forums in it.
6724
     *
6725
     * @return string
6726
     */
6727
    public function get_forums()
6728
    {
6729
        $forumCategories = get_forum_categories();
6730
        $forumsInNoCategory = get_forums_in_category(0);
6731
        if (!empty($forumsInNoCategory)) {
6732
            $forumCategories = array_merge(
6733
                $forumCategories,
6734
                [
6735
                    [
6736
                        'cat_id' => 0,
6737
                        'session_id' => 0,
6738
                        'visibility' => 1,
6739
                        'cat_comment' => null,
6740
                    ],
6741
                ]
6742
            );
6743
        }
6744
6745
        $a_forums = [];
6746
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6747
        $sessionEntity = api_get_session_entity(api_get_session_id());
6748
6749
        foreach ($forumCategories as $forumCategory) {
6750
            // The forums in this category.
6751
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6752
            if (!empty($forumsInCategory)) {
6753
                foreach ($forumsInCategory as $forum) {
6754
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6755
                        $a_forums[] = $forum;
6756
                    }
6757
                }
6758
            }
6759
        }
6760
6761
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6762
6763
        // First add link
6764
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6765
        $return .= Display::getMdiIcon('comment-quote	', 'ch-tool-icon', null, 32, get_lang('Create a new forum'));
6766
        $return .= Display::url(
6767
            get_lang('Create a new forum'),
6768
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6769
                'action' => 'add',
6770
                'content' => 'forum',
6771
                'lp_id' => $this->lp_id,
6772
            ]),
6773
            ['title' => get_lang('Create a new forum')]
6774
        );
6775
        $return .= '</li>';
6776
6777
        $return .= '<script>
6778
            function toggle_forum(forum_id) {
6779
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6780
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6781
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6782
                } else {
6783
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6784
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6785
                }
6786
            }
6787
        </script>';
6788
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6789
        foreach ($a_forums as $forum) {
6790
            $forumId = $forum->getIid();
6791
            $title = Security::remove_XSS($forum->getForumTitle());
6792
            $link = Display::url(
6793
                Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6794
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6795
                ['target' => '_blank']
6796
            );
6797
6798
            $return .= '<li
6799
                    class="list-group-item lp_resource_element"
6800
                    id="'.$forumId.'"
6801
                    data-id="'.$forumId.'"
6802
                    >';
6803
            $return .= '<a class="moved" href="#">';
6804
            $return .= $moveIcon;
6805
            $return .= ' </a>';
6806
            $return .= Display::getMdiIcon('comment-quote', 'ch-tool-icon', null, 16, get_lang('Forum'));
6807
6808
            $moveLink = Display::url(
6809
                $title.' '.$link,
6810
                api_get_self().'?'.
6811
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6812
                [
6813
                    'class' => 'moved link_with_id',
6814
                    'data-id' => $forumId,
6815
                    'data_type' => TOOL_FORUM,
6816
                    'title' => $title,
6817
                    'style' => 'vertical-align:middle',
6818
                ]
6819
            );
6820
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
6821
                            <img
6822
                                src="'.Display::returnIconPath('add.png').'"
6823
                                id="forum_'.$forumId.'_opener" align="absbottom"
6824
                             />
6825
                        </a>
6826
                        '.$moveLink;
6827
            $return .= '</li>';
6828
6829
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
6830
            $threads = get_threads($forumId);
6831
            if (is_array($threads)) {
6832
                foreach ($threads as $thread) {
6833
                    $threadId = $thread->getIid();
6834
                    $link = Display::url(
6835
                        Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6836
                        api_get_path(WEB_CODE_PATH).
6837
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
6838
                        ['target' => '_blank']
6839
                    );
6840
6841
                    $return .= '<li
6842
                        class="list-group-item lp_resource_element"
6843
                      id="'.$threadId.'"
6844
                        data-id="'.$threadId.'"
6845
                    >';
6846
                    $return .= '&nbsp;<a class="moved" href="#">';
6847
                    $return .= $moveIcon;
6848
                    $return .= ' </a>';
6849
                    $return .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, 16, get_lang('Thread'));
6850
                    $return .= '<a
6851
                        class="moved link_with_id"
6852
                        data-id="'.$threadId.'"
6853
                        data_type="'.TOOL_THREAD.'"
6854
                        title="'.$thread->getThreadTitle().'"
6855
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
6856
                        >'.
6857
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
6858
                    $return .= '</li>';
6859
                }
6860
            }
6861
            $return .= '</div>';
6862
        }
6863
        $return .= '</ul>';
6864
6865
        return $return;
6866
    }
6867
6868
    /**
6869
     * Temp function to be moved in main_api or the best place around for this.
6870
     * Creates a file path if it doesn't exist.
6871
     *
6872
     * @param string $path
6873
     */
6874
    public function create_path($path)
6875
    {
6876
        $path_bits = explode('/', dirname($path));
6877
6878
        // IS_WINDOWS_OS has been defined in main_api.lib.php
6879
        $path_built = IS_WINDOWS_OS ? '' : '/';
6880
        foreach ($path_bits as $bit) {
6881
            if (!empty($bit)) {
6882
                $new_path = $path_built.$bit;
6883
                if (is_dir($new_path)) {
6884
                    $path_built = $new_path.'/';
6885
                } else {
6886
                    mkdir($new_path, api_get_permissions_for_new_directories());
6887
                    $path_built = $new_path.'/';
6888
                }
6889
            }
6890
        }
6891
    }
6892
6893
    /**
6894
     * @param int    $lp_id
6895
     * @param string $status
6896
     */
6897
    public function set_autolaunch($lp_id, $status)
6898
    {
6899
        $course_id = api_get_course_int_id();
6900
        $lp_id = (int) $lp_id;
6901
        $status = (int) $status;
6902
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6903
6904
        // Setting everything to autolaunch = 0
6905
        $attributes['autolaunch'] = 0;
6906
        $where = [
6907
            'session_id = ? AND c_id = ? ' => [
6908
                api_get_session_id(),
6909
                $course_id,
6910
            ],
6911
        ];
6912
        Database::update($lp_table, $attributes, $where);
6913
        if (1 == $status) {
6914
            //Setting my lp_id to autolaunch = 1
6915
            $attributes['autolaunch'] = 1;
6916
            $where = [
6917
                'iid = ? AND session_id = ? AND c_id = ?' => [
6918
                    $lp_id,
6919
                    api_get_session_id(),
6920
                    $course_id,
6921
                ],
6922
            ];
6923
            Database::update($lp_table, $attributes, $where);
6924
        }
6925
    }
6926
6927
    /**
6928
     * Gets previous_item_id for the next element of the lp_item table.
6929
     *
6930
     * @author Isaac flores paz
6931
     *
6932
     * @return int Previous item ID
6933
     */
6934
    public function select_previous_item_id()
6935
    {
6936
        $course_id = api_get_course_int_id();
6937
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6938
6939
        // Get the max order of the items
6940
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
6941
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6942
        $rs_max_order = Database::query($sql);
6943
        $row_max_order = Database::fetch_object($rs_max_order);
6944
        $max_order = $row_max_order->display_order;
6945
        // Get the previous item ID
6946
        $sql = "SELECT iid as previous FROM $table_lp_item
6947
                WHERE
6948
                    c_id = $course_id AND
6949
                    lp_id = ".$this->lp_id." AND
6950
                    display_order = '$max_order' ";
6951
        $rs_max = Database::query($sql);
6952
        $row_max = Database::fetch_object($rs_max);
6953
6954
        // Return the previous item ID
6955
        return $row_max->previous;
6956
    }
6957
6958
    /**
6959
     * Copies an LP.
6960
     */
6961
    public function copy()
6962
    {
6963
        // Course builder
6964
        $cb = new CourseBuilder();
6965
6966
        //Setting tools that will be copied
6967
        $cb->set_tools_to_build(['learnpaths']);
6968
6969
        //Setting elements that will be copied
6970
        $cb->set_tools_specific_id_list(
6971
            ['learnpaths' => [$this->lp_id]]
6972
        );
6973
6974
        $course = $cb->build();
6975
6976
        //Course restorer
6977
        $course_restorer = new CourseRestorer($course);
6978
        $course_restorer->set_add_text_in_items(true);
6979
        $course_restorer->set_tool_copy_settings(
6980
            ['learnpaths' => ['reset_dates' => true]]
6981
        );
6982
        $course_restorer->restore(
6983
            api_get_course_id(),
6984
            api_get_session_id(),
6985
            false,
6986
            false
6987
        );
6988
    }
6989
6990
    /**
6991
     * Verify document size.
6992
     *
6993
     * @param string $s
6994
     *
6995
     * @return bool
6996
     */
6997
    public static function verify_document_size($s)
6998
    {
6999
        $post_max = ini_get('post_max_size');
7000
        if ('M' == substr($post_max, -1, 1)) {
7001
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7002
        } elseif ('G' == substr($post_max, -1, 1)) {
7003
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7004
        }
7005
        $upl_max = ini_get('upload_max_filesize');
7006
        if ('M' == substr($upl_max, -1, 1)) {
7007
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7008
        } elseif ('G' == substr($upl_max, -1, 1)) {
7009
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7010
        }
7011
7012
        $repo = Container::getDocumentRepository();
7013
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7014
7015
        $course_max_space = DocumentManager::get_course_quota();
7016
        $total_size = filesize($s) + $documents_total_space;
7017
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7018
            return true;
7019
        }
7020
7021
        return false;
7022
    }
7023
7024
    /**
7025
     * Clear LP prerequisites.
7026
     */
7027
    public function clearPrerequisites()
7028
    {
7029
        $course_id = $this->get_course_int_id();
7030
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7031
        $lp_id = $this->get_id();
7032
        // Cleaning prerequisites
7033
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7034
                WHERE lp_id = $lp_id";
7035
        Database::query($sql);
7036
7037
        // Cleaning mastery score for exercises
7038
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7039
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7040
        Database::query($sql);
7041
    }
7042
7043
    public function set_previous_step_as_prerequisite_for_all_items()
7044
    {
7045
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7046
        $course_id = $this->get_course_int_id();
7047
        $lp_id = $this->get_id();
7048
7049
        if (!empty($this->items)) {
7050
            $previous_item_id = null;
7051
            $previous_item_max = 0;
7052
            $previous_item_type = null;
7053
            $last_item_not_dir = null;
7054
            $last_item_not_dir_type = null;
7055
            $last_item_not_dir_max = null;
7056
7057
            foreach ($this->ordered_items as $itemId) {
7058
                $item = $this->getItem($itemId);
7059
                // if there was a previous item... (otherwise jump to set it)
7060
                if (!empty($previous_item_id)) {
7061
                    $current_item_id = $item->get_id(); //save current id
7062
                    if ('dir' != $item->get_type()) {
7063
                        // Current item is not a folder, so it qualifies to get a prerequisites
7064
                        if ('quiz' == $last_item_not_dir_type) {
7065
                            // if previous is quiz, mark its max score as default score to be achieved
7066
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7067
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7068
                            Database::query($sql);
7069
                        }
7070
                        // now simply update the prerequisite to set it to the last non-chapter item
7071
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7072
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7073
                        Database::query($sql);
7074
                        // record item as 'non-chapter' reference
7075
                        $last_item_not_dir = $item->get_id();
7076
                        $last_item_not_dir_type = $item->get_type();
7077
                        $last_item_not_dir_max = $item->get_max();
7078
                    }
7079
                } else {
7080
                    if ('dir' != $item->get_type()) {
7081
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7082
                        $last_item_not_dir = $item->get_id();
7083
                        $last_item_not_dir_type = $item->get_type();
7084
                        $last_item_not_dir_max = $item->get_max();
7085
                    }
7086
                }
7087
                // Saving the item as "previous item" for the next loop
7088
                $previous_item_id = $item->get_id();
7089
                $previous_item_max = $item->get_max();
7090
                $previous_item_type = $item->get_type();
7091
            }
7092
        }
7093
    }
7094
7095
    /**
7096
     * @param array $params
7097
     *
7098
     * @return int
7099
     */
7100
    public static function createCategory($params)
7101
    {
7102
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7103
7104
        $item = new CLpCategory();
7105
        $item
7106
            ->setName($params['name'])
7107
            ->setParent($courseEntity)
7108
            ->addCourseLink($courseEntity, api_get_session_entity())
7109
        ;
7110
7111
        $repo = Container::getLpCategoryRepository();
7112
        $repo->create($item);
7113
7114
        return $item->getIid();
7115
    }
7116
7117
    /**
7118
     * @param array $params
7119
     */
7120
    public static function updateCategory($params)
7121
    {
7122
        $em = Database::getManager();
7123
        /** @var CLpCategory $item */
7124
        $item = $em->find(CLpCategory::class, $params['id']);
7125
        if ($item) {
7126
            $item->setName($params['name']);
7127
            $em->persist($item);
7128
            $em->flush();
7129
        }
7130
    }
7131
7132
    /**
7133
     * @param int $id
7134
     */
7135
    public static function moveUpCategory($id)
7136
    {
7137
        $id = (int) $id;
7138
        $em = Database::getManager();
7139
        /** @var CLpCategory $item */
7140
        $item = $em->find(CLpCategory::class, $id);
7141
        if ($item) {
7142
            $position = $item->getPosition() - 1;
7143
            $item->setPosition($position);
7144
            $em->persist($item);
7145
            $em->flush();
7146
        }
7147
    }
7148
7149
    /**
7150
     * @param int $id
7151
     */
7152
    public static function moveDownCategory($id)
7153
    {
7154
        $id = (int) $id;
7155
        $em = Database::getManager();
7156
        /** @var CLpCategory $item */
7157
        $item = $em->find(CLpCategory::class, $id);
7158
        if ($item) {
7159
            $position = $item->getPosition() + 1;
7160
            $item->setPosition($position);
7161
            $em->persist($item);
7162
            $em->flush();
7163
        }
7164
    }
7165
7166
    /**
7167
     * @param int $courseId
7168
     *
7169
     * @return int
7170
     */
7171
    public static function getCountCategories($courseId)
7172
    {
7173
        if (empty($courseId)) {
7174
            return 0;
7175
        }
7176
        $repo = Container::getLpCategoryRepository();
7177
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7178
        $qb->addSelect('count(resource)');
7179
7180
        return (int) $qb->getQuery()->getSingleScalarResult();
7181
    }
7182
7183
    /**
7184
     * @param int $courseId
7185
     *
7186
     * @return CLpCategory[]
7187
     */
7188
    public static function getCategories($courseId)
7189
    {
7190
        // Using doctrine extensions
7191
        $repo = Container::getLpCategoryRepository();
7192
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity());
7193
7194
        return $qb->getQuery()->getResult();
7195
    }
7196
7197
    public static function getCategorySessionId($id)
7198
    {
7199
        if ('true' !== api_get_setting('lp.allow_session_lp_category')) {
7200
            return 0;
7201
        }
7202
7203
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
7204
        $id = (int) $id;
7205
7206
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
7207
        $result = Database::query($sql);
7208
        $result = Database::fetch_array($result, 'ASSOC');
7209
7210
        if ($result) {
7211
            return (int) $result['session_id'];
7212
        }
7213
7214
        return 0;
7215
    }
7216
7217
    /**
7218
     * @param int $id
7219
     */
7220
    public static function deleteCategory($id): bool
7221
    {
7222
        $repo = Container::getLpCategoryRepository();
7223
        /** @var CLpCategory $category */
7224
        $category = $repo->find($id);
7225
        if ($category) {
7226
            $em = Database::getManager();
7227
            $lps = $category->getLps();
7228
7229
            foreach ($lps as $lp) {
7230
                $lp->setCategory(null);
7231
                $em->persist($lp);
7232
            }
7233
7234
            // Removing category.
7235
            $em->remove($category);
7236
            $em->flush();
7237
7238
            return true;
7239
        }
7240
7241
        return false;
7242
    }
7243
7244
    /**
7245
     * @param int  $courseId
7246
     * @param bool $addSelectOption
7247
     *
7248
     * @return array
7249
     */
7250
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7251
    {
7252
        $repo = Container::getLpCategoryRepository();
7253
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7254
        $items = $qb->getQuery()->getResult();
7255
7256
        $cats = [];
7257
        if ($addSelectOption) {
7258
            $cats = [get_lang('Select a category')];
7259
        }
7260
7261
        if (!empty($items)) {
7262
            foreach ($items as $cat) {
7263
                $cats[$cat->getIid()] = $cat->getName();
7264
            }
7265
        }
7266
7267
        return $cats;
7268
    }
7269
7270
    /**
7271
     * @param int   $courseId
7272
     * @param int   $lpId
7273
     * @param int   $user_id
7274
     *
7275
     * @return learnpath
7276
     */
7277
    public static function getLpFromSession(int $courseId, int $lpId, int $user_id)
7278
    {
7279
        $debug = 0;
7280
        $learnPath = null;
7281
        $lpObject = Session::read('lpobject');
7282
7283
        $repo = Container::getLpRepository();
7284
        $lp = $repo->find($lpId);
7285
        if (null !== $lpObject) {
7286
            /** @var learnpath $learnPath */
7287
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7288
            $learnPath->entity = $lp;
7289
            if ($debug) {
7290
                error_log('getLpFromSession: unserialize');
7291
                error_log('------getLpFromSession------');
7292
                error_log('------unserialize------');
7293
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7294
                error_log("api_get_sessionid: ".api_get_session_id());
7295
            }
7296
        }
7297
7298
        if (!is_object($learnPath)) {
7299
            $learnPath = new learnpath($lp, api_get_course_info_by_id($courseId), $user_id);
7300
            if ($debug) {
7301
                error_log('------getLpFromSession------');
7302
                error_log('getLpFromSession: create new learnpath');
7303
                error_log("create new LP with $courseId - $lpId - $user_id");
7304
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7305
                error_log("api_get_sessionid: ".api_get_session_id());
7306
            }
7307
        }
7308
7309
        return $learnPath;
7310
    }
7311
7312
    /**
7313
     * @param int $itemId
7314
     *
7315
     * @return learnpathItem|false
7316
     */
7317
    public function getItem($itemId)
7318
    {
7319
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7320
            return $this->items[$itemId];
7321
        }
7322
7323
        return false;
7324
    }
7325
7326
    /**
7327
     * @return int
7328
     */
7329
    public function getCurrentAttempt()
7330
    {
7331
        $attempt = $this->getItem($this->get_current_item_id());
7332
        if ($attempt) {
7333
            return $attempt->get_attempt_id();
7334
        }
7335
7336
        return 0;
7337
    }
7338
7339
    /**
7340
     * @return int
7341
     */
7342
    public function getCategoryId()
7343
    {
7344
        return (int) $this->categoryId;
7345
    }
7346
7347
    /**
7348
     * Get whether this is a learning path with the possibility to subscribe
7349
     * users or not.
7350
     *
7351
     * @return int
7352
     */
7353
    public function getSubscribeUsers()
7354
    {
7355
        return $this->subscribeUsers;
7356
    }
7357
7358
    /**
7359
     * Calculate the count of stars for a user in this LP
7360
     * This calculation is based on the following rules:
7361
     * - the student gets one star when he gets to 50% of the learning path
7362
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7363
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7364
     * - the student gets the final star when the score for the *last* test is >= 80%.
7365
     *
7366
     * @param int $sessionId Optional. The session ID
7367
     *
7368
     * @return int The count of stars
7369
     */
7370
    public function getCalculateStars($sessionId = 0)
7371
    {
7372
        $stars = 0;
7373
        $progress = self::getProgress(
7374
            $this->lp_id,
7375
            $this->user_id,
7376
            $this->course_int_id,
7377
            $sessionId
7378
        );
7379
7380
        if ($progress >= 50) {
7381
            $stars++;
7382
        }
7383
7384
        // Calculate stars chapters evaluation
7385
        $exercisesItems = $this->getExercisesItems();
7386
7387
        if (!empty($exercisesItems)) {
7388
            $totalResult = 0;
7389
7390
            foreach ($exercisesItems as $exerciseItem) {
7391
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7392
                    $this->user_id,
7393
                    $exerciseItem->path,
7394
                    $this->course_int_id,
7395
                    $sessionId,
7396
                    $this->lp_id,
7397
                    $exerciseItem->db_id
7398
                );
7399
7400
                $exerciseResultInfo = end($exerciseResultInfo);
7401
7402
                if (!$exerciseResultInfo) {
7403
                    continue;
7404
                }
7405
7406
                if (!empty($exerciseResultInfo['max_score'])) {
7407
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7408
                } else {
7409
                    $exerciseResult = 0;
7410
                }
7411
                $totalResult += $exerciseResult;
7412
            }
7413
7414
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7415
7416
            if ($totalExerciseAverage >= 50) {
7417
                $stars++;
7418
            }
7419
7420
            if ($totalExerciseAverage >= 80) {
7421
                $stars++;
7422
            }
7423
        }
7424
7425
        // Calculate star for final evaluation
7426
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7427
7428
        if (!empty($finalEvaluationItem)) {
7429
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7430
                $this->user_id,
7431
                $finalEvaluationItem->path,
7432
                $this->course_int_id,
7433
                $sessionId,
7434
                $this->lp_id,
7435
                $finalEvaluationItem->db_id
7436
            );
7437
7438
            $evaluationResultInfo = end($evaluationResultInfo);
7439
7440
            if ($evaluationResultInfo) {
7441
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7442
                if ($evaluationResult >= 80) {
7443
                    $stars++;
7444
                }
7445
            }
7446
        }
7447
7448
        return $stars;
7449
    }
7450
7451
    /**
7452
     * Get the items of exercise type.
7453
     *
7454
     * @return array The items. Otherwise return false
7455
     */
7456
    public function getExercisesItems()
7457
    {
7458
        $exercises = [];
7459
        foreach ($this->items as $item) {
7460
            if ('quiz' !== $item->type) {
7461
                continue;
7462
            }
7463
            $exercises[] = $item;
7464
        }
7465
7466
        array_pop($exercises);
7467
7468
        return $exercises;
7469
    }
7470
7471
    /**
7472
     * Get the item of exercise type (evaluation type).
7473
     *
7474
     * @return array The final evaluation. Otherwise return false
7475
     */
7476
    public function getFinalEvaluationItem()
7477
    {
7478
        $exercises = [];
7479
        foreach ($this->items as $item) {
7480
            if (TOOL_QUIZ !== $item->type) {
7481
                continue;
7482
            }
7483
7484
            $exercises[] = $item;
7485
        }
7486
7487
        return array_pop($exercises);
7488
    }
7489
7490
    /**
7491
     * Calculate the total points achieved for the current user in this learning path.
7492
     *
7493
     * @param int $sessionId Optional. The session Id
7494
     *
7495
     * @return int
7496
     */
7497
    public function getCalculateScore($sessionId = 0)
7498
    {
7499
        // Calculate stars chapters evaluation
7500
        $exercisesItems = $this->getExercisesItems();
7501
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7502
        $totalExercisesResult = 0;
7503
        $totalEvaluationResult = 0;
7504
7505
        if (false !== $exercisesItems) {
7506
            foreach ($exercisesItems as $exerciseItem) {
7507
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7508
                    $this->user_id,
7509
                    $exerciseItem->path,
7510
                    $this->course_int_id,
7511
                    $sessionId,
7512
                    $this->lp_id,
7513
                    $exerciseItem->db_id
7514
                );
7515
7516
                $exerciseResultInfo = end($exerciseResultInfo);
7517
7518
                if (!$exerciseResultInfo) {
7519
                    continue;
7520
                }
7521
7522
                $totalExercisesResult += $exerciseResultInfo['score'];
7523
            }
7524
        }
7525
7526
        if (!empty($finalEvaluationItem)) {
7527
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7528
                $this->user_id,
7529
                $finalEvaluationItem->path,
7530
                $this->course_int_id,
7531
                $sessionId,
7532
                $this->lp_id,
7533
                $finalEvaluationItem->db_id
7534
            );
7535
7536
            $evaluationResultInfo = end($evaluationResultInfo);
7537
7538
            if ($evaluationResultInfo) {
7539
                $totalEvaluationResult += $evaluationResultInfo['score'];
7540
            }
7541
        }
7542
7543
        return $totalExercisesResult + $totalEvaluationResult;
7544
    }
7545
7546
    /**
7547
     * Check if URL is not allowed to be show in a iframe.
7548
     *
7549
     * @param string $src
7550
     *
7551
     * @return string
7552
     */
7553
    public function fixBlockedLinks($src)
7554
    {
7555
        $urlInfo = parse_url($src);
7556
7557
        $platformProtocol = 'https';
7558
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7559
            $platformProtocol = 'http';
7560
        }
7561
7562
        $protocolFixApplied = false;
7563
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7564
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7565
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7566
7567
        if ($platformProtocol != $scheme) {
7568
            Session::write('x_frame_source', $src);
7569
            $src = 'blank.php?error=x_frames_options';
7570
            $protocolFixApplied = true;
7571
        }
7572
7573
        if (false == $protocolFixApplied) {
7574
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7575
                // Check X-Frame-Options
7576
                $ch = curl_init();
7577
                $options = [
7578
                    CURLOPT_URL => $src,
7579
                    CURLOPT_RETURNTRANSFER => true,
7580
                    CURLOPT_HEADER => true,
7581
                    CURLOPT_FOLLOWLOCATION => true,
7582
                    CURLOPT_ENCODING => "",
7583
                    CURLOPT_AUTOREFERER => true,
7584
                    CURLOPT_CONNECTTIMEOUT => 120,
7585
                    CURLOPT_TIMEOUT => 120,
7586
                    CURLOPT_MAXREDIRS => 10,
7587
                ];
7588
7589
                $proxySettings = api_get_setting('platform.proxy_settings', true);
7590
                if (!empty($proxySettings) &&
7591
                    isset($proxySettings['curl_setopt_array'])
7592
                ) {
7593
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7594
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7595
                }
7596
7597
                curl_setopt_array($ch, $options);
7598
                $response = curl_exec($ch);
7599
                $httpCode = curl_getinfo($ch);
7600
                $headers = substr($response, 0, $httpCode['header_size']);
7601
7602
                $error = false;
7603
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7604
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7605
                ) {
7606
                    $error = true;
7607
                }
7608
7609
                if ($error) {
7610
                    Session::write('x_frame_source', $src);
7611
                    $src = 'blank.php?error=x_frames_options';
7612
                }
7613
            }
7614
        }
7615
7616
        return $src;
7617
    }
7618
7619
    /**
7620
     * Check if this LP has a created forum in the basis course.
7621
     *
7622
     * @deprecated
7623
     *
7624
     * @return bool
7625
     */
7626
    public function lpHasForum()
7627
    {
7628
        $forumTable = Database::get_course_table(TABLE_FORUM);
7629
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7630
7631
        $fakeFrom = "
7632
            $forumTable f
7633
            INNER JOIN $itemProperty ip
7634
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7635
        ";
7636
7637
        $resultData = Database::select(
7638
            'COUNT(f.iid) AS qty',
7639
            $fakeFrom,
7640
            [
7641
                'where' => [
7642
                    'ip.visibility != ? AND ' => 2,
7643
                    'ip.tool = ? AND ' => TOOL_FORUM,
7644
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7645
                    'f.lp_id = ?' => intval($this->lp_id),
7646
                ],
7647
            ],
7648
            'first'
7649
        );
7650
7651
        return $resultData['qty'] > 0;
7652
    }
7653
7654
    /**
7655
     * Get the forum for this learning path.
7656
     *
7657
     * @param int $sessionId
7658
     *
7659
     * @return array
7660
     */
7661
    public function getForum($sessionId = 0)
7662
    {
7663
        $repo = Container::getForumRepository();
7664
7665
        $course = api_get_course_entity();
7666
        $session = api_get_session_entity($sessionId);
7667
        $qb = $repo->getResourcesByCourse($course, $session);
7668
7669
        return $qb->getQuery()->getResult();
7670
    }
7671
7672
    /**
7673
     * Get the LP Final Item form.
7674
     *
7675
     * @throws Exception
7676
     *
7677
     *
7678
     * @return string
7679
     */
7680
    public function getFinalItemForm()
7681
    {
7682
        $finalItem = $this->getFinalItem();
7683
        $title = '';
7684
7685
        if ($finalItem) {
7686
            $title = $finalItem->get_title();
7687
            $buttonText = get_lang('Save');
7688
            $content = $this->getSavedFinalItem();
7689
        } else {
7690
            $buttonText = get_lang('Add this document to the course');
7691
            $content = $this->getFinalItemTemplate();
7692
        }
7693
7694
        $editorConfig = [
7695
            'ToolbarSet' => 'LearningPathDocuments',
7696
            'Width' => '100%',
7697
            'Height' => '500',
7698
            'FullPage' => true,
7699
        ];
7700
7701
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7702
            'type' => 'document',
7703
            'lp_id' => $this->lp_id,
7704
        ]);
7705
7706
        $form = new FormValidator('final_item', 'POST', $url);
7707
        $form->addText('title', get_lang('Title'));
7708
        $form->addButtonSave($buttonText);
7709
        $form->addHtml(
7710
            Display::return_message(
7711
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7712
                'normal',
7713
                false
7714
            )
7715
        );
7716
7717
        $renderer = $form->defaultRenderer();
7718
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7719
7720
        $form->addHtmlEditor(
7721
            'content_lp_certificate',
7722
            null,
7723
            true,
7724
            false,
7725
            $editorConfig
7726
        );
7727
        $form->addHidden('action', 'add_final_item');
7728
        $form->addHidden('path', Session::read('pathItem'));
7729
        $form->addHidden('previous', $this->get_last());
7730
        $form->setDefaults(
7731
            ['title' => $title, 'content_lp_certificate' => $content]
7732
        );
7733
7734
        if ($form->validate()) {
7735
            $values = $form->exportValues();
7736
            $lastItemId = $this->getLastInFirstLevel();
7737
7738
            if (!$finalItem) {
7739
                $documentId = $this->create_document(
7740
                    $this->course_info,
7741
                    $values['content_lp_certificate'],
7742
                    $values['title']
7743
                );
7744
                $this->add_item(
7745
                    0,
7746
                    $lastItemId,
7747
                    'final_item',
7748
                    $documentId,
7749
                    $values['title'],
7750
                );
7751
7752
                Display::addFlash(
7753
                    Display::return_message(get_lang('Added'))
7754
                );
7755
            } else {
7756
                $this->edit_document();
7757
            }
7758
        }
7759
7760
        return $form->returnForm();
7761
    }
7762
7763
    /**
7764
     * Check if the current lp item is first, both, last or none from lp list.
7765
     *
7766
     * @param int $currentItemId
7767
     *
7768
     * @return string
7769
     */
7770
    public function isFirstOrLastItem($currentItemId)
7771
    {
7772
        $lpItemId = [];
7773
        $typeListNotToVerify = self::getChapterTypes();
7774
7775
        // Using get_toc() function instead $this->items because returns the correct order of the items
7776
        foreach ($this->get_toc() as $item) {
7777
            if (!in_array($item['type'], $typeListNotToVerify)) {
7778
                $lpItemId[] = $item['id'];
7779
            }
7780
        }
7781
7782
        $lastLpItemIndex = count($lpItemId) - 1;
7783
        $position = array_search($currentItemId, $lpItemId);
7784
7785
        switch ($position) {
7786
            case 0:
7787
                if (!$lastLpItemIndex) {
7788
                    $answer = 'both';
7789
                    break;
7790
                }
7791
7792
                $answer = 'first';
7793
                break;
7794
            case $lastLpItemIndex:
7795
                $answer = 'last';
7796
                break;
7797
            default:
7798
                $answer = 'none';
7799
        }
7800
7801
        return $answer;
7802
    }
7803
7804
    /**
7805
     * Get whether this is a learning path with the accumulated SCORM time or not.
7806
     *
7807
     * @return int
7808
     */
7809
    public function getAccumulateScormTime()
7810
    {
7811
        return $this->accumulateScormTime;
7812
    }
7813
7814
    /**
7815
     * Returns an HTML-formatted link to a resource, to incorporate directly into
7816
     * the new learning path tool.
7817
     *
7818
     * The function is a big switch on tool type.
7819
     * In each case, we query the corresponding table for information and build the link
7820
     * with that information.
7821
     *
7822
     * @author Yannick Warnier <[email protected]> - rebranding based on
7823
     * previous work (display_addedresource_link_in_learnpath())
7824
     *
7825
     * @param int $course_id      Course code
7826
     * @param int $learningPathId The learning path ID (in lp table)
7827
     * @param int $id_in_path     the unique index in the items table
7828
     * @param int $lpViewId
7829
     *
7830
     * @return string
7831
     */
7832
    public static function rl_get_resource_link_for_learnpath(
7833
        $course_id,
7834
        $learningPathId,
7835
        $id_in_path,
7836
        $lpViewId
7837
    ) {
7838
        $session_id = api_get_session_id();
7839
7840
        $learningPathId = (int) $learningPathId;
7841
        $id_in_path = (int) $id_in_path;
7842
        $lpViewId = (int) $lpViewId;
7843
7844
        $em = Database::getManager();
7845
        $lpItemRepo = $em->getRepository(CLpItem::class);
7846
7847
        /** @var CLpItem $rowItem */
7848
        $rowItem = $lpItemRepo->findOneBy([
7849
            'lp' => $learningPathId,
7850
            'iid' => $id_in_path,
7851
        ]);
7852
        $type = $rowItem->getItemType();
7853
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
7854
        $main_dir_path = api_get_path(WEB_CODE_PATH);
7855
        $link = '';
7856
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
7857
7858
        switch ($type) {
7859
            case 'dir':
7860
                return $main_dir_path.'lp/blank.php';
7861
            case TOOL_CALENDAR_EVENT:
7862
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
7863
            case TOOL_ANNOUNCEMENT:
7864
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
7865
            case TOOL_LINK:
7866
                $linkInfo = Link::getLinkInfo($id);
7867
                if (isset($linkInfo['url'])) {
7868
                    return $linkInfo['url'];
7869
                }
7870
7871
                return '';
7872
            case TOOL_QUIZ:
7873
                if (empty($id)) {
7874
                    return '';
7875
                }
7876
7877
                // Get the lp_item_view with the highest view_count.
7878
                $learnpathItemViewResult = $em
7879
                    ->getRepository(CLpItemView::class)
7880
                    ->findBy(
7881
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
7882
                        ['viewCount' => 'DESC'],
7883
                        1
7884
                    );
7885
                /** @var CLpItemView $learnpathItemViewData */
7886
                $learnpathItemViewData = current($learnpathItemViewResult);
7887
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
7888
7889
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
7890
                    .http_build_query([
7891
                        'lp_init' => 1,
7892
                        'learnpath_item_view_id' => $learnpathItemViewId,
7893
                        'learnpath_id' => $learningPathId,
7894
                        'learnpath_item_id' => $id_in_path,
7895
                        'exerciseId' => $id,
7896
                    ]);
7897
            case TOOL_HOTPOTATOES:
7898
                return '';
7899
            case TOOL_FORUM:
7900
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
7901
            case TOOL_THREAD:
7902
                // forum post
7903
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
7904
                if (empty($id)) {
7905
                    return '';
7906
                }
7907
                $sql = "SELECT * FROM $tbl_topics WHERE iid=$id";
7908
                $result = Database::query($sql);
7909
                $row = Database::fetch_array($result);
7910
7911
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$row['forum_id'].'&lp=true&'
7912
                    .$extraParams;
7913
            case TOOL_POST:
7914
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
7915
                $result = Database::query("SELECT * FROM $tbl_post WHERE post_id=$id");
7916
                $row = Database::fetch_array($result);
7917
7918
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$row['thread_id'].'&forum='
7919
                    .$row['forum_id'].'&lp=true&'.$extraParams;
7920
            case TOOL_READOUT_TEXT:
7921
                return api_get_path(WEB_CODE_PATH).
7922
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
7923
            case TOOL_DOCUMENT:
7924
                $repo = Container::getDocumentRepository();
7925
                $document = $repo->find($rowItem->getPath());
7926
                if ($document) {
7927
                    $params = [
7928
                        'cid' => $course_id,
7929
                        'sid' => $session_id,
7930
                    ];
7931
7932
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
7933
                }
7934
7935
                return null;
7936
            case TOOL_LP_FINAL_ITEM:
7937
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
7938
                    .$extraParams;
7939
            case 'assignments':
7940
                return $main_dir_path.'work/work.php?'.$extraParams;
7941
            case TOOL_DROPBOX:
7942
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
7943
            case 'introduction_text': //DEPRECATED
7944
                return '';
7945
            case TOOL_COURSE_DESCRIPTION:
7946
                return $main_dir_path.'course_description?'.$extraParams;
7947
            case TOOL_GROUP:
7948
                return $main_dir_path.'group/group.php?'.$extraParams;
7949
            case TOOL_USER:
7950
                return $main_dir_path.'user/user.php?'.$extraParams;
7951
            case TOOL_STUDENTPUBLICATION:
7952
                if (!empty($rowItem->getPath())) {
7953
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
7954
                }
7955
7956
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
7957
        }
7958
7959
        return $link;
7960
    }
7961
7962
    /**
7963
     * Gets the name of a resource (generally used in learnpath when no name is provided).
7964
     *
7965
     * @author Yannick Warnier <[email protected]>
7966
     *
7967
     * @param string $course_code    Course code
7968
     * @param int    $learningPathId
7969
     * @param int    $id_in_path     The resource ID
7970
     *
7971
     * @return string
7972
     */
7973
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
7974
    {
7975
        $_course = api_get_course_info($course_code);
7976
        if (empty($_course)) {
7977
            return '';
7978
        }
7979
        $course_id = $_course['real_id'];
7980
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7981
        $learningPathId = (int) $learningPathId;
7982
        $id_in_path = (int) $id_in_path;
7983
7984
        $sql = "SELECT item_type, title, ref
7985
                FROM $tbl_lp_item
7986
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
7987
        $res_item = Database::query($sql);
7988
7989
        if (Database::num_rows($res_item) < 1) {
7990
            return '';
7991
        }
7992
        $row_item = Database::fetch_array($res_item);
7993
        $type = strtolower($row_item['item_type']);
7994
        $id = $row_item['ref'];
7995
        $output = '';
7996
7997
        switch ($type) {
7998
            case TOOL_CALENDAR_EVENT:
7999
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8000
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8001
                $myrow = Database::fetch_array($result);
8002
                $output = $myrow['title'];
8003
                break;
8004
            case TOOL_ANNOUNCEMENT:
8005
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8006
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8007
                $myrow = Database::fetch_array($result);
8008
                $output = $myrow['title'];
8009
                break;
8010
            case TOOL_LINK:
8011
                // Doesn't take $target into account.
8012
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8013
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8014
                $myrow = Database::fetch_array($result);
8015
                $output = $myrow['title'];
8016
                break;
8017
            case TOOL_QUIZ:
8018
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8019
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8020
                $myrow = Database::fetch_array($result);
8021
                $output = $myrow['title'];
8022
                break;
8023
            case TOOL_FORUM:
8024
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8025
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8026
                $myrow = Database::fetch_array($result);
8027
                $output = $myrow['forum_name'];
8028
                break;
8029
            case TOOL_THREAD:
8030
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8031
                // Grabbing the title of the post.
8032
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8033
                $result_title = Database::query($sql_title);
8034
                $myrow_title = Database::fetch_array($result_title);
8035
                $output = $myrow_title['post_title'];
8036
                break;
8037
            case TOOL_POST:
8038
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8039
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8040
                $result = Database::query($sql);
8041
                $post = Database::fetch_array($result);
8042
                $output = $post['post_title'];
8043
                break;
8044
            case 'dir':
8045
            case TOOL_DOCUMENT:
8046
                $title = $row_item['title'];
8047
                $output = '-';
8048
                if (!empty($title)) {
8049
                    $output = $title;
8050
                }
8051
                break;
8052
            case 'hotpotatoes':
8053
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8054
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8055
                $myrow = Database::fetch_array($result);
8056
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8057
                $last = count($pathname) - 1; // Making a correct name for the link.
8058
                $filename = $pathname[$last]; // Making a correct name for the link.
8059
                $myrow['path'] = rawurlencode($myrow['path']);
8060
                $output = $filename;
8061
                break;
8062
        }
8063
8064
        return stripslashes($output);
8065
    }
8066
8067
    /**
8068
     * Get the parent names for the current item.
8069
     *
8070
     * @param int $newItemId Optional. The item ID
8071
     */
8072
    public function getCurrentItemParentNames($newItemId = 0): array
8073
    {
8074
        $newItemId = $newItemId ?: $this->get_current_item_id();
8075
        $return = [];
8076
        $item = $this->getItem($newItemId);
8077
8078
        $parent = null;
8079
        if ($item) {
8080
            $parent = $this->getItem($item->get_parent());
8081
        }
8082
8083
        while ($parent) {
8084
            $return[] = $parent->get_title();
8085
            $parent = $this->getItem($parent->get_parent());
8086
        }
8087
8088
        return array_reverse($return);
8089
    }
8090
8091
    /**
8092
     * Reads and process "lp_subscription_settings" setting.
8093
     *
8094
     * @return array
8095
     */
8096
    public static function getSubscriptionSettings()
8097
    {
8098
        $subscriptionSettings = api_get_setting('lp.lp_subscription_settings', true);
8099
        if (!is_array($subscriptionSettings)) {
8100
            // By default, allow both settings
8101
            $subscriptionSettings = [
8102
                'allow_add_users_to_lp' => true,
8103
                'allow_add_users_to_lp_category' => true,
8104
            ];
8105
        } else {
8106
            $subscriptionSettings = $subscriptionSettings['options'];
8107
        }
8108
8109
        return $subscriptionSettings;
8110
    }
8111
8112
    /**
8113
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8114
     */
8115
    public function exportToCourseBuildFormat()
8116
    {
8117
        if (!api_is_allowed_to_edit()) {
8118
            return false;
8119
        }
8120
8121
        $courseBuilder = new CourseBuilder();
8122
        $itemList = [];
8123
        /** @var learnpathItem $item */
8124
        foreach ($this->items as $item) {
8125
            $itemList[$item->get_type()][] = $item->get_path();
8126
        }
8127
8128
        if (empty($itemList)) {
8129
            return false;
8130
        }
8131
8132
        if (isset($itemList['document'])) {
8133
            // Get parents
8134
            foreach ($itemList['document'] as $documentId) {
8135
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8136
                if (!empty($documentInfo['parents'])) {
8137
                    foreach ($documentInfo['parents'] as $parentInfo) {
8138
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8139
                            continue;
8140
                        }
8141
                        $itemList['document'][] = $parentInfo['iid'];
8142
                    }
8143
                }
8144
            }
8145
8146
            $courseInfo = api_get_course_info();
8147
            foreach ($itemList['document'] as $documentId) {
8148
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8149
                $items = DocumentManager::get_resources_from_source_html(
8150
                    $documentInfo['absolute_path'],
8151
                    true,
8152
                    TOOL_DOCUMENT
8153
                );
8154
8155
                if (!empty($items)) {
8156
                    foreach ($items as $item) {
8157
                        // Get information about source url
8158
                        $url = $item[0]; // url
8159
                        $scope = $item[1]; // scope (local, remote)
8160
                        $type = $item[2]; // type (rel, abs, url)
8161
8162
                        $origParseUrl = parse_url($url);
8163
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8164
8165
                        if ('local' === $scope) {
8166
                            if ('abs' === $type || 'rel' === $type) {
8167
                                $documentFile = strstr($realOrigPath, 'document');
8168
                                if (false !== strpos($realOrigPath, $documentFile)) {
8169
                                    $documentFile = str_replace('document', '', $documentFile);
8170
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8171
                                    // Document found! Add it to the list
8172
                                    if ($itemDocumentId) {
8173
                                        $itemList['document'][] = $itemDocumentId;
8174
                                    }
8175
                                }
8176
                            }
8177
                        }
8178
                    }
8179
                }
8180
            }
8181
8182
            $courseBuilder->build_documents(
8183
                api_get_session_id(),
8184
                $this->get_course_int_id(),
8185
                true,
8186
                $itemList['document']
8187
            );
8188
        }
8189
8190
        if (isset($itemList['quiz'])) {
8191
            $courseBuilder->build_quizzes(
8192
                api_get_session_id(),
8193
                $this->get_course_int_id(),
8194
                true,
8195
                $itemList['quiz']
8196
            );
8197
        }
8198
8199
        if (!empty($itemList['thread'])) {
8200
            $threadList = [];
8201
            $repo = Container::getForumThreadRepository();
8202
            foreach ($itemList['thread'] as $threadId) {
8203
                /** @var CForumThread $thread */
8204
                $thread = $repo->find($threadId);
8205
                if ($thread) {
8206
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8207
                    $threadList[] = $thread->getIid();
8208
                }
8209
            }
8210
8211
            if (!empty($threadList)) {
8212
                $courseBuilder->build_forum_topics(
8213
                    api_get_session_id(),
8214
                    $this->get_course_int_id(),
8215
                    null,
8216
                    $threadList
8217
                );
8218
            }
8219
        }
8220
8221
        $forumCategoryList = [];
8222
        if (isset($itemList['forum'])) {
8223
            foreach ($itemList['forum'] as $forumId) {
8224
                $forumInfo = get_forums($forumId);
8225
                $forumCategoryList[] = $forumInfo['forum_category'];
8226
            }
8227
        }
8228
8229
        if (!empty($forumCategoryList)) {
8230
            $courseBuilder->build_forum_category(
8231
                api_get_session_id(),
8232
                $this->get_course_int_id(),
8233
                true,
8234
                $forumCategoryList
8235
            );
8236
        }
8237
8238
        if (!empty($itemList['forum'])) {
8239
            $courseBuilder->build_forums(
8240
                api_get_session_id(),
8241
                $this->get_course_int_id(),
8242
                true,
8243
                $itemList['forum']
8244
            );
8245
        }
8246
8247
        if (isset($itemList['link'])) {
8248
            $courseBuilder->build_links(
8249
                api_get_session_id(),
8250
                $this->get_course_int_id(),
8251
                true,
8252
                $itemList['link']
8253
            );
8254
        }
8255
8256
        if (!empty($itemList['student_publication'])) {
8257
            $courseBuilder->build_works(
8258
                api_get_session_id(),
8259
                $this->get_course_int_id(),
8260
                true,
8261
                $itemList['student_publication']
8262
            );
8263
        }
8264
8265
        $courseBuilder->build_learnpaths(
8266
            api_get_session_id(),
8267
            $this->get_course_int_id(),
8268
            true,
8269
            [$this->get_id()],
8270
            false
8271
        );
8272
8273
        $courseBuilder->restoreDocumentsFromList();
8274
8275
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8276
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8277
        $result = DocumentManager::file_send_for_download(
8278
            $zipPath,
8279
            true,
8280
            $this->get_name().'.zip'
8281
        );
8282
8283
        if ($result) {
8284
            api_not_allowed();
8285
        }
8286
8287
        return true;
8288
    }
8289
8290
    /**
8291
     * Get whether this is a learning path with the accumulated work time or not.
8292
     *
8293
     * @return int
8294
     */
8295
    public function getAccumulateWorkTime()
8296
    {
8297
        return (int) $this->accumulateWorkTime;
8298
    }
8299
8300
    /**
8301
     * Get whether this is a learning path with the accumulated work time or not.
8302
     *
8303
     * @return int
8304
     */
8305
    public function getAccumulateWorkTimeTotalCourse()
8306
    {
8307
        $table = Database::get_course_table(TABLE_LP_MAIN);
8308
        $sql = "SELECT SUM(accumulate_work_time) AS total
8309
                FROM $table
8310
                WHERE c_id = ".$this->course_int_id;
8311
        $result = Database::query($sql);
8312
        $row = Database::fetch_array($result);
8313
8314
        return (int) $row['total'];
8315
    }
8316
8317
    /**
8318
     * @param int $lpId
8319
     * @param int $courseId
8320
     *
8321
     * @return mixed
8322
     */
8323
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8324
    {
8325
        $lpId = (int) $lpId;
8326
        $table = Database::get_course_table(TABLE_LP_MAIN);
8327
        $sql = "SELECT accumulate_work_time
8328
                FROM $table
8329
                WHERE iid = $lpId";
8330
        $result = Database::query($sql);
8331
        $row = Database::fetch_array($result);
8332
8333
        return $row['accumulate_work_time'];
8334
    }
8335
8336
    /**
8337
     * @param int $courseId
8338
     *
8339
     * @return int
8340
     */
8341
    public static function getAccumulateWorkTimeTotal($courseId)
8342
    {
8343
        $table = Database::get_course_table(TABLE_LP_MAIN);
8344
        $courseId = (int) $courseId;
8345
        $sql = "SELECT SUM(accumulate_work_time) AS total
8346
                FROM $table
8347
                WHERE c_id = $courseId";
8348
        $result = Database::query($sql);
8349
        $row = Database::fetch_array($result);
8350
8351
        return (int) $row['total'];
8352
    }
8353
8354
    /**
8355
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8356
     * and put the images in.
8357
     *
8358
     * @return array
8359
     */
8360
    public static function getIconSelect()
8361
    {
8362
        $theme = api_get_visual_theme();
8363
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8364
        $icons = ['' => get_lang('Please select an option')];
8365
8366
        if (is_dir($path)) {
8367
            $finder = new Finder();
8368
            $finder->files()->in($path);
8369
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8370
            /** @var SplFileInfo $file */
8371
            foreach ($finder as $file) {
8372
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8373
                    $icons[$file->getFilename()] = $file->getFilename();
8374
                }
8375
            }
8376
        }
8377
8378
        return $icons;
8379
    }
8380
8381
    /**
8382
     * @param int $lpId
8383
     *
8384
     * @return string
8385
     */
8386
    public static function getSelectedIcon($lpId)
8387
    {
8388
        $extraFieldValue = new ExtraFieldValue('lp');
8389
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8390
        $icon = '';
8391
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8392
            $icon = $lpIcon['value'];
8393
        }
8394
8395
        return $icon;
8396
    }
8397
8398
    /**
8399
     * @param int $lpId
8400
     *
8401
     * @return string
8402
     */
8403
    public static function getSelectedIconHtml($lpId)
8404
    {
8405
        $icon = self::getSelectedIcon($lpId);
8406
8407
        if (empty($icon)) {
8408
            return '';
8409
        }
8410
8411
        $theme = api_get_visual_theme();
8412
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8413
8414
        return Display::img($path);
8415
    }
8416
8417
    /**
8418
     * @param string $value
8419
     *
8420
     * @return string
8421
     */
8422
    public function cleanItemTitle($value)
8423
    {
8424
        $value = Security::remove_XSS(strip_tags($value));
8425
8426
        return $value;
8427
    }
8428
8429
    public function setItemTitle(FormValidator $form)
8430
    {
8431
        if ('true' === api_get_setting('editor.save_titles_as_html')) {
8432
            $form->addHtmlEditor(
8433
                'title',
8434
                get_lang('Title'),
8435
                true,
8436
                false,
8437
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8438
            );
8439
        } else {
8440
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8441
            $form->applyFilter('title', 'trim');
8442
            $form->applyFilter('title', 'html_filter');
8443
        }
8444
    }
8445
8446
    /**
8447
     * @return array
8448
     */
8449
    public function getItemsForForm($addParentCondition = false)
8450
    {
8451
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8452
8453
        $sql = "SELECT * FROM $tbl_lp_item
8454
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8455
8456
        if ($addParentCondition) {
8457
            $sql .= ' AND parent_item_id IS NULL ';
8458
        }
8459
        $sql .= ' ORDER BY display_order ASC';
8460
8461
        $result = Database::query($sql);
8462
        $arrLP = [];
8463
        while ($row = Database::fetch_array($result)) {
8464
            $arrLP[] = [
8465
                'iid' => $row['iid'],
8466
                'id' => $row['iid'],
8467
                'item_type' => $row['item_type'],
8468
                'title' => $this->cleanItemTitle($row['title']),
8469
                'title_raw' => $row['title'],
8470
                'path' => $row['path'],
8471
                'description' => Security::remove_XSS($row['description']),
8472
                'parent_item_id' => $row['parent_item_id'],
8473
                'previous_item_id' => $row['previous_item_id'],
8474
                'next_item_id' => $row['next_item_id'],
8475
                'display_order' => $row['display_order'],
8476
                'max_score' => $row['max_score'],
8477
                'min_score' => $row['min_score'],
8478
                'mastery_score' => $row['mastery_score'],
8479
                'prerequisite' => $row['prerequisite'],
8480
                'max_time_allowed' => $row['max_time_allowed'],
8481
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8482
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8483
            ];
8484
        }
8485
8486
        return $arrLP;
8487
    }
8488
8489
    /**
8490
     * Gets whether this SCORM learning path has been marked to use the score
8491
     * as progress. Takes into account whether the learnpath matches (SCORM
8492
     * content + less than 2 items).
8493
     *
8494
     * @return bool True if the score should be used as progress, false otherwise
8495
     */
8496
    public function getUseScoreAsProgress()
8497
    {
8498
        // If not a SCORM, we don't care about the setting
8499
        if (2 != $this->get_type()) {
8500
            return false;
8501
        }
8502
        // If more than one step in the SCORM, we don't care about the setting
8503
        if ($this->get_total_items_count() > 1) {
8504
            return false;
8505
        }
8506
        $extraFieldValue = new ExtraFieldValue('lp');
8507
        $doUseScore = false;
8508
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8509
            $this->get_id(),
8510
            'use_score_as_progress'
8511
        );
8512
        if (!empty($useScore) && isset($useScore['value'])) {
8513
            $doUseScore = $useScore['value'];
8514
        }
8515
8516
        return $doUseScore;
8517
    }
8518
8519
    /**
8520
     * Get the user identifier (user_id or username
8521
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8522
     *
8523
     * @return string User ID or username, depending on configuration setting
8524
     */
8525
    public static function getUserIdentifierForExternalServices()
8526
    {
8527
        $scormApiExtraFieldUseStudentId = api_get_setting('lp.scorm_api_extrafield_to_use_as_student_id');
8528
        $extraFieldValue = new ExtraFieldValue('user');
8529
        $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8530
            api_get_user_id(),
8531
            $scormApiExtraFieldUseStudentId
8532
        );
8533
        if (is_array($extrafield) && isset($extrafield['value'])) {
8534
            return $extrafield['value'];
8535
        } else {
8536
            if ('true' === $scormApiExtraFieldUseStudentId) {
8537
                return api_get_user_info(api_get_user_id())['username'];
8538
            } else {
8539
                return api_get_user_id();
8540
            }
8541
        }
8542
    }
8543
8544
    /**
8545
     * Save the new order for learning path items.
8546
     *
8547
     * @param array $orderList A associative array with id and parent_id keys.
8548
     */
8549
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true, $lpItemRepo = null, $em = null)
8550
    {
8551
        if (empty($orderList)) {
8552
            return true;
8553
        }
8554
        if (!isset($lpItemRepo)) {
8555
            $lpItemRepo = Container::getLpItemRepository();
8556
        }
8557
        if (!isset($em)) {
8558
            $em = Database::getManager();
8559
        }
8560
        $counter = 2;
8561
        $rootItem->setDisplayOrder(1);
8562
        $rootItem->setPreviousItemId(null);
8563
        $em->persist($rootItem);
8564
        if ($flush) {
8565
            $em->flush();
8566
        }
8567
8568
        foreach ($orderList as $item) {
8569
            $itemId = $item->id ?? 0;
8570
            if (empty($itemId)) {
8571
                continue;
8572
            }
8573
            $parentId = $item->parent_id ?? 0;
8574
            $parent = $rootItem;
8575
            if (!empty($parentId)) {
8576
                $parentExists = $lpItemRepo->find($parentId);
8577
                if (null !== $parentExists) {
8578
                    $parent = $parentExists;
8579
                }
8580
            }
8581
8582
            /** @var CLpItem $itemEntity */
8583
            $itemEntity = $lpItemRepo->find($itemId);
8584
            $itemEntity->setParent($parent);
8585
            $itemEntity->setPreviousItemId(null);
8586
            $itemEntity->setNextItemId(null);
8587
            $itemEntity->setDisplayOrder($counter);
8588
8589
            $em->persist($itemEntity);
8590
            if ($flush) {
8591
                $em->flush();
8592
            }
8593
            $counter++;
8594
        }
8595
8596
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8597
        $em->persist($rootItem);
8598
        if ($flush) {
8599
            $em->flush();
8600
        }
8601
8602
        return true;
8603
    }
8604
8605
    public static function move(int $lpId, string $direction)
8606
    {
8607
        $em = Database::getManager();
8608
        /** @var CLp $lp */
8609
        $lp = Container::getLpRepository()->find($lpId);
8610
        if ($lp) {
8611
            $resourceNode = $lp->getResourceNode();
8612
            $position = $resourceNode->getDisplayOrder();
8613
            $resourceNodeId = $resourceNode->getId();
8614
8615
            $item = $em->find(ResourceNode::class, $resourceNodeId);
8616
            if ($item) {
8617
                $newPosition = 0;
8618
                if ('down' === $direction) {
8619
                    $newPosition = $position + 1;
8620
                }
8621
                if ('up' === $direction) {
8622
                    $newPosition = $position - 1;
8623
                }
8624
                $item->setDisplayOrder($newPosition);
8625
                $em->persist($item);
8626
                $em->flush();
8627
            }
8628
        }
8629
    }
8630
8631
    /**
8632
     * Get the depth level of LP item.
8633
     *
8634
     * @param array $items
8635
     * @param int   $currentItemId
8636
     *
8637
     * @return int
8638
     */
8639
    private static function get_level_for_item($items, $currentItemId)
8640
    {
8641
        $parentItemId = 0;
8642
        if (isset($items[$currentItemId])) {
8643
            $parentItemId = $items[$currentItemId]->parent;
8644
        }
8645
8646
        if (0 == $parentItemId) {
8647
            return 0;
8648
        }
8649
8650
        return self::get_level_for_item($items, $parentItemId) + 1;
8651
    }
8652
8653
    /**
8654
     * Generate the link for a learnpath category as course tool.
8655
     *
8656
     * @param int $categoryId
8657
     *
8658
     * @return string
8659
     */
8660
    private static function getCategoryLinkForTool($categoryId)
8661
    {
8662
        $categoryId = (int) $categoryId;
8663
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8664
            .http_build_query(
8665
                [
8666
                    'action' => 'view_category',
8667
                    'id' => $categoryId,
8668
                ]
8669
            );
8670
    }
8671
8672
    /**
8673
     * Check and obtain the lp final item if exist.
8674
     *
8675
     * @return learnpathItem
8676
     */
8677
    private function getFinalItem()
8678
    {
8679
        if (empty($this->items)) {
8680
            return null;
8681
        }
8682
8683
        foreach ($this->items as $item) {
8684
            if ('final_item' !== $item->type) {
8685
                continue;
8686
            }
8687
8688
            return $item;
8689
        }
8690
    }
8691
8692
    /**
8693
     * Get the LP Final Item Template.
8694
     *
8695
     * @return string
8696
     */
8697
    private function getFinalItemTemplate()
8698
    {
8699
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8700
    }
8701
8702
    /**
8703
     * Get the LP Final Item Url.
8704
     *
8705
     * @return string
8706
     */
8707
    private function getSavedFinalItem()
8708
    {
8709
        $finalItem = $this->getFinalItem();
8710
8711
        $repo = Container::getDocumentRepository();
8712
        /** @var CDocument $document */
8713
        $document = $repo->find($finalItem->path);
8714
8715
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8716
            return $repo->getResourceFileContent($document);
8717
        }
8718
8719
        return '';
8720
    }
8721
}
8722