Passed
Pull Request — master (#5698)
by
unknown
07:08
created

learnpath::getIconSelect()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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