Passed
Pull Request — master (#5143)
by Angel Fernando Quiroz
16:06 queued 07:35
created

learnpath::getFinalItemTemplate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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