Passed
Pull Request — master (#5143)
by Angel Fernando Quiroz
08:25
created

learnpath::getForum()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

    return false;
}

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

Loading history...
1866
    }
1867
1868
    public static function isGroupSubscribedToLp(
1869
        CLp $lp,
1870
        int $studentId,
1871
        Course $course,
1872
        SessionEntity $session = null
1873
    ): bool {
1874
1875
        // Subscribed groups to a LP
1876
        $links = $lp->getResourceNode()->getResourceLinks();
1877
        $selectedChoices = [];
1878
        foreach ($links as $link) {
1879
            if (null !== $link->getGroup()) {
1880
                $selectedChoices[] = $link->getGroup()->getIid();
1881
            }
1882
        }
1883
1884
        $isVisible = false;
1885
        $userGroups = GroupManager::getAllGroupPerUserSubscription($studentId, $course->getId());
1886
        if (!empty($userGroups)) {
1887
            foreach ($userGroups as $groupInfo) {
1888
                $groupId = $groupInfo['iid'];
1889
                if (in_array($groupId, $selectedChoices)) {
1890
                    $isVisible = true;
1891
                    break;
1892
                }
1893
            }
1894
        }
1895
1896
        return $isVisible;
1897
    }
1898
1899
    public static function isUserSubscribedToLp(
1900
        CLp $lp,
1901
        int $studentId,
1902
        Course $course,
1903
        SessionEntity $session = null
1904
    ): bool {
1905
1906
        $isVisible = true;
1907
        $em = Database::getManager();
1908
1909
        /** @var CLpRelUserRepository $cLpRelUserRepo */
1910
        $cLpRelUserRepo = $em->getRepository(CLpRelUser::class);
1911
1912
        // Getting subscribed users to a LP.
1913
        $subscribedUsersInLp = $cLpRelUserRepo->getUsersSubscribedToItem(
1914
            $lp,
1915
            $course,
1916
            $session
1917
        );
1918
1919
        $selectedChoices = [];
1920
        foreach ($subscribedUsersInLp as $users) {
1921
            /** @var \Chamilo\CourseBundle\Entity\CLpRelUser $users */
1922
            $selectedChoices[] = $users->getUser()->getId();
1923
        }
1924
1925
        if (!api_is_allowed_to_edit() && !in_array($studentId, $selectedChoices)) {
1926
            $isVisible = false;
1927
        }
1928
1929
        return $isVisible;
1930
    }
1931
1932
    /**
1933
     * @param int $lpId
1934
     * @param int $userId
1935
     * @param int $courseId
1936
     * @param int $sessionId
1937
     *
1938
     * @return int
1939
     */
1940
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1941
    {
1942
        $lpId = (int) $lpId;
1943
        $userId = (int) $userId;
1944
        $courseId = (int) $courseId;
1945
        $sessionId = (int) $sessionId;
1946
1947
        $sessionCondition = api_get_session_condition($sessionId);
1948
        $table = Database::get_course_table(TABLE_LP_VIEW);
1949
        $sql = "SELECT progress FROM $table
1950
                WHERE
1951
                    c_id = $courseId AND
1952
                    lp_id = $lpId AND
1953
                    user_id = $userId $sessionCondition ";
1954
        $res = Database::query($sql);
1955
1956
        $progress = 0;
1957
        if (Database::num_rows($res) > 0) {
1958
            $row = Database::fetch_array($res);
1959
            $progress = (int) $row['progress'];
1960
        }
1961
1962
        return $progress;
1963
    }
1964
1965
    /**
1966
     * @param array $lpList
1967
     * @param int   $userId
1968
     * @param int   $courseId
1969
     * @param int   $sessionId
1970
     *
1971
     * @return array
1972
     */
1973
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1974
    {
1975
        $lpList = array_map('intval', $lpList);
1976
        if (empty($lpList)) {
1977
            return [];
1978
        }
1979
1980
        $lpList = implode("','", $lpList);
1981
1982
        $userId = (int) $userId;
1983
        $courseId = (int) $courseId;
1984
        $sessionId = (int) $sessionId;
1985
1986
        $sessionCondition = api_get_session_condition($sessionId);
1987
        $table = Database::get_course_table(TABLE_LP_VIEW);
1988
        $sql = "SELECT lp_id, progress FROM $table
1989
                WHERE
1990
                    c_id = $courseId AND
1991
                    lp_id IN ('".$lpList."') AND
1992
                    user_id = $userId $sessionCondition ";
1993
        $res = Database::query($sql);
1994
1995
        if (Database::num_rows($res) > 0) {
1996
            $list = [];
1997
            while ($row = Database::fetch_array($res)) {
1998
                $list[$row['lp_id']] = $row['progress'];
1999
            }
2000
2001
            return $list;
2002
        }
2003
2004
        return [];
2005
    }
2006
2007
    /**
2008
     * Displays a progress bar
2009
     * completed so far.
2010
     *
2011
     * @param int    $percentage Progress value to display
2012
     * @param string $text_add   Text to display near the progress value
2013
     *
2014
     * @return string HTML string containing the progress bar
2015
     */
2016
    public static function get_progress_bar($percentage = -1, $text_add = '')
2017
    {
2018
        $text = $percentage.$text_add;
2019
2020
        return '<div class="progress">
2021
            <div id="progress_bar_value"
2022
                class="progress-bar progress-bar-success" role="progressbar"
2023
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2024
            '.$text.'
2025
            </div>
2026
        </div>';
2027
    }
2028
2029
    /**
2030
     * @param string $mode can be '%' or 'abs'
2031
     *                     otherwise this value will be used $this->progress_bar_mode
2032
     *
2033
     * @return string
2034
     */
2035
    public function getProgressBar($mode = null)
2036
    {
2037
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2038
2039
        return self::get_progress_bar($percentage, $text_add);
2040
    }
2041
2042
    /**
2043
     * Gets the progress bar info to display inside the progress bar.
2044
     * Also used by scorm_api.php.
2045
     *
2046
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2047
     *                     we display a number of completed elements per total elements
2048
     * @param int    $add  Additional steps to fake as completed
2049
     *
2050
     * @return array Percentage or number and symbol (% or /xx)
2051
     */
2052
    public function get_progress_bar_text($mode = '', $add = 0)
2053
    {
2054
        if (empty($mode)) {
2055
            $mode = $this->progress_bar_mode;
2056
        }
2057
        $text = '';
2058
        $percentage = 0;
2059
        // If the option to use the score as progress is set for this learning
2060
        // path, then the rules are completely different: we assume only one
2061
        // item exists and the progress of the LP depends on the score
2062
        $scoreAsProgressSetting = ('true' === api_get_setting('lp.lp_score_as_progress_enable'));
2063
        if (true === $scoreAsProgressSetting) {
2064
            $scoreAsProgress = $this->getUseScoreAsProgress();
2065
            if ($scoreAsProgress) {
2066
                // Get single item's score
2067
                $itemId = $this->get_current_item_id();
2068
                $item = $this->getItem($itemId);
2069
                $score = $item->get_score();
2070
                $maxScore = $item->get_max();
2071
                if ($mode = '%') {
2072
                    if (!empty($maxScore)) {
2073
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2074
                    }
2075
                    $percentage = number_format($percentage, 0);
2076
                    $text = '%';
2077
                } else {
2078
                    $percentage = $score;
2079
                    $text = '/'.$maxScore;
2080
                }
2081
2082
                return [$percentage, $text];
2083
            }
2084
        }
2085
        // otherwise just continue the normal processing of progress
2086
        $total_items = $this->getTotalItemsCountWithoutDirs();
2087
        $completeItems = $this->get_complete_items_count();
2088
        if (0 != $add) {
2089
            $completeItems += $add;
2090
        }
2091
        if ($completeItems > $total_items) {
2092
            $completeItems = $total_items;
2093
        }
2094
        if ('%' === $mode) {
2095
            if ($total_items > 0) {
2096
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2097
            }
2098
            $percentage = number_format($percentage, 0);
2099
            $text = '%';
2100
        } elseif ('abs' === $mode) {
2101
            $percentage = $completeItems;
2102
            $text = '/'.$total_items;
2103
        }
2104
2105
        return [
2106
            $percentage,
2107
            $text,
2108
        ];
2109
    }
2110
2111
    /**
2112
     * Gets the progress bar mode.
2113
     *
2114
     * @return string The progress bar mode attribute
2115
     */
2116
    public function get_progress_bar_mode()
2117
    {
2118
        if (!empty($this->progress_bar_mode)) {
2119
            return $this->progress_bar_mode;
2120
        }
2121
2122
        return '%';
2123
    }
2124
2125
    /**
2126
     * Gets the learnpath theme (remote or local).
2127
     *
2128
     * @return string Learnpath theme
2129
     */
2130
    public function get_theme()
2131
    {
2132
        if (!empty($this->theme)) {
2133
            return $this->theme;
2134
        }
2135
2136
        return '';
2137
    }
2138
2139
    /**
2140
     * Gets the learnpath session id.
2141
     *
2142
     * @return int
2143
     */
2144
    public function get_lp_session_id()
2145
    {
2146
        $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-row justify-center mb-2">';
2665
                $html .= "<a
2666
                    class='btn btn-sm btn--plain mx-1'
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 mx-1"
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;
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 the final item form (see BT#11048) .
5350
        $finish = $this->getFinalItemForm();
5351
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5352
        $headers = [
5353
            Display::getMdiIcon('bookshelf', 'ch-tool-icon-gradient', '', 64, get_lang('Documents')),
5354
            Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon-gradient', '', 64, get_lang('Tests')),
5355
            Display::getMdiIcon('file-link', 'ch-tool-icon-gradient', '', 64, get_lang('Links')),
5356
            Display::getMdiIcon('inbox-full', 'ch-tool-icon-gradient', '', 64, get_lang('Assignments')),
5357
            Display::getMdiIcon('comment-quote', 'ch-tool-icon-gradient', '', 64, get_lang('Forums')),
5358
            Display::getMdiIcon('bookmark-multiple', 'ch-tool-icon-gradient', '', 64, get_lang('Add section')),
5359
            Display::getMdiIcon('certificate', 'ch-tool-icon-gradient', '', 64, get_lang('Certificate')),
5360
        ];
5361
        $content = '';
5362
        /*$content = Display::return_message(
5363
            get_lang('Click on the [Learner view] button to see your learning path'),
5364
            'normal'
5365
        );*/
5366
        $section = $this->displayNewSectionForm();
5367
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5368
5369
        return Display::tabs(
5370
            $headers,
5371
            [
5372
                $documents,
5373
                $exercises,
5374
                $links,
5375
                $works,
5376
                $forums,
5377
                $section,
5378
                $finish,
5379
            ],
5380
            'resource_tab',
5381
            [],
5382
            [],
5383
            $selected
5384
        );
5385
    }
5386
5387
    /**
5388
     * Returns the extension of a document.
5389
     *
5390
     * @param string $filename
5391
     *
5392
     * @return string Extension (part after the last dot)
5393
     */
5394
    public function get_extension($filename)
5395
    {
5396
        $explode = explode('.', $filename);
5397
5398
        return $explode[count($explode) - 1];
5399
    }
5400
5401
    /**
5402
     * @return string
5403
     */
5404
    public function getCurrentBuildingModeURL()
5405
    {
5406
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
5407
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
5408
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
5409
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
5410
5411
        $currentUrl = api_get_self().'?'.api_get_cidreq().
5412
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
5413
5414
        return $currentUrl;
5415
    }
5416
5417
    /**
5418
     * Displays a document by id.
5419
     *
5420
     * @param CDocument $document
5421
     * @param bool      $show_title
5422
     * @param bool      $iframe
5423
     * @param bool      $edit_link
5424
     *
5425
     * @return string
5426
     */
5427
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
5428
    {
5429
        $return = '';
5430
        if (!$document) {
5431
            return '';
5432
        }
5433
5434
        $repo = Container::getDocumentRepository();
5435
5436
        // TODO: Add a path filter.
5437
        if ($iframe) {
5438
            $url = $repo->getResourceFileUrl($document);
5439
5440
            $return .= '<iframe
5441
                id="learnpath_preview_frame"
5442
                frameborder="0"
5443
                height="400"
5444
                width="100%"
5445
                scrolling="auto"
5446
                src="'.$url.'"></iframe>';
5447
        } else {
5448
            $return = $repo->getResourceFileContent($document);
5449
        }
5450
5451
        return $return;
5452
    }
5453
5454
    /**
5455
     * Return HTML form to add/edit a link item.
5456
     *
5457
     * @param string  $action (add/edit)
5458
     * @param CLpItem $lpItem
5459
     * @param CLink   $link
5460
     *
5461
     * @throws Exception
5462
     *
5463
     *
5464
     * @return string HTML form
5465
     */
5466
    public function display_link_form($action, $lpItem, $link)
5467
    {
5468
        $item_url = '';
5469
        if ($link) {
5470
            $item_url = stripslashes($link->getUrl());
5471
        }
5472
        $form = new FormValidator(
5473
            'edit_link',
5474
            'POST',
5475
            $this->getCurrentBuildingModeURL()
5476
        );
5477
5478
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5479
5480
        $urlAttributes = ['class' => 'learnpath_item_form'];
5481
        $urlAttributes['disabled'] = 'disabled';
5482
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
5483
        $form->setDefault('url', $item_url);
5484
5485
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5486
5487
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5488
    }
5489
5490
    /**
5491
     * Return HTML form to add/edit a quiz.
5492
     *
5493
     * @param string  $action   Action (add/edit)
5494
     * @param CLpItem $lpItem   Item ID if already exists
5495
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
5496
     *
5497
     * @throws Exception
5498
     *
5499
     * @return string HTML form
5500
     */
5501
    public function display_quiz_form($action, $lpItem, $exercise)
5502
    {
5503
        $form = new FormValidator(
5504
            'quiz_form',
5505
            'POST',
5506
            $this->getCurrentBuildingModeURL()
5507
        );
5508
5509
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5510
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5511
5512
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5513
    }
5514
5515
    /**
5516
     * Return the form to display the forum edit/add option.
5517
     *
5518
     * @param CLpItem $lpItem
5519
     *
5520
     * @throws Exception
5521
     *
5522
     * @return string HTML form
5523
     */
5524
    public function display_forum_form($action, $lpItem, $resource)
5525
    {
5526
        $form = new FormValidator(
5527
            'forum_form',
5528
            'POST',
5529
            $this->getCurrentBuildingModeURL()
5530
        );
5531
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5532
5533
        if ('add' === $action) {
5534
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
5535
        } else {
5536
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
5537
        }
5538
5539
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
5540
    }
5541
5542
    /**
5543
     * Return HTML form to add/edit forum threads.
5544
     *
5545
     * @param string  $action
5546
     * @param CLpItem $lpItem
5547
     * @param string  $resource
5548
     *
5549
     * @throws Exception
5550
     *
5551
     * @return string HTML form
5552
     */
5553
    public function display_thread_form($action, $lpItem, $resource)
5554
    {
5555
        $form = new FormValidator(
5556
            'thread_form',
5557
            'POST',
5558
            $this->getCurrentBuildingModeURL()
5559
        );
5560
5561
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5562
5563
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5564
5565
        return $form->returnForm();
5566
    }
5567
5568
    /**
5569
     * Return the HTML form to display an item (generally a dir item).
5570
     *
5571
     * @param CLpItem $lpItem
5572
     * @param string  $action
5573
     *
5574
     * @throws Exception
5575
     *
5576
     *
5577
     * @return string HTML form
5578
     */
5579
    public function display_item_form(
5580
        $lpItem,
5581
        $action = 'add_item'
5582
    ) {
5583
        $item_type = $lpItem->getItemType();
5584
5585
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5586
5587
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5588
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5589
5590
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5591
5592
        return $form->returnForm();
5593
    }
5594
5595
    /**
5596
     * Return HTML form to add/edit a student publication (work).
5597
     *
5598
     * @param string              $action
5599
     * @param CStudentPublication $resource
5600
     *
5601
     * @throws Exception
5602
     *
5603
     * @return string HTML form
5604
     */
5605
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
5606
    {
5607
        $form = new FormValidator('frm_student_publication', 'post', '#');
5608
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
5609
5610
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5611
5612
        $return = '<div class="sectioncomment">';
5613
        $return .= $form->returnForm();
5614
        $return .= '</div>';
5615
5616
        return $return;
5617
    }
5618
5619
    public function displayNewSectionForm()
5620
    {
5621
        $action = 'add_item';
5622
        $item_type = 'dir';
5623
5624
        $lpItem = (new CLpItem())
5625
            ->setTitle('')
5626
            ->setItemType('dir')
5627
        ;
5628
5629
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
5630
5631
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
5632
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
5633
5634
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
5635
        $form->addElement('hidden', 'type', 'dir');
5636
5637
        return $form->returnForm();
5638
    }
5639
5640
    /**
5641
     * Returns the form to update or create a document.
5642
     *
5643
     * @param string  $action (add/edit)
5644
     * @param CLpItem $lpItem
5645
     *
5646
     *
5647
     * @throws Exception
5648
     *
5649
     * @return string HTML form
5650
     */
5651
    public function displayDocumentForm($action = 'add', $lpItem = null)
5652
    {
5653
        $courseInfo = api_get_course_info();
5654
5655
        $form = new FormValidator(
5656
            'form',
5657
            'POST',
5658
            $this->getCurrentBuildingModeURL(),
5659
            '',
5660
            ['enctype' => 'multipart/form-data']
5661
        );
5662
5663
        $data = $this->generate_lp_folder($courseInfo);
5664
5665
        if (null !== $lpItem) {
5666
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
5667
        }
5668
5669
        switch ($action) {
5670
            case 'add':
5671
                $folders = DocumentManager::get_all_document_folders(
5672
                    $courseInfo,
5673
                    0,
5674
                    true
5675
                );
5676
                DocumentManager::build_directory_selector(
5677
                    $folders,
5678
                    '',
5679
                    [],
5680
                    true,
5681
                    $form,
5682
                    'directory_parent_id'
5683
                );
5684
5685
                if ($data) {
5686
                    $defaults['directory_parent_id'] = $data->getIid();
5687
                }
5688
5689
                break;
5690
        }
5691
5692
        $form->addButtonSave(get_lang('Save'), 'submit_button');
5693
5694
        return $form->returnForm();
5695
    }
5696
5697
    /**
5698
     * @param array  $courseInfo
5699
     * @param string $content
5700
     * @param string $title
5701
     * @param int    $parentId
5702
     *
5703
     * @return int
5704
     */
5705
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
5706
    {
5707
        $creatorId = api_get_user_id();
5708
        $sessionId = api_get_session_id();
5709
5710
        // Generates folder
5711
        $result = $this->generate_lp_folder($courseInfo);
5712
        $dir = $result['dir'];
5713
5714
        if (empty($parentId) || '/' === $parentId) {
5715
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
5716
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
5717
5718
            if ('/' === $parentId) {
5719
                $dir = '/';
5720
            }
5721
5722
            // Please, do not modify this dirname formatting.
5723
            if (strstr($dir, '..')) {
5724
                $dir = '/';
5725
            }
5726
5727
            if (!empty($dir[0]) && '.' == $dir[0]) {
5728
                $dir = substr($dir, 1);
5729
            }
5730
            if (!empty($dir[0]) && '/' != $dir[0]) {
5731
                $dir = '/'.$dir;
5732
            }
5733
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
5734
                $dir .= '/';
5735
            }
5736
        } else {
5737
            $parentInfo = DocumentManager::get_document_data_by_id(
5738
                $parentId,
5739
                $courseInfo['code']
5740
            );
5741
            if (!empty($parentInfo)) {
5742
                $dir = $parentInfo['path'].'/';
5743
            }
5744
        }
5745
5746
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5747
5748
        if (!is_dir($filepath)) {
5749
            $dir = '/';
5750
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
5751
        }
5752
5753
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5754
5755
        if (!empty($title)) {
5756
            $title = api_replace_dangerous_char(stripslashes($title));
5757
        } else {
5758
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5759
        }
5760
5761
        $title = disable_dangerous_file($title);
5762
        $filename = $title;
5763
        $content = !empty($content) ? $content : $_POST['content_lp'];
5764
        $tmpFileName = $filename;
5765
5766
        $i = 0;
5767
        while (file_exists($filepath.$tmpFileName.'.html')) {
5768
            $tmpFileName = $filename.'_'.++$i;
5769
        }
5770
5771
        $filename = $tmpFileName.'.html';
5772
        $content = stripslashes($content);
5773
5774
        if (file_exists($filepath.$filename)) {
5775
            return 0;
5776
        }
5777
5778
        $putContent = file_put_contents($filepath.$filename, $content);
5779
5780
        if (false === $putContent) {
5781
            return 0;
5782
        }
5783
5784
        $fileSize = filesize($filepath.$filename);
5785
        $saveFilePath = $dir.$filename;
5786
5787
        $document = DocumentManager::addDocument(
5788
            $courseInfo,
5789
            $saveFilePath,
5790
            'file',
5791
            $fileSize,
5792
            $tmpFileName,
5793
            '',
5794
            0, //readonly
5795
            true,
5796
            null,
5797
            $sessionId,
5798
            $creatorId
5799
        );
5800
5801
        $documentId = $document->getIid();
5802
5803
        if (!$document) {
5804
            return 0;
5805
        }
5806
5807
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5808
        $newTitle = $originalTitle;
5809
5810
        if ($newComment || $newTitle) {
5811
            $em = Database::getManager();
5812
5813
            if ($newComment) {
5814
                $document->setComment($newComment);
5815
            }
5816
5817
            if ($newTitle) {
5818
                $document->setTitle($newTitle);
5819
            }
5820
5821
            $em->persist($document);
5822
            $em->flush();
5823
        }
5824
5825
        return $documentId;
5826
    }
5827
5828
    /**
5829
     * Displays the menu for manipulating a step.
5830
     *
5831
     * @return string
5832
     */
5833
    public function displayItemMenu(CLpItem $lpItem)
5834
    {
5835
        $item_id = $lpItem->getIid();
5836
        $audio = $lpItem->getAudio();
5837
        $itemType = $lpItem->getItemType();
5838
        $path = $lpItem->getPath();
5839
5840
        $return = '';
5841
        $audio_player = null;
5842
        // We display an audio player if needed.
5843
        if (!empty($audio)) {
5844
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
5845
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
5846
                .'<audio src="'.$webAudioPath.'" controls>'
5847
                .'</div><br>';*/
5848
        }
5849
5850
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
5851
5852
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
5853
            $return .= Display::url(
5854
                Display::getMdiIcon('pencil', 'ch-tool-icon', null, 22, get_lang('Edit')),
5855
                $url.'&action=edit_item&path_item='.$path
5856
            );
5857
5858
            /*$return .= Display::url(
5859
                Display::getMdiIcon('arrow-right-bold', 'ch-tool-icon', null, 22, get_lang('Move')),
5860
                $url.'&action=move_item'
5861
            );*/
5862
        }
5863
5864
        // Commented for now as prerequisites cannot be added to chapters.
5865
        if ('dir' !== $itemType) {
5866
            $return .= Display::url(
5867
                Display::getMdiIcon('graph', 'ch-tool-icon', null, 22, get_lang('Prerequisites')),
5868
                $url.'&action=edit_item_prereq'
5869
            );
5870
        }
5871
        $return .= Display::url(
5872
            Display::getMdiIcon('delete', 'ch-tool-icon', null, 22, get_lang('Delete')),
5873
            $url.'&action=delete_item'
5874
        );
5875
5876
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
5877
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
5878
            if (empty($documentData)) {
5879
                // Try with iid
5880
                $table = Database::get_course_table(TABLE_DOCUMENT);
5881
                $sql = "SELECT path FROM $table
5882
                        WHERE
5883
                              c_id = ".api_get_course_int_id()." AND
5884
                              iid = ".$path." AND
5885
                              path NOT LIKE '%_DELETED_%'";
5886
                $result = Database::query($sql);
5887
                $documentData = Database::fetch_array($result);
5888
                if ($documentData) {
5889
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
5890
                }
5891
            }
5892
            if (isset($documentData['absolute_path_from_document'])) {
5893
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
5894
            }
5895
        }*/
5896
5897
        if (!empty($audio_player)) {
5898
            $return .= $audio_player;
5899
        }
5900
5901
        return Display::toolbarAction('lp_item', [$return]);
5902
    }
5903
5904
    /**
5905
     * Creates the javascript needed for filling up the checkboxes without page reload.
5906
     *
5907
     * @return string
5908
     */
5909
    public function get_js_dropdown_array()
5910
    {
5911
        $return = 'var child_name = new Array();'."\n";
5912
        $return .= 'var child_value = new Array();'."\n\n";
5913
        $return .= 'child_name[0] = new Array();'."\n";
5914
        $return .= 'child_value[0] = new Array();'."\n\n";
5915
5916
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
5917
        $sql = "SELECT * FROM ".$tbl_lp_item."
5918
                WHERE
5919
                    lp_id = ".$this->lp_id." AND
5920
                    parent_item_id = 0
5921
                ORDER BY display_order ASC";
5922
        Database::query($sql);
5923
        $i = 0;
5924
5925
        $list = $this->getItemsForForm(true);
5926
5927
        foreach ($list as $row_zero) {
5928
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
5929
                if (TOOL_QUIZ == $row_zero['item_type']) {
5930
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
5931
                }
5932
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
5933
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
5934
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
5935
            }
5936
        }
5937
5938
        $return .= "\n";
5939
        $sql = "SELECT * FROM $tbl_lp_item
5940
                WHERE lp_id = ".$this->lp_id;
5941
        $res = Database::query($sql);
5942
        while ($row = Database::fetch_array($res)) {
5943
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
5944
                           WHERE
5945
                                parent_item_id = ".$row['iid']."
5946
                           ORDER BY display_order ASC";
5947
            $res_parent = Database::query($sql_parent);
5948
            $i = 0;
5949
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
5950
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
5951
5952
            while ($row_parent = Database::fetch_array($res_parent)) {
5953
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
5954
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
5955
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
5956
            }
5957
            $return .= "\n";
5958
        }
5959
5960
        $return .= "
5961
            function load_cbo(id) {
5962
                if (!id) {
5963
                    return false;
5964
                }
5965
5966
                var cbo = document.getElementById('previous');
5967
                if (cbo) {
5968
                    for(var i = cbo.length - 1; i > 0; i--) {
5969
                        cbo.options[i] = null;
5970
                    }
5971
                    var k=0;
5972
                    for (var i = 1; i <= child_name[id].length; i++){
5973
                        var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
5974
                        option.style.paddingLeft = '40px';
5975
                        cbo.options[i] = option;
5976
                        k = i;
5977
                    }
5978
                    cbo.options[k].selected = true;
5979
                }
5980
5981
                //$('#previous').selectpicker('refresh');
5982
            }";
5983
5984
        return $return;
5985
    }
5986
5987
    /**
5988
     * Display the form to allow moving an item.
5989
     *
5990
     * @param CLpItem $lpItem
5991
     *
5992
     * @throws Exception
5993
     *
5994
     *
5995
     * @return string HTML form
5996
     */
5997
    public function display_move_item($lpItem)
5998
    {
5999
        $return = '';
6000
        $path = $lpItem->getPath();
6001
6002
        if ($lpItem) {
6003
            $itemType = $lpItem->getItemType();
6004
            switch ($itemType) {
6005
                case 'dir':
6006
                case 'asset':
6007
                    $return .= $this->displayItemMenu($lpItem);
6008
                    $return .= $this->display_item_form(
6009
                        $lpItem,
6010
                        get_lang('Move the current section'),
6011
                        'move',
6012
                        $row
6013
                    );
6014
                    break;
6015
                case TOOL_DOCUMENT:
6016
                    $return .= $this->displayItemMenu($lpItem);
6017
                    $return .= $this->displayDocumentForm('move', $lpItem);
6018
                    break;
6019
                case TOOL_LINK:
6020
                    $link = null;
6021
                    if (!empty($path)) {
6022
                        $repo = Container::getLinkRepository();
6023
                        $link = $repo->find($path);
6024
                    }
6025
                    $return .= $this->displayItemMenu($lpItem);
6026
                    $return .= $this->display_link_form('move', $lpItem, $link);
6027
                    break;
6028
                case TOOL_HOTPOTATOES:
6029
                    $return .= $this->displayItemMenu($lpItem);
6030
                    $return .= $this->display_link_form('move', $lpItem, $row);
6031
                    break;
6032
                case TOOL_QUIZ:
6033
                    $return .= $this->displayItemMenu($lpItem);
6034
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6035
                    break;
6036
                case TOOL_STUDENTPUBLICATION:
6037
                    $return .= $this->displayItemMenu($lpItem);
6038
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6039
                    break;
6040
                case TOOL_FORUM:
6041
                    $return .= $this->displayItemMenu($lpItem);
6042
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6043
                    break;
6044
                case TOOL_THREAD:
6045
                    $return .= $this->displayItemMenu($lpItem);
6046
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6047
                    break;
6048
            }
6049
        }
6050
6051
        return $return;
6052
    }
6053
6054
    /**
6055
     * Return HTML form to allow prerequisites selection.
6056
     *
6057
     * @todo use FormValidator
6058
     *
6059
     * @return string HTML form
6060
     */
6061
    public function displayItemPrerequisitesForm(CLpItem $lpItem)
6062
    {
6063
        $courseId = api_get_course_int_id();
6064
        $preRequisiteId = $lpItem->getPrerequisite();
6065
        $itemId = $lpItem->getIid();
6066
6067
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6068
6069
        $return .= '<form method="POST">';
6070
        $return .= '<div class="table-responsive">';
6071
        $return .= '<table class="table table-hover">';
6072
        $return .= '<thead>';
6073
        $return .= '<tr>';
6074
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6075
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6076
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6077
        $return .= '</tr>';
6078
        $return .= '</thead>';
6079
6080
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6081
        $return .= '<tbody>';
6082
        $return .= '<tr>';
6083
        $return .= '<td colspan="3">';
6084
        $return .= '<div class="radio learnpath"><label for="idnone">';
6085
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6086
        $return .= get_lang('none').'</label>';
6087
        $return .= '</div>';
6088
        $return .= '</tr>';
6089
6090
        // @todo use entitites
6091
        $tblLpItem = Database::get_course_table(TABLE_LP_ITEM);
6092
        $sql = "SELECT * FROM $tblLpItem
6093
                WHERE lp_id = ".$this->lp_id;
6094
        $result = Database::query($sql);
6095
6096
        $selectedMinScore = [];
6097
        $selectedMaxScore = [];
6098
        $masteryScore = [];
6099
        while ($row = Database::fetch_array($result)) {
6100
            if ($row['iid'] == $itemId) {
6101
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6102
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6103
            }
6104
            $masteryScore[$row['iid']] = $row['mastery_score'];
6105
        }
6106
6107
        $displayOrder = $lpItem->getDisplayOrder();
6108
        $lpItemRepo = Container::getLpItemRepository();
6109
        $itemRoot = $lpItemRepo->getRootItem($this->get_id());
6110
        $em = Database::getManager();
6111
6112
        $currentItemId = $itemId;
6113
        $options = [
6114
            'decorate' => true,
6115
            'rootOpen' => function () {
6116
                return '';
6117
            },
6118
            'rootClose' => function () {
6119
                return '';
6120
            },
6121
            'childOpen' => function () {
6122
                return '';
6123
            },
6124
            'childClose' => '',
6125
            'nodeDecorator' => function ($item) use (
6126
                $currentItemId,
6127
                $preRequisiteId,
6128
                $courseId,
6129
                $selectedMaxScore,
6130
                $selectedMinScore,
6131
                $displayOrder,
6132
                $lpItemRepo,
6133
                $em
6134
            ) {
6135
                $itemId = $item['iid'];
6136
                $type = $item['itemType'];
6137
                $iconName = str_replace(' ', '', $type);
6138
                switch ($iconName) {
6139
                    case 'category':
6140
                    case 'chapter':
6141
                    case 'folder':
6142
                    case 'dir':
6143
                        $icon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', '', ICON_SIZE_TINY);
6144
                        break;
6145
                    default:
6146
                        $icon = Display::getMdiIcon(ObjectIcon::SINGLE_ELEMENT, 'ch-tool-icon', '', ICON_SIZE_TINY);
6147
                        break;
6148
                }
6149
6150
                if ($itemId == $currentItemId) {
6151
                    return '';
6152
                }
6153
6154
                if ($displayOrder < $item['displayOrder']) {
6155
                    return '';
6156
                }
6157
6158
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6159
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6160
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6161
6162
                $return = '<tr>';
6163
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6164
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6165
                $return .= '<label for="id'.$itemId.'">';
6166
6167
                $checked = '';
6168
                if (null !== $preRequisiteId) {
6169
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6170
                }
6171
6172
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6173
6174
                $return .= '<input
6175
                    '.$checked.' '.$disabled.'
6176
                    id="id'.$itemId.'"
6177
                    name="prerequisites"
6178
                    type="radio"
6179
                    value="'.$itemId.'" />';
6180
6181
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6182
                $return .= '</div>';
6183
                $return .= '</td>';
6184
6185
                if (TOOL_QUIZ == $type) {
6186
                    // let's update max_score Tests information depending of the Tests Advanced properties
6187
                    $exercise = new Exercise($courseId);
6188
                    /** @var CLpItem $itemEntity */
6189
                    $itemEntity = $lpItemRepo->find($itemId);
6190
                    $exercise->read($item['path']);
6191
                    $itemEntity->setMaxScore($exercise->getMaxScore());
6192
                    $em->persist($itemEntity);
6193
                    $em->flush($itemEntity);
6194
6195
                    $item['maxScore'] = $exercise->getMaxScore();
6196
6197
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6198
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6199
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6200
                    }
6201
                    $return .= '<td>';
6202
                    $return .= '<input
6203
                        class="form-control"
6204
                        size="4" maxlength="3"
6205
                        name="min_'.$itemId.'"
6206
                        type="number"
6207
                        min="0"
6208
                        step="any"
6209
                        max="'.$item['maxScore'].'"
6210
                        value="'.$selectedMinScoreValue.'"
6211
                    />';
6212
                    $return .= '</td>';
6213
                    $return .= '<td>';
6214
                    $return .= '<input
6215
                        class="form-control"
6216
                        size="4"
6217
                        maxlength="3"
6218
                        name="max_'.$itemId.'"
6219
                        type="number"
6220
                        min="0"
6221
                        step="any"
6222
                        max="'.$item['maxScore'].'"
6223
                        value="'.$selectedMaxScoreValue.'"
6224
                    />';
6225
                        $return .= '</td>';
6226
                    }
6227
6228
                if (TOOL_HOTPOTATOES == $type) {
6229
                    $return .= '<td>';
6230
                    $return .= '<input
6231
                        size="4"
6232
                        maxlength="3"
6233
                        name="min_'.$itemId.'"
6234
                        type="number"
6235
                        min="0"
6236
                        step="any"
6237
                        max="'.$item['maxScore'].'"
6238
                        value="'.$selectedMinScoreValue.'"
6239
                    />';
6240
                        $return .= '</td>';
6241
                        $return .= '<td>';
6242
                        $return .= '<input
6243
                        size="4"
6244
                        maxlength="3"
6245
                        name="max_'.$itemId.'"
6246
                        type="number"
6247
                        min="0"
6248
                        step="any"
6249
                        max="'.$item['maxScore'].'"
6250
                        value="'.$selectedMaxScoreValue.'"
6251
                    />';
6252
                    $return .= '</td>';
6253
                }
6254
                $return .= '</tr>';
6255
6256
                return $return;
6257
            },
6258
        ];
6259
6260
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6261
        $return .= $tree;
6262
        $return .= '</tbody>';
6263
        $return .= '</table>';
6264
        $return .= '</div>';
6265
        $return .= '<div class="form-group">';
6266
        $return .= '<button class="btn btn--primary" name="submit_button" type="submit">'.
6267
            get_lang('Save prerequisites settings').'</button>';
6268
        $return .= '</form>';
6269
6270
        return $return;
6271
    }
6272
6273
    /**
6274
     * Return HTML list to allow prerequisites selection for lp.
6275
     */
6276
    public function display_lp_prerequisites_list(FormValidator $form)
6277
    {
6278
        $lp_id = $this->lp_id;
6279
        $lp = api_get_lp_entity($lp_id);
6280
        $prerequisiteId = $lp->getPrerequisite();
6281
6282
        $repo = Container::getLpRepository();
6283
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6284
        /** @var CLp[] $lps */
6285
        $lps = $qb->getQuery()->getResult();
6286
6287
        //$session_id = api_get_session_id();
6288
        /*$session_condition = api_get_session_condition($session_id, true, true);
6289
        $sql = "SELECT * FROM $tbl_lp
6290
                WHERE c_id = $course_id $session_condition
6291
                ORDER BY display_order ";
6292
        $rs = Database::query($sql);*/
6293
6294
        $items = [get_lang('none')];
6295
        foreach ($lps as $lp) {
6296
            $myLpId = $lp->getIid();
6297
            if ($myLpId == $lp_id) {
6298
                continue;
6299
            }
6300
            $items[$myLpId] = $lp->getTitle();
6301
            /*$return .= '<option
6302
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6303
                $lp->getName().
6304
                '</option>';*/
6305
        }
6306
6307
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6308
        $select->setSelected($prerequisiteId);
6309
    }
6310
6311
    /**
6312
     * Creates a list with all the documents in it.
6313
     *
6314
     * @param bool $showInvisibleFiles
6315
     *
6316
     * @throws Exception
6317
     *
6318
     *
6319
     * @return string
6320
     */
6321
    public function get_documents($showInvisibleFiles = false)
6322
    {
6323
        $sessionId = api_get_session_id();
6324
        $documentTree = DocumentManager::get_document_preview(
6325
            api_get_course_entity(),
6326
            $this->lp_id,
6327
            null,
6328
            $sessionId,
6329
            true,
6330
            null,
6331
            null,
6332
            $showInvisibleFiles,
6333
            true
6334
        );
6335
6336
        $form = new FormValidator(
6337
            'form_upload',
6338
            'POST',
6339
            $this->getCurrentBuildingModeURL(),
6340
            '',
6341
            ['enctype' => 'multipart/form-data']
6342
        );
6343
6344
        $folders = DocumentManager::get_all_document_folders(
6345
            api_get_course_info(),
6346
            0,
6347
            true
6348
        );
6349
6350
        $folder = $this->generate_lp_folder(api_get_course_info());
6351
6352
        DocumentManager::build_directory_selector(
6353
            $folders,
6354
            $folder->getIid(),
6355
            [],
6356
            true,
6357
            $form,
6358
            'directory_parent_id'
6359
        );
6360
6361
        $group = [
6362
            $form->createElement(
6363
                'radio',
6364
                'if_exists',
6365
                get_lang('If file exists:'),
6366
                get_lang('Do nothing'),
6367
                'nothing'
6368
            ),
6369
            $form->createElement(
6370
                'radio',
6371
                'if_exists',
6372
                null,
6373
                get_lang('Overwrite the existing file'),
6374
                'overwrite'
6375
            ),
6376
            $form->createElement(
6377
                'radio',
6378
                'if_exists',
6379
                null,
6380
                get_lang('Rename the uploaded file if it exists'),
6381
                'rename'
6382
            ),
6383
        ];
6384
        $form->addGroup($group, null, get_lang('If file exists:'));
6385
6386
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
6387
        $defaultFileExistsOption = 'rename';
6388
        if (!empty($fileExistsOption)) {
6389
            $defaultFileExistsOption = $fileExistsOption;
6390
        }
6391
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
6392
6393
        // Check box options
6394
        $form->addCheckBox(
6395
            'unzip',
6396
            get_lang('Options'),
6397
            get_lang('Uncompress zip')
6398
        );
6399
6400
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
6401
        $form->addMultipleUpload($url);
6402
6403
        $lpItem = (new CLpItem())
6404
            ->setTitle('')
6405
            ->setItemType(TOOL_DOCUMENT)
6406
        ;
6407
        $new = $this->displayDocumentForm('add', $lpItem);
6408
6409
        /*$lpItem = new CLpItem();
6410
        $lpItem->setItemType(TOOL_READOUT_TEXT);
6411
        $frmReadOutText = $this->displayDocumentForm('add');*/
6412
6413
        $headers = [
6414
            get_lang('Files'),
6415
            get_lang('Create a new document'),
6416
            //get_lang('Create read-out text'),
6417
            get_lang('Upload'),
6418
        ];
6419
6420
        return Display::tabs(
6421
            $headers,
6422
            [$documentTree, $new, $form->returnForm()],
6423
            'subtab',
6424
            ['class' => 'mt-2']
6425
        );
6426
    }
6427
6428
    /**
6429
     * Creates a list with all the exercises (quiz) in it.
6430
     *
6431
     * @return string
6432
     */
6433
    public function get_exercises()
6434
    {
6435
        $course_id = api_get_course_int_id();
6436
        $session_id = api_get_session_id();
6437
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
6438
6439
        //$activeCondition = ' active <> -1 ';
6440
        $active = 2;
6441
        if ($setting) {
6442
            $active = 1;
6443
            //$activeCondition = ' active = 1 ';
6444
        }
6445
6446
        $categoryCondition = '';
6447
6448
        $keyword = $_REQUEST['keyword'] ?? null;
6449
        $categoryId = $_REQUEST['category_id'] ?? null;
6450
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
6451
            $categoryCondition = " AND exercise_category_id = $categoryId ";
6452
        }
6453
6454
        $keywordCondition = '';
6455
6456
        if (!empty($keyword)) {
6457
            $keyword = Database::escape_string($keyword);
6458
            $keywordCondition = " AND title LIKE '%$keyword%' ";
6459
        }
6460
        */
6461
        $course = api_get_course_entity($course_id);
6462
        $session = api_get_session_entity($session_id);
6463
6464
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
6465
        /** @var CQuiz[] $exercises */
6466
        $exercises = $qb->getQuery()->getResult();
6467
6468
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
6469
                     WHERE
6470
                            c_id = $course_id AND
6471
                            $activeCondition
6472
                            $condition_session
6473
                            $categoryCondition
6474
                            $keywordCondition
6475
                     ORDER BY title ASC";
6476
        $res_quiz = Database::query($sql_quiz);*/
6477
6478
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
6479
6480
        // Create a search-box
6481
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
6482
        $form->addHidden('action', 'add_item');
6483
        $form->addHidden('type', 'step');
6484
        $form->addHidden('lp_id', $this->lp_id);
6485
        $form->addHidden('lp_build_selected', '2');
6486
6487
        $form->addCourseHiddenParams();
6488
        $form->addText(
6489
            'keyword',
6490
            get_lang('Search'),
6491
            false,
6492
            [
6493
                'aria-label' => get_lang('Search'),
6494
            ]
6495
        );
6496
6497
        if (api_get_configuration_value('allow_exercise_categories')) {
6498
            $manager = new ExerciseCategoryManager();
6499
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
6500
            if (!empty($options)) {
6501
                $form->addSelect(
6502
                    'category_id',
6503
                    get_lang('Category'),
6504
                    $options,
6505
                    ['placeholder' => get_lang('Please select an option')]
6506
                );
6507
            }
6508
        }
6509
6510
        $form->addButtonSearch(get_lang('Search'));
6511
        $return = $form->returnForm();*/
6512
6513
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6514
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6515
        $return .= Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 32, get_lang('New test'));
6516
        $return .= '<a
6517
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
6518
            get_lang('New test').'</a>';
6519
        $return .= '</li>';
6520
6521
        $previewIcon = Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview'));
6522
        $quizIcon = Display::getMdiIcon('order-bool-ascending-variant', 'ch-tool-icon', null, 16, get_lang('Exercise'));
6523
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6524
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
6525
        foreach ($exercises as $exercise) {
6526
            $exerciseId = $exercise->getIid();
6527
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
6528
            $visibility = $exercise->isVisible($course, $session);
6529
6530
            $link = Display::url(
6531
                $previewIcon,
6532
                $exerciseUrl.'&exerciseId='.$exerciseId,
6533
                ['target' => '_blank']
6534
            );
6535
            $return .= '<li
6536
                class="list-group-item lp_resource_element"
6537
                id="'.$exerciseId.'"
6538
                data-id="'.$exerciseId.'"
6539
                title="'.$title.'">';
6540
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
6541
            $return .= $quizIcon;
6542
            $sessionStar = '';
6543
            /*$sessionStar = api_get_session_image(
6544
                $row_quiz['session_id'],
6545
                $userInfo['status']
6546
            );*/
6547
            $return .= Display::url(
6548
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
6549
                api_get_self().'?'.
6550
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
6551
                [
6552
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
6553
                    'data_type' => 'quiz',
6554
                    'data-id' => $exerciseId,
6555
                ]
6556
            );
6557
            $return .= '</li>';
6558
        }
6559
6560
        $return .= '</ul>';
6561
6562
        return $return;
6563
    }
6564
6565
    /**
6566
     * Creates a list with all the links in it.
6567
     *
6568
     * @return string
6569
     */
6570
    public function get_links()
6571
    {
6572
        $sessionId = api_get_session_id();
6573
        $repo = Container::getLinkRepository();
6574
6575
        $course = api_get_course_entity();
6576
        $session = api_get_session_entity($sessionId);
6577
        $qb = $repo->getResourcesByCourse($course, $session);
6578
        /** @var CLink[] $links */
6579
        $links = $qb->getQuery()->getResult();
6580
6581
        $selfUrl = api_get_self();
6582
        $courseIdReq = api_get_cidreq();
6583
        $userInfo = api_get_user_info();
6584
6585
        $moveEverywhereIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6586
6587
        $categorizedLinks = [];
6588
        $categories = [];
6589
6590
        foreach ($links as $link) {
6591
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
6592
            if (empty($categoryId)) {
6593
                $categories[0] = get_lang('Uncategorized');
6594
            } else {
6595
                $category = $link->getCategory();
6596
                $categories[$categoryId] = $category->getTitle();
6597
            }
6598
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
6599
        }
6600
6601
        $linksHtmlCode =
6602
            '<script>
6603
            function toggle_tool(tool, id) {
6604
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
6605
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
6606
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6607
                } else {
6608
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
6609
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6610
                }
6611
            }
6612
        </script>
6613
6614
        <ul class="mt-2 bg-white list-group lp_resource">
6615
            <li class="list-group-item lp_resource_element disable_drag ">
6616
                '.Display::getMdiIcon(ObjectIcon::LINK, 'ch-tool-icon', null, ICON_SIZE_SMALL).'
6617
                <a
6618
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
6619
                title="'.get_lang('Add a link').'">'.
6620
                get_lang('Add a link').'
6621
                </a>
6622
            </li>';
6623
        $linkIcon = Display::getMdiIcon('file-link', 'ch-tool-icon', null, 16, get_lang('Link'));
6624
        foreach ($categorizedLinks as $categoryId => $links) {
6625
            $linkNodes = null;
6626
            /** @var CLink $link */
6627
            foreach ($links as $key => $link) {
6628
                $title = $link->getTitle();
6629
                $id = $link->getIid();
6630
                $linkUrl = Display::url(
6631
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6632
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
6633
                    ['target' => '_blank']
6634
                );
6635
6636
                if ($link->isVisible($course, $session)) {
6637
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
6638
                    $sessionStar = '';
6639
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
6640
                    $link = Display::url(
6641
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
6642
                        $url,
6643
                        [
6644
                            'class' => 'moved link_with_id',
6645
                            'data-id' => $key,
6646
                            'data_type' => TOOL_LINK,
6647
                            'title' => $title,
6648
                        ]
6649
                    );
6650
                    $linkNodes .=
6651
                        "<li
6652
                            class='list-group-item lp_resource_element'
6653
                            id= $id
6654
                            data-id= $id
6655
                            >
6656
                         <a class='moved' href='#'>
6657
                            $moveEverywhereIcon
6658
                        </a>
6659
                        $linkIcon $link
6660
                        </li>";
6661
                }
6662
            }
6663
            $linksHtmlCode .=
6664
                '<li class="list-group-item disable_drag">
6665
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
6666
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
6667
                        align="absbottom" />
6668
                    </a>
6669
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
6670
                </li>
6671
            '.
6672
                $linkNodes.
6673
            '';
6674
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
6675
        }
6676
        $linksHtmlCode .= '</ul>';
6677
6678
        return $linksHtmlCode;
6679
    }
6680
6681
    /**
6682
     * Creates a list with all the student publications in it.
6683
     *
6684
     * @return string
6685
     */
6686
    public function get_student_publications()
6687
    {
6688
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6689
        $return .= '<li class="list-group-item lp_resource_element">';
6690
        $works = getWorkListTeacher(0, 100, null, null, null);
6691
        if (!empty($works)) {
6692
            $icon = Display::getMdiIcon('inbox-full', 'ch-tool-icon',null, 16, get_lang('Student publication'));
6693
            foreach ($works as $work) {
6694
                $workId = $work['iid'];
6695
                $link = Display::url(
6696
                    Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6697
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
6698
                    ['target' => '_blank']
6699
                );
6700
6701
                $return .= '<li
6702
                    class="list-group-item lp_resource_element"
6703
                    id="'.$workId.'"
6704
                    data-id="'.$workId.'"
6705
                    >';
6706
                $return .= '<a class="moved" href="#">';
6707
                $return .= Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6708
                $return .= '</a> ';
6709
6710
                $return .= $icon;
6711
                $return .= Display::url(
6712
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
6713
                    api_get_self().'?'.
6714
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
6715
                    [
6716
                        'class' => 'moved link_with_id',
6717
                        'data-id' => $work['iid'],
6718
                        'data_type' => TOOL_STUDENTPUBLICATION,
6719
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
6720
                    ]
6721
                );
6722
                $return .= '</li>';
6723
            }
6724
        }
6725
6726
        $return .= '</ul>';
6727
6728
        return $return;
6729
    }
6730
6731
    /**
6732
     * Creates a list with all the forums in it.
6733
     *
6734
     * @return string
6735
     */
6736
    public function get_forums()
6737
    {
6738
        $forumCategories = get_forum_categories();
6739
        $forumsInNoCategory = get_forums_in_category(0);
6740
        if (!empty($forumsInNoCategory)) {
6741
            $forumCategories = array_merge(
6742
                $forumCategories,
6743
                [
6744
                    [
6745
                        'cat_id' => 0,
6746
                        'session_id' => 0,
6747
                        'visibility' => 1,
6748
                        'cat_comment' => null,
6749
                    ],
6750
                ]
6751
            );
6752
        }
6753
6754
        $a_forums = [];
6755
        $courseEntity = api_get_course_entity(api_get_course_int_id());
6756
        $sessionEntity = api_get_session_entity(api_get_session_id());
6757
6758
        foreach ($forumCategories as $forumCategory) {
6759
            // The forums in this category.
6760
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
6761
            if (!empty($forumsInCategory)) {
6762
                foreach ($forumsInCategory as $forum) {
6763
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
6764
                        $a_forums[] = $forum;
6765
                    }
6766
                }
6767
            }
6768
        }
6769
6770
        $return = '<ul class="mt-2 bg-white list-group lp_resource">';
6771
6772
        // First add link
6773
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
6774
        $return .= Display::getMdiIcon('comment-quote	', 'ch-tool-icon', null, 32, get_lang('Create a new forum'));
6775
        $return .= Display::url(
6776
            get_lang('Create a new forum'),
6777
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
6778
                'action' => 'add',
6779
                'content' => 'forum',
6780
                'lp_id' => $this->lp_id,
6781
            ]),
6782
            ['title' => get_lang('Create a new forum')]
6783
        );
6784
        $return .= '</li>';
6785
6786
        $return .= '<script>
6787
            function toggle_forum(forum_id) {
6788
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
6789
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
6790
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
6791
                } else {
6792
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
6793
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
6794
                }
6795
            }
6796
        </script>';
6797
        $moveIcon = Display::getMdiIcon('cursor-move', 'ch-tool-icon', '', 16, get_lang('Move'));
6798
        foreach ($a_forums as $forum) {
6799
            $forumId = $forum->getIid();
6800
            $title = Security::remove_XSS($forum->getTitle());
6801
            $link = Display::url(
6802
                Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6803
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
6804
                ['target' => '_blank']
6805
            );
6806
6807
            $return .= '<li
6808
                    class="list-group-item lp_resource_element"
6809
                    id="'.$forumId.'"
6810
                    data-id="'.$forumId.'"
6811
                    >';
6812
            $return .= '<a class="moved" href="#">';
6813
            $return .= $moveIcon;
6814
            $return .= ' </a>';
6815
            $return .= Display::getMdiIcon('comment-quote', 'ch-tool-icon', null, 16, get_lang('Forum'));
6816
6817
            $moveLink = Display::url(
6818
                $title.' '.$link,
6819
                api_get_self().'?'.
6820
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
6821
                [
6822
                    'class' => 'moved link_with_id',
6823
                    'data-id' => $forumId,
6824
                    'data_type' => TOOL_FORUM,
6825
                    'title' => $title,
6826
                    'style' => 'vertical-align:middle',
6827
                ]
6828
            );
6829
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
6830
                            <img
6831
                                src="'.Display::returnIconPath('add.png').'"
6832
                                id="forum_'.$forumId.'_opener" align="absbottom"
6833
                             />
6834
                        </a>
6835
                        '.$moveLink;
6836
            $return .= '</li>';
6837
6838
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
6839
            $threads = get_threads($forumId);
6840
            if (is_array($threads)) {
6841
                foreach ($threads as $thread) {
6842
                    $threadId = $thread->getIid();
6843
                    $link = Display::url(
6844
                        Display::getMdiIcon('magnify-plus-outline', 'ch-tool-icon', null, 22, get_lang('Preview')),
6845
                        api_get_path(WEB_CODE_PATH).
6846
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
6847
                        ['target' => '_blank']
6848
                    );
6849
6850
                    $return .= '<li
6851
                        class="list-group-item lp_resource_element"
6852
                      id="'.$threadId.'"
6853
                        data-id="'.$threadId.'"
6854
                    >';
6855
                    $return .= '&nbsp;<a class="moved" href="#">';
6856
                    $return .= $moveIcon;
6857
                    $return .= ' </a>';
6858
                    $return .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, 16, get_lang('Thread'));
6859
                    $return .= '<a
6860
                        class="moved link_with_id"
6861
                        data-id="'.$threadId.'"
6862
                        data_type="'.TOOL_THREAD.'"
6863
                        title="'.$thread->getTitle().'"
6864
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
6865
                        >'.
6866
                        Security::remove_XSS($thread->getTitle()).' '.$link.'</a>';
6867
                    $return .= '</li>';
6868
                }
6869
            }
6870
            $return .= '</div>';
6871
        }
6872
        $return .= '</ul>';
6873
6874
        return $return;
6875
    }
6876
6877
    /**
6878
     * Temp function to be moved in main_api or the best place around for this.
6879
     * Creates a file path if it doesn't exist.
6880
     *
6881
     * @param string $path
6882
     */
6883
    public function create_path($path)
6884
    {
6885
        $path_bits = explode('/', dirname($path));
6886
6887
        // IS_WINDOWS_OS has been defined in main_api.lib.php
6888
        $path_built = IS_WINDOWS_OS ? '' : '/';
6889
        foreach ($path_bits as $bit) {
6890
            if (!empty($bit)) {
6891
                $new_path = $path_built.$bit;
6892
                if (is_dir($new_path)) {
6893
                    $path_built = $new_path.'/';
6894
                } else {
6895
                    mkdir($new_path, api_get_permissions_for_new_directories());
6896
                    $path_built = $new_path.'/';
6897
                }
6898
            }
6899
        }
6900
    }
6901
6902
    /**
6903
     * @param int    $lp_id
6904
     * @param string $status
6905
     */
6906
    public function set_autolaunch($lp_id, $status)
6907
    {
6908
        $course_id = api_get_course_int_id();
6909
        $lp_id = (int) $lp_id;
6910
        $status = (int) $status;
6911
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6912
6913
        // Setting everything to autolaunch = 0
6914
        $attributes['autolaunch'] = 0;
6915
        $where = [
6916
            'session_id = ? AND c_id = ? ' => [
6917
                api_get_session_id(),
6918
                $course_id,
6919
            ],
6920
        ];
6921
        Database::update($lp_table, $attributes, $where);
6922
        if (1 == $status) {
6923
            //Setting my lp_id to autolaunch = 1
6924
            $attributes['autolaunch'] = 1;
6925
            $where = [
6926
                'iid = ? AND session_id = ? AND c_id = ?' => [
6927
                    $lp_id,
6928
                    api_get_session_id(),
6929
                    $course_id,
6930
                ],
6931
            ];
6932
            Database::update($lp_table, $attributes, $where);
6933
        }
6934
    }
6935
6936
    /**
6937
     * Gets previous_item_id for the next element of the lp_item table.
6938
     *
6939
     * @author Isaac flores paz
6940
     *
6941
     * @return int Previous item ID
6942
     */
6943
    public function select_previous_item_id()
6944
    {
6945
        $course_id = api_get_course_int_id();
6946
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6947
6948
        // Get the max order of the items
6949
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
6950
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6951
        $rs_max_order = Database::query($sql);
6952
        $row_max_order = Database::fetch_object($rs_max_order);
6953
        $max_order = $row_max_order->display_order;
6954
        // Get the previous item ID
6955
        $sql = "SELECT iid as previous FROM $table_lp_item
6956
                WHERE
6957
                    c_id = $course_id AND
6958
                    lp_id = ".$this->lp_id." AND
6959
                    display_order = '$max_order' ";
6960
        $rs_max = Database::query($sql);
6961
        $row_max = Database::fetch_object($rs_max);
6962
6963
        // Return the previous item ID
6964
        return $row_max->previous;
6965
    }
6966
6967
    /**
6968
     * Copies an LP.
6969
     */
6970
    public function copy()
6971
    {
6972
        // Course builder
6973
        $cb = new CourseBuilder();
6974
6975
        //Setting tools that will be copied
6976
        $cb->set_tools_to_build(['learnpaths']);
6977
6978
        //Setting elements that will be copied
6979
        $cb->set_tools_specific_id_list(
6980
            ['learnpaths' => [$this->lp_id]]
6981
        );
6982
6983
        $course = $cb->build();
6984
6985
        //Course restorer
6986
        $course_restorer = new CourseRestorer($course);
6987
        $course_restorer->set_add_text_in_items(true);
6988
        $course_restorer->set_tool_copy_settings(
6989
            ['learnpaths' => ['reset_dates' => true]]
6990
        );
6991
        $course_restorer->restore(
6992
            api_get_course_id(),
6993
            api_get_session_id(),
6994
            false,
6995
            false
6996
        );
6997
    }
6998
6999
    /**
7000
     * Verify document size.
7001
     *
7002
     * @param string $s
7003
     *
7004
     * @return bool
7005
     */
7006
    public static function verify_document_size($s)
7007
    {
7008
        $post_max = ini_get('post_max_size');
7009
        if ('M' == substr($post_max, -1, 1)) {
7010
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7011
        } elseif ('G' == substr($post_max, -1, 1)) {
7012
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7013
        }
7014
        $upl_max = ini_get('upload_max_filesize');
7015
        if ('M' == substr($upl_max, -1, 1)) {
7016
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7017
        } elseif ('G' == substr($upl_max, -1, 1)) {
7018
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7019
        }
7020
7021
        $repo = Container::getDocumentRepository();
7022
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7023
7024
        $course_max_space = DocumentManager::get_course_quota();
7025
        $total_size = filesize($s) + $documents_total_space;
7026
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7027
            return true;
7028
        }
7029
7030
        return false;
7031
    }
7032
7033
    /**
7034
     * Clear LP prerequisites.
7035
     */
7036
    public function clearPrerequisites()
7037
    {
7038
        $course_id = $this->get_course_int_id();
7039
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7040
        $lp_id = $this->get_id();
7041
        // Cleaning prerequisites
7042
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7043
                WHERE lp_id = $lp_id";
7044
        Database::query($sql);
7045
7046
        // Cleaning mastery score for exercises
7047
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7048
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7049
        Database::query($sql);
7050
    }
7051
7052
    public function set_previous_step_as_prerequisite_for_all_items()
7053
    {
7054
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7055
        $course_id = $this->get_course_int_id();
7056
        $lp_id = $this->get_id();
7057
7058
        if (!empty($this->items)) {
7059
            $previous_item_id = null;
7060
            $previous_item_max = 0;
7061
            $previous_item_type = null;
7062
            $last_item_not_dir = null;
7063
            $last_item_not_dir_type = null;
7064
            $last_item_not_dir_max = null;
7065
7066
            foreach ($this->ordered_items as $itemId) {
7067
                $item = $this->getItem($itemId);
7068
                // if there was a previous item... (otherwise jump to set it)
7069
                if (!empty($previous_item_id)) {
7070
                    $current_item_id = $item->get_id(); //save current id
7071
                    if ('dir' != $item->get_type()) {
7072
                        // Current item is not a folder, so it qualifies to get a prerequisites
7073
                        if ('quiz' == $last_item_not_dir_type) {
7074
                            // if previous is quiz, mark its max score as default score to be achieved
7075
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7076
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7077
                            Database::query($sql);
7078
                        }
7079
                        // now simply update the prerequisite to set it to the last non-chapter item
7080
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7081
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7082
                        Database::query($sql);
7083
                        // record item as 'non-chapter' reference
7084
                        $last_item_not_dir = $item->get_id();
7085
                        $last_item_not_dir_type = $item->get_type();
7086
                        $last_item_not_dir_max = $item->get_max();
7087
                    }
7088
                } else {
7089
                    if ('dir' != $item->get_type()) {
7090
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7091
                        $last_item_not_dir = $item->get_id();
7092
                        $last_item_not_dir_type = $item->get_type();
7093
                        $last_item_not_dir_max = $item->get_max();
7094
                    }
7095
                }
7096
                // Saving the item as "previous item" for the next loop
7097
                $previous_item_id = $item->get_id();
7098
                $previous_item_max = $item->get_max();
7099
                $previous_item_type = $item->get_type();
7100
            }
7101
        }
7102
    }
7103
7104
    /**
7105
     * @param array $params
7106
     *
7107
     * @return int
7108
     */
7109
    public static function createCategory($params)
7110
    {
7111
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7112
7113
        $item = new CLpCategory();
7114
        $item
7115
            ->setTitle($params['name'])
7116
            ->setParent($courseEntity)
7117
            ->addCourseLink($courseEntity, api_get_session_entity())
7118
        ;
7119
7120
        $repo = Container::getLpCategoryRepository();
7121
        $repo->create($item);
7122
7123
        return $item->getIid();
7124
    }
7125
7126
    /**
7127
     * @param array $params
7128
     */
7129
    public static function updateCategory($params)
7130
    {
7131
        $em = Database::getManager();
7132
        /** @var CLpCategory $item */
7133
        $item = $em->find(CLpCategory::class, $params['id']);
7134
        if ($item) {
7135
            $item->setTitle($params['name']);
7136
            $em->persist($item);
7137
            $em->flush();
7138
        }
7139
    }
7140
7141
    public static function moveUpCategory(int $id): void
7142
    {
7143
        $em = Database::getManager();
7144
        /** @var CLpCategory $item */
7145
        $item = $em->find(CLpCategory::class, $id);
7146
        if ($item) {
7147
            $course = api_get_course_entity();
7148
            $session = api_get_session_entity();
7149
7150
            $link = $item->resourceNode->getResourceLinkByContext($course, $session);
7151
7152
            if ($link) {
7153
                $link->setDisplayOrder(
7154
                    $link->getDisplayOrder() - 1
7155
                );
7156
7157
                $em->flush();
7158
            }
7159
        }
7160
    }
7161
7162
    public static function moveDownCategory(int $id): void
7163
    {
7164
        $em = Database::getManager();
7165
        /** @var CLpCategory $item */
7166
        $item = $em->find(CLpCategory::class, $id);
7167
        if ($item) {
7168
            $course = api_get_course_entity();
7169
            $session = api_get_session_entity();
7170
7171
            $link = $item->resourceNode->getResourceLinkByContext($course, $session);
7172
7173
            if ($link) {
7174
                $link->setDisplayOrder(
7175
                    $link->getDisplayOrder() + 1
7176
                );
7177
7178
                $em->flush();
7179
            }
7180
        }
7181
    }
7182
7183
    /**
7184
     * @param int $courseId
7185
     *
7186
     * @return int
7187
     */
7188
    public static function getCountCategories($courseId)
7189
    {
7190
        if (empty($courseId)) {
7191
            return 0;
7192
        }
7193
        $repo = Container::getLpCategoryRepository();
7194
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7195
        $qb->addSelect('count(resource)');
7196
7197
        return (int) $qb->getQuery()->getSingleScalarResult();
7198
    }
7199
7200
    /**
7201
     * @param int $courseId
7202
     *
7203
     * @return CLpCategory[]
7204
     */
7205
    public static function getCategories($courseId)
7206
    {
7207
        // Using doctrine extensions
7208
        $repo = Container::getLpCategoryRepository();
7209
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity(), null, null, true, true);
7210
7211
        return $qb->getQuery()->getResult();
7212
    }
7213
7214
    public static function getCategorySessionId($id)
7215
    {
7216
        if ('true' !== api_get_setting('lp.allow_session_lp_category')) {
7217
            return 0;
7218
        }
7219
7220
        $repo = Container::getLpCategoryRepository();
7221
        /** @var CLpCategory $category */
7222
        $category = $repo->find($id);
7223
7224
        $sessionId = 0;
7225
        $link = $category->getFirstResourceLink();
7226
        if ($link && $link->getSession()) {
7227
            $sessionId = (int) $link->getSession()->getId();
7228
        }
7229
7230
        return $sessionId;
7231
    }
7232
7233
    public static function deleteCategory(int $id): bool
7234
    {
7235
        $repo = Container::getLpCategoryRepository();
7236
        /** @var CLpCategory $category */
7237
        $category = $repo->find($id);
7238
        if ($category) {
7239
            $em = Database::getManager();
7240
            $lps = $category->getLps();
7241
7242
            foreach ($lps as $lp) {
7243
                $lp->setCategory(null);
7244
            }
7245
7246
            $em->persist($lp);
7247
7248
            $course = api_get_course_entity();
7249
            $session = api_get_session_entity();
7250
7251
            $em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session);
7252
7253
            return true;
7254
        }
7255
7256
        return false;
7257
    }
7258
7259
    /**
7260
     * @param int  $courseId
7261
     * @param bool $addSelectOption
7262
     *
7263
     * @return array
7264
     */
7265
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7266
    {
7267
        $repo = Container::getLpCategoryRepository();
7268
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity());
7269
        $items = $qb->getQuery()->getResult();
7270
7271
        $cats = [];
7272
        if ($addSelectOption) {
7273
            $cats = [get_lang('Select a category')];
7274
        }
7275
7276
        if (!empty($items)) {
7277
            foreach ($items as $cat) {
7278
                $cats[$cat->getIid()] = $cat->getTitle();
7279
            }
7280
        }
7281
7282
        return $cats;
7283
    }
7284
7285
    /**
7286
     * @param int   $courseId
7287
     * @param int   $lpId
7288
     * @param int   $user_id
7289
     *
7290
     * @return learnpath
7291
     */
7292
    public static function getLpFromSession(int $courseId, int $lpId, int $user_id)
7293
    {
7294
        $debug = 0;
7295
        $learnPath = null;
7296
        $lpObject = Session::read('lpobject');
7297
7298
        $repo = Container::getLpRepository();
7299
        $lp = $repo->find($lpId);
7300
        if (null !== $lpObject) {
7301
            /** @var learnpath $learnPath */
7302
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7303
            $learnPath->entity = $lp;
7304
            if ($debug) {
7305
                error_log('getLpFromSession: unserialize');
7306
                error_log('------getLpFromSession------');
7307
                error_log('------unserialize------');
7308
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7309
                error_log("api_get_sessionid: ".api_get_session_id());
7310
            }
7311
        }
7312
7313
        if (!is_object($learnPath)) {
7314
            $learnPath = new learnpath($lp, api_get_course_info_by_id($courseId), $user_id);
7315
            if ($debug) {
7316
                error_log('------getLpFromSession------');
7317
                error_log('getLpFromSession: create new learnpath');
7318
                error_log("create new LP with $courseId - $lpId - $user_id");
7319
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7320
                error_log("api_get_sessionid: ".api_get_session_id());
7321
            }
7322
        }
7323
7324
        return $learnPath;
7325
    }
7326
7327
    /**
7328
     * @param int $itemId
7329
     *
7330
     * @return learnpathItem|false
7331
     */
7332
    public function getItem($itemId)
7333
    {
7334
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7335
            return $this->items[$itemId];
7336
        }
7337
7338
        return false;
7339
    }
7340
7341
    /**
7342
     * @return int
7343
     */
7344
    public function getCurrentAttempt()
7345
    {
7346
        $attempt = $this->getItem($this->get_current_item_id());
7347
        if ($attempt) {
7348
            return $attempt->get_attempt_id();
7349
        }
7350
7351
        return 0;
7352
    }
7353
7354
    /**
7355
     * @return int
7356
     */
7357
    public function getCategoryId()
7358
    {
7359
        return (int) $this->categoryId;
7360
    }
7361
7362
    /**
7363
     * Get whether this is a learning path with the possibility to subscribe
7364
     * users or not.
7365
     *
7366
     * @return int
7367
     */
7368
    public function getSubscribeUsers()
7369
    {
7370
        return $this->subscribeUsers;
7371
    }
7372
7373
    /**
7374
     * Calculate the count of stars for a user in this LP
7375
     * This calculation is based on the following rules:
7376
     * - the student gets one star when he gets to 50% of the learning path
7377
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
7378
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
7379
     * - the student gets the final star when the score for the *last* test is >= 80%.
7380
     *
7381
     * @param int $sessionId Optional. The session ID
7382
     *
7383
     * @return int The count of stars
7384
     */
7385
    public function getCalculateStars($sessionId = 0)
7386
    {
7387
        $stars = 0;
7388
        $progress = self::getProgress(
7389
            $this->lp_id,
7390
            $this->user_id,
7391
            $this->course_int_id,
7392
            $sessionId
7393
        );
7394
7395
        if ($progress >= 50) {
7396
            $stars++;
7397
        }
7398
7399
        // Calculate stars chapters evaluation
7400
        $exercisesItems = $this->getExercisesItems();
7401
7402
        if (!empty($exercisesItems)) {
7403
            $totalResult = 0;
7404
7405
            foreach ($exercisesItems as $exerciseItem) {
7406
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7407
                    $this->user_id,
7408
                    $exerciseItem->path,
7409
                    $this->course_int_id,
7410
                    $sessionId,
7411
                    $this->lp_id,
7412
                    $exerciseItem->db_id
7413
                );
7414
7415
                $exerciseResultInfo = end($exerciseResultInfo);
7416
7417
                if (!$exerciseResultInfo) {
7418
                    continue;
7419
                }
7420
7421
                if (!empty($exerciseResultInfo['max_score'])) {
7422
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
7423
                } else {
7424
                    $exerciseResult = 0;
7425
                }
7426
                $totalResult += $exerciseResult;
7427
            }
7428
7429
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
7430
7431
            if ($totalExerciseAverage >= 50) {
7432
                $stars++;
7433
            }
7434
7435
            if ($totalExerciseAverage >= 80) {
7436
                $stars++;
7437
            }
7438
        }
7439
7440
        // Calculate star for final evaluation
7441
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7442
7443
        if (!empty($finalEvaluationItem)) {
7444
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7445
                $this->user_id,
7446
                $finalEvaluationItem->path,
7447
                $this->course_int_id,
7448
                $sessionId,
7449
                $this->lp_id,
7450
                $finalEvaluationItem->db_id
7451
            );
7452
7453
            $evaluationResultInfo = end($evaluationResultInfo);
7454
7455
            if ($evaluationResultInfo) {
7456
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
7457
                if ($evaluationResult >= 80) {
7458
                    $stars++;
7459
                }
7460
            }
7461
        }
7462
7463
        return $stars;
7464
    }
7465
7466
    /**
7467
     * Get the items of exercise type.
7468
     *
7469
     * @return array The items. Otherwise return false
7470
     */
7471
    public function getExercisesItems()
7472
    {
7473
        $exercises = [];
7474
        foreach ($this->items as $item) {
7475
            if ('quiz' !== $item->type) {
7476
                continue;
7477
            }
7478
            $exercises[] = $item;
7479
        }
7480
7481
        array_pop($exercises);
7482
7483
        return $exercises;
7484
    }
7485
7486
    /**
7487
     * Get the item of exercise type (evaluation type).
7488
     *
7489
     * @return array The final evaluation. Otherwise return false
7490
     */
7491
    public function getFinalEvaluationItem()
7492
    {
7493
        $exercises = [];
7494
        foreach ($this->items as $item) {
7495
            if (TOOL_QUIZ !== $item->type) {
7496
                continue;
7497
            }
7498
7499
            $exercises[] = $item;
7500
        }
7501
7502
        return array_pop($exercises);
7503
    }
7504
7505
    /**
7506
     * Calculate the total points achieved for the current user in this learning path.
7507
     *
7508
     * @param int $sessionId Optional. The session Id
7509
     *
7510
     * @return int
7511
     */
7512
    public function getCalculateScore($sessionId = 0)
7513
    {
7514
        // Calculate stars chapters evaluation
7515
        $exercisesItems = $this->getExercisesItems();
7516
        $finalEvaluationItem = $this->getFinalEvaluationItem();
7517
        $totalExercisesResult = 0;
7518
        $totalEvaluationResult = 0;
7519
7520
        if (false !== $exercisesItems) {
7521
            foreach ($exercisesItems as $exerciseItem) {
7522
                $exerciseResultInfo = Event::getExerciseResultsByUser(
7523
                    $this->user_id,
7524
                    $exerciseItem->path,
7525
                    $this->course_int_id,
7526
                    $sessionId,
7527
                    $this->lp_id,
7528
                    $exerciseItem->db_id
7529
                );
7530
7531
                $exerciseResultInfo = end($exerciseResultInfo);
7532
7533
                if (!$exerciseResultInfo) {
7534
                    continue;
7535
                }
7536
7537
                $totalExercisesResult += $exerciseResultInfo['score'];
7538
            }
7539
        }
7540
7541
        if (!empty($finalEvaluationItem)) {
7542
            $evaluationResultInfo = Event::getExerciseResultsByUser(
7543
                $this->user_id,
7544
                $finalEvaluationItem->path,
7545
                $this->course_int_id,
7546
                $sessionId,
7547
                $this->lp_id,
7548
                $finalEvaluationItem->db_id
7549
            );
7550
7551
            $evaluationResultInfo = end($evaluationResultInfo);
7552
7553
            if ($evaluationResultInfo) {
7554
                $totalEvaluationResult += $evaluationResultInfo['score'];
7555
            }
7556
        }
7557
7558
        return $totalExercisesResult + $totalEvaluationResult;
7559
    }
7560
7561
    /**
7562
     * Check if URL is not allowed to be show in a iframe.
7563
     *
7564
     * @param string $src
7565
     *
7566
     * @return string
7567
     */
7568
    public function fixBlockedLinks($src)
7569
    {
7570
        $urlInfo = parse_url($src);
7571
7572
        $platformProtocol = 'https';
7573
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
7574
            $platformProtocol = 'http';
7575
        }
7576
7577
        $protocolFixApplied = false;
7578
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
7579
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
7580
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
7581
7582
        if ($platformProtocol != $scheme) {
7583
            Session::write('x_frame_source', $src);
7584
            $src = 'blank.php?error=x_frames_options';
7585
            $protocolFixApplied = true;
7586
        }
7587
7588
        if (false == $protocolFixApplied) {
7589
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
7590
                // Check X-Frame-Options
7591
                $ch = curl_init();
7592
                $options = [
7593
                    CURLOPT_URL => $src,
7594
                    CURLOPT_RETURNTRANSFER => true,
7595
                    CURLOPT_HEADER => true,
7596
                    CURLOPT_FOLLOWLOCATION => true,
7597
                    CURLOPT_ENCODING => "",
7598
                    CURLOPT_AUTOREFERER => true,
7599
                    CURLOPT_CONNECTTIMEOUT => 120,
7600
                    CURLOPT_TIMEOUT => 120,
7601
                    CURLOPT_MAXREDIRS => 10,
7602
                ];
7603
7604
                $proxySettings = api_get_setting('platform.proxy_settings', true);
7605
                if (!empty($proxySettings) &&
7606
                    isset($proxySettings['curl_setopt_array'])
7607
                ) {
7608
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
7609
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
7610
                }
7611
7612
                curl_setopt_array($ch, $options);
7613
                $response = curl_exec($ch);
7614
                $httpCode = curl_getinfo($ch);
7615
                $headers = substr($response, 0, $httpCode['header_size']);
7616
7617
                $error = false;
7618
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
7619
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
7620
                ) {
7621
                    $error = true;
7622
                }
7623
7624
                if ($error) {
7625
                    Session::write('x_frame_source', $src);
7626
                    $src = 'blank.php?error=x_frames_options';
7627
                }
7628
            }
7629
        }
7630
7631
        return $src;
7632
    }
7633
7634
    /**
7635
     * Check if this LP has a created forum in the basis course.
7636
     *
7637
     * @deprecated
7638
     *
7639
     * @return bool
7640
     */
7641
    public function lpHasForum()
7642
    {
7643
        $forumTable = Database::get_course_table(TABLE_FORUM);
7644
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
7645
7646
        $fakeFrom = "
7647
            $forumTable f
7648
            INNER JOIN $itemProperty ip
7649
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
7650
        ";
7651
7652
        $resultData = Database::select(
7653
            'COUNT(f.iid) AS qty',
7654
            $fakeFrom,
7655
            [
7656
                'where' => [
7657
                    'ip.visibility != ? AND ' => 2,
7658
                    'ip.tool = ? AND ' => TOOL_FORUM,
7659
                    'f.c_id = ? AND ' => intval($this->course_int_id),
7660
                    'f.lp_id = ?' => intval($this->lp_id),
7661
                ],
7662
            ],
7663
            'first'
7664
        );
7665
7666
        return $resultData['qty'] > 0;
7667
    }
7668
7669
    /**
7670
     * Get the forum for this learning path.
7671
     *
7672
     * @param int $sessionId
7673
     *
7674
     * @return array
7675
     */
7676
    public function getForum($sessionId = 0)
7677
    {
7678
        $repo = Container::getForumRepository();
7679
7680
        $course = api_get_course_entity();
7681
        $session = api_get_session_entity($sessionId);
7682
        $qb = $repo->getResourcesByCourse($course, $session);
7683
7684
        return $qb->getQuery()->getResult();
7685
    }
7686
7687
    /**
7688
     * Get the LP Final Item form.
7689
     *
7690
     * @throws Exception
7691
     *
7692
     *
7693
     * @return string
7694
     */
7695
    public function getFinalItemForm()
7696
    {
7697
        $finalItem = $this->getFinalItem();
7698
        $title = '';
7699
7700
        if ($finalItem) {
7701
            $title = $finalItem->get_title();
7702
            $buttonText = get_lang('Save');
7703
            $content = $this->getSavedFinalItem();
7704
        } else {
7705
            $buttonText = get_lang('Add this document to the course');
7706
            $content = $this->getFinalItemTemplate();
7707
        }
7708
7709
        $editorConfig = [
7710
            'ToolbarSet' => 'LearningPathDocuments',
7711
            'Width' => '100%',
7712
            'Height' => '500',
7713
            'FullPage' => true,
7714
        ];
7715
7716
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
7717
            'type' => 'document',
7718
            'lp_id' => $this->lp_id,
7719
        ]);
7720
7721
        $form = new FormValidator('final_item', 'POST', $url);
7722
        $form->addText('title', get_lang('Title'));
7723
        $form->addButtonSave($buttonText);
7724
        $form->addHtml(
7725
            Display::return_message(
7726
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
7727
                'normal',
7728
                false
7729
            )
7730
        );
7731
7732
        $renderer = $form->defaultRenderer();
7733
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
7734
7735
        $form->addHtmlEditor(
7736
            'content_lp_certificate',
7737
            null,
7738
            true,
7739
            false,
7740
            $editorConfig
7741
        );
7742
        $form->addHidden('action', 'add_final_item');
7743
        $form->addHidden('path', Session::read('pathItem'));
7744
        $form->addHidden('previous', $this->get_last());
7745
        $form->setDefaults(
7746
            ['title' => $title, 'content_lp_certificate' => $content]
7747
        );
7748
7749
        if ($form->validate()) {
7750
            $values = $form->exportValues();
7751
            $lastItemId = $this->getLastInFirstLevel();
7752
7753
            if (!$finalItem) {
7754
                $documentId = $this->create_document(
7755
                    $this->course_info,
7756
                    $values['content_lp_certificate'],
7757
                    $values['title']
7758
                );
7759
                $this->add_item(
7760
                    0,
7761
                    $lastItemId,
7762
                    'final_item',
7763
                    $documentId,
7764
                    $values['title'],
7765
                );
7766
7767
                Display::addFlash(
7768
                    Display::return_message(get_lang('Added'))
7769
                );
7770
            } else {
7771
                $this->edit_document();
7772
            }
7773
        }
7774
7775
        return $form->returnForm();
7776
    }
7777
7778
    /**
7779
     * Check if the current lp item is first, both, last or none from lp list.
7780
     *
7781
     * @param int $currentItemId
7782
     *
7783
     * @return string
7784
     */
7785
    public function isFirstOrLastItem($currentItemId)
7786
    {
7787
        $lpItemId = [];
7788
        $typeListNotToVerify = self::getChapterTypes();
7789
7790
        // Using get_toc() function instead $this->items because returns the correct order of the items
7791
        foreach ($this->get_toc() as $item) {
7792
            if (!in_array($item['type'], $typeListNotToVerify)) {
7793
                $lpItemId[] = $item['id'];
7794
            }
7795
        }
7796
7797
        $lastLpItemIndex = count($lpItemId) - 1;
7798
        $position = array_search($currentItemId, $lpItemId);
7799
7800
        switch ($position) {
7801
            case 0:
7802
                if (!$lastLpItemIndex) {
7803
                    $answer = 'both';
7804
                    break;
7805
                }
7806
7807
                $answer = 'first';
7808
                break;
7809
            case $lastLpItemIndex:
7810
                $answer = 'last';
7811
                break;
7812
            default:
7813
                $answer = 'none';
7814
        }
7815
7816
        return $answer;
7817
    }
7818
7819
    /**
7820
     * Get whether this is a learning path with the accumulated SCORM time or not.
7821
     *
7822
     * @return int
7823
     */
7824
    public function getAccumulateScormTime()
7825
    {
7826
        return $this->accumulateScormTime;
7827
    }
7828
7829
    /**
7830
     * Returns an HTML-formatted link to a resource, to incorporate directly into
7831
     * the new learning path tool.
7832
     *
7833
     * The function is a big switch on tool type.
7834
     * In each case, we query the corresponding table for information and build the link
7835
     * with that information.
7836
     *
7837
     * @author Yannick Warnier <[email protected]> - rebranding based on
7838
     * previous work (display_addedresource_link_in_learnpath())
7839
     *
7840
     * @param int $course_id      Course code
7841
     * @param int $learningPathId The learning path ID (in lp table)
7842
     * @param int $id_in_path     the unique index in the items table
7843
     * @param int $lpViewId
7844
     *
7845
     * @return string
7846
     */
7847
    public static function rl_get_resource_link_for_learnpath(
7848
        $course_id,
7849
        $learningPathId,
7850
        $id_in_path,
7851
        $lpViewId
7852
    ) {
7853
        $session_id = api_get_session_id();
7854
7855
        $learningPathId = (int) $learningPathId;
7856
        $id_in_path = (int) $id_in_path;
7857
        $lpViewId = (int) $lpViewId;
7858
7859
        $em = Database::getManager();
7860
        $lpItemRepo = $em->getRepository(CLpItem::class);
7861
7862
        /** @var CLpItem $rowItem */
7863
        $rowItem = $lpItemRepo->findOneBy([
7864
            'lp' => $learningPathId,
7865
            'iid' => $id_in_path,
7866
        ]);
7867
        $type = $rowItem->getItemType();
7868
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
7869
        $main_dir_path = api_get_path(WEB_CODE_PATH);
7870
        $link = '';
7871
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
7872
7873
        switch ($type) {
7874
            case 'dir':
7875
                return $main_dir_path.'lp/blank.php';
7876
            case TOOL_CALENDAR_EVENT:
7877
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
7878
            case TOOL_ANNOUNCEMENT:
7879
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
7880
            case TOOL_LINK:
7881
                $linkInfo = Link::getLinkInfo($id);
7882
                if (isset($linkInfo['url'])) {
7883
                    return $linkInfo['url'];
7884
                }
7885
7886
                return '';
7887
            case TOOL_QUIZ:
7888
                if (empty($id)) {
7889
                    return '';
7890
                }
7891
7892
                // Get the lp_item_view with the highest view_count.
7893
                $learnpathItemViewResult = $em
7894
                    ->getRepository(CLpItemView::class)
7895
                    ->findBy(
7896
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
7897
                        ['viewCount' => 'DESC'],
7898
                        1
7899
                    );
7900
                /** @var CLpItemView $learnpathItemViewData */
7901
                $learnpathItemViewData = current($learnpathItemViewResult);
7902
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
7903
7904
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
7905
                    .http_build_query([
7906
                        'lp_init' => 1,
7907
                        'learnpath_item_view_id' => $learnpathItemViewId,
7908
                        'learnpath_id' => $learningPathId,
7909
                        'learnpath_item_id' => $id_in_path,
7910
                        'exerciseId' => $id,
7911
                    ]);
7912
            case TOOL_HOTPOTATOES:
7913
                return '';
7914
            case TOOL_FORUM:
7915
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
7916
            case TOOL_THREAD:
7917
                // forum post
7918
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
7919
                if (empty($id)) {
7920
                    return '';
7921
                }
7922
                $sql = "SELECT * FROM $tbl_topics WHERE iid=$id";
7923
                $result = Database::query($sql);
7924
                $row = Database::fetch_array($result);
7925
7926
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$row['forum_id'].'&lp=true&'
7927
                    .$extraParams;
7928
            case TOOL_POST:
7929
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
7930
                $result = Database::query("SELECT * FROM $tbl_post WHERE post_id=$id");
7931
                $row = Database::fetch_array($result);
7932
7933
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$row['thread_id'].'&forum='
7934
                    .$row['forum_id'].'&lp=true&'.$extraParams;
7935
            case TOOL_READOUT_TEXT:
7936
                return api_get_path(WEB_CODE_PATH).
7937
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
7938
            case TOOL_DOCUMENT:
7939
                $repo = Container::getDocumentRepository();
7940
                $document = $repo->find($rowItem->getPath());
7941
                if ($document) {
7942
                    $params = [
7943
                        'cid' => $course_id,
7944
                        'sid' => $session_id,
7945
                    ];
7946
7947
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
7948
                }
7949
7950
                return null;
7951
            case TOOL_LP_FINAL_ITEM:
7952
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
7953
                    .$extraParams;
7954
            case 'assignments':
7955
                return $main_dir_path.'work/work.php?'.$extraParams;
7956
            case TOOL_DROPBOX:
7957
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
7958
            case 'introduction_text': //DEPRECATED
7959
                return '';
7960
            case TOOL_COURSE_DESCRIPTION:
7961
                return $main_dir_path.'course_description?'.$extraParams;
7962
            case TOOL_GROUP:
7963
                return $main_dir_path.'group/group.php?'.$extraParams;
7964
            case TOOL_USER:
7965
                return $main_dir_path.'user/user.php?'.$extraParams;
7966
            case TOOL_STUDENTPUBLICATION:
7967
                if (!empty($rowItem->getPath())) {
7968
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
7969
                }
7970
7971
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
7972
        }
7973
7974
        return $link;
7975
    }
7976
7977
    /**
7978
     * Gets the name of a resource (generally used in learnpath when no name is provided).
7979
     *
7980
     * @author Yannick Warnier <[email protected]>
7981
     *
7982
     * @param string $course_code    Course code
7983
     * @param int    $learningPathId
7984
     * @param int    $id_in_path     The resource ID
7985
     *
7986
     * @return string
7987
     */
7988
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
7989
    {
7990
        $_course = api_get_course_info($course_code);
7991
        if (empty($_course)) {
7992
            return '';
7993
        }
7994
        $course_id = $_course['real_id'];
7995
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7996
        $learningPathId = (int) $learningPathId;
7997
        $id_in_path = (int) $id_in_path;
7998
7999
        $sql = "SELECT item_type, title, ref
8000
                FROM $tbl_lp_item
8001
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8002
        $res_item = Database::query($sql);
8003
8004
        if (Database::num_rows($res_item) < 1) {
8005
            return '';
8006
        }
8007
        $row_item = Database::fetch_array($res_item);
8008
        $type = strtolower($row_item['item_type']);
8009
        $id = $row_item['ref'];
8010
        $output = '';
8011
8012
        switch ($type) {
8013
            case TOOL_CALENDAR_EVENT:
8014
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8015
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8016
                $myrow = Database::fetch_array($result);
8017
                $output = $myrow['title'];
8018
                break;
8019
            case TOOL_ANNOUNCEMENT:
8020
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8021
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8022
                $myrow = Database::fetch_array($result);
8023
                $output = $myrow['title'];
8024
                break;
8025
            case TOOL_LINK:
8026
                // Doesn't take $target into account.
8027
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8028
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8029
                $myrow = Database::fetch_array($result);
8030
                $output = $myrow['title'];
8031
                break;
8032
            case TOOL_QUIZ:
8033
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8034
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8035
                $myrow = Database::fetch_array($result);
8036
                $output = $myrow['title'];
8037
                break;
8038
            case TOOL_FORUM:
8039
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8040
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8041
                $myrow = Database::fetch_array($result);
8042
                $output = $myrow['title'];
8043
                break;
8044
            case TOOL_THREAD:
8045
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8046
                // Grabbing the title of the post.
8047
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8048
                $result_title = Database::query($sql_title);
8049
                $myrow_title = Database::fetch_array($result_title);
8050
                $output = $myrow_title['title'];
8051
                break;
8052
            case TOOL_POST:
8053
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8054
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8055
                $result = Database::query($sql);
8056
                $post = Database::fetch_array($result);
8057
                $output = $post['title'];
8058
                break;
8059
            case 'dir':
8060
            case TOOL_DOCUMENT:
8061
                $title = $row_item['title'];
8062
                $output = '-';
8063
                if (!empty($title)) {
8064
                    $output = $title;
8065
                }
8066
                break;
8067
            case 'hotpotatoes':
8068
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8069
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8070
                $myrow = Database::fetch_array($result);
8071
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8072
                $last = count($pathname) - 1; // Making a correct name for the link.
8073
                $filename = $pathname[$last]; // Making a correct name for the link.
8074
                $myrow['path'] = rawurlencode($myrow['path']);
8075
                $output = $filename;
8076
                break;
8077
        }
8078
8079
        return stripslashes($output);
8080
    }
8081
8082
    /**
8083
     * Get the parent names for the current item.
8084
     *
8085
     * @param int $newItemId Optional. The item ID
8086
     */
8087
    public function getCurrentItemParentNames($newItemId = 0): array
8088
    {
8089
        $newItemId = $newItemId ?: $this->get_current_item_id();
8090
        $return = [];
8091
        $item = $this->getItem($newItemId);
8092
8093
        $parent = null;
8094
        if ($item) {
8095
            $parent = $this->getItem($item->get_parent());
8096
        }
8097
8098
        while ($parent) {
8099
            $return[] = $parent->get_title();
8100
            $parent = $this->getItem($parent->get_parent());
8101
        }
8102
8103
        return array_reverse($return);
8104
    }
8105
8106
    /**
8107
     * Reads and process "lp_subscription_settings" setting.
8108
     *
8109
     * @return array
8110
     */
8111
    public static function getSubscriptionSettings()
8112
    {
8113
        $subscriptionSettings = api_get_setting('lp.lp_subscription_settings', true);
8114
        if (!is_array($subscriptionSettings)) {
8115
            // By default, allow both settings
8116
            $subscriptionSettings = [
8117
                'allow_add_users_to_lp' => true,
8118
                'allow_add_users_to_lp_category' => true,
8119
            ];
8120
        } else {
8121
            $subscriptionSettings = $subscriptionSettings['options'];
8122
        }
8123
8124
        return $subscriptionSettings;
8125
    }
8126
8127
    /**
8128
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8129
     */
8130
    public function exportToCourseBuildFormat()
8131
    {
8132
        if (!api_is_allowed_to_edit()) {
8133
            return false;
8134
        }
8135
8136
        $courseBuilder = new CourseBuilder();
8137
        $itemList = [];
8138
        /** @var learnpathItem $item */
8139
        foreach ($this->items as $item) {
8140
            $itemList[$item->get_type()][] = $item->get_path();
8141
        }
8142
8143
        if (empty($itemList)) {
8144
            return false;
8145
        }
8146
8147
        if (isset($itemList['document'])) {
8148
            // Get parents
8149
            foreach ($itemList['document'] as $documentId) {
8150
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8151
                if (!empty($documentInfo['parents'])) {
8152
                    foreach ($documentInfo['parents'] as $parentInfo) {
8153
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8154
                            continue;
8155
                        }
8156
                        $itemList['document'][] = $parentInfo['iid'];
8157
                    }
8158
                }
8159
            }
8160
8161
            $courseInfo = api_get_course_info();
8162
            foreach ($itemList['document'] as $documentId) {
8163
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8164
                $items = DocumentManager::get_resources_from_source_html(
8165
                    $documentInfo['absolute_path'],
8166
                    true,
8167
                    TOOL_DOCUMENT
8168
                );
8169
8170
                if (!empty($items)) {
8171
                    foreach ($items as $item) {
8172
                        // Get information about source url
8173
                        $url = $item[0]; // url
8174
                        $scope = $item[1]; // scope (local, remote)
8175
                        $type = $item[2]; // type (rel, abs, url)
8176
8177
                        $origParseUrl = parse_url($url);
8178
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8179
8180
                        if ('local' === $scope) {
8181
                            if ('abs' === $type || 'rel' === $type) {
8182
                                $documentFile = strstr($realOrigPath, 'document');
8183
                                if (false !== strpos($realOrigPath, $documentFile)) {
8184
                                    $documentFile = str_replace('document', '', $documentFile);
8185
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8186
                                    // Document found! Add it to the list
8187
                                    if ($itemDocumentId) {
8188
                                        $itemList['document'][] = $itemDocumentId;
8189
                                    }
8190
                                }
8191
                            }
8192
                        }
8193
                    }
8194
                }
8195
            }
8196
8197
            $courseBuilder->build_documents(
8198
                api_get_session_id(),
8199
                $this->get_course_int_id(),
8200
                true,
8201
                $itemList['document']
8202
            );
8203
        }
8204
8205
        if (isset($itemList['quiz'])) {
8206
            $courseBuilder->build_quizzes(
8207
                api_get_session_id(),
8208
                $this->get_course_int_id(),
8209
                true,
8210
                $itemList['quiz']
8211
            );
8212
        }
8213
8214
        if (!empty($itemList['thread'])) {
8215
            $threadList = [];
8216
            $repo = Container::getForumThreadRepository();
8217
            foreach ($itemList['thread'] as $threadId) {
8218
                /** @var CForumThread $thread */
8219
                $thread = $repo->find($threadId);
8220
                if ($thread) {
8221
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8222
                    $threadList[] = $thread->getIid();
8223
                }
8224
            }
8225
8226
            if (!empty($threadList)) {
8227
                $courseBuilder->build_forum_topics(
8228
                    api_get_session_id(),
8229
                    $this->get_course_int_id(),
8230
                    null,
8231
                    $threadList
8232
                );
8233
            }
8234
        }
8235
8236
        $forumCategoryList = [];
8237
        if (isset($itemList['forum'])) {
8238
            foreach ($itemList['forum'] as $forumId) {
8239
                $forumInfo = get_forums($forumId);
8240
                $forumCategoryList[] = $forumInfo['forum_category'];
8241
            }
8242
        }
8243
8244
        if (!empty($forumCategoryList)) {
8245
            $courseBuilder->build_forum_category(
8246
                api_get_session_id(),
8247
                $this->get_course_int_id(),
8248
                true,
8249
                $forumCategoryList
8250
            );
8251
        }
8252
8253
        if (!empty($itemList['forum'])) {
8254
            $courseBuilder->build_forums(
8255
                api_get_session_id(),
8256
                $this->get_course_int_id(),
8257
                true,
8258
                $itemList['forum']
8259
            );
8260
        }
8261
8262
        if (isset($itemList['link'])) {
8263
            $courseBuilder->build_links(
8264
                api_get_session_id(),
8265
                $this->get_course_int_id(),
8266
                true,
8267
                $itemList['link']
8268
            );
8269
        }
8270
8271
        if (!empty($itemList['student_publication'])) {
8272
            $courseBuilder->build_works(
8273
                api_get_session_id(),
8274
                $this->get_course_int_id(),
8275
                true,
8276
                $itemList['student_publication']
8277
            );
8278
        }
8279
8280
        $courseBuilder->build_learnpaths(
8281
            api_get_session_id(),
8282
            $this->get_course_int_id(),
8283
            true,
8284
            [$this->get_id()],
8285
            false
8286
        );
8287
8288
        $courseBuilder->restoreDocumentsFromList();
8289
8290
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
8291
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
8292
        $result = DocumentManager::file_send_for_download(
8293
            $zipPath,
8294
            true,
8295
            $this->get_name().'.zip'
8296
        );
8297
8298
        if ($result) {
8299
            api_not_allowed();
8300
        }
8301
8302
        return true;
8303
    }
8304
8305
    /**
8306
     * Get whether this is a learning path with the accumulated work time or not.
8307
     *
8308
     * @return int
8309
     */
8310
    public function getAccumulateWorkTime()
8311
    {
8312
        return (int) $this->accumulateWorkTime;
8313
    }
8314
8315
    /**
8316
     * Get whether this is a learning path with the accumulated work time or not.
8317
     *
8318
     * @return int
8319
     */
8320
    public function getAccumulateWorkTimeTotalCourse()
8321
    {
8322
        $table = Database::get_course_table(TABLE_LP_MAIN);
8323
        $sql = "SELECT SUM(accumulate_work_time) AS total
8324
                FROM $table
8325
                WHERE c_id = ".$this->course_int_id;
8326
        $result = Database::query($sql);
8327
        $row = Database::fetch_array($result);
8328
8329
        return (int) $row['total'];
8330
    }
8331
8332
    /**
8333
     * @param int $lpId
8334
     * @param int $courseId
8335
     *
8336
     * @return mixed
8337
     */
8338
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
8339
    {
8340
        $lpId = (int) $lpId;
8341
        $table = Database::get_course_table(TABLE_LP_MAIN);
8342
        $sql = "SELECT accumulate_work_time
8343
                FROM $table
8344
                WHERE iid = $lpId";
8345
        $result = Database::query($sql);
8346
        $row = Database::fetch_array($result);
8347
8348
        return $row['accumulate_work_time'];
8349
    }
8350
8351
    /**
8352
     * @param int $courseId
8353
     *
8354
     * @return int
8355
     */
8356
    public static function getAccumulateWorkTimeTotal($courseId)
8357
    {
8358
        $table = Database::get_course_table(TABLE_LP_MAIN);
8359
        $courseId = (int) $courseId;
8360
        $sql = "SELECT SUM(accumulate_work_time) AS total
8361
                FROM $table
8362
                WHERE c_id = $courseId";
8363
        $result = Database::query($sql);
8364
        $row = Database::fetch_array($result);
8365
8366
        return (int) $row['total'];
8367
    }
8368
8369
    /**
8370
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
8371
     * and put the images in.
8372
     *
8373
     * @return array
8374
     */
8375
    public static function getIconSelect()
8376
    {
8377
        $theme = api_get_visual_theme();
8378
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
8379
        $icons = ['' => get_lang('Please select an option')];
8380
8381
        if (is_dir($path)) {
8382
            $finder = new Finder();
8383
            $finder->files()->in($path);
8384
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
8385
            /** @var SplFileInfo $file */
8386
            foreach ($finder as $file) {
8387
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
8388
                    $icons[$file->getFilename()] = $file->getFilename();
8389
                }
8390
            }
8391
        }
8392
8393
        return $icons;
8394
    }
8395
8396
    /**
8397
     * @param int $lpId
8398
     *
8399
     * @return string
8400
     */
8401
    public static function getSelectedIcon($lpId)
8402
    {
8403
        $extraFieldValue = new ExtraFieldValue('lp');
8404
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
8405
        $icon = '';
8406
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
8407
            $icon = $lpIcon['value'];
8408
        }
8409
8410
        return $icon;
8411
    }
8412
8413
    /**
8414
     * @param int $lpId
8415
     *
8416
     * @return string
8417
     */
8418
    public static function getSelectedIconHtml($lpId)
8419
    {
8420
        $icon = self::getSelectedIcon($lpId);
8421
8422
        if (empty($icon)) {
8423
            return '';
8424
        }
8425
8426
        $theme = api_get_visual_theme();
8427
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
8428
8429
        return Display::img($path);
8430
    }
8431
8432
    /**
8433
     * @param string $value
8434
     *
8435
     * @return string
8436
     */
8437
    public function cleanItemTitle($value)
8438
    {
8439
        $value = Security::remove_XSS(strip_tags($value));
8440
8441
        return $value;
8442
    }
8443
8444
    public function setItemTitle(FormValidator $form)
8445
    {
8446
        if ('true' === api_get_setting('editor.save_titles_as_html')) {
8447
            $form->addHtmlEditor(
8448
                'title',
8449
                get_lang('Title'),
8450
                true,
8451
                false,
8452
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
8453
            );
8454
        } else {
8455
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
8456
            $form->applyFilter('title', 'trim');
8457
            $form->applyFilter('title', 'html_filter');
8458
        }
8459
    }
8460
8461
    /**
8462
     * @return array
8463
     */
8464
    public function getItemsForForm($addParentCondition = false)
8465
    {
8466
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8467
8468
        $sql = "SELECT * FROM $tbl_lp_item
8469
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
8470
8471
        if ($addParentCondition) {
8472
            $sql .= ' AND parent_item_id IS NULL ';
8473
        }
8474
        $sql .= ' ORDER BY display_order ASC';
8475
8476
        $result = Database::query($sql);
8477
        $arrLP = [];
8478
        while ($row = Database::fetch_array($result)) {
8479
            $arrLP[] = [
8480
                'iid' => $row['iid'],
8481
                'id' => $row['iid'],
8482
                'item_type' => $row['item_type'],
8483
                'title' => $this->cleanItemTitle($row['title']),
8484
                'title_raw' => $row['title'],
8485
                'path' => $row['path'],
8486
                'description' => Security::remove_XSS($row['description']),
8487
                'parent_item_id' => $row['parent_item_id'],
8488
                'previous_item_id' => $row['previous_item_id'],
8489
                'next_item_id' => $row['next_item_id'],
8490
                'display_order' => $row['display_order'],
8491
                'max_score' => $row['max_score'],
8492
                'min_score' => $row['min_score'],
8493
                'mastery_score' => $row['mastery_score'],
8494
                'prerequisite' => $row['prerequisite'],
8495
                'max_time_allowed' => $row['max_time_allowed'],
8496
                'prerequisite_min_score' => $row['prerequisite_min_score'],
8497
                'prerequisite_max_score' => $row['prerequisite_max_score'],
8498
            ];
8499
        }
8500
8501
        return $arrLP;
8502
    }
8503
8504
    /**
8505
     * Gets whether this SCORM learning path has been marked to use the score
8506
     * as progress. Takes into account whether the learnpath matches (SCORM
8507
     * content + less than 2 items).
8508
     *
8509
     * @return bool True if the score should be used as progress, false otherwise
8510
     */
8511
    public function getUseScoreAsProgress()
8512
    {
8513
        // If not a SCORM, we don't care about the setting
8514
        if (2 != $this->get_type()) {
8515
            return false;
8516
        }
8517
        // If more than one step in the SCORM, we don't care about the setting
8518
        if ($this->get_total_items_count() > 1) {
8519
            return false;
8520
        }
8521
        $extraFieldValue = new ExtraFieldValue('lp');
8522
        $doUseScore = false;
8523
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
8524
            $this->get_id(),
8525
            'use_score_as_progress'
8526
        );
8527
        if (!empty($useScore) && isset($useScore['value'])) {
8528
            $doUseScore = $useScore['value'];
8529
        }
8530
8531
        return $doUseScore;
8532
    }
8533
8534
    /**
8535
     * Get the user identifier (user_id or username
8536
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
8537
     *
8538
     * @return string User ID or username, depending on configuration setting
8539
     */
8540
    public static function getUserIdentifierForExternalServices()
8541
    {
8542
        $scormApiExtraFieldUseStudentId = api_get_setting('lp.scorm_api_extrafield_to_use_as_student_id');
8543
        $extraFieldValue = new ExtraFieldValue('user');
8544
        $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
8545
            api_get_user_id(),
8546
            $scormApiExtraFieldUseStudentId
8547
        );
8548
        if (is_array($extrafield) && isset($extrafield['value'])) {
8549
            return $extrafield['value'];
8550
        } else {
8551
            if ('true' === $scormApiExtraFieldUseStudentId) {
8552
                return api_get_user_info(api_get_user_id())['username'];
8553
            } else {
8554
                return api_get_user_id();
8555
            }
8556
        }
8557
    }
8558
8559
    /**
8560
     * Save the new order for learning path items.
8561
     *
8562
     * @param array $orderList A associative array with id and parent_id keys.
8563
     */
8564
    public static function sortItemByOrderList(CLpItem $rootItem, array $orderList = [], $flush = true, $lpItemRepo = null, $em = null)
8565
    {
8566
        if (empty($orderList)) {
8567
            return true;
8568
        }
8569
        if (!isset($lpItemRepo)) {
8570
            $lpItemRepo = Container::getLpItemRepository();
8571
        }
8572
        if (!isset($em)) {
8573
            $em = Database::getManager();
8574
        }
8575
        $counter = 2;
8576
        $rootItem->setDisplayOrder(1);
8577
        $rootItem->setPreviousItemId(null);
8578
        $em->persist($rootItem);
8579
        if ($flush) {
8580
            $em->flush();
8581
        }
8582
8583
        foreach ($orderList as $item) {
8584
            $itemId = $item->id ?? 0;
8585
            if (empty($itemId)) {
8586
                continue;
8587
            }
8588
            $parentId = $item->parent_id ?? 0;
8589
            $parent = $rootItem;
8590
            if (!empty($parentId)) {
8591
                $parentExists = $lpItemRepo->find($parentId);
8592
                if (null !== $parentExists) {
8593
                    $parent = $parentExists;
8594
                }
8595
            }
8596
8597
            /** @var CLpItem $itemEntity */
8598
            $itemEntity = $lpItemRepo->find($itemId);
8599
            $itemEntity->setParent($parent);
8600
            $itemEntity->setPreviousItemId(null);
8601
            $itemEntity->setNextItemId(null);
8602
            $itemEntity->setDisplayOrder($counter);
8603
8604
            $em->persist($itemEntity);
8605
            if ($flush) {
8606
                $em->flush();
8607
            }
8608
            $counter++;
8609
        }
8610
8611
        $lpItemRepo->recoverNode($rootItem, 'displayOrder');
8612
        $em->persist($rootItem);
8613
        if ($flush) {
8614
            $em->flush();
8615
        }
8616
8617
        return true;
8618
    }
8619
8620
    public static function move(int $lpId, string $direction)
8621
    {
8622
        $em = Database::getManager();
8623
        /** @var CLp $lp */
8624
        $lp = Container::getLpRepository()->find($lpId);
8625
        if ($lp) {
8626
            $course = api_get_course_entity();
8627
            $session = api_get_session_entity();
8628
            $group = api_get_group_entity();
8629
8630
            $link = $lp->getResourceNode()->getResourceLinkByContext($course, $session, $group);
8631
8632
            if ($link) {
8633
                if ('down' === $direction) {
8634
                    $link->setDisplayOrder(
8635
                        $link->getDisplayOrder() + 1
8636
                    );
8637
                }
8638
                if ('up' === $direction) {
8639
                    $link->setDisplayOrder(
8640
                        $link->getDisplayOrder() - 1
8641
                    );
8642
                }
8643
8644
                $em->flush();
8645
            }
8646
        }
8647
    }
8648
8649
    /**
8650
     * Get the depth level of LP item.
8651
     *
8652
     * @param array $items
8653
     * @param int   $currentItemId
8654
     *
8655
     * @return int
8656
     */
8657
    private static function get_level_for_item($items, $currentItemId)
8658
    {
8659
        $parentItemId = 0;
8660
        if (isset($items[$currentItemId])) {
8661
            $parentItemId = $items[$currentItemId]->parent;
8662
        }
8663
8664
        if (0 == $parentItemId) {
8665
            return 0;
8666
        }
8667
8668
        return self::get_level_for_item($items, $parentItemId) + 1;
8669
    }
8670
8671
    /**
8672
     * Generate the link for a learnpath category as course tool.
8673
     *
8674
     * @param int $categoryId
8675
     *
8676
     * @return string
8677
     */
8678
    private static function getCategoryLinkForTool($categoryId)
8679
    {
8680
        $categoryId = (int) $categoryId;
8681
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
8682
            .http_build_query(
8683
                [
8684
                    'action' => 'view_category',
8685
                    'id' => $categoryId,
8686
                ]
8687
            );
8688
    }
8689
8690
    /**
8691
     * Check and obtain the lp final item if exist.
8692
     *
8693
     * @return learnpathItem
8694
     */
8695
    private function getFinalItem()
8696
    {
8697
        if (empty($this->items)) {
8698
            return null;
8699
        }
8700
8701
        foreach ($this->items as $item) {
8702
            if ('final_item' !== $item->type) {
8703
                continue;
8704
            }
8705
8706
            return $item;
8707
        }
8708
    }
8709
8710
    /**
8711
     * Get the LP Final Item Template.
8712
     *
8713
     * @return string
8714
     */
8715
    private function getFinalItemTemplate()
8716
    {
8717
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
8718
    }
8719
8720
    /**
8721
     * Get the LP Final Item Url.
8722
     *
8723
     * @return string
8724
     */
8725
    private function getSavedFinalItem()
8726
    {
8727
        $finalItem = $this->getFinalItem();
8728
8729
        $repo = Container::getDocumentRepository();
8730
        /** @var CDocument $document */
8731
        $document = $repo->find($finalItem->path);
8732
8733
        if ($document && $document->getResourceNode()->hasResourceFile()) {
8734
            return $repo->getResourceFileContent($document);
8735
        }
8736
8737
        return '';
8738
    }
8739
}
8740