Passed
Push — master ( 6c23c3...c1688f )
by Julito
09:45
created

learnpath::has_audio()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 0
dl 0
loc 11
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\User;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
12
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
13
use Chamilo\CourseBundle\Entity\CDocument;
14
use Chamilo\CourseBundle\Entity\CForumCategory;
15
use Chamilo\CourseBundle\Entity\CForumThread;
16
use Chamilo\CourseBundle\Entity\CLink;
17
use Chamilo\CourseBundle\Entity\CLp;
18
use Chamilo\CourseBundle\Entity\CLpCategory;
19
use Chamilo\CourseBundle\Entity\CLpItem;
20
use Chamilo\CourseBundle\Entity\CLpItemView;
21
use Chamilo\CourseBundle\Entity\CQuiz;
22
use Chamilo\CourseBundle\Entity\CStudentPublication;
23
use Chamilo\CourseBundle\Entity\CTool;
24
use ChamiloSession as Session;
25
use Doctrine\Common\Collections\Criteria;
26
use PhpZip\ZipFile;
27
use Symfony\Component\Finder\Finder;
28
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29
30
/**
31
 * Class learnpath
32
 * This class defines the parent attributes and methods for Chamilo learnpaths
33
 * and SCORM learnpaths. It is used by the scorm class.
34
 *
35
 * @todo decouple class
36
 *
37
 * @author  Yannick Warnier <[email protected]>
38
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
39
 */
40
class learnpath
41
{
42
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
43
    public const STATUS_CSS_CLASS_NAME = [
44
        'not attempted' => 'scorm_not_attempted',
45
        'incomplete' => 'scorm_not_attempted',
46
        'failed' => 'scorm_failed',
47
        'completed' => 'scorm_completed',
48
        'passed' => 'scorm_completed',
49
        'succeeded' => 'scorm_completed',
50
        'browsed' => 'scorm_completed',
51
    ];
52
53
    public $attempt = 0; // The number for the current ID view.
54
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
55
    public $current; // Id of the current item the user is viewing.
56
    public $current_score; // The score of the current item.
57
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
58
    public $current_time_stop; // The time the user closed this resource.
59
    public $default_status = 'not attempted';
60
    public $encoding = 'UTF-8';
61
    public $error = '';
62
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
63
    public $index; // The index of the active learnpath_item in $ordered_items array.
64
    /** @var learnpathItem[] */
65
    public $items = [];
66
    public $last; // item_id of last item viewed in the learning path.
67
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
68
    public $license; // Which license this course has been given - not used yet on 20060522.
69
    public $lp_id; // DB iid for this learnpath.
70
    public $lp_view_id; // DB ID for lp_view
71
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
72
    public $message = '';
73
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
74
    public $name; // Learnpath name (they generally have one).
75
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
76
    public $path = ''; // Path inside the scorm directory (if scorm).
77
    public $theme; // The current theme of the learning path.
78
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
79
    public $accumulateWorkTime; // The min time of learnpath
80
81
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
82
    public $prevent_reinit = 1;
83
84
    // Describes the mode of progress bar display.
85
    public $seriousgame_mode = 0;
86
    public $progress_bar_mode = '%';
87
88
    // Percentage progress as saved in the db.
89
    public $progress_db = 0;
90
    public $proximity; // Wether the content is distant or local or unknown.
91
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
92
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
93
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
94
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
95
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
96
    public $user_id; //ID of the user that is viewing/using the course
97
    public $update_queue = [];
98
    public $scorm_debug = 0;
99
    public $arrMenu = []; // Array for the menu items.
100
    public $debug = 0; // Logging level.
101
    public $lp_session_id = 0;
102
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
103
    public $prerequisite = 0;
104
    public $use_max_score = 1; // 1 or 0
105
    public $subscribeUsers = 0; // Subscribe users or not
106
    public $created_on = '';
107
    public $modified_on = '';
108
    public $publicated_on = '';
109
    public $expired_on = '';
110
    public $ref;
111
    public $course_int_id;
112
    public $course_info;
113
    public $categoryId;
114
    public $scormUrl;
115
    public $entity;
116
117
    public function __construct(CLp $entity = null, $course_info, $user_id)
118
    {
119
        $debug = $this->debug;
120
        $user_id = (int) $user_id;
121
        $this->encoding = api_get_system_encoding();
122
        $lp_id = 0;
123
        if (null !== $entity) {
124
            $lp_id = (int) $entity->getIid();
125
        }
126
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
127
        $course_id = (int) $course_info['real_id'];
128
        $this->course_info = $course_info;
129
        $this->set_course_int_id($course_id);
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            $this->entity = $entity;
134
            $this->lp_id = $lp_id;
135
            $this->type = $entity->getLpType();
136
            $this->name = stripslashes($entity->getName());
137
            $this->proximity = $entity->getContentLocal();
138
            $this->theme = $entity->getTheme();
139
            $this->maker = $entity->getContentLocal();
140
            $this->prevent_reinit = $entity->getPreventReinit();
141
            $this->seriousgame_mode = $entity->getSeriousgameMode();
142
            $this->license = $entity->getContentLicense();
143
            $this->scorm_debug = $entity->getDebug();
144
            $this->js_lib = $entity->getJsLib();
145
            $this->path = $entity->getPath();
146
            $this->author = $entity->getAuthor();
147
            $this->hide_toc_frame = $entity->getHideTocFrame();
148
            //$this->lp_session_id = $entity->getSessionId();
149
            $this->use_max_score = $entity->getUseMaxScore();
150
            $this->subscribeUsers = $entity->getSubscribeUsers();
151
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
152
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
153
            $this->ref = $entity->getRef();
154
            $this->categoryId = 0;
155
            if ($entity->getCategory()) {
156
                $this->categoryId = $entity->getCategory()->getIid();
157
            }
158
159
            if ($entity->hasAsset()) {
160
                $asset = $entity->getAsset();
161
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/';
162
            }
163
164
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
165
166
            if (!empty($entity->getPublicatedOn())) {
167
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
168
            }
169
170
            if (!empty($entity->getExpiredOn())) {
171
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
172
            }
173
            if (2 == $this->type) {
174
                if (1 == $entity->getForceCommit()) {
175
                    $this->force_commit = true;
176
                }
177
            }
178
            $this->mode = $entity->getDefaultViewMod();
179
180
            // Check user ID.
181
            if (empty($user_id)) {
182
                $this->error = 'User ID is empty';
183
            } else {
184
                $this->user_id = $user_id;
185
            }
186
187
            // End of variables checking.
188
            $session_id = api_get_session_id();
189
            //  Get the session condition for learning paths of the base + session.
190
            $session = api_get_session_condition($session_id);
191
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
192
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
193
194
            // Selecting by view_count descending allows to get the highest view_count first.
195
            $sql = "SELECT * FROM $lp_table
196
                    WHERE
197
                        c_id = $course_id AND
198
                        lp_id = $lp_id AND
199
                        user_id = $user_id
200
                        $session
201
                    ORDER BY view_count DESC";
202
            $res = Database::query($sql);
203
204
            if (Database::num_rows($res) > 0) {
205
                $row = Database::fetch_array($res);
206
                $this->attempt = $row['view_count'];
207
                $this->lp_view_id = $row['iid'];
208
                $this->last_item_seen = $row['last_item'];
209
                $this->progress_db = $row['progress'];
210
                $this->lp_view_session_id = $row['session_id'];
211
            } elseif (!api_is_invitee()) {
212
                $this->attempt = 1;
213
                $params = [
214
                    'c_id' => $course_id,
215
                    'lp_id' => $lp_id,
216
                    'user_id' => $user_id,
217
                    'view_count' => 1,
218
                    //'session_id' => $session_id,
219
                    'last_item' => 0,
220
                ];
221
                if (!empty($session_id)) {
222
                    $params['session_id'] = $session_id;
223
                }
224
                $this->last_item_seen = 0;
225
                $this->lp_view_session_id = $session_id;
226
                $this->lp_view_id = Database::insert($lp_table, $params);
227
            }
228
229
            $criteria = Criteria::create()
230
                ->orderBy(
231
                    [
232
                        'parent' => Criteria::ASC,
233
                        'displayOrder' => Criteria::ASC,
234
                    ]
235
                );
236
            $items = $this->entity->getItems()->matching($criteria);
237
            $lp_item_id_list = [];
238
            foreach ($items as $item) {
239
                $itemId = $item->getIid();
240
                $lp_item_id_list[] = $itemId;
241
                switch ($this->type) {
242
                    case 3: //aicc
243
                        $oItem = new aiccItem('db', $itemId, $course_id);
244
                        if (is_object($oItem)) {
245
                            $my_item_id = $oItem->get_id();
246
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
247
                            $oItem->set_prevent_reinit($this->prevent_reinit);
248
                            // Don't use reference here as the next loop will make the pointed object change.
249
                            $this->items[$my_item_id] = $oItem;
250
                            $this->refs_list[$oItem->ref] = $my_item_id;
251
                        }
252
                        break;
253
                    case 2:
254
                        $oItem = new scormItem('db', $itemId, $course_id);
255
                        if (is_object($oItem)) {
256
                            $my_item_id = $oItem->get_id();
257
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
258
                            $oItem->set_prevent_reinit($this->prevent_reinit);
259
                            // Don't use reference here as the next loop will make the pointed object change.
260
                            $this->items[$my_item_id] = $oItem;
261
                            $this->refs_list[$oItem->ref] = $my_item_id;
262
                        }
263
                        break;
264
                    case 1:
265
                    default:
266
                        $oItem = new learnpathItem($itemId, $course_id, $item);
267
                        if (is_object($oItem)) {
268
                            $my_item_id = $oItem->get_id();
269
                            // Moved down to when we are sure the item_view exists.
270
                            //$oItem->set_lp_view($this->lp_view_id);
271
                            $oItem->set_prevent_reinit($this->prevent_reinit);
272
                            // Don't use reference here as the next loop will make the pointed object change.
273
                            $this->items[$my_item_id] = $oItem;
274
                            $this->refs_list[$my_item_id] = $my_item_id;
275
                        }
276
                        break;
277
                }
278
279
                // Setting the object level with variable $this->items[$i][parent]
280
                foreach ($this->items as $itemLPObject) {
281
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
282
                    $itemLPObject->level = $level;
283
                }
284
285
                // Setting the view in the item object.
286
                if (is_object($this->items[$itemId])) {
287
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
288
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
289
                        $this->items[$itemId]->current_start_time = 0;
290
                        $this->items[$itemId]->current_stop_time = 0;
291
                    }
292
                }
293
            }
294
295
            if (!empty($lp_item_id_list)) {
296
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
297
                if (!empty($lp_item_id_list_to_string)) {
298
                    // Get last viewing vars.
299
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
300
                    // This query should only return one or zero result.
301
                    $sql = "SELECT lp_item_id, status
302
                            FROM $itemViewTable
303
                            WHERE
304
                                lp_view_id = ".$this->get_view_id()." AND
305
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
306
                            ORDER BY view_count DESC ";
307
                    $status_list = [];
308
                    $res = Database::query($sql);
309
                    while ($row = Database:: fetch_array($res)) {
310
                        $status_list[$row['lp_item_id']] = $row['status'];
311
                    }
312
313
                    foreach ($lp_item_id_list as $item_id) {
314
                        if (isset($status_list[$item_id])) {
315
                            $status = $status_list[$item_id];
316
                            if (is_object($this->items[$item_id])) {
317
                                $this->items[$item_id]->set_status($status);
318
                                if (empty($status)) {
319
                                    $this->items[$item_id]->set_status(
320
                                        $this->default_status
321
                                    );
322
                                }
323
                            }
324
                        } else {
325
                            if (!api_is_invitee()) {
326
                                if (is_object($this->items[$item_id])) {
327
                                    $this->items[$item_id]->set_status(
328
                                        $this->default_status
329
                                    );
330
                                }
331
332
                                if (!empty($this->lp_view_id)) {
333
                                    // Add that row to the lp_item_view table so that
334
                                    // we have something to show in the stats page.
335
                                    $params = [
336
                                        'lp_item_id' => $item_id,
337
                                        'lp_view_id' => $this->lp_view_id,
338
                                        'view_count' => 1,
339
                                        'status' => 'not attempted',
340
                                        'start_time' => time(),
341
                                        'total_time' => 0,
342
                                        'score' => 0,
343
                                    ];
344
                                    Database::insert($itemViewTable, $params);
345
346
                                    $this->items[$item_id]->set_lp_view(
347
                                        $this->lp_view_id,
348
                                        $course_id
349
                                    );
350
                                }
351
                            }
352
                        }
353
                    }
354
                }
355
            }
356
357
            $this->ordered_items = self::get_flat_ordered_items_list($this->entity, null);
358
            $this->max_ordered_items = 0;
359
            foreach ($this->ordered_items as $index => $dummy) {
360
                if ($index > $this->max_ordered_items && !empty($dummy)) {
361
                    $this->max_ordered_items = $index;
362
                }
363
            }
364
            // TODO: Define the current item better.
365
            $this->first();
366
            if ($debug) {
367
                error_log('lp_view_session_id '.$this->lp_view_session_id);
368
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
369
            }
370
        }
371
    }
372
373
    /**
374
     * @return int
375
     */
376
    public function get_course_int_id()
377
    {
378
        return $this->course_int_id ?? api_get_course_int_id();
379
    }
380
381
    /**
382
     * @param $course_id
383
     *
384
     * @return int
385
     */
386
    public function set_course_int_id($course_id)
387
    {
388
        return $this->course_int_id = (int) $course_id;
389
    }
390
391
    /**
392
     * Function rewritten based on old_add_item() from Yannick Warnier.
393
     * Due the fact that users can decide where the item should come, I had to overlook this function and
394
     * I found it better to rewrite it. Old function is still available.
395
     * Added also the possibility to add a description.
396
     *
397
     * @param CLpItem $parent
398
     * @param int     $previousId
399
     * @param string  $type
400
     * @param int     $id resource ID (ref)
401
     * @param string  $title
402
     * @param string  $description
403
     * @param int     $prerequisites
404
     * @param int     $max_time_allowed
405
     * @param int     $userId
406
     *
407
     * @return int
408
     */
409
    public function add_item(
410
        ?CLpItem $parent,
411
        $previousId,
412
        $type,
413
        $id,
414
        $title,
415
        $description = '',
416
        $prerequisites = 0,
417
        $max_time_allowed = 0
418
    ) {
419
        $type = empty($type) ? 'dir' : $type;
420
        $course_id = $this->course_info['real_id'];
421
        if (empty($course_id)) {
422
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
423
            $this->course_info = api_get_course_info($this->cc);
424
            $course_id = $this->course_info['real_id'];
425
        }
426
        $id = (int) $id;
427
        $max_time_allowed = (int) $max_time_allowed;
428
        if (empty($max_time_allowed)) {
429
            $max_time_allowed = 0;
430
        }
431
        $max_score = 100;
432
        if ('quiz' === $type && $id) {
433
            /*$sql = 'SELECT SUM(ponderation)
434
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
435
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
436
                    ON q.iid = quiz_rel_question.question_id
437
                    WHERE
438
                        quiz_rel_question.quiz_id = '.$id;
439
            $rsQuiz = Database::query($sql);
440
            $max_score = Database::result($rsQuiz, 0, 0);*/
441
442
            // Disabling the exercise if we add it inside a LP
443
            $exercise = new Exercise($course_id);
444
            $exercise->read($id);
445
            $max_score = $exercise->get_max_score();
446
447
            $exercise->disable();
448
            $exercise->save();
449
            $title = $exercise->get_formated_title();
450
        }
451
452
        $lpItem = new CLpItem();
453
        $lpItem
454
            ->setTitle($title)
455
            ->setDescription($description)
456
            ->setPath($id)
457
            ->setLp($this->entity)
458
            ->setItemType($type)
459
            ->setMaxScore($max_score)
460
            ->setMaxTimeAllowed($max_time_allowed)
461
            ->setPrerequisite($prerequisites)
462
            //->setDisplayOrder($display_order + 1)
463
            //->setNextItemId((int) $next)
464
            //->setPreviousItemId($previous)
465
        ;
466
467
        if (!empty($parent))  {
468
            $lpItem->setParent($parent);
469
        }
470
        $em = Database::getManager();
471
        $em->persist($lpItem);
472
        $em->flush();
473
474
        $new_item_id = $lpItem->getIid();
475
        if ($new_item_id) {
476
            // @todo fix upload audio.
477
            // Upload audio.
478
            /*if (!empty($_FILES['mp3']['name'])) {
479
                // Create the audio folder if it does not exist yet.
480
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
481
                if (!is_dir($filepath.'audio')) {
482
                    mkdir(
483
                        $filepath.'audio',
484
                        api_get_permissions_for_new_directories()
485
                    );
486
                    DocumentManager::addDocument(
487
                        $_course,
488
                        '/audio',
489
                        'folder',
490
                        0,
491
                        'audio',
492
                        '',
493
                        0,
494
                        true,
495
                        null,
496
                        $sessionId,
497
                        $userId
498
                    );
499
                }
500
501
                $file_path = handle_uploaded_document(
502
                    $_course,
503
                    $_FILES['mp3'],
504
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
505
                    '/audio',
506
                    $userId,
507
                    '',
508
                    '',
509
                    '',
510
                    '',
511
                    false
512
                );
513
514
                // Getting the filename only.
515
                $file_components = explode('/', $file_path);
516
                $file = $file_components[count($file_components) - 1];
517
518
                // Store the mp3 file in the lp_item table.
519
                $sql = "UPDATE $tbl_lp_item SET
520
                          audio = '".Database::escape_string($file)."'
521
                        WHERE iid = '".intval($new_item_id)."'";
522
                Database::query($sql);
523
            }*/
524
        }
525
526
        return $new_item_id;
527
    }
528
529
    /**
530
     * Static admin function allowing addition of a learnpath to a course.
531
     *
532
     * @param string $courseCode
533
     * @param string $name
534
     * @param string $description
535
     * @param string $learnpath
536
     * @param string $origin
537
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
538
     * @param string $publicated_on
539
     * @param string $expired_on
540
     * @param int    $categoryId
541
     * @param int    $userId
542
     *
543
     * @return CLp
544
     */
545
    public static function add_lp(
546
        $courseCode,
547
        $name,
548
        $description = '',
549
        $learnpath = 'guess',
550
        $origin = 'zip',
551
        $zipname = '',
552
        $publicated_on = '',
553
        $expired_on = '',
554
        $categoryId = 0,
555
        $userId = 0
556
    ) {
557
        global $charset;
558
559
        if (!empty($courseCode)) {
560
            $courseInfo = api_get_course_info($courseCode);
561
            $course_id = $courseInfo['real_id'];
562
        } else {
563
            $course_id = api_get_course_int_id();
564
            $courseInfo = api_get_course_info();
565
        }
566
567
        $categoryId = (int) $categoryId;
568
569
        if (empty($publicated_on)) {
570
            $publicated_on = null;
571
        } else {
572
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
573
        }
574
575
        if (empty($expired_on)) {
576
            $expired_on = null;
577
        } else {
578
            $expired_on = api_get_utc_datetime($expired_on, true, true);
579
        }
580
581
        /*$check_name = "SELECT * FROM $tbl_lp
582
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
583
        $res_name = Database::query($check_name);
584
585
        while (Database::num_rows($res_name)) {
586
            // There is already one such name, update the current one a bit.
587
            $i++;
588
            $name = $name.' - '.$i;
589
            $check_name = "SELECT * FROM $tbl_lp
590
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
591
            $res_name = Database::query($check_name);
592
        }*/
593
        // New name does not exist yet; keep it.
594
        // Escape description.
595
        // Kevin: added htmlentities().
596
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
597
        $type = 1;
598
        switch ($learnpath) {
599
            case 'guess':
600
            case 'aicc':
601
                break;
602
            case 'dokeos':
603
            case 'chamilo':
604
                $type = 1;
605
                break;
606
        }
607
608
        $lpItemRepo = Database::getManager()->getRepository(CLpItem::class);
609
        $sessionEntity = api_get_session_entity();
610
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
611
        $lp = null;
612
        switch ($origin) {
613
            case 'zip':
614
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
615
                break;
616
            case 'manual':
617
            default:
618
                /*$get_max = "SELECT MAX(display_order)
619
                            FROM $tbl_lp WHERE c_id = $course_id";
620
                $res_max = Database::query($get_max);
621
                if (Database::num_rows($res_max) < 1) {
622
                    $dsp = 1;
623
                } else {
624
                    $row = Database::fetch_array($res_max);
625
                    $dsp = $row[0] + 1;
626
                }*/
627
628
                $dsp = 1;
629
                $category = null;
630
                if (!empty($categoryId)) {
631
                    $category = Container::getLpCategoryRepository()->find($categoryId);
632
                }
633
634
                $lp = new CLp();
635
                $lp
636
                    ->setLpType($type)
637
                    ->setName($name)
638
                    ->setDescription($description)
639
                    ->setDisplayOrder($dsp)
640
                    ->setCategory($category)
641
                    ->setPublicatedOn($publicated_on)
642
                    ->setExpiredOn($expired_on)
643
                    ->setParent($courseEntity)
644
                    ->addCourseLink($courseEntity, $sessionEntity)
645
                ;
646
647
                $em = Database::getManager();
648
                $em->persist($lp);
649
                $em->flush();
650
                $lpId = $lp->getIid();
651
652
                if ($lpId) {
653
                    // Creates first root item, so all items will be parent of this item.
654
                    $lpItem = new CLpItem();
655
                    $lpItem
656
                        ->setTitle('root')
657
                        ->setPath('root')
658
                        ->setLp($lp)
659
                        ->setItemType('root');
660
                    $em = Database::getManager();
661
                    $em->persist($lpItem);
662
                    $em->flush();
663
                }
664
665
                break;
666
        }
667
668
        return $lp;
669
    }
670
671
    /**
672
     * Auto completes the parents of an item in case it's been completed or passed.
673
     *
674
     * @param int $item Optional ID of the item from which to look for parents
675
     */
676
    public function autocomplete_parents($item)
677
    {
678
        $debug = $this->debug;
679
680
        if (empty($item)) {
681
            $item = $this->current;
682
        }
683
684
        $currentItem = $this->getItem($item);
685
        if ($currentItem) {
686
            $parent_id = $currentItem->get_parent();
687
            $parent = $this->getItem($parent_id);
688
            if ($parent) {
689
                // if $item points to an object and there is a parent.
690
                if ($debug) {
691
                    error_log(
692
                        'Autocompleting parent of item '.$item.' '.
693
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
694
                        0
695
                    );
696
                }
697
698
                // New experiment including failed and browsed in completed status.
699
                //$current_status = $currentItem->get_status();
700
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
701
                // Fixes chapter auto complete
702
                if (true) {
703
                    // If the current item is completed or passes or succeeded.
704
                    $updateParentStatus = true;
705
                    if ($debug) {
706
                        error_log('Status of current item is alright');
707
                    }
708
709
                    foreach ($parent->get_children() as $childItemId) {
710
                        $childItem = $this->getItem($childItemId);
711
712
                        // If children was not set try to get the info
713
                        if (empty($childItem->db_item_view_id)) {
714
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
715
                        }
716
717
                        // Check all his brothers (parent's children) for completion status.
718
                        if ($childItemId != $item) {
719
                            if ($debug) {
720
                                error_log(
721
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
722
                                    0
723
                                );
724
                            }
725
                            // Trying completing parents of failed and browsed items as well.
726
                            if ($childItem->status_is(
727
                                [
728
                                    'completed',
729
                                    'passed',
730
                                    'succeeded',
731
                                    'browsed',
732
                                    'failed',
733
                                ]
734
                            )
735
                            ) {
736
                                // Keep completion status to true.
737
                                continue;
738
                            } else {
739
                                if ($debug > 2) {
740
                                    error_log(
741
                                        '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,
742
                                        0
743
                                    );
744
                                }
745
                                $updateParentStatus = false;
746
                                break;
747
                            }
748
                        }
749
                    }
750
751
                    if ($updateParentStatus) {
752
                        // If all the children were completed:
753
                        $parent->set_status('completed');
754
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
755
                        // Force the status to "completed"
756
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
757
                        $this->update_queue[$parent->get_id()] = 'completed';
758
                        if ($debug) {
759
                            error_log(
760
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
761
                                print_r($this->update_queue, 1),
762
                                0
763
                            );
764
                        }
765
                        // Recursive call.
766
                        $this->autocomplete_parents($parent->get_id());
767
                    }
768
                }
769
            } else {
770
                if ($debug) {
771
                    error_log("Parent #$parent_id does not exists");
772
                }
773
            }
774
        } else {
775
            if ($debug) {
776
                error_log("#$item is an item that doesn't have parents");
777
            }
778
        }
779
    }
780
781
    /**
782
     * Closes the current resource.
783
     *
784
     * Stops the timer
785
     * Saves into the database if required
786
     * Clears the current resource data from this object
787
     *
788
     * @return bool True on success, false on failure
789
     */
790
    public function close()
791
    {
792
        if (empty($this->lp_id)) {
793
            $this->error = 'Trying to close this learnpath but no ID is set';
794
795
            return false;
796
        }
797
        $this->current_time_stop = time();
798
        $this->ordered_items = [];
799
        $this->index = 0;
800
        unset($this->lp_id);
801
        //unset other stuff
802
        return true;
803
    }
804
805
    /**
806
     * Static admin function allowing removal of a learnpath.
807
     *
808
     * @param array  $courseInfo
809
     * @param int    $id         Learnpath ID
810
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
811
     *
812
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
813
     */
814
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
815
    {
816
        $course_id = api_get_course_int_id();
817
        if (!empty($courseInfo)) {
818
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
819
        }
820
821
        // TODO: Implement a way of getting this to work when the current object is not set.
822
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
823
        // If an ID is specifically given and the current LP is not the same, prevent delete.
824
        if (!empty($id) && ($id != $this->lp_id)) {
825
            return false;
826
        }
827
828
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
829
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
830
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
831
832
        // Delete lp item id.
833
        foreach ($this->items as $lpItemId => $dummy) {
834
            $sql = "DELETE FROM $lp_item_view
835
                    WHERE lp_item_id = '".$lpItemId."'";
836
            Database::query($sql);
837
        }
838
839
        // Proposed by Christophe (nickname: clefevre)
840
        $sql = "DELETE FROM $lp_item
841
                WHERE lp_id = ".$this->lp_id;
842
        Database::query($sql);
843
844
        $sql = "DELETE FROM $lp_view
845
                WHERE lp_id = ".$this->lp_id;
846
        Database::query($sql);
847
848
        //self::toggleVisibility($this->lp_id, 0);
849
850
        /*if (2 == $this->type || 3 == $this->type) {
851
            // This is a scorm learning path, delete the files as well.
852
            $sql = "SELECT path FROM $lp
853
                    WHERE iid = ".$this->lp_id;
854
            $res = Database::query($sql);
855
            if (Database::num_rows($res) > 0) {
856
                $row = Database::fetch_array($res);
857
                $path = $row['path'];
858
                $sql = "SELECT iid FROM $lp
859
                        WHERE
860
                            c_id = $course_id AND
861
                            path = '$path' AND
862
                            iid != ".$this->lp_id;
863
                $res = Database::query($sql);
864
                if (Database::num_rows($res) > 0) {
865
                    // Another learning path uses this directory, so don't delete it.
866
                    if ($this->debug > 2) {
867
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
868
                    }
869
                } else {
870
                    // No other LP uses that directory, delete it.
871
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
872
                    // The absolute system path for this course.
873
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
874
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
875
                        if ($this->debug > 2) {
876
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
877
                        }
878
                        // Proposed by Christophe (clefevre).
879
                        if (0 == strcmp(substr($path, -2), "/.")) {
880
                            $path = substr($path, 0, -1); // Remove "." at the end.
881
                        }
882
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
883
                        rmdirr($course_scorm_dir.$path);
884
                    }
885
                }
886
            }
887
        }*/
888
889
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
890
        $sql = "DELETE FROM $table
891
                WHERE
892
                    lp_id = {$this->lp_id}";
893
        Database::query($sql);
894
895
        $repo = Container::getLpRepository();
896
        $lp = $repo->find($this->lp_id);
897
        Database::getManager()->remove($lp);
898
        Database::getManager()->flush();
899
900
        // Updates the display order of all lps.
901
        $this->update_display_order();
902
903
        $link_info = GradebookUtils::isResourceInCourseGradebook(
904
            api_get_course_id(),
905
            4,
906
            $id,
907
            api_get_session_id()
908
        );
909
910
        if (false !== $link_info) {
911
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
912
        }
913
914
        if ('true' === api_get_setting('search_enabled')) {
915
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
916
        }
917
    }
918
919
    /**
920
     * Removes all the children of one item - dangerous!
921
     *
922
     * @param int $id Element ID of which children have to be removed
923
     *
924
     * @return int Total number of children removed
925
     */
926
    public function delete_children_items($id)
927
    {
928
        $course_id = $this->course_info['real_id'];
929
930
        $num = 0;
931
        $id = (int) $id;
932
        if (empty($id) || empty($course_id)) {
933
            return false;
934
        }
935
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
936
        $sql = "SELECT * FROM $lp_item
937
                WHERE parent_item_id = $id";
938
        $res = Database::query($sql);
939
        while ($row = Database::fetch_array($res)) {
940
            $num += $this->delete_children_items($row['iid']);
941
            $sql = "DELETE FROM $lp_item
942
                    WHERE iid = ".$row['iid'];
943
            Database::query($sql);
944
            $num++;
945
        }
946
947
        return $num;
948
    }
949
950
    /**
951
     * Removes an item from the current learnpath.
952
     *
953
     * @param int $id Elem ID (0 if first)
954
     *
955
     * @return int Number of elements moved
956
     *
957
     * @todo implement resource removal
958
     */
959
    public function delete_item($id)
960
    {
961
        $course_id = api_get_course_int_id();
962
        $id = (int) $id;
963
        // TODO: Implement the resource removal.
964
        if (empty($id) || empty($course_id)) {
965
            return false;
966
        }
967
968
        $repo = Container::getLpItemRepository();
969
        $item = $repo->find($id);
970
        if (null === $item) {
971
            return false;
972
        }
973
974
        $em = Database::getManager();
975
        $repo->removeFromTree($item);
976
        $em->flush();
977
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
978
979
        //Removing prerequisites since the item will not longer exist
980
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
981
                    WHERE prerequisite = '$id'";
982
        Database::query($sql_all);
983
984
        $sql = "UPDATE $lp_item
985
                SET previous_item_id = ".$this->getLastInFirstLevel()."
986
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
987
        Database::query($sql);
988
989
        // Remove from search engine if enabled.
990
        if ('true' === api_get_setting('search_enabled')) {
991
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
992
            $sql = 'SELECT * FROM %s
993
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
994
                    LIMIT 1';
995
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
996
            $res = Database::query($sql);
997
            if (Database::num_rows($res) > 0) {
998
                $row2 = Database::fetch_array($res);
999
                $di = new ChamiloIndexer();
1000
                $di->remove_document($row2['search_did']);
1001
            }
1002
            $sql = 'DELETE FROM %s
1003
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1004
                    LIMIT 1';
1005
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1006
            Database::query($sql);
1007
        }
1008
    }
1009
1010
    /**
1011
     * Updates an item's content in place.
1012
     *
1013
     * @param int    $id               Element ID
1014
     * @param int    $parent           Parent item ID
1015
     * @param int    $previous         Previous item ID
1016
     * @param string $title            Item title
1017
     * @param string $description      Item description
1018
     * @param string $prerequisites    Prerequisites (optional)
1019
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1020
     * @param int    $max_time_allowed
1021
     * @param string $url
1022
     *
1023
     * @return bool True on success, false on error
1024
     */
1025
    public function edit_item(
1026
        $id,
1027
        $parent,
1028
        $previous,
1029
        $title,
1030
        $description,
1031
        $prerequisites = '0',
1032
        $audio = [],
1033
        $max_time_allowed = 0,
1034
        $url = ''
1035
    ) {
1036
        $_course = api_get_course_info();
1037
        $id = (int) $id;
1038
1039
        if (empty($id) || empty($_course)) {
1040
            return false;
1041
        }
1042
        $repo = Container::getLpItemRepository();
1043
        /** @var CLpItem $item */
1044
        $item = $repo->find($id);
1045
        if (null === $item) {
1046
            return false;
1047
        }
1048
1049
        $item
1050
            ->setTitle($title)
1051
            ->setDescription($description)
1052
            ->setPrerequisite($prerequisites)
1053
            ->setMaxTimeAllowed((int) $max_time_allowed)
1054
        ;
1055
1056
        $em = Database::getManager();
1057
        if (!empty($parent)) {
1058
            $parent = $repo->find($parent);
1059
            $item->setParent($parent);
1060
        } else {
1061
            $item->setParent(null);
1062
        }
1063
1064
        if (!empty($previous)) {
1065
            $previous = $repo->find($previous);
1066
            $repo->persistAsNextSiblingOf( $item, $previous);
1067
        } else {
1068
            $em->persist($item);
1069
        }
1070
1071
        $em->flush();
1072
1073
        if ('link' === $item->getItemType()) {
1074
            $link = new Link();
1075
            $linkId = $item->getPath();
1076
            $link->updateLink($linkId, $url);
1077
        }
1078
    }
1079
1080
    /**
1081
     * Updates an item's prereq in place.
1082
     *
1083
     * @param int    $id              Element ID
1084
     * @param string $prerequisite_id Prerequisite Element ID
1085
     * @param int    $minScore        Prerequisite min score
1086
     * @param int    $maxScore        Prerequisite max score
1087
     *
1088
     * @return bool True on success, false on error
1089
     */
1090
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1091
    {
1092
        $id = (int) $id;
1093
1094
        if (empty($id)) {
1095
            return false;
1096
        }
1097
        $prerequisite_id = (int) $prerequisite_id;
1098
1099
        if (empty($minScore) || $minScore < 0) {
1100
            $minScore = 0;
1101
        }
1102
1103
        if (empty($maxScore) || $maxScore < 0) {
1104
            $maxScore = 100;
1105
        }
1106
1107
        $minScore = (float) $minScore;
1108
        $maxScore = (float) $maxScore;
1109
1110
        if (empty($prerequisite_id)) {
1111
            $prerequisite_id = 'NULL';
1112
            $minScore = 0;
1113
            $maxScore = 100;
1114
        }
1115
1116
        $table = Database::get_course_table(TABLE_LP_ITEM);
1117
        $sql = " UPDATE $table
1118
                 SET
1119
                    prerequisite = $prerequisite_id ,
1120
                    prerequisite_min_score = $minScore ,
1121
                    prerequisite_max_score = $maxScore
1122
                 WHERE iid = $id";
1123
        Database::query($sql);
1124
1125
        return true;
1126
    }
1127
1128
    /**
1129
     * Get the specific prefix index terms of this learning path.
1130
     *
1131
     * @param string $prefix
1132
     *
1133
     * @return array Array of terms
1134
     */
1135
    public function get_common_index_terms_by_prefix($prefix)
1136
    {
1137
        $terms = get_specific_field_values_list_by_prefix(
1138
            $prefix,
1139
            $this->cc,
1140
            TOOL_LEARNPATH,
1141
            $this->lp_id
1142
        );
1143
        $prefix_terms = [];
1144
        if (!empty($terms)) {
1145
            foreach ($terms as $term) {
1146
                $prefix_terms[] = $term['value'];
1147
            }
1148
        }
1149
1150
        return $prefix_terms;
1151
    }
1152
1153
    /**
1154
     * Gets the number of items currently completed.
1155
     *
1156
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1157
     *
1158
     * @return int The number of items currently completed
1159
     */
1160
    public function get_complete_items_count($failedStatusException = false)
1161
    {
1162
        $i = 0;
1163
        $completedStatusList = [
1164
            'completed',
1165
            'passed',
1166
            'succeeded',
1167
            'browsed',
1168
        ];
1169
1170
        if (!$failedStatusException) {
1171
            $completedStatusList[] = 'failed';
1172
        }
1173
1174
        foreach ($this->items as $id => $dummy) {
1175
            // Trying failed and browsed considered "progressed" as well.
1176
            if ($this->items[$id]->status_is($completedStatusList) &&
1177
                'dir' !== $this->items[$id]->get_type()
1178
            ) {
1179
                $i++;
1180
            }
1181
        }
1182
1183
        return $i;
1184
    }
1185
1186
    /**
1187
     * Gets the current item ID.
1188
     *
1189
     * @return int The current learnpath item id
1190
     */
1191
    public function get_current_item_id()
1192
    {
1193
        $current = 0;
1194
        if (!empty($this->current)) {
1195
            $current = (int) $this->current;
1196
        }
1197
1198
        return $current;
1199
    }
1200
1201
    /**
1202
     * Force to get the first learnpath item id.
1203
     *
1204
     * @return int The current learnpath item id
1205
     */
1206
    public function get_first_item_id()
1207
    {
1208
        $current = 0;
1209
        if (is_array($this->ordered_items)) {
1210
            $current = $this->ordered_items[0];
1211
        }
1212
1213
        return $current;
1214
    }
1215
1216
    /**
1217
     * Gets the total number of items available for viewing in this SCORM.
1218
     *
1219
     * @return int The total number of items
1220
     */
1221
    public function get_total_items_count()
1222
    {
1223
        return count($this->items);
1224
    }
1225
1226
    /**
1227
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1228
     *
1229
     * @return int The total no-chapters number of items
1230
     */
1231
    public function getTotalItemsCountWithoutDirs()
1232
    {
1233
        $total = 0;
1234
        $typeListNotToCount = self::getChapterTypes();
1235
        foreach ($this->items as $temp2) {
1236
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1237
                $total++;
1238
            }
1239
        }
1240
1241
        return $total;
1242
    }
1243
1244
    /**
1245
     *  Sets the first element URL.
1246
     */
1247
    public function first()
1248
    {
1249
        if ($this->debug > 0) {
1250
            error_log('In learnpath::first()', 0);
1251
            error_log('$this->last_item_seen '.$this->last_item_seen);
1252
        }
1253
1254
        // Test if the last_item_seen exists and is not a dir.
1255
        if (0 == count($this->ordered_items)) {
1256
            $this->index = 0;
1257
        }
1258
1259
        if (!empty($this->last_item_seen) &&
1260
            !empty($this->items[$this->last_item_seen]) &&
1261
            'dir' != $this->items[$this->last_item_seen]->get_type()
1262
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1263
            //&& !$this->items[$this->last_item_seen]->is_done()
1264
        ) {
1265
            if ($this->debug > 2) {
1266
                error_log(
1267
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1268
                    $this->items[$this->last_item_seen]->get_type()
1269
                );
1270
            }
1271
            $index = -1;
1272
            foreach ($this->ordered_items as $myindex => $item_id) {
1273
                if ($item_id == $this->last_item_seen) {
1274
                    $index = $myindex;
1275
                    break;
1276
                }
1277
            }
1278
            if (-1 == $index) {
1279
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1280
                if ($this->debug > 2) {
1281
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1282
                }
1283
1284
                return false;
1285
            } else {
1286
                $this->last = $this->last_item_seen;
1287
                $this->current = $this->last_item_seen;
1288
                $this->index = $index;
1289
            }
1290
        } else {
1291
            if ($this->debug > 2) {
1292
                error_log('In learnpath::first() - No last item seen', 0);
1293
            }
1294
            $index = 0;
1295
            // Loop through all ordered items and stop at the first item that is
1296
            // not a directory *and* that has not been completed yet.
1297
            while (!empty($this->ordered_items[$index]) &&
1298
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1299
                (
1300
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1301
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1302
                ) && $index < $this->max_ordered_items) {
1303
                $index++;
1304
            }
1305
1306
            $this->last = $this->current;
1307
            // current is
1308
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1309
            $this->index = $index;
1310
            if ($this->debug > 2) {
1311
                error_log('$index '.$index);
1312
                error_log('In learnpath::first() - No last item seen');
1313
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1314
            }
1315
        }
1316
        if ($this->debug > 2) {
1317
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1318
        }
1319
    }
1320
1321
    /**
1322
     * Gets the js library from the database.
1323
     *
1324
     * @return string The name of the javascript library to be used
1325
     */
1326
    public function get_js_lib()
1327
    {
1328
        $lib = '';
1329
        if (!empty($this->js_lib)) {
1330
            $lib = $this->js_lib;
1331
        }
1332
1333
        return $lib;
1334
    }
1335
1336
    /**
1337
     * Gets the learnpath database ID.
1338
     *
1339
     * @return int Learnpath ID in the lp table
1340
     */
1341
    public function get_id()
1342
    {
1343
        if (!empty($this->lp_id)) {
1344
            return (int) $this->lp_id;
1345
        }
1346
1347
        return 0;
1348
    }
1349
1350
    /**
1351
     * Gets the last element URL.
1352
     *
1353
     * @return string URL to load into the viewer
1354
     */
1355
    public function get_last()
1356
    {
1357
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1358
        if (count($this->ordered_items) > 0) {
1359
            $this->index = count($this->ordered_items) - 1;
1360
1361
            return $this->ordered_items[$this->index];
1362
        }
1363
1364
        return false;
1365
    }
1366
1367
    /**
1368
     * Get the last element in the first level.
1369
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1370
     *
1371
     * @return mixed
1372
     */
1373
    public function getLastInFirstLevel()
1374
    {
1375
        try {
1376
            $lastId = Database::getManager()
1377
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1378
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1379
                ->setMaxResults(1)
1380
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1381
                ->getSingleScalarResult();
1382
1383
            return $lastId;
1384
        } catch (Exception $exception) {
1385
            return 0;
1386
        }
1387
    }
1388
1389
    /**
1390
     * Gets the navigation bar for the learnpath display screen.
1391
     *
1392
     * @param string $barId
1393
     *
1394
     * @return string The HTML string to use as a navigation bar
1395
     */
1396
    public function get_navigation_bar($barId = '')
1397
    {
1398
        if (empty($barId)) {
1399
            $barId = 'control-top';
1400
        }
1401
        $lpId = $this->lp_id;
1402
        $mycurrentitemid = $this->get_current_item_id();
1403
        $reportingText = get_lang('Reporting');
1404
        $previousText = get_lang('Previous');
1405
        $nextText = get_lang('Next');
1406
        $fullScreenText = get_lang('Back to normal screen');
1407
1408
        $settings = api_get_configuration_value('lp_view_settings');
1409
        $display = $settings['display'] ?? false;
1410
        $reportingIcon = '
1411
            <a class="icon-toolbar"
1412
                id="stats_link"
1413
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1414
                onclick="window.parent.API.save_asset(); return true;"
1415
                target="content_name" title="'.$reportingText.'">
1416
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1417
            </a>';
1418
1419
        if (!empty($display)) {
1420
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1421
            if (false === $showReporting) {
1422
                $reportingIcon = '';
1423
            }
1424
        }
1425
1426
        $hideArrows = false;
1427
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1428
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1429
        }
1430
1431
        $previousIcon = '';
1432
        $nextIcon = '';
1433
        if (false === $hideArrows) {
1434
            $previousIcon = '
1435
                <a class="icon-toolbar" id="scorm-previous" href="#"
1436
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1437
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1438
                </a>';
1439
1440
            $nextIcon = '
1441
                <a class="icon-toolbar" id="scorm-next" href="#"
1442
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1443
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1444
                </a>';
1445
        }
1446
1447
        if ('fullscreen' === $this->mode) {
1448
            $navbar = '
1449
                  <span id="'.$barId.'" class="buttons">
1450
                    '.$reportingIcon.'
1451
                    '.$previousIcon.'
1452
                    '.$nextIcon.'
1453
                    <a class="icon-toolbar" id="view-embedded"
1454
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1455
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1456
                    </a>
1457
                  </span>';
1458
        } else {
1459
            $navbar = '
1460
                 <span id="'.$barId.'" class="buttons text-right">
1461
                    '.$reportingIcon.'
1462
                    '.$previousIcon.'
1463
                    '.$nextIcon.'
1464
                </span>';
1465
        }
1466
1467
        return $navbar;
1468
    }
1469
1470
    /**
1471
     * Gets the next resource in queue (url).
1472
     *
1473
     * @return string URL to load into the viewer
1474
     */
1475
    public function get_next_index()
1476
    {
1477
        // TODO
1478
        $index = $this->index;
1479
        $index++;
1480
        while (
1481
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1482
            $index < $this->max_ordered_items
1483
        ) {
1484
            $index++;
1485
            if ($index == $this->max_ordered_items) {
1486
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1487
                    return $this->index;
1488
                }
1489
1490
                return $index;
1491
            }
1492
        }
1493
        if (empty($this->ordered_items[$index])) {
1494
            return $this->index;
1495
        }
1496
1497
        return $index;
1498
    }
1499
1500
    /**
1501
     * Gets item_id for the next element.
1502
     *
1503
     * @return int Next item (DB) ID
1504
     */
1505
    public function get_next_item_id()
1506
    {
1507
        $new_index = $this->get_next_index();
1508
        if (!empty($new_index)) {
1509
            if (isset($this->ordered_items[$new_index])) {
1510
                return $this->ordered_items[$new_index];
1511
            }
1512
        }
1513
1514
        return 0;
1515
    }
1516
1517
    /**
1518
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1519
     *
1520
     * Generally, the package provided is in the form of a zip file, so the function
1521
     * has been written to test a zip file. If not a zip, the function will return the
1522
     * default return value: ''
1523
     *
1524
     * @param string $filePath the path to the file
1525
     * @param string $file_name the original name of the file
1526
     *
1527
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1528
     *                if the package is empty, or '' if the package cannot be recognized
1529
     */
1530
    public static function getPackageType($filePath, $file_name)
1531
    {
1532
        // Get name of the zip file without the extension.
1533
        $file_info = pathinfo($file_name);
1534
        $extension = $file_info['extension']; // Extension only.
1535
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1536
                'dll',
1537
                'exe',
1538
            ])) {
1539
            return 'oogie';
1540
        }
1541
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1542
                'dll',
1543
                'exe',
1544
            ])) {
1545
            return 'woogie';
1546
        }
1547
1548
        $zipFile = new ZipFile();
1549
        $zipFile->openFile($filePath);
1550
        $zipContentArray = $zipFile->getEntries();
1551
        $package_type = '';
1552
        $manifest = '';
1553
        $aicc_match_crs = 0;
1554
        $aicc_match_au = 0;
1555
        $aicc_match_des = 0;
1556
        $aicc_match_cst = 0;
1557
        $countItems = 0;
1558
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1559
        if ($zipContentArray) {
1560
            $countItems = count($zipContentArray);
1561
            if ($countItems > 0) {
1562
                foreach ($zipContentArray as $thisContent) {
1563
                    $fileName = basename($thisContent->getName());
1564
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1565
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1566
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1567
                        $manifest = $fileName; // Just the relative directory inside scorm/
1568
                        $package_type = 'scorm';
1569
                        break; // Exit the foreach loop.
1570
                    } elseif (
1571
                        preg_match('/aicc\//i', $fileName) ||
1572
                        in_array(
1573
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1574
                            ['crs', 'au', 'des', 'cst']
1575
                        )
1576
                    ) {
1577
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1578
                        switch ($ext) {
1579
                            case 'crs':
1580
                                $aicc_match_crs = 1;
1581
                                break;
1582
                            case 'au':
1583
                                $aicc_match_au = 1;
1584
                                break;
1585
                            case 'des':
1586
                                $aicc_match_des = 1;
1587
                                break;
1588
                            case 'cst':
1589
                                $aicc_match_cst = 1;
1590
                                break;
1591
                            default:
1592
                                break;
1593
                        }
1594
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1595
                    } else {
1596
                        $package_type = '';
1597
                    }
1598
                }
1599
            }
1600
        }
1601
1602
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1603
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1604
            $package_type = 'aicc';
1605
        }
1606
1607
        // Try with chamilo course builder
1608
        if (empty($package_type)) {
1609
            // Sometimes users will try to upload an empty zip, or a zip with
1610
            // only a folder. Catch that and make the calling function aware.
1611
            // If the single file was the imsmanifest.xml, then $package_type
1612
            // would be 'scorm' and we wouldn't be here.
1613
            if ($countItems < 2) {
1614
                return 'error-empty-package';
1615
            }
1616
            $package_type = 'chamilo';
1617
        }
1618
1619
        return $package_type;
1620
    }
1621
1622
    /**
1623
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1624
     *
1625
     * @return string URL to load into the viewer
1626
     */
1627
    public function get_previous_index()
1628
    {
1629
        $index = $this->index;
1630
        if (isset($this->ordered_items[$index - 1])) {
1631
            $index--;
1632
            while (isset($this->ordered_items[$index]) &&
1633
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1634
            ) {
1635
                $index--;
1636
                if ($index < 0) {
1637
                    return $this->index;
1638
                }
1639
            }
1640
        }
1641
1642
        return $index;
1643
    }
1644
1645
    /**
1646
     * Gets item_id for the next element.
1647
     *
1648
     * @return int Previous item (DB) ID
1649
     */
1650
    public function get_previous_item_id()
1651
    {
1652
        $index = $this->get_previous_index();
1653
1654
        return $this->ordered_items[$index];
1655
    }
1656
1657
    /**
1658
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1659
     *
1660
     * @param int    $lpItemId
1661
     * @param string $autostart
1662
     *
1663
     * @return string The mediaplayer HTML
1664
     */
1665
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1666
    {
1667
        $courseInfo = api_get_course_info();
1668
        $lpItemId = (int) $lpItemId;
1669
1670
        if (empty($courseInfo) || empty($lpItemId)) {
1671
            return '';
1672
        }
1673
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1674
1675
        if (empty($item)) {
1676
            return '';
1677
        }
1678
1679
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1680
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1681
        $itemViewId = (int) $item->db_item_view_id;
1682
1683
        // Getting all the information about the item.
1684
        $sql = "SELECT lp_view.status
1685
                FROM $tbl_lp_item as lpi
1686
                INNER JOIN $tbl_lp_item_view as lp_view
1687
                ON (lpi.iid = lp_view.lp_item_id)
1688
                WHERE
1689
                    lp_view.iid = $itemViewId AND
1690
                    lpi.iid = $lpItemId
1691
                ";
1692
        $result = Database::query($sql);
1693
        $row = Database::fetch_assoc($result);
1694
        $output = '';
1695
        $audio = $item->audio;
1696
1697
        if (!empty($audio)) {
1698
            $list = $_SESSION['oLP']->get_toc();
1699
1700
            switch ($item->get_type()) {
1701
                case 'quiz':
1702
                    $type_quiz = false;
1703
                    foreach ($list as $toc) {
1704
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1705
                            $type_quiz = true;
1706
                        }
1707
                    }
1708
1709
                    if ($type_quiz) {
1710
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1711
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1712
                        } else {
1713
                            $autostart_audio = $autostart;
1714
                        }
1715
                    }
1716
                    break;
1717
                case TOOL_READOUT_TEXT:
1718
                    $autostart_audio = 'false';
1719
                    break;
1720
                default:
1721
                    $autostart_audio = 'true';
1722
            }
1723
1724
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1725
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1726
1727
            $player = Display::getMediaPlayer(
1728
                $file,
1729
                [
1730
                    'id' => 'lp_audio_media_player',
1731
                    'url' => $url,
1732
                    'autoplay' => $autostart_audio,
1733
                    'width' => '100%',
1734
                ]
1735
            );
1736
1737
            // The mp3 player.
1738
            $output = '<div id="container">';
1739
            $output .= $player;
1740
            $output .= '</div>';
1741
        }
1742
1743
        return $output;
1744
    }
1745
1746
    /**
1747
     * @param int    $studentId
1748
     * @param int    $prerequisite
1749
     * @param Course $course
1750
     * @param int    $sessionId
1751
     *
1752
     * @return bool
1753
     */
1754
    public static function isBlockedByPrerequisite(
1755
        $studentId,
1756
        $prerequisite,
1757
        Course $course,
1758
        $sessionId
1759
    ) {
1760
        if (null === $course) {
1761
            return false;
1762
        }
1763
1764
        $courseId = $course->getId();
1765
1766
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1767
        if ($allow) {
1768
            if (api_is_allowed_to_edit() ||
1769
                api_is_platform_admin(true) ||
1770
                api_is_drh() ||
1771
                api_is_coach($sessionId, $courseId, false)
1772
            ) {
1773
                return false;
1774
            }
1775
        }
1776
1777
        $isBlocked = false;
1778
        if (!empty($prerequisite)) {
1779
            $progress = self::getProgress(
1780
                $prerequisite,
1781
                $studentId,
1782
                $courseId,
1783
                $sessionId
1784
            );
1785
            if ($progress < 100) {
1786
                $isBlocked = true;
1787
            }
1788
1789
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1790
                // Block if it does not exceed minimum time
1791
                // Minimum time (in minutes) to pass the learning path
1792
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1793
1794
                if ($accumulateWorkTime > 0) {
1795
                    // Total time in course (sum of times in learning paths from course)
1796
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1797
1798
                    // Connect with the plugin_licences_course_session table
1799
                    // which indicates what percentage of the time applies
1800
                    // Minimum connection percentage
1801
                    $perc = 100;
1802
                    // Time from the course
1803
                    $tc = $accumulateWorkTimeTotal;
1804
1805
                    // Percentage of the learning paths
1806
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1807
                    // Minimum time for each learning path
1808
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1809
1810
                    // Spent time (in seconds) so far in the learning path
1811
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1812
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1813
1814
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1815
                        $isBlocked = true;
1816
                    }
1817
                }
1818
            }
1819
        }
1820
1821
        return $isBlocked;
1822
    }
1823
1824
    /**
1825
     * Checks if the learning path is visible for student after the progress
1826
     * of its prerequisite is completed, considering the time availability and
1827
     * the LP visibility.
1828
     */
1829
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1830
    {
1831
        if (null === $course) {
1832
            return false;
1833
        }
1834
1835
        $sessionId = $session ? $session->getId() : 0;
1836
        $courseId = $course->getId();
1837
        $visibility = $lp->isVisible($course, $session);
1838
1839
        // If the item was deleted.
1840
        if (false === $visibility) {
1841
            return false;
1842
        }
1843
1844
        $now = time();
1845
        if ($lp->hasCategory()) {
1846
            $category = $lp->getCategory();
1847
1848
            if (false === self::categoryIsVisibleForStudent(
1849
                    $category,
1850
                    api_get_user_entity($student_id),
1851
                    $course,
1852
                    $session
1853
                )) {
1854
                return false;
1855
            }
1856
1857
            $prerequisite = $lp->getPrerequisite();
1858
            $is_visible = true;
1859
1860
            $isBlocked = self::isBlockedByPrerequisite(
1861
                $student_id,
1862
                $prerequisite,
1863
                $course,
1864
                $sessionId
1865
            );
1866
1867
            if ($isBlocked) {
1868
                $is_visible = false;
1869
            }
1870
1871
            // Also check the time availability of the LP
1872
            if ($is_visible) {
1873
                // Adding visibility restrictions
1874
                if (null !== $lp->getPublicatedOn()) {
1875
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1876
                        $is_visible = false;
1877
                    }
1878
                }
1879
                // Blocking empty start times see BT#2800
1880
                global $_custom;
1881
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1882
                    $_custom['lps_hidden_when_no_start_date']
1883
                ) {
1884
                    if (null !== $lp->getPublicatedOn()) {
1885
                        $is_visible = false;
1886
                    }
1887
                }
1888
1889
                if (null !== $lp->getExpiredOn()) {
1890
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1891
                        $is_visible = false;
1892
                    }
1893
                }
1894
            }
1895
1896
            if ($is_visible) {
1897
                $subscriptionSettings = self::getSubscriptionSettings();
1898
1899
                // Check if the subscription users/group to a LP is ON
1900
                if (1 == $lp->getSubscribeUsers() &&
1901
                    true === $subscriptionSettings['allow_add_users_to_lp']
1902
                ) {
1903
                    // Try group
1904
                    $is_visible = false;
1905
                    // Checking only the user visibility
1906
                    // @todo fix visibility
1907
                    $userVisibility = 1;
1908
                    if (1 == $userVisibility) {
1909
                        $is_visible = true;
1910
                    } else {
1911
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1912
                        if (!empty($userGroups)) {
1913
                            foreach ($userGroups as $groupInfo) {
1914
                                $groupId = $groupInfo['iid'];
1915
                                // @todo fix visibility.
1916
                                $userVisibility = 1;
1917
                                if (1 == $userVisibility) {
1918
                                    $is_visible = true;
1919
                                    break;
1920
                                }
1921
                            }
1922
                        }
1923
                    }
1924
                }
1925
            }
1926
1927
            return $is_visible;
1928
        }
1929
1930
        return true;
1931
    }
1932
1933
    /**
1934
     * @param int $lpId
1935
     * @param int $userId
1936
     * @param int $courseId
1937
     * @param int $sessionId
1938
     *
1939
     * @return int
1940
     */
1941
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1942
    {
1943
        $lpId = (int) $lpId;
1944
        $userId = (int) $userId;
1945
        $courseId = (int) $courseId;
1946
        $sessionId = (int) $sessionId;
1947
1948
        $sessionCondition = api_get_session_condition($sessionId);
1949
        $table = Database::get_course_table(TABLE_LP_VIEW);
1950
        $sql = "SELECT progress FROM $table
1951
                WHERE
1952
                    c_id = $courseId AND
1953
                    lp_id = $lpId AND
1954
                    user_id = $userId $sessionCondition ";
1955
        $res = Database::query($sql);
1956
1957
        $progress = 0;
1958
        if (Database::num_rows($res) > 0) {
1959
            $row = Database::fetch_array($res);
1960
            $progress = (int) $row['progress'];
1961
        }
1962
1963
        return $progress;
1964
    }
1965
1966
    /**
1967
     * @param array $lpList
1968
     * @param int   $userId
1969
     * @param int   $courseId
1970
     * @param int   $sessionId
1971
     *
1972
     * @return array
1973
     */
1974
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
1975
    {
1976
        $lpList = array_map('intval', $lpList);
1977
        if (empty($lpList)) {
1978
            return [];
1979
        }
1980
1981
        $lpList = implode("','", $lpList);
1982
1983
        $userId = (int) $userId;
1984
        $courseId = (int) $courseId;
1985
        $sessionId = (int) $sessionId;
1986
1987
        $sessionCondition = api_get_session_condition($sessionId);
1988
        $table = Database::get_course_table(TABLE_LP_VIEW);
1989
        $sql = "SELECT lp_id, progress FROM $table
1990
                WHERE
1991
                    c_id = $courseId AND
1992
                    lp_id IN ('".$lpList."') AND
1993
                    user_id = $userId $sessionCondition ";
1994
        $res = Database::query($sql);
1995
1996
        if (Database::num_rows($res) > 0) {
1997
            $list = [];
1998
            while ($row = Database::fetch_array($res)) {
1999
                $list[$row['lp_id']] = $row['progress'];
2000
            }
2001
2002
            return $list;
2003
        }
2004
2005
        return [];
2006
    }
2007
2008
    /**
2009
     * Displays a progress bar
2010
     * completed so far.
2011
     *
2012
     * @param int    $percentage Progress value to display
2013
     * @param string $text_add   Text to display near the progress value
2014
     *
2015
     * @return string HTML string containing the progress bar
2016
     */
2017
    public static function get_progress_bar($percentage = -1, $text_add = '')
2018
    {
2019
        $text = $percentage.$text_add;
2020
2021
        return '<div class="progress">
2022
            <div id="progress_bar_value"
2023
                class="progress-bar progress-bar-success" role="progressbar"
2024
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2025
            '.$text.'
2026
            </div>
2027
        </div>';
2028
    }
2029
2030
    /**
2031
     * @param string $mode can be '%' or 'abs'
2032
     *                     otherwise this value will be used $this->progress_bar_mode
2033
     *
2034
     * @return string
2035
     */
2036
    public function getProgressBar($mode = null)
2037
    {
2038
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2039
2040
        return self::get_progress_bar($percentage, $text_add);
2041
    }
2042
2043
    /**
2044
     * Gets the progress bar info to display inside the progress bar.
2045
     * Also used by scorm_api.php.
2046
     *
2047
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2048
     *                     we display a number of completed elements per total elements
2049
     * @param int    $add  Additional steps to fake as completed
2050
     *
2051
     * @return array Percentage or number and symbol (% or /xx)
2052
     */
2053
    public function get_progress_bar_text($mode = '', $add = 0)
2054
    {
2055
        if (empty($mode)) {
2056
            $mode = $this->progress_bar_mode;
2057
        }
2058
        $text = '';
2059
        $percentage = 0;
2060
        // If the option to use the score as progress is set for this learning
2061
        // path, then the rules are completely different: we assume only one
2062
        // item exists and the progress of the LP depends on the score
2063
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2064
        if (true === $scoreAsProgressSetting) {
2065
            $scoreAsProgress = $this->getUseScoreAsProgress();
2066
            if ($scoreAsProgress) {
2067
                // Get single item's score
2068
                $itemId = $this->get_current_item_id();
2069
                $item = $this->getItem($itemId);
2070
                $score = $item->get_score();
2071
                $maxScore = $item->get_max();
2072
                if ($mode = '%') {
2073
                    if (!empty($maxScore)) {
2074
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2075
                    }
2076
                    $percentage = number_format($percentage, 0);
2077
                    $text = '%';
2078
                } else {
2079
                    $percentage = $score;
2080
                    $text = '/'.$maxScore;
2081
                }
2082
2083
                return [$percentage, $text];
2084
            }
2085
        }
2086
        // otherwise just continue the normal processing of progress
2087
        $total_items = $this->getTotalItemsCountWithoutDirs();
2088
        $completeItems = $this->get_complete_items_count();
2089
        if (0 != $add) {
2090
            $completeItems += $add;
2091
        }
2092
        if ($completeItems > $total_items) {
2093
            $completeItems = $total_items;
2094
        }
2095
        if ('%' === $mode) {
2096
            if ($total_items > 0) {
2097
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2098
            }
2099
            $percentage = number_format($percentage, 0);
2100
            $text = '%';
2101
        } elseif ('abs' === $mode) {
2102
            $percentage = $completeItems;
2103
            $text = '/'.$total_items;
2104
        }
2105
2106
        return [
2107
            $percentage,
2108
            $text,
2109
        ];
2110
    }
2111
2112
    /**
2113
     * Gets the progress bar mode.
2114
     *
2115
     * @return string The progress bar mode attribute
2116
     */
2117
    public function get_progress_bar_mode()
2118
    {
2119
        if (!empty($this->progress_bar_mode)) {
2120
            return $this->progress_bar_mode;
2121
        }
2122
2123
        return '%';
2124
    }
2125
2126
    /**
2127
     * Gets the learnpath theme (remote or local).
2128
     *
2129
     * @return string Learnpath theme
2130
     */
2131
    public function get_theme()
2132
    {
2133
        if (!empty($this->theme)) {
2134
            return $this->theme;
2135
        }
2136
2137
        return '';
2138
    }
2139
2140
    /**
2141
     * Gets the learnpath session id.
2142
     *
2143
     * @return int
2144
     */
2145
    public function get_lp_session_id()
2146
    {
2147
        if (!empty($this->lp_session_id)) {
2148
            return (int) $this->lp_session_id;
2149
        }
2150
2151
        return 0;
2152
    }
2153
2154
    /**
2155
     * Generate a new prerequisites string for a given item. If this item was a sco and
2156
     * its prerequisites were strings (instead of IDs), then transform those strings into
2157
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2158
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2159
     * same rule as the scormExport() method.
2160
     *
2161
     * @param int $item_id Item ID
2162
     *
2163
     * @return string Prerequisites string ready for the export as SCORM
2164
     */
2165
    public function get_scorm_prereq_string($item_id)
2166
    {
2167
        if ($this->debug > 0) {
2168
            error_log('In learnpath::get_scorm_prereq_string()');
2169
        }
2170
        if (!is_object($this->items[$item_id])) {
2171
            return false;
2172
        }
2173
        /** @var learnpathItem $oItem */
2174
        $oItem = $this->items[$item_id];
2175
        $prereq = $oItem->get_prereq_string();
2176
2177
        if (empty($prereq)) {
2178
            return '';
2179
        }
2180
        if (preg_match('/^\d+$/', $prereq) &&
2181
            isset($this->items[$prereq]) &&
2182
            is_object($this->items[$prereq])
2183
        ) {
2184
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2185
            // then simply return it (with the ITEM_ prefix).
2186
            //return 'ITEM_' . $prereq;
2187
            return $this->items[$prereq]->ref;
2188
        } else {
2189
            if (isset($this->refs_list[$prereq])) {
2190
                // It's a simple string item from which the ID can be found in the refs list,
2191
                // so we can transform it directly to an ID for export.
2192
                return $this->items[$this->refs_list[$prereq]]->ref;
2193
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2194
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2195
            } else {
2196
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2197
                // and replace them, one by one, by the internal IDs (chamilo db)
2198
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2199
                // by a space as well.
2200
                $find = [
2201
                    '&',
2202
                    '|',
2203
                    '~',
2204
                    '=',
2205
                    '<>',
2206
                    '{',
2207
                    '}',
2208
                    '*',
2209
                    '(',
2210
                    ')',
2211
                ];
2212
                $replace = [
2213
                    ' ',
2214
                    ' ',
2215
                    ' ',
2216
                    ' ',
2217
                    ' ',
2218
                    ' ',
2219
                    ' ',
2220
                    ' ',
2221
                    ' ',
2222
                    ' ',
2223
                ];
2224
                $prereq_mod = str_replace($find, $replace, $prereq);
2225
                $ids = explode(' ', $prereq_mod);
2226
                foreach ($ids as $id) {
2227
                    $id = trim($id);
2228
                    if (isset($this->refs_list[$id])) {
2229
                        $prereq = preg_replace(
2230
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2231
                            'ITEM_'.$this->refs_list[$id],
2232
                            $prereq
2233
                        );
2234
                    }
2235
                }
2236
2237
                return $prereq;
2238
            }
2239
        }
2240
    }
2241
2242
    /**
2243
     * Returns the XML DOM document's node.
2244
     *
2245
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2246
     * @param string   $id       The identifier to look for
2247
     *
2248
     * @return mixed The reference to the element found with that identifier. False if not found
2249
     */
2250
    public function get_scorm_xml_node(&$children, $id)
2251
    {
2252
        for ($i = 0; $i < $children->length; $i++) {
2253
            $item_temp = $children->item($i);
2254
            if ('item' === $item_temp->nodeName) {
2255
                if ($item_temp->getAttribute('identifier') == $id) {
2256
                    return $item_temp;
2257
                }
2258
            }
2259
            $subchildren = $item_temp->childNodes;
2260
            if ($subchildren && $subchildren->length > 0) {
2261
                $val = $this->get_scorm_xml_node($subchildren, $id);
2262
                if (is_object($val)) {
2263
                    return $val;
2264
                }
2265
            }
2266
        }
2267
2268
        return false;
2269
    }
2270
2271
    /**
2272
     * Gets the status list for all LP's items.
2273
     *
2274
     * @return array Array of [index] => [item ID => current status]
2275
     */
2276
    public function get_items_status_list()
2277
    {
2278
        $list = [];
2279
        foreach ($this->ordered_items as $item_id) {
2280
            $list[] = [
2281
                $item_id => $this->items[$item_id]->get_status(),
2282
            ];
2283
        }
2284
2285
        return $list;
2286
    }
2287
2288
    /**
2289
     * Return the number of interactions for the given learnpath Item View ID.
2290
     * This method can be used as static.
2291
     *
2292
     * @param int $lp_iv_id  Item View ID
2293
     * @param int $course_id course id
2294
     *
2295
     * @return int
2296
     */
2297
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2298
    {
2299
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2300
        $lp_iv_id = (int) $lp_iv_id;
2301
        $course_id = (int) $course_id;
2302
2303
        $sql = "SELECT count(*) FROM $table
2304
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2305
        $res = Database::query($sql);
2306
        $num = 0;
2307
        if (Database::num_rows($res)) {
2308
            $row = Database::fetch_array($res);
2309
            $num = $row[0];
2310
        }
2311
2312
        return $num;
2313
    }
2314
2315
    /**
2316
     * Return the interactions as an array for the given lp_iv_id.
2317
     * This method can be used as static.
2318
     *
2319
     * @param int $lp_iv_id Learnpath Item View ID
2320
     *
2321
     * @return array
2322
     *
2323
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2324
     */
2325
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2326
    {
2327
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2328
        $list = [];
2329
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2330
        $lp_iv_id = (int) $lp_iv_id;
2331
2332
        if (empty($lp_iv_id) || empty($course_id)) {
2333
            return [];
2334
        }
2335
2336
        $sql = "SELECT * FROM $table
2337
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2338
                ORDER BY order_id ASC";
2339
        $res = Database::query($sql);
2340
        $num = Database::num_rows($res);
2341
        if ($num > 0) {
2342
            $list[] = [
2343
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2344
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2345
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2346
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2347
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2348
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2349
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2350
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2351
                'student_response_formatted' => '',
2352
            ];
2353
            while ($row = Database::fetch_array($res)) {
2354
                $studentResponseFormatted = urldecode($row['student_response']);
2355
                $content_student_response = explode('__|', $studentResponseFormatted);
2356
                if (count($content_student_response) > 0) {
2357
                    if (count($content_student_response) >= 3) {
2358
                        // Pop the element off the end of array.
2359
                        array_pop($content_student_response);
2360
                    }
2361
                    $studentResponseFormatted = implode(',', $content_student_response);
2362
                }
2363
2364
                $list[] = [
2365
                    'order_id' => $row['order_id'] + 1,
2366
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2367
                    'type' => $row['interaction_type'],
2368
                    'time' => $row['completion_time'],
2369
                    'correct_responses' => '', // Hide correct responses from students.
2370
                    'student_response' => $row['student_response'],
2371
                    'result' => $row['result'],
2372
                    'latency' => $row['latency'],
2373
                    'student_response_formatted' => $studentResponseFormatted,
2374
                ];
2375
            }
2376
        }
2377
2378
        return $list;
2379
    }
2380
2381
    /**
2382
     * Return the number of objectives for the given learnpath Item View ID.
2383
     * This method can be used as static.
2384
     *
2385
     * @param int $lp_iv_id  Item View ID
2386
     * @param int $course_id Course ID
2387
     *
2388
     * @return int Number of objectives
2389
     */
2390
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2391
    {
2392
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2393
        $course_id = (int) $course_id;
2394
        $lp_iv_id = (int) $lp_iv_id;
2395
        $sql = "SELECT count(*) FROM $table
2396
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2397
        //@todo seems that this always returns 0
2398
        $res = Database::query($sql);
2399
        $num = 0;
2400
        if (Database::num_rows($res)) {
2401
            $row = Database::fetch_array($res);
2402
            $num = $row[0];
2403
        }
2404
2405
        return $num;
2406
    }
2407
2408
    /**
2409
     * Return the objectives as an array for the given lp_iv_id.
2410
     * This method can be used as static.
2411
     *
2412
     * @param int $lpItemViewId Learnpath Item View ID
2413
     * @param int $course_id
2414
     *
2415
     * @return array
2416
     *
2417
     * @todo    Translate labels
2418
     */
2419
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2420
    {
2421
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2422
        $lpItemViewId = (int) $lpItemViewId;
2423
2424
        if (empty($course_id) || empty($lpItemViewId)) {
2425
            return [];
2426
        }
2427
2428
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2429
        $sql = "SELECT * FROM $table
2430
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2431
                ORDER BY order_id ASC";
2432
        $res = Database::query($sql);
2433
        $num = Database::num_rows($res);
2434
        $list = [];
2435
        if ($num > 0) {
2436
            $list[] = [
2437
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2438
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2439
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2440
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2441
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2442
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2443
            ];
2444
            while ($row = Database::fetch_array($res)) {
2445
                $list[] = [
2446
                    'order_id' => $row['order_id'] + 1,
2447
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2448
                    'score_raw' => $row['score_raw'],
2449
                    'score_max' => $row['score_max'],
2450
                    'score_min' => $row['score_min'],
2451
                    'status' => $row['status'],
2452
                ];
2453
            }
2454
        }
2455
2456
        return $list;
2457
    }
2458
2459
    /**
2460
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2461
     * used by get_html_toc() to be ready to display.
2462
     *
2463
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2464
     */
2465
    public function get_toc()
2466
    {
2467
        $toc = [];
2468
        foreach ($this->ordered_items as $item_id) {
2469
            // TODO: Change this link generation and use new function instead.
2470
            $toc[] = [
2471
                'id' => $item_id,
2472
                'title' => $this->items[$item_id]->get_title(),
2473
                'status' => $this->items[$item_id]->get_status(),
2474
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status()),
2475
                'level' => $this->items[$item_id]->get_level(),
2476
                'type' => $this->items[$item_id]->get_type(),
2477
                'description' => $this->items[$item_id]->get_description(),
2478
                'path' => $this->items[$item_id]->get_path(),
2479
                'parent' => $this->items[$item_id]->get_parent(),
2480
            ];
2481
        }
2482
2483
        return $toc;
2484
    }
2485
2486
    /**
2487
     * Returns the CSS class name associated with a given item status.
2488
     *
2489
     * @param $status string an item status
2490
     *
2491
     * @return string CSS class name
2492
     */
2493
    public static function getStatusCSSClassName($status)
2494
    {
2495
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2496
            return self::STATUS_CSS_CLASS_NAME[$status];
2497
        }
2498
2499
        return '';
2500
    }
2501
2502
    /**
2503
     * Generate and return the table of contents for this learnpath. The JS
2504
     * table returned is used inside of scorm_api.php.
2505
     *
2506
     * @param string $varname
2507
     *
2508
     * @return string A JS array variable construction
2509
     */
2510
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2511
    {
2512
        $toc = $varname.' = new Array();';
2513
        foreach ($this->ordered_items as $item_id) {
2514
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2515
        }
2516
2517
        return $toc;
2518
    }
2519
2520
    /**
2521
     * Gets the learning path type.
2522
     *
2523
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2524
     *
2525
     * @return mixed Type ID or name, depending on the parameter
2526
     */
2527
    public function get_type($get_name = false)
2528
    {
2529
        $res = false;
2530
        if (!empty($this->type) && (!$get_name)) {
2531
            $res = $this->type;
2532
        }
2533
2534
        return $res;
2535
    }
2536
2537
    /**
2538
     * Gets the learning path type as static method.
2539
     *
2540
     * @param int $lp_id
2541
     *
2542
     * @return mixed Type ID or name, depending on the parameter
2543
     */
2544
    public static function get_type_static($lp_id = 0)
2545
    {
2546
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2547
        $lp_id = (int) $lp_id;
2548
        $sql = "SELECT lp_type FROM $tbl_lp
2549
                WHERE iid = $lp_id";
2550
        $res = Database::query($sql);
2551
        if (false === $res) {
2552
            return null;
2553
        }
2554
        if (Database::num_rows($res) <= 0) {
2555
            return null;
2556
        }
2557
        $row = Database::fetch_array($res);
2558
2559
        return $row['lp_type'];
2560
    }
2561
2562
    /**
2563
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2564
     * This method can be used as abstract and is recursive.
2565
     *
2566
     * @param CLp $lp
2567
     * @param int $parent    Parent ID of the items to look for
2568
     *
2569
     * @return array Ordered list of item IDs (empty array on error)
2570
     */
2571
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2572
    {
2573
        $parent = (int) $parent;
2574
2575
        $criteria = Criteria::create()
2576
            ->orderBy(
2577
                [
2578
                    'displayOrder' => Criteria::ASC,
2579
                ]
2580
            );
2581
        $items = $lp->getItems()->matching($criteria);
2582
        $items = $items->filter(
2583
            function (CLpItem $element) use ($parent) {
2584
                if (empty($parent)) {
2585
                    $parent = null;
2586
                    return $element->getParent() === $parent;
2587
                } else {
2588
                    if (null !== $element->getParent()) {
2589
                        return $element->getParent()->getIid() === $parent;
2590
                    }
2591
                    return false;
2592
                }
2593
            }
2594
        );
2595
2596
        /*
2597
         $lp = (int) $lp;
2598
        $parent = (int) $parent;
2599
2600
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2601
        $sql = "SELECT iid FROM $tbl_lp_item
2602
                WHERE lp_id = $lp AND parent_item_id = $parent
2603
                ORDER BY display_order";
2604
2605
        $res = Database::query($sql);
2606
        while ($row = Database::fetch_array($res)) {
2607
            $sublist = self::get_flat_ordered_items_list(
2608
                $lp,
2609
                $row['iid'],
2610
                $course_id
2611
            );
2612
            $list[] = $row['iid'];
2613
            foreach ($sublist as $item) {
2614
                $list[] = $item;
2615
            }
2616
        }
2617
        */
2618
2619
        $list = [];
2620
        foreach ($items as $item) {
2621
            $itemId = $item->getIid();
2622
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2623
            $list[] = $itemId;
2624
            foreach ($sublist as $subItem) {
2625
                $list[] = $subItem;
2626
            }
2627
        }
2628
2629
        return $list;
2630
    }
2631
2632
    public static function getChapterTypes(): array
2633
    {
2634
        return [
2635
            'dir',
2636
        ];
2637
    }
2638
2639
    /**
2640
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2641
     *
2642
     * @param array $toc_list
2643
     *
2644
     * @return array HTML TOC ready to display
2645
     */
2646
    public function getListArrayToc($toc_list = [])
2647
    {
2648
        $lpItemRepo = Container::getLpItemRepository();
2649
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
2650
        $options = [
2651
            'decorate' => false,
2652
        ];
2653
        $list = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2654
2655
        return $list;
2656
2657
2658
        if (empty($toc_list)) {
0 ignored issues
show
Unused Code introduced by
IfNode 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...
2659
            $toc_list = $this->get_toc();
2660
        }
2661
        // Temporary variables.
2662
        $currentItemId = $this->get_current_item_id();
2663
        $list = [];
2664
        $arrayList = [];
2665
2666
        foreach ($toc_list as $item) {
2667
            $list['id'] = $item['id'];
2668
            $list['status'] = $item['status'];
2669
            $cssStatus = null;
2670
2671
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
2672
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
2673
            }
2674
2675
            $classStyle = ' ';
2676
            $dirTypes = self::getChapterTypes();
2677
2678
            if (in_array($item['type'], $dirTypes)) {
2679
                $classStyle = 'scorm_item_section ';
2680
            }
2681
            if ($item['id'] == $this->current) {
2682
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
2683
            } elseif (!in_array($item['type'], $dirTypes)) {
2684
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
2685
            }
2686
            $title = $item['title'];
2687
            if (empty($title)) {
2688
                $title = self::rl_get_resource_name(
2689
                    api_get_course_id(),
2690
                    $this->get_id(),
2691
                    $item['id']
2692
                );
2693
            }
2694
            $title = Security::remove_XSS($item['title']);
2695
2696
            if (empty($item['description'])) {
2697
                $list['description'] = $title;
2698
            } else {
2699
                $list['description'] = $item['description'];
2700
            }
2701
2702
            $list['class'] = $classStyle.' '.$cssStatus;
2703
            $list['level'] = $item['level'];
2704
            $list['type'] = $item['type'];
2705
2706
            if (in_array($item['type'], $dirTypes)) {
2707
                $list['css_level'] = 'level_'.$item['level'];
2708
            } else {
2709
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
2710
            }
2711
2712
            if (in_array($item['type'], $dirTypes)) {
2713
                $list['title'] = stripslashes($title);
2714
            } else {
2715
                $list['title'] = stripslashes($title);
2716
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
2717
                $list['current_id'] = $currentItemId;
2718
            }
2719
            $arrayList[] = $list;
2720
        }
2721
2722
        return $arrayList;
2723
    }
2724
2725
    /**
2726
     * Returns an HTML-formatted string ready to display with teacher buttons
2727
     * in LP view menu.
2728
     *
2729
     * @return string HTML TOC ready to display
2730
     */
2731
    public function get_teacher_toc_buttons()
2732
    {
2733
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2734
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2735
        $html = '';
2736
        if ($isAllow && false == $hideIcons) {
2737
            if ($this->get_lp_session_id() == api_get_session_id()) {
2738
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2739
                $html .= '<div class="flex flex-row justify-center">';
2740
                $html .= "<a
2741
                    class='btn btn-sm btn-default'
2742
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2743
                    target='_parent'>".
2744
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
2745
                $html .= "<a
2746
                    class='btn btn-sm btn-default'
2747
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2748
                    target='_parent'>".
2749
                    Display::returnFontAwesomeIcon('pencil-alt').get_lang('Edit')."</a>";
2750
                $html .= '<a
2751
                    class="btn btn-sm btn-default"
2752
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2753
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
2754
                $html .= '</div>';
2755
                $html .= '</div>';
2756
            }
2757
        }
2758
2759
        return $html;
2760
    }
2761
2762
    /**
2763
     * Gets the learnpath name/title.
2764
     *
2765
     * @return string Learnpath name/title
2766
     */
2767
    public function get_name()
2768
    {
2769
        if (!empty($this->name)) {
2770
            return $this->name;
2771
        }
2772
2773
        return 'N/A';
2774
    }
2775
2776
    /**
2777
     * @return string
2778
     */
2779
    public function getNameNoTags()
2780
    {
2781
        return strip_tags($this->get_name());
2782
    }
2783
2784
    /**
2785
     * Gets a link to the resource from the present location, depending on item ID.
2786
     *
2787
     * @param string $type         Type of link expected
2788
     * @param int    $item_id      Learnpath item ID
2789
     * @param bool   $provided_toc
2790
     *
2791
     * @return string $provided_toc Link to the lp_item resource
2792
     */
2793
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2794
    {
2795
        $course_id = $this->get_course_int_id();
2796
        $item_id = (int) $item_id;
2797
2798
        if (empty($item_id)) {
2799
            $item_id = $this->get_current_item_id();
2800
2801
            if (empty($item_id)) {
2802
                //still empty, this means there was no item_id given and we are not in an object context or
2803
                //the object property is empty, return empty link
2804
                $this->first();
2805
2806
                return '';
2807
            }
2808
        }
2809
2810
        $file = '';
2811
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2812
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2813
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2814
2815
        $sql = "SELECT
2816
                    l.lp_type as ltype,
2817
                    l.path as lpath,
2818
                    li.item_type as litype,
2819
                    li.path as lipath,
2820
                    li.parameters as liparams
2821
        		FROM $lp_table l
2822
                INNER JOIN $lp_item_table li
2823
                ON (li.lp_id = l.iid)
2824
        		WHERE
2825
        		    li.iid = $item_id
2826
        		";
2827
        $res = Database::query($sql);
2828
        if (Database::num_rows($res) > 0) {
2829
            $row = Database::fetch_array($res);
2830
            $lp_type = $row['ltype'];
2831
            $lp_path = $row['lpath'];
2832
            $lp_item_type = $row['litype'];
2833
            $lp_item_path = $row['lipath'];
2834
            $lp_item_params = $row['liparams'];
2835
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2836
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2837
            }
2838
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2839
            if ('http' === $type) {
2840
                //web path
2841
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2842
            } else {
2843
                //$course_path = $sys_course_path; //system path
2844
            }
2845
2846
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2847
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2848
            if (in_array(
2849
                $lp_item_type,
2850
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2851
            )
2852
            ) {
2853
                $lp_type = CLp::LP_TYPE;
2854
            }
2855
2856
            // Now go through the specific cases to get the end of the path
2857
            // @todo Use constants instead of int values.
2858
            switch ($lp_type) {
2859
                case CLp::LP_TYPE:
2860
                    $file = self::rl_get_resource_link_for_learnpath(
2861
                        $course_id,
2862
                        $this->get_id(),
2863
                        $item_id,
2864
                        $this->get_view_id()
2865
                    );
2866
                    switch ($lp_item_type) {
2867
                        case 'document':
2868
                            // Shows a button to download the file instead of just downloading the file directly.
2869
                            $documentPathInfo = pathinfo($file);
2870
                            if (isset($documentPathInfo['extension'])) {
2871
                                $parsed = parse_url($documentPathInfo['extension']);
2872
                                if (isset($parsed['path'])) {
2873
                                    $extension = $parsed['path'];
2874
                                    $extensionsToDownload = [
2875
                                        'zip',
2876
                                        'ppt',
2877
                                        'pptx',
2878
                                        'ods',
2879
                                        'xlsx',
2880
                                        'xls',
2881
                                        'csv',
2882
                                        'doc',
2883
                                        'docx',
2884
                                        'dot',
2885
                                    ];
2886
2887
                                    if (in_array($extension, $extensionsToDownload)) {
2888
                                        $file = api_get_path(WEB_CODE_PATH).
2889
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2890
                                    }
2891
                                }
2892
                            }
2893
                            break;
2894
                        case 'dir':
2895
                            $file = 'lp_content.php?type=dir';
2896
                            break;
2897
                        case 'link':
2898
                            if (Link::is_youtube_link($file)) {
2899
                                $src = Link::get_youtube_video_id($file);
2900
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2901
                            } elseif (Link::isVimeoLink($file)) {
2902
                                $src = Link::getVimeoLinkId($file);
2903
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2904
                            } else {
2905
                                // If the current site is HTTPS and the link is
2906
                                // HTTP, browsers will refuse opening the link
2907
                                $urlId = api_get_current_access_url_id();
2908
                                $url = api_get_access_url($urlId, false);
2909
                                $protocol = substr($url['url'], 0, 5);
2910
                                if ('https' === $protocol) {
2911
                                    $linkProtocol = substr($file, 0, 5);
2912
                                    if ('http:' === $linkProtocol) {
2913
                                        //this is the special intervention case
2914
                                        $file = api_get_path(WEB_CODE_PATH).
2915
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2916
                                    }
2917
                                }
2918
                            }
2919
                            break;
2920
                        case 'quiz':
2921
                            // Check how much attempts of a exercise exits in lp
2922
                            $lp_item_id = $this->get_current_item_id();
2923
                            $lp_view_id = $this->get_view_id();
2924
2925
                            $prevent_reinit = null;
2926
                            if (isset($this->items[$this->current])) {
2927
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2928
                            }
2929
2930
                            if (empty($provided_toc)) {
2931
                                $list = $this->get_toc();
2932
                            } else {
2933
                                $list = $provided_toc;
2934
                            }
2935
2936
                            $type_quiz = false;
2937
                            foreach ($list as $toc) {
2938
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2939
                                    $type_quiz = true;
2940
                                }
2941
                            }
2942
2943
                            if ($type_quiz) {
2944
                                $lp_item_id = (int) $lp_item_id;
2945
                                $lp_view_id = (int) $lp_view_id;
2946
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2947
                                        WHERE
2948
                                            lp_item_id='".$lp_item_id."' AND
2949
                                            lp_view_id ='".$lp_view_id."' AND
2950
                                            status='completed'";
2951
                                $result = Database::query($sql);
2952
                                $row_count = Database:: fetch_row($result);
2953
                                $count_item_view = (int) $row_count[0];
2954
                                $not_multiple_attempt = 0;
2955
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2956
                                    $not_multiple_attempt = 1;
2957
                                }
2958
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2959
                            }
2960
                            break;
2961
                    }
2962
2963
                    $tmp_array = explode('/', $file);
2964
                    $document_name = $tmp_array[count($tmp_array) - 1];
2965
                    if (strpos($document_name, '_DELETED_')) {
2966
                        $file = 'blank.php?error=document_deleted';
2967
                    }
2968
                    break;
2969
                case CLp::SCORM_TYPE:
2970
                    if ('dir' !== $lp_item_type) {
2971
                        // Quite complex here:
2972
                        // We want to make sure 'http://' (and similar) links can
2973
                        // be loaded as is (withouth the Chamilo path in front) but
2974
                        // some contents use this form: resource.htm?resource=http://blablabla
2975
                        // which means we have to find a protocol at the path's start, otherwise
2976
                        // it should not be considered as an external URL.
2977
                        // if ($this->prerequisites_match($item_id)) {
2978
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
2979
                            if ($this->debug > 2) {
2980
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
2981
                            }
2982
                            // Distant url, return as is.
2983
                            $file = $lp_item_path;
2984
                        } else {
2985
                            if ($this->debug > 2) {
2986
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
2987
                            }
2988
                            // Prevent getting untranslatable urls.
2989
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
2990
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
2991
2992
                            /*$asset = $this->getEntity()->getAsset();
2993
                            $folder = Container::getAssetRepository()->getFolder($asset);
2994
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
2995
                            $file = null;
2996
                            if ($hasFile) {
2997
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
2998
                            }*/
2999
                            $file = $this->scormUrl.$lp_item_path;
3000
3001
                            // Prepare the path.
3002
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3003
                            // TODO: Fix this for urls with protocol header.
3004
                            $file = str_replace('//', '/', $file);
3005
                            $file = str_replace(':/', '://', $file);
3006
                            if ('/' === substr($lp_path, -1)) {
3007
                                $lp_path = substr($lp_path, 0, -1);
3008
                            }*/
3009
                            /*if (!$hasFile) {
3010
                                // if file not found.
3011
                                $decoded = html_entity_decode($lp_item_path);
3012
                                [$decoded] = explode('?', $decoded);
3013
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3014
                                    $file = self::rl_get_resource_link_for_learnpath(
3015
                                        $course_id,
3016
                                        $this->get_id(),
3017
                                        $item_id,
3018
                                        $this->get_view_id()
3019
                                    );
3020
                                    if (empty($file)) {
3021
                                        $file = 'blank.php?error=document_not_found';
3022
                                    } else {
3023
                                        $tmp_array = explode('/', $file);
3024
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3025
                                        if (strpos($document_name, '_DELETED_')) {
3026
                                            $file = 'blank.php?error=document_deleted';
3027
                                        } else {
3028
                                            $file = 'blank.php?error=document_not_found';
3029
                                        }
3030
                                    }
3031
                                } else {
3032
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3033
                                }
3034
                            }*/
3035
                        }
3036
3037
                        // We want to use parameters if they were defined in the imsmanifest
3038
                        if (false === strpos($file, 'blank.php')) {
3039
                            $lp_item_params = ltrim($lp_item_params, '?');
3040
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3041
                        }
3042
                    } else {
3043
                        $file = 'lp_content.php?type=dir';
3044
                    }
3045
                    break;
3046
                case CLp::AICC_TYPE:
3047
                    // Formatting AICC HACP append URL.
3048
                    $aicc_append = '?aicc_sid='.
3049
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3050
                    if (!empty($lp_item_params)) {
3051
                        $aicc_append .= $lp_item_params.'&';
3052
                    }
3053
                    if ('dir' !== $lp_item_type) {
3054
                        // Quite complex here:
3055
                        // We want to make sure 'http://' (and similar) links can
3056
                        // be loaded as is (withouth the Chamilo path in front) but
3057
                        // some contents use this form: resource.htm?resource=http://blablabla
3058
                        // which means we have to find a protocol at the path's start, otherwise
3059
                        // it should not be considered as an external URL.
3060
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3061
                            if ($this->debug > 2) {
3062
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3063
                            }
3064
                            // Distant url, return as is.
3065
                            $file = $lp_item_path;
3066
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3067
                            /*
3068
                            if (stristr($file,'<servername>') !== false) {
3069
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3070
                            }
3071
                            */
3072
                            if (false !== stripos($file, '<servername>')) {
3073
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3074
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3075
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3076
                            }
3077
3078
                            $file .= $aicc_append;
3079
                        } else {
3080
                            if ($this->debug > 2) {
3081
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3082
                            }
3083
                            // Prevent getting untranslatable urls.
3084
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3085
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3086
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3087
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3088
                            // TODO: Fix this for urls with protocol header.
3089
                            $file = str_replace('//', '/', $file);
3090
                            $file = str_replace(':/', '://', $file);
3091
                            $file .= $aicc_append;
3092
                        }
3093
                    } else {
3094
                        $file = 'lp_content.php?type=dir';
3095
                    }
3096
                    break;
3097
                case 4:
3098
                default:
3099
                    break;
3100
            }
3101
            // Replace &amp; by & because &amp; will break URL with params
3102
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3103
        }
3104
        if ($this->debug > 2) {
3105
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3106
        }
3107
3108
        return $file;
3109
    }
3110
3111
    /**
3112
     * Gets the latest usable view or generate a new one.
3113
     *
3114
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3115
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3116
     *
3117
     * @return int DB lp_view id
3118
     */
3119
    public function get_view($attempt_num = 0, $userId = null)
3120
    {
3121
        $search = '';
3122
        $attempt_num = (int) $attempt_num;
3123
        // Use $attempt_num to enable multi-views management (disabled so far).
3124
        if (!empty($attempt_num)) {
3125
            $search = 'AND view_count = '.$attempt_num;
3126
        }
3127
3128
        $course_id = api_get_course_int_id();
3129
        $sessionId = api_get_session_id();
3130
3131
        // Check user ID.
3132
        if (empty($userId)) {
3133
            if (empty($this->get_user_id())) {
3134
                $this->error = 'User ID is empty in learnpath::get_view()';
3135
3136
                return null;
3137
            } else {
3138
                $userId = $this->get_user_id();
3139
            }
3140
        }
3141
        $sessionCondition = api_get_session_condition($sessionId);
3142
3143
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3144
        $table = Database::get_course_table(TABLE_LP_VIEW);
3145
        $sql = "SELECT iid FROM $table
3146
        		WHERE
3147
        		    c_id = $course_id AND
3148
        		    lp_id = ".$this->get_id()." AND
3149
        		    user_id = ".$userId."
3150
        		    $sessionCondition
3151
        		    $search
3152
                ORDER BY view_count DESC";
3153
        $res = Database::query($sql);
3154
        if (Database::num_rows($res) > 0) {
3155
            $row = Database::fetch_array($res);
3156
            $this->lp_view_id = $row['iid'];
3157
        } elseif (!api_is_invitee()) {
3158
            $params = [
3159
                'c_id' => $course_id,
3160
                'lp_id' => $this->get_id(),
3161
                'user_id' => $this->get_user_id(),
3162
                'view_count' => 1,
3163
                'last_item' => 0,
3164
            ];
3165
            if (!empty($sessionId)) {
3166
                $params['session_id']  = $sessionId;
3167
            }
3168
            $this->lp_view_id = Database::insert($table, $params);
3169
        }
3170
3171
        return $this->lp_view_id;
3172
    }
3173
3174
    /**
3175
     * Gets the current view id.
3176
     *
3177
     * @return int View ID (from lp_view)
3178
     */
3179
    public function get_view_id()
3180
    {
3181
        if (!empty($this->lp_view_id)) {
3182
            return (int) $this->lp_view_id;
3183
        }
3184
3185
        return 0;
3186
    }
3187
3188
    /**
3189
     * Gets the update queue.
3190
     *
3191
     * @return array Array containing IDs of items to be updated by JavaScript
3192
     */
3193
    public function get_update_queue()
3194
    {
3195
        return $this->update_queue;
3196
    }
3197
3198
    /**
3199
     * Gets the user ID.
3200
     *
3201
     * @return int User ID
3202
     */
3203
    public function get_user_id()
3204
    {
3205
        if (!empty($this->user_id)) {
3206
            return (int) $this->user_id;
3207
        }
3208
3209
        return false;
3210
    }
3211
3212
    /**
3213
     * Checks if any of the items has an audio element attached.
3214
     *
3215
     * @return bool True or false
3216
     */
3217
    public function has_audio()
3218
    {
3219
        $has = false;
3220
        foreach ($this->items as $i => $item) {
3221
            if (!empty($this->items[$i]->audio)) {
3222
                $has = true;
3223
                break;
3224
            }
3225
        }
3226
3227
        return $has;
3228
    }
3229
3230
    /**
3231
     * Moves an item up and down at its level.
3232
     *
3233
     * @param int    $id        Item to move up and down
3234
     * @param string $direction Direction 'up' or 'down'
3235
     *
3236
     * @return bool|int
3237
     */
3238
    public function move_item($id, $direction)
3239
    {
3240
        $course_id = api_get_course_int_id();
3241
        if (empty($id) || empty($direction)) {
3242
            return false;
3243
        }
3244
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3245
        $sql_sel = "SELECT *
3246
                    FROM $tbl_lp_item
3247
                    WHERE
3248
                        iid = $id
3249
                    ";
3250
        $res_sel = Database::query($sql_sel);
3251
        // Check if elem exists.
3252
        if (Database::num_rows($res_sel) < 1) {
3253
            return false;
3254
        }
3255
        // Gather data.
3256
        $row = Database::fetch_array($res_sel);
3257
        $previous = $row['previous_item_id'];
3258
        $next = $row['next_item_id'];
3259
        $display = $row['display_order'];
3260
        $parent = $row['parent_item_id'];
3261
        $lp = $row['lp_id'];
3262
        // Update the item (switch with previous/next one).
3263
        switch ($direction) {
3264
            case 'up':
3265
                if ($display > 1) {
3266
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3267
                                 WHERE iid = $previous";
3268
                    $res_sel2 = Database::query($sql_sel2);
3269
                    if (Database::num_rows($res_sel2) < 1) {
3270
                        $previous_previous = 0;
3271
                    }
3272
                    // Gather data.
3273
                    $row2 = Database::fetch_array($res_sel2);
3274
                    $previous_previous = $row2['previous_item_id'];
3275
                    // Update previous_previous item (switch "next" with current).
3276
                    if (0 != $previous_previous) {
3277
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3278
                                        next_item_id = $id
3279
                                    WHERE iid = $previous_previous";
3280
                        Database::query($sql_upd2);
3281
                    }
3282
                    // Update previous item (switch with current).
3283
                    if (0 != $previous) {
3284
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3285
                                    next_item_id = $next,
3286
                                    previous_item_id = $id,
3287
                                    display_order = display_order +1
3288
                                    WHERE iid = $previous";
3289
                        Database::query($sql_upd2);
3290
                    }
3291
3292
                    // Update current item (switch with previous).
3293
                    if (0 != $id) {
3294
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3295
                                        next_item_id = $previous,
3296
                                        previous_item_id = $previous_previous,
3297
                                        display_order = display_order-1
3298
                                    WHERE c_id = ".$course_id." AND id = $id";
3299
                        Database::query($sql_upd2);
3300
                    }
3301
                    // Update next item (new previous item).
3302
                    if (!empty($next)) {
3303
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3304
                                     WHERE iid = $next";
3305
                        Database::query($sql_upd2);
3306
                    }
3307
                    $display = $display - 1;
3308
                }
3309
                break;
3310
            case 'down':
3311
                if (0 != $next) {
3312
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3313
                                 WHERE iid = $next";
3314
                    $res_sel2 = Database::query($sql_sel2);
3315
                    if (Database::num_rows($res_sel2) < 1) {
3316
                        $next_next = 0;
3317
                    }
3318
                    // Gather data.
3319
                    $row2 = Database::fetch_array($res_sel2);
3320
                    $next_next = $row2['next_item_id'];
3321
                    // Update previous item (switch with current).
3322
                    if (0 != $previous) {
3323
                        $sql_upd2 = "UPDATE $tbl_lp_item
3324
                                     SET next_item_id = $next
3325
                                     WHERE iid = $previous";
3326
                        Database::query($sql_upd2);
3327
                    }
3328
                    // Update current item (switch with previous).
3329
                    if (0 != $id) {
3330
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3331
                                     previous_item_id = $next,
3332
                                     next_item_id = $next_next,
3333
                                     display_order = display_order + 1
3334
                                     WHERE iid = $id";
3335
                        Database::query($sql_upd2);
3336
                    }
3337
3338
                    // Update next item (new previous item).
3339
                    if (0 != $next) {
3340
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3341
                                     previous_item_id = $previous,
3342
                                     next_item_id = $id,
3343
                                     display_order = display_order-1
3344
                                     WHERE iid = $next";
3345
                        Database::query($sql_upd2);
3346
                    }
3347
3348
                    // Update next_next item (switch "previous" with current).
3349
                    if (0 != $next_next) {
3350
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3351
                                     previous_item_id = $id
3352
                                     WHERE iid = $next_next";
3353
                        Database::query($sql_upd2);
3354
                    }
3355
                    $display = $display + 1;
3356
                }
3357
                break;
3358
            default:
3359
                return false;
3360
        }
3361
3362
        return $display;
3363
    }
3364
3365
    /**
3366
     * Move a LP up (display_order).
3367
     *
3368
     * @param int $lp_id      Learnpath ID
3369
     * @param int $categoryId Category ID
3370
     *
3371
     * @return bool
3372
     */
3373
    public static function move_up($lp_id, $categoryId = 0)
3374
    {
3375
        $courseId = api_get_course_int_id();
3376
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3377
3378
        $categoryCondition = '';
3379
        if (!empty($categoryId)) {
3380
            $categoryId = (int) $categoryId;
3381
            $categoryCondition = " AND category_id = $categoryId";
3382
        }
3383
        $sql = "SELECT * FROM $lp_table
3384
                WHERE c_id = $courseId
3385
                $categoryCondition
3386
                ORDER BY display_order";
3387
        $res = Database::query($sql);
3388
        if (false === $res) {
3389
            return false;
3390
        }
3391
3392
        $lps = [];
3393
        $lp_order = [];
3394
        $num = Database::num_rows($res);
3395
        // First check the order is correct, globally (might be wrong because
3396
        // of versions < 1.8.4)
3397
        if ($num > 0) {
3398
            $i = 1;
3399
            while ($row = Database::fetch_array($res)) {
3400
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3401
                    $sql = "UPDATE $lp_table SET display_order = $i
3402
                            WHERE iid = ".$row['iid'];
3403
                    Database::query($sql);
3404
                }
3405
                $row['display_order'] = $i;
3406
                $lps[$row['iid']] = $row;
3407
                $lp_order[$i] = $row['iid'];
3408
                $i++;
3409
            }
3410
        }
3411
        if ($num > 1) { // If there's only one element, no need to sort.
3412
            $order = $lps[$lp_id]['display_order'];
3413
            if ($order > 1) { // If it's the first element, no need to move up.
3414
                $sql = "UPDATE $lp_table SET display_order = $order
3415
                        WHERE iid = ".$lp_order[$order - 1];
3416
                Database::query($sql);
3417
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3418
                        WHERE iid = $lp_id";
3419
                Database::query($sql);
3420
            }
3421
        }
3422
3423
        return true;
3424
    }
3425
3426
    /**
3427
     * Move a learnpath down (display_order).
3428
     *
3429
     * @param int $lp_id      Learnpath ID
3430
     * @param int $categoryId Category ID
3431
     *
3432
     * @return bool
3433
     */
3434
    public static function move_down($lp_id, $categoryId = 0)
3435
    {
3436
        $courseId = api_get_course_int_id();
3437
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3438
3439
        $categoryCondition = '';
3440
        if (!empty($categoryId)) {
3441
            $categoryId = (int) $categoryId;
3442
            $categoryCondition = " AND category_id = $categoryId";
3443
        }
3444
3445
        $sql = "SELECT * FROM $lp_table
3446
                WHERE c_id = $courseId
3447
                $categoryCondition
3448
                ORDER BY display_order";
3449
        $res = Database::query($sql);
3450
        if (false === $res) {
3451
            return false;
3452
        }
3453
        $lps = [];
3454
        $lp_order = [];
3455
        $num = Database::num_rows($res);
3456
        $max = 0;
3457
        // First check the order is correct, globally (might be wrong because
3458
        // of versions < 1.8.4).
3459
        if ($num > 0) {
3460
            $i = 1;
3461
            while ($row = Database::fetch_array($res)) {
3462
                $max = $i;
3463
                if ($row['display_order'] != $i) {
3464
                    // If we find a gap in the order, we need to fix it.
3465
                    $sql = "UPDATE $lp_table SET display_order = $i
3466
                              WHERE iid = ".$row['iid'];
3467
                    Database::query($sql);
3468
                }
3469
                $row['display_order'] = $i;
3470
                $lps[$row['iid']] = $row;
3471
                $lp_order[$i] = $row['iid'];
3472
                $i++;
3473
            }
3474
        }
3475
        if ($num > 1) { // If there's only one element, no need to sort.
3476
            $order = $lps[$lp_id]['display_order'];
3477
            if ($order < $max) { // If it's the first element, no need to move up.
3478
                $sql = "UPDATE $lp_table SET display_order = $order
3479
                        WHERE iid = ".$lp_order[$order + 1];
3480
                Database::query($sql);
3481
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
3482
                        WHERE iid = $lp_id";
3483
                Database::query($sql);
3484
            }
3485
        }
3486
3487
        return true;
3488
    }
3489
3490
    /**
3491
     * Updates learnpath attributes to point to the next element
3492
     * The last part is similar to set_current_item but processing the other way around.
3493
     */
3494
    public function next()
3495
    {
3496
        if ($this->debug > 0) {
3497
            error_log('In learnpath::next()', 0);
3498
        }
3499
        $this->last = $this->get_current_item_id();
3500
        $this->items[$this->last]->save(
3501
            false,
3502
            $this->prerequisites_match($this->last)
3503
        );
3504
        $this->autocomplete_parents($this->last);
3505
        $new_index = $this->get_next_index();
3506
        if ($this->debug > 2) {
3507
            error_log('New index: '.$new_index, 0);
3508
        }
3509
        $this->index = $new_index;
3510
        if ($this->debug > 2) {
3511
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3512
        }
3513
        $this->current = $this->ordered_items[$new_index];
3514
        if ($this->debug > 2) {
3515
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3516
        }
3517
    }
3518
3519
    /**
3520
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3521
     * class, this might be redefined to allow several behaviours depending on the document type.
3522
     *
3523
     * @param int $id Resource ID
3524
     */
3525
    public function open($id)
3526
    {
3527
        // TODO:
3528
        // set the current resource attribute to this resource
3529
        // switch on element type (redefine in child class?)
3530
        // set status for this item to "opened"
3531
        // start timer
3532
        // initialise score
3533
        $this->index = 0; //or = the last item seen (see $this->last)
3534
    }
3535
3536
    /**
3537
     * Check that all prerequisites are fulfilled. Returns true and an
3538
     * empty string on success, returns false
3539
     * and the prerequisite string on error.
3540
     * This function is based on the rules for aicc_script language as
3541
     * described in the SCORM 1.2 CAM documentation page 108.
3542
     *
3543
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3544
     *
3545
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3546
     *              string otherwise
3547
     */
3548
    public function prerequisites_match($itemId = null)
3549
    {
3550
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3551
        if ($allow) {
3552
            if (api_is_allowed_to_edit() ||
3553
                api_is_platform_admin(true) ||
3554
                api_is_drh() ||
3555
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3556
            ) {
3557
                return true;
3558
            }
3559
        }
3560
3561
        $debug = $this->debug;
3562
        if ($debug > 0) {
3563
            error_log('In learnpath::prerequisites_match()');
3564
        }
3565
3566
        if (empty($itemId)) {
3567
            $itemId = $this->current;
3568
        }
3569
3570
        $currentItem = $this->getItem($itemId);
3571
3572
        if ($currentItem) {
3573
            if (2 == $this->type) {
3574
                // Getting prereq from scorm
3575
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3576
            } else {
3577
                $prereq_string = $currentItem->get_prereq_string();
3578
            }
3579
3580
            if (empty($prereq_string)) {
3581
                if ($debug > 0) {
3582
                    error_log('Found prereq_string is empty return true');
3583
                }
3584
3585
                return true;
3586
            }
3587
3588
            // Clean spaces.
3589
            $prereq_string = str_replace(' ', '', $prereq_string);
3590
            if ($debug > 0) {
3591
                error_log('Found prereq_string: '.$prereq_string, 0);
3592
            }
3593
3594
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3595
            $result = $currentItem->parse_prereq(
3596
                $prereq_string,
3597
                $this->items,
3598
                $this->refs_list,
3599
                $this->get_user_id()
3600
            );
3601
3602
            if (false === $result) {
3603
                $this->set_error_msg($currentItem->prereq_alert);
3604
            }
3605
        } else {
3606
            $result = true;
3607
            if ($debug > 1) {
3608
                error_log('$this->items['.$itemId.'] was not an object', 0);
3609
            }
3610
        }
3611
3612
        if ($debug > 1) {
3613
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3614
        }
3615
3616
        return $result;
3617
    }
3618
3619
    /**
3620
     * Updates learnpath attributes to point to the previous element
3621
     * The last part is similar to set_current_item but processing the other way around.
3622
     */
3623
    public function previous()
3624
    {
3625
        $this->last = $this->get_current_item_id();
3626
        $this->items[$this->last]->save(
3627
            false,
3628
            $this->prerequisites_match($this->last)
3629
        );
3630
        $this->autocomplete_parents($this->last);
3631
        $new_index = $this->get_previous_index();
3632
        $this->index = $new_index;
3633
        $this->current = $this->ordered_items[$new_index];
3634
    }
3635
3636
    /**
3637
     * Publishes a learnpath. This basically means show or hide the learnpath
3638
     * to normal users.
3639
     * Can be used as abstract.
3640
     *
3641
     * @param int $id         Learnpath ID
3642
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3643
     *
3644
     * @return bool
3645
     */
3646
    public static function toggleVisibility($id, $visibility = 1)
3647
    {
3648
        $repo = Container::getLpRepository();
3649
        $lp = $repo->find($id);
3650
3651
        if (!$lp) {
3652
            return false;
3653
        }
3654
3655
        $visibility = (int) $visibility;
3656
3657
        if (1 === $visibility) {
3658
            $repo->setVisibilityPublished($lp);
3659
        } else {
3660
            $repo->setVisibilityDraft($lp);
3661
        }
3662
3663
        return true;
3664
    }
3665
3666
    /**
3667
     * Publishes a learnpath category.
3668
     * This basically means show or hide the learnpath category to normal users.
3669
     *
3670
     * @param int $id
3671
     * @param int $visibility
3672
     *
3673
     * @return bool
3674
     */
3675
    public static function toggleCategoryVisibility($id, $visibility = 1)
3676
    {
3677
        $repo = Container::getLpCategoryRepository();
3678
        $resource = $repo->find($id);
3679
3680
        if (!$resource) {
3681
            return false;
3682
        }
3683
3684
        $visibility = (int) $visibility;
3685
3686
        if (1 === $visibility) {
3687
            $repo->setVisibilityPublished($resource);
3688
        } else {
3689
            $repo->setVisibilityDraft($resource);
3690
            self::toggleCategoryPublish($id, 0);
3691
        }
3692
3693
        return false;
3694
    }
3695
3696
    /**
3697
     * Publishes a learnpath. This basically means show or hide the learnpath
3698
     * on the course homepage.
3699
     *
3700
     * @param int    $id            Learnpath id
3701
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3702
     *
3703
     * @return bool
3704
     */
3705
    public static function togglePublish($id, $setVisibility = 'v')
3706
    {
3707
        $addShortcut = false;
3708
        if ('v' === $setVisibility) {
3709
            $addShortcut = true;
3710
        }
3711
        $repo = Container::getLpRepository();
3712
        /** @var CLp $lp */
3713
        $lp = $repo->find($id);
3714
        if (null === $lp) {
3715
            return false;
3716
        }
3717
        $repoShortcut = Container::getShortcutRepository();
3718
        $courseEntity = api_get_course_entity();
3719
3720
        if ($addShortcut) {
3721
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3722
        } else {
3723
            $repoShortcut->removeShortCut($lp);
3724
        }
3725
3726
        return true;
3727
    }
3728
3729
    /**
3730
     * Show or hide the learnpath category on the course homepage.
3731
     *
3732
     * @param int $id
3733
     * @param int $setVisibility
3734
     *
3735
     * @return bool
3736
     */
3737
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3738
    {
3739
        $setVisibility = (int) $setVisibility;
3740
        $addShortcut = false;
3741
        if (1 === $setVisibility) {
3742
            $addShortcut = true;
3743
        }
3744
3745
        $repo = Container::getLpCategoryRepository();
3746
        /** @var CLpCategory $lp */
3747
        $category = $repo->find($id);
3748
3749
        if (null === $category) {
3750
            return false;
3751
        }
3752
3753
        $repoShortcut = Container::getShortcutRepository();
3754
        if ($addShortcut) {
3755
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3756
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3757
        } else {
3758
            $repoShortcut->removeShortCut($category);
3759
        }
3760
3761
        return true;
3762
    }
3763
3764
    /**
3765
     * Check if the learnpath category is visible for a user.
3766
     *
3767
     * @return bool
3768
     */
3769
    public static function categoryIsVisibleForStudent(
3770
        CLpCategory $category,
3771
        User $user,
3772
        Course $course,
3773
        Session $session = null
3774
    ) {
3775
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3776
3777
        if ($isAllowedToEdit) {
3778
            return true;
3779
        }
3780
3781
        $categoryVisibility = $category->isVisible($course, $session);
3782
3783
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
3784
            return false;
3785
        }
3786
3787
        $subscriptionSettings = self::getSubscriptionSettings();
3788
3789
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
3790
            return true;
3791
        }
3792
3793
        $noUserSubscribed = false;
3794
        $noGroupSubscribed = true;
3795
        $users = $category->getUsers();
3796
        if (empty($users) || !$users->count()) {
3797
            $noUserSubscribed = true;
3798
        } elseif ($category->hasUserAdded($user)) {
3799
            return true;
3800
        }
3801
3802
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3803
        $em = Database::getManager();
3804
3805
        /** @var ItemPropertyRepository $itemRepo */
3806
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
3807
3808
        /** @var CourseRepository $courseRepo */
3809
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
3810
        $session = null;
3811
        if (!empty($sessionId)) {
3812
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
3813
        }
3814
3815
        $course = $courseRepo->find($courseId);
3816
3817
        if (0 != $courseId) {
3818
            // Subscribed groups to a LP
3819
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
3820
                    TOOL_LEARNPATH_CATEGORY,
3821
                    $category->getId(),
3822
                    $course,
3823
                    $session
3824
                );
3825
        }
3826
3827
        if (!empty($subscribedGroupsInLp)) {
3828
            $noGroupSubscribed = false;
3829
            if (!empty($groups)) {
3830
                $groups = array_column($groups, 'iid');
3831
                /** @var CItemProperty $item */
3832
                foreach ($subscribedGroupsInLp as $item) {
3833
                    if ($item->getGroup() &&
3834
                        in_array($item->getGroup()->getId(), $groups)
3835
                    ) {
3836
                        return true;
3837
                    }
3838
                }
3839
            }
3840
        }
3841
        $response = $noGroupSubscribed && $noUserSubscribed;
3842
3843
        return $response;
3844
    }
3845
3846
    /**
3847
     * Check if a learnpath category is published as course tool.
3848
     *
3849
     * @param int $courseId
3850
     *
3851
     * @return bool
3852
     */
3853
    public static function categoryIsPublished(CLpCategory $category, $courseId)
3854
    {
3855
        return false;
3856
        $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...
3857
        $em = Database::getManager();
3858
3859
        $tools = $em
3860
            ->createQuery("
3861
                SELECT t FROM ChamiloCourseBundle:CTool t
3862
                WHERE t.course = :course AND
3863
                    t.name = :name AND
3864
                    t.image LIKE 'lp_category.%' AND
3865
                    t.link LIKE :link
3866
            ")
3867
            ->setParameters([
3868
                'course' => $courseId,
3869
                'name' => strip_tags($category->getName()),
3870
                'link' => "$link%",
3871
            ])
3872
            ->getResult();
3873
3874
        /** @var CTool $tool */
3875
        $tool = current($tools);
3876
3877
        return $tool ? $tool->getVisibility() : false;
3878
    }
3879
3880
    /**
3881
     * Restart the whole learnpath. Return the URL of the first element.
3882
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
3883
     * To use a similar method  statically, use the create_new_attempt() method.
3884
     *
3885
     * @return bool
3886
     */
3887
    public function restart()
3888
    {
3889
        if ($this->debug > 0) {
3890
            error_log('In learnpath::restart()', 0);
3891
        }
3892
        // TODO
3893
        // Call autosave method to save the current progress.
3894
        //$this->index = 0;
3895
        if (api_is_invitee()) {
3896
            return false;
3897
        }
3898
        $session_id = api_get_session_id();
3899
        $course_id = api_get_course_int_id();
3900
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3901
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
3902
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
3903
        if ($this->debug > 2) {
3904
            error_log('Inserting new lp_view for restart: '.$sql, 0);
3905
        }
3906
        Database::query($sql);
3907
        $view_id = Database::insert_id();
3908
3909
        if ($view_id) {
3910
            $this->lp_view_id = $view_id;
3911
            $this->attempt = $this->attempt + 1;
3912
        } else {
3913
            $this->error = 'Could not insert into item_view table...';
3914
3915
            return false;
3916
        }
3917
        $this->autocomplete_parents($this->current);
3918
        foreach ($this->items as $index => $dummy) {
3919
            $this->items[$index]->restart();
3920
            $this->items[$index]->set_lp_view($this->lp_view_id);
3921
        }
3922
        $this->first();
3923
3924
        return true;
3925
    }
3926
3927
    /**
3928
     * Saves the current item.
3929
     *
3930
     * @return bool
3931
     */
3932
    public function save_current()
3933
    {
3934
        $debug = $this->debug;
3935
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3936
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3937
        if ($debug) {
3938
            error_log('save_current() saving item '.$this->current, 0);
3939
            error_log(''.print_r($this->items, true), 0);
3940
        }
3941
        if (isset($this->items[$this->current]) &&
3942
            is_object($this->items[$this->current])
3943
        ) {
3944
            if ($debug) {
3945
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3946
            }
3947
3948
            $res = $this->items[$this->current]->save(
3949
                false,
3950
                $this->prerequisites_match($this->current)
3951
            );
3952
            $this->autocomplete_parents($this->current);
3953
            $status = $this->items[$this->current]->get_status();
3954
            $this->update_queue[$this->current] = $status;
3955
3956
            if ($debug) {
3957
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
3958
            }
3959
3960
            return $res;
3961
        }
3962
3963
        return false;
3964
    }
3965
3966
    /**
3967
     * Saves the given item.
3968
     *
3969
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
3970
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
3971
     *
3972
     * @return bool
3973
     */
3974
    public function save_item($item_id = null, $from_outside = true)
3975
    {
3976
        $debug = $this->debug;
3977
        if ($debug) {
3978
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
3979
        }
3980
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
3981
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
3982
        if (empty($item_id)) {
3983
            $item_id = (int) $_REQUEST['id'];
3984
        }
3985
3986
        if (empty($item_id)) {
3987
            $item_id = $this->get_current_item_id();
3988
        }
3989
        if (isset($this->items[$item_id]) &&
3990
            is_object($this->items[$item_id])
3991
        ) {
3992
            if ($debug) {
3993
                error_log('Object exists');
3994
            }
3995
3996
            // Saving the item.
3997
            $res = $this->items[$item_id]->save(
3998
                $from_outside,
3999
                $this->prerequisites_match($item_id)
4000
            );
4001
4002
            if ($debug) {
4003
                error_log('update_queue before:');
4004
                error_log(print_r($this->update_queue, 1));
4005
            }
4006
            $this->autocomplete_parents($item_id);
4007
4008
            $status = $this->items[$item_id]->get_status();
4009
            $this->update_queue[$item_id] = $status;
4010
4011
            if ($debug) {
4012
                error_log('get_status(): '.$status);
4013
                error_log('update_queue after:');
4014
                error_log(print_r($this->update_queue, 1));
4015
            }
4016
4017
            return $res;
4018
        }
4019
4020
        return false;
4021
    }
4022
4023
    /**
4024
     * Saves the last item seen's ID only in case.
4025
     */
4026
    public function save_last()
4027
    {
4028
        $course_id = api_get_course_int_id();
4029
        $debug = $this->debug;
4030
        if ($debug) {
4031
            error_log('In learnpath::save_last()', 0);
4032
        }
4033
        $session_condition = api_get_session_condition(
4034
            api_get_session_id(),
4035
            true,
4036
            false
4037
        );
4038
        $table = Database::get_course_table(TABLE_LP_VIEW);
4039
4040
        $userId = $this->get_user_id();
4041
        if (empty($userId)) {
4042
            $userId = api_get_user_id();
4043
            if ($debug) {
4044
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4045
            }
4046
        }
4047
        if (isset($this->current) && !api_is_invitee()) {
4048
            if ($debug) {
4049
                error_log('Saving current item ('.$this->current.') for later review', 0);
4050
            }
4051
            $sql = "UPDATE $table SET
4052
                        last_item = ".$this->get_current_item_id()."
4053
                    WHERE
4054
                        c_id = $course_id AND
4055
                        lp_id = ".$this->get_id()." AND
4056
                        user_id = ".$userId." ".$session_condition;
4057
4058
            if ($debug) {
4059
                error_log('Saving last item seen : '.$sql, 0);
4060
            }
4061
            Database::query($sql);
4062
        }
4063
4064
        if (!api_is_invitee()) {
4065
            // Save progress.
4066
            [$progress] = $this->get_progress_bar_text('%');
4067
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4068
            $scoreAsProgress = $this->getUseScoreAsProgress();
4069
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4070
                if ($debug) {
4071
                    error_log("Return false: Dont save score: $score");
4072
                    error_log("progress: $progress");
4073
                }
4074
4075
                return false;
4076
            }
4077
4078
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4079
                $storedProgress = self::getProgress(
4080
                    $this->get_id(),
4081
                    $userId,
4082
                    $course_id,
4083
                    $this->get_lp_session_id()
4084
                );
4085
4086
                // Check if the stored progress is higher than the new value
4087
                if ($storedProgress >= $progress) {
4088
                    if ($debug) {
4089
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4090
                    }
4091
4092
                    return false;
4093
                }
4094
            }
4095
            if ($progress >= 0 && $progress <= 100) {
4096
                $progress = (int) $progress;
4097
                $sql = "UPDATE $table SET
4098
                            progress = $progress
4099
                        WHERE
4100
                            c_id = $course_id AND
4101
                            lp_id = ".$this->get_id()." AND
4102
                            user_id = ".$userId." ".$session_condition;
4103
                // Ignore errors as some tables might not have the progress field just yet.
4104
                Database::query($sql);
4105
                $this->progress_db = $progress;
4106
            }
4107
        }
4108
    }
4109
4110
    /**
4111
     * Sets the current item ID (checks if valid and authorized first).
4112
     *
4113
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4114
     */
4115
    public function set_current_item($item_id = null)
4116
    {
4117
        $debug = $this->debug;
4118
        if ($debug) {
4119
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4120
        }
4121
        if (empty($item_id)) {
4122
            if ($debug) {
4123
                error_log('No new current item given, ignore...', 0);
4124
            }
4125
            // Do nothing.
4126
        } else {
4127
            if ($debug) {
4128
                error_log('New current item given is '.$item_id.'...', 0);
4129
            }
4130
            if (is_numeric($item_id)) {
4131
                $item_id = (int) $item_id;
4132
                // TODO: Check in database here.
4133
                $this->last = $this->current;
4134
                $this->current = $item_id;
4135
                // TODO: Update $this->index as well.
4136
                foreach ($this->ordered_items as $index => $item) {
4137
                    if ($item == $this->current) {
4138
                        $this->index = $index;
4139
                        break;
4140
                    }
4141
                }
4142
                if ($debug) {
4143
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4144
                }
4145
            } else {
4146
                if ($debug) {
4147
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4148
                }
4149
            }
4150
        }
4151
    }
4152
4153
    /**
4154
     * Set index specified prefix terms for all items in this path.
4155
     *
4156
     * @param string $terms_string Comma-separated list of terms
4157
     * @param string $prefix       Xapian term prefix
4158
     *
4159
     * @return bool False on error, true otherwise
4160
     */
4161
    public function set_terms_by_prefix($terms_string, $prefix)
4162
    {
4163
        $course_id = api_get_course_int_id();
4164
        if ('true' !== api_get_setting('search_enabled')) {
4165
            return false;
4166
        }
4167
4168
        if (!extension_loaded('xapian')) {
4169
            return false;
4170
        }
4171
4172
        $terms_string = trim($terms_string);
4173
        $terms = explode(',', $terms_string);
4174
        array_walk($terms, 'trim_value');
4175
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4176
4177
        // Don't do anything if no change, verify only at DB, not the search engine.
4178
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
4179
            return false;
4180
        }
4181
4182
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4183
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4184
4185
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4186
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4187
        $lp_id = (int) $_POST['lp_id'];
4188
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4189
        $result = Database::query($sql);
4190
        $di = new ChamiloIndexer();
4191
4192
        while ($lp_item = Database::fetch_array($result)) {
4193
            // Get search_did.
4194
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4195
            $sql = 'SELECT * FROM %s
4196
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4197
                    LIMIT 1';
4198
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4199
4200
            //echo $sql; echo '<br>';
4201
            $res = Database::query($sql);
4202
            if (Database::num_rows($res) > 0) {
4203
                $se_ref = Database::fetch_array($res);
4204
                // Compare terms.
4205
                $doc = $di->get_document($se_ref['search_did']);
4206
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
4207
                $xterms = [];
4208
                foreach ($xapian_terms as $xapian_term) {
4209
                    $xterms[] = substr($xapian_term['name'], 1);
4210
                }
4211
4212
                $dterms = $terms;
4213
                $missing_terms = array_diff($dterms, $xterms);
4214
                $deprecated_terms = array_diff($xterms, $dterms);
4215
4216
                // Save it to search engine.
4217
                foreach ($missing_terms as $term) {
4218
                    $doc->add_term($prefix.$term, 1);
4219
                }
4220
                foreach ($deprecated_terms as $term) {
4221
                    $doc->remove_term($prefix.$term);
4222
                }
4223
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
4224
                $di->getDb()->flush();
4225
            }
4226
        }
4227
4228
        return true;
4229
    }
4230
4231
    /**
4232
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
4233
     *
4234
     * @param int $id DB ID of the item
4235
     */
4236
    public function set_previous_item($id)
4237
    {
4238
        if ($this->debug > 0) {
4239
            error_log('In learnpath::set_previous_item()', 0);
4240
        }
4241
        $this->last = $id;
4242
    }
4243
4244
    /**
4245
     * Sets and saves the expired_on date.
4246
     *
4247
     * @return bool Returns true if author's name is not empty
4248
     */
4249
    public function set_modified_on()
4250
    {
4251
        $this->modified_on = api_get_utc_datetime();
4252
        $table = Database::get_course_table(TABLE_LP_MAIN);
4253
        $lp_id = $this->get_id();
4254
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
4255
                WHERE iid = $lp_id";
4256
        Database::query($sql);
4257
4258
        return true;
4259
    }
4260
4261
    /**
4262
     * Sets the object's error message.
4263
     *
4264
     * @param string $error Error message. If empty, reinits the error string
4265
     */
4266
    public function set_error_msg($error = '')
4267
    {
4268
        if ($this->debug > 0) {
4269
            error_log('In learnpath::set_error_msg()', 0);
4270
        }
4271
        if (empty($error)) {
4272
            $this->error = '';
4273
        } else {
4274
            $this->error .= $error;
4275
        }
4276
    }
4277
4278
    /**
4279
     * Launches the current item if not 'sco'
4280
     * (starts timer and make sure there is a record ready in the DB).
4281
     *
4282
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
4283
     *
4284
     * @return bool
4285
     */
4286
    public function start_current_item($allow_new_attempt = false)
4287
    {
4288
        $debug = $this->debug;
4289
        if ($debug) {
4290
            error_log('In learnpath::start_current_item()');
4291
            error_log('current: '.$this->current);
4292
        }
4293
        if (0 != $this->current && isset($this->items[$this->current]) &&
4294
            is_object($this->items[$this->current])
4295
        ) {
4296
            $type = $this->get_type();
4297
            $item_type = $this->items[$this->current]->get_type();
4298
            if ($debug) {
4299
                error_log('item type: '.$item_type);
4300
                error_log('lp type: '.$type);
4301
            }
4302
            if ((2 == $type && 'sco' !== $item_type) ||
4303
                (3 == $type && 'au' !== $item_type) ||
4304
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
4305
            ) {
4306
                $this->items[$this->current]->open($allow_new_attempt);
4307
                $this->autocomplete_parents($this->current);
4308
                $prereq_check = $this->prerequisites_match($this->current);
4309
                if ($debug) {
4310
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
4311
                }
4312
                $this->items[$this->current]->save(false, $prereq_check);
4313
            }
4314
            // If sco, then it is supposed to have been updated by some other call.
4315
            if ('sco' === $item_type) {
4316
                $this->items[$this->current]->restart();
4317
            }
4318
        }
4319
        if ($debug) {
4320
            error_log('lp_view_session_id');
4321
            error_log($this->lp_view_session_id);
4322
            error_log('api session id');
4323
            error_log(api_get_session_id());
4324
            error_log('End of learnpath::start_current_item()');
4325
        }
4326
4327
        return true;
4328
    }
4329
4330
    /**
4331
     * Stops the processing and counters for the old item (as held in $this->last).
4332
     *
4333
     * @return bool True/False
4334
     */
4335
    public function stop_previous_item()
4336
    {
4337
        $debug = $this->debug;
4338
        if ($debug) {
4339
            error_log('In learnpath::stop_previous_item()', 0);
4340
        }
4341
4342
        if (0 != $this->last && $this->last != $this->current &&
4343
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
4344
        ) {
4345
            if ($debug) {
4346
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
4347
            }
4348
            switch ($this->get_type()) {
4349
                case '3':
4350
                    if ('au' != $this->items[$this->last]->get_type()) {
4351
                        if ($debug) {
4352
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
4353
                        }
4354
                        $this->items[$this->last]->close();
4355
                    } else {
4356
                        if ($debug) {
4357
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
4358
                        }
4359
                    }
4360
                    break;
4361
                case '2':
4362
                    if ('sco' != $this->items[$this->last]->get_type()) {
4363
                        if ($debug) {
4364
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
4365
                        }
4366
                        $this->items[$this->last]->close();
4367
                    } else {
4368
                        if ($debug) {
4369
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
4370
                        }
4371
                    }
4372
                    break;
4373
                case '1':
4374
                default:
4375
                    if ($debug) {
4376
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
4377
                    }
4378
                    $this->items[$this->last]->close();
4379
                    break;
4380
            }
4381
        } else {
4382
            if ($debug) {
4383
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4384
            }
4385
4386
            return false;
4387
        }
4388
4389
        return true;
4390
    }
4391
4392
    /**
4393
     * Updates the default view mode from fullscreen to embedded and inversely.
4394
     *
4395
     * @return string The current default view mode ('fullscreen' or 'embedded')
4396
     */
4397
    public function update_default_view_mode()
4398
    {
4399
        $table = Database::get_course_table(TABLE_LP_MAIN);
4400
        $sql = "SELECT * FROM $table
4401
                WHERE iid = ".$this->get_id();
4402
        $res = Database::query($sql);
4403
        if (Database::num_rows($res) > 0) {
4404
            $row = Database::fetch_array($res);
4405
            $default_view_mode = $row['default_view_mod'];
4406
            $view_mode = $default_view_mode;
4407
            switch ($default_view_mode) {
4408
                case 'fullscreen': // default with popup
4409
                    $view_mode = 'embedded';
4410
                    break;
4411
                case 'embedded': // default view with left menu
4412
                    $view_mode = 'embedframe';
4413
                    break;
4414
                case 'embedframe': //folded menu
4415
                    $view_mode = 'impress';
4416
                    break;
4417
                case 'impress':
4418
                    $view_mode = 'fullscreen';
4419
                    break;
4420
            }
4421
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4422
                    WHERE iid = ".$this->get_id();
4423
            Database::query($sql);
4424
            $this->mode = $view_mode;
4425
4426
            return $view_mode;
4427
        }
4428
4429
        return -1;
4430
    }
4431
4432
    /**
4433
     * Updates the default behaviour about auto-commiting SCORM updates.
4434
     *
4435
     * @return bool True if auto-commit has been set to 'on', false otherwise
4436
     */
4437
    public function update_default_scorm_commit()
4438
    {
4439
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4440
        $sql = "SELECT * FROM $lp_table
4441
                WHERE iid = ".$this->get_id();
4442
        $res = Database::query($sql);
4443
        if (Database::num_rows($res) > 0) {
4444
            $row = Database::fetch_array($res);
4445
            $force = $row['force_commit'];
4446
            if (1 == $force) {
4447
                $force = 0;
4448
                $force_return = false;
4449
            } elseif (0 == $force) {
4450
                $force = 1;
4451
                $force_return = true;
4452
            }
4453
            $sql = "UPDATE $lp_table SET force_commit = $force
4454
                    WHERE iid = ".$this->get_id();
4455
            Database::query($sql);
4456
            $this->force_commit = $force_return;
4457
4458
            return $force_return;
4459
        }
4460
4461
        return -1;
4462
    }
4463
4464
    /**
4465
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4466
     *
4467
     * @return bool True on success, false on failure
4468
     */
4469
    public function update_display_order()
4470
    {
4471
        return;
4472
        $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...
4473
        $table = Database::get_course_table(TABLE_LP_MAIN);
4474
        $sql = "SELECT * FROM $table
4475
                WHERE c_id = $course_id
4476
                ORDER BY display_order";
4477
        $res = Database::query($sql);
4478
        if (false === $res) {
4479
            return false;
4480
        }
4481
4482
        $num = Database::num_rows($res);
4483
        // First check the order is correct, globally (might be wrong because
4484
        // of versions < 1.8.4).
4485
        if ($num > 0) {
4486
            $i = 1;
4487
            while ($row = Database::fetch_array($res)) {
4488
                if ($row['display_order'] != $i) {
4489
                    // If we find a gap in the order, we need to fix it.
4490
                    $sql = "UPDATE $table SET display_order = $i
4491
                            WHERE iid = ".$row['iid'];
4492
                    Database::query($sql);
4493
                }
4494
                $i++;
4495
            }
4496
        }
4497
4498
        return true;
4499
    }
4500
4501
    /**
4502
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4503
     *
4504
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4505
     */
4506
    public function update_reinit()
4507
    {
4508
        $force = $this->entity->getPreventReinit();
4509
        if (1 == $force) {
4510
            $force = 0;
4511
        } elseif (0 == $force) {
4512
            $force = 1;
4513
        }
4514
4515
        $table = Database::get_course_table(TABLE_LP_MAIN);
4516
        $sql = "UPDATE $table SET prevent_reinit = $force
4517
                WHERE iid = ".$this->get_id();
4518
        Database::query($sql);
4519
        $this->prevent_reinit = $force;
4520
4521
        return $force;
4522
    }
4523
4524
    /**
4525
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4526
     *
4527
     * @return string 'single', 'multi' or 'seriousgame'
4528
     *
4529
     * @author ndiechburg <[email protected]>
4530
     */
4531
    public function get_attempt_mode()
4532
    {
4533
        //Set default value for seriousgame_mode
4534
        if (!isset($this->seriousgame_mode)) {
4535
            $this->seriousgame_mode = 0;
4536
        }
4537
        // Set default value for prevent_reinit
4538
        if (!isset($this->prevent_reinit)) {
4539
            $this->prevent_reinit = 1;
4540
        }
4541
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4542
            return 'seriousgame';
4543
        }
4544
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4545
            return 'single';
4546
        }
4547
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4548
            return 'multiple';
4549
        }
4550
4551
        return 'single';
4552
    }
4553
4554
    /**
4555
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4556
     *
4557
     * @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...
4558
     *
4559
     * @return bool
4560
     *
4561
     * @author ndiechburg <[email protected]>
4562
     */
4563
    public function set_attempt_mode($mode)
4564
    {
4565
        switch ($mode) {
4566
            case 'seriousgame':
4567
                $sg_mode = 1;
4568
                $prevent_reinit = 1;
4569
                break;
4570
            case 'single':
4571
                $sg_mode = 0;
4572
                $prevent_reinit = 1;
4573
                break;
4574
            case 'multiple':
4575
                $sg_mode = 0;
4576
                $prevent_reinit = 0;
4577
                break;
4578
            default:
4579
                $sg_mode = 0;
4580
                $prevent_reinit = 0;
4581
                break;
4582
        }
4583
        $this->prevent_reinit = $prevent_reinit;
4584
        $this->seriousgame_mode = $sg_mode;
4585
        $table = Database::get_course_table(TABLE_LP_MAIN);
4586
        $sql = "UPDATE $table SET
4587
                prevent_reinit = $prevent_reinit ,
4588
                seriousgame_mode = $sg_mode
4589
                WHERE iid = ".$this->get_id();
4590
        $res = Database::query($sql);
4591
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4592
            return true;
4593
        } else {
4594
            return false;
4595
        }
4596
    }
4597
4598
    /**
4599
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4600
     *
4601
     * @author ndiechburg <[email protected]>
4602
     */
4603
    public function switch_attempt_mode()
4604
    {
4605
        $mode = $this->get_attempt_mode();
4606
        switch ($mode) {
4607
            case 'single':
4608
                $next_mode = 'multiple';
4609
                break;
4610
            case 'multiple':
4611
                $next_mode = 'seriousgame';
4612
                break;
4613
            case 'seriousgame':
4614
            default:
4615
                $next_mode = 'single';
4616
                break;
4617
        }
4618
        $this->set_attempt_mode($next_mode);
4619
    }
4620
4621
    /**
4622
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4623
     * but possibility to do again a completed item.
4624
     *
4625
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4626
     *
4627
     * @author ndiechburg <[email protected]>
4628
     */
4629
    public function set_seriousgame_mode()
4630
    {
4631
        $table = Database::get_course_table(TABLE_LP_MAIN);
4632
        $force = $this->entity->getSeriousgameMode();
4633
        if (1 == $force) {
4634
            $force = 0;
4635
        } elseif (0 == $force) {
4636
            $force = 1;
4637
        }
4638
        $sql = "UPDATE $table SET seriousgame_mode = $force
4639
                WHERE iid = ".$this->get_id();
4640
        Database::query($sql);
4641
        $this->seriousgame_mode = $force;
4642
4643
        return $force;
4644
    }
4645
4646
    /**
4647
     * Updates the "scorm_debug" value that shows or hide the debug window.
4648
     *
4649
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4650
     */
4651
    public function update_scorm_debug()
4652
    {
4653
        $table = Database::get_course_table(TABLE_LP_MAIN);
4654
        $force = $this->entity->getDebug();
4655
        if (1 == $force) {
4656
            $force = 0;
4657
        } elseif (0 == $force) {
4658
            $force = 1;
4659
        }
4660
        $sql = "UPDATE $table SET debug = $force
4661
                WHERE iid = ".$this->get_id();
4662
        Database::query($sql);
4663
        $this->scorm_debug = $force;
4664
4665
        return $force;
4666
    }
4667
4668
    /**
4669
     * Function that makes a call to the function sort_tree_array and create_tree_array.
4670
     *
4671
     * @author Kevin Van Den Haute
4672
     *
4673
     * @param  array
4674
     */
4675
    public function tree_array($array)
4676
    {
4677
        $array = $this->sort_tree_array($array);
4678
        $this->create_tree_array($array);
4679
    }
4680
4681
    /**
4682
     * Creates an array with the elements of the learning path tree in it.
4683
     *
4684
     * @author Kevin Van Den Haute
4685
     *
4686
     * @param array $array
4687
     * @param int   $parent
4688
     * @param int   $depth
4689
     * @param array $tmp
4690
     */
4691
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
4692
    {
4693
        if (is_array($array)) {
4694
            for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4695
                if ($array[$i]['parent_item_id'] == $parent) {
4696
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
4697
                        $tmp[] = $array[$i]['parent_item_id'];
4698
                        $depth++;
4699
                    }
4700
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
4701
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
4702
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
4703
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
4704
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
4705
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
4706
                    $this->arrMenu[] = [
4707
                        'id' => $array[$i]['id'],
4708
                        'ref' => $ref,
4709
                        'item_type' => $array[$i]['item_type'],
4710
                        'title' => $array[$i]['title'],
4711
                        'title_raw' => $array[$i]['title_raw'],
4712
                        'path' => $path,
4713
                        'description' => $array[$i]['description'],
4714
                        'parent_item_id' => $array[$i]['parent_item_id'],
4715
                        'previous_item_id' => $array[$i]['previous_item_id'],
4716
                        'next_item_id' => $array[$i]['next_item_id'],
4717
                        'min_score' => $array[$i]['min_score'],
4718
                        'max_score' => $array[$i]['max_score'],
4719
                        'mastery_score' => $array[$i]['mastery_score'],
4720
                        'display_order' => $array[$i]['display_order'],
4721
                        'prerequisite' => $preq,
4722
                        'depth' => $depth,
4723
                        'audio' => $audio,
4724
                        'prerequisite_min_score' => $prerequisiteMinScore,
4725
                        'prerequisite_max_score' => $prerequisiteMaxScore,
4726
                    ];
4727
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
4728
                }
4729
            }
4730
        }
4731
    }
4732
4733
    /**
4734
     * Sorts a multi dimensional array by parent id and display order.
4735
     *
4736
     * @author Kevin Van Den Haute
4737
     *
4738
     * @param array $array (array with al the learning path items in it)
4739
     *
4740
     * @return array
4741
     */
4742
    public function sort_tree_array($array)
4743
    {
4744
        foreach ($array as $key => $row) {
4745
            $parent[$key] = $row['parent_item_id'];
4746
            $position[$key] = $row['display_order'];
4747
        }
4748
4749
        if (count($array) > 0) {
4750
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
4751
        }
4752
4753
        return $array;
4754
    }
4755
4756
    /**
4757
     * Function that creates a html list of learning path items so that we can add audio files to them.
4758
     *
4759
     * @author Kevin Van Den Haute
4760
     *
4761
     * @return string
4762
     */
4763
    public function overview()
4764
    {
4765
        $return = '';
4766
        $update_audio = $_GET['updateaudio'] ?? null;
4767
4768
        // we need to start a form when we want to update all the mp3 files
4769
        if ('true' == $update_audio) {
4770
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS(
4771
                    $_GET['updateaudio']
4772
                ).'&action='.Security::remove_XSS(
4773
                    $_GET['action']
4774
                ).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4775
        }
4776
        $return .= '<div id="message"></div>';
4777
        if (0 == count($this->items)) {
4778
            $return .= Display::return_message(
4779
                get_lang(
4780
                    'You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'
4781
                ),
4782
                'normal'
4783
            );
4784
        } else {
4785
            $return_audio = '<table class="table table-hover table-striped data_table">';
4786
            $return_audio .= '<tr>';
4787
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4788
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4789
            $return_audio .= '</tr>';
4790
4791
            if ('true' != $update_audio) {
4792
                /*$return .= '<div class="col-md-12">';
4793
                $return .= self::return_new_tree($update_audio);
4794
                $return .= '</div>';*/
4795
                $return .= Display::div(
4796
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
4797
                    ['style' => 'float:left; margin-top:15px;width:100%']
4798
                );
4799
            } else {
4800
                //$return_audio .= self::return_new_tree($update_audio);
4801
                $return .= $return_audio.'</table>';
4802
            }
4803
4804
            // We need to close the form when we are updating the mp3 files.
4805
            if ('true' == $update_audio) {
4806
                $return .= '<div class="footer-audio">';
4807
                $return .= Display::button(
4808
                    'save_audio',
4809
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
4810
                    ['class' => 'btn btn-primary', 'type' => 'submit']
4811
                );
4812
                $return .= '</div>';
4813
            }
4814
        }
4815
4816
        // We need to close the form when we are updating the mp3 files.
4817
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
4818
            $return .= '</form>';
4819
        }
4820
4821
        return $return;
4822
    }
4823
4824
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
4825
    {
4826
        $sureToDelete = trim(get_lang('Are you sure to delete?'));
4827
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
4828
4829
        $content = '
4830
        <script>
4831
            /*
4832
            Script to manipulate Learning Path items with Drag and drop
4833
             */
4834
            $(function() {
4835
                function refreshTree() {
4836
                    var params = "&a=get_lp_item_tree";
4837
                    $.get(
4838
                        "'.$ajax_url.'",
4839
                        params,
4840
                        function(result) {
4841
                            serialized = [];
4842
                            $("#lp_item_list").html(result);
4843
                            nestedSortable();
4844
                        }
4845
                    );
4846
                }
4847
4848
                const nestedQuery = ".nested-sortable";
4849
                const identifier = "id";
4850
                const root = document.getElementById("lp_item_list");
4851
4852
                var serialized = [];
4853
                function serialize(sortable) {
4854
                  var children = [].slice.call(sortable.children);
4855
                  for (var i in children) {
4856
                    var nested = children[i].querySelector(nestedQuery);
4857
                    var parentId = $(children[i]).parent().parent().attr("id");
4858
                    var id = children[i].dataset[identifier];
4859
                    if (typeof id === "undefined") {
4860
                        return;
4861
                    }
4862
                    serialized.push({
4863
                      id: children[i].dataset[identifier],
4864
                      parent_id: parentId
4865
                    });
4866
4867
                    if (nested) {
4868
                        serialize(nested);
4869
                    }
4870
                  }
4871
4872
                  return serialized;
4873
                }
4874
4875
                function nestedSortable() {
4876
                    let left = document.getElementsByClassName("nested-sortable");
4877
                    Array.prototype.forEach.call(left, function(resource) {
4878
                        Sortable.create(resource, {
4879
                            group: "nested",
4880
                            put: ["nested-sortable", ".lp_resource", ".nested-source"],
4881
                            animation: 150,
4882
                            //fallbackOnBody: true,
4883
                            swapThreshold: 0.65,
4884
                            dataIdAttr: "data-id",
4885
                            store: {
4886
                                set: function (sortable) {
4887
                                    var order = sortable.toArray();
4888
                                    console.log(order);
4889
                                }
4890
                            },
4891
                            onEnd: function(evt) {
4892
                                console.log("onEnd");
4893
                                let list = serialize(root);
4894
                                let order = "&a=update_lp_item_order&new_order=" + JSON.stringify(list);
4895
                                $.get(
4896
                                    "'.$ajax_url.'",
4897
                                    order,
4898
                                    function(reponse) {
4899
                                        $("#message").html(reponse);
4900
                                        refreshTree();
4901
                                    }
4902
                                );
4903
                            },
4904
                        });
4905
                    });
4906
                }
4907
4908
                nestedSortable();
4909
4910
                let resources = document.getElementsByClassName("lp_resource");
4911
                Array.prototype.forEach.call(resources, function(resource) {
4912
                    Sortable.create(resource, {
4913
                        group: "nested",
4914
                        put: ["nested-sortable"],
4915
                        filter: ".disable_drag",
4916
                        animation: 150,
4917
                        fallbackOnBody: true,
4918
                        swapThreshold: 0.65,
4919
                        dataIdAttr: "data-id",
4920
                        onRemove: function(evt) {
4921
                            console.log("onRemove");
4922
                            var itemEl = evt.item;
4923
                            var newIndex = evt.newIndex;
4924
                            var id = $(itemEl).attr("id");
4925
                            var parent_id = $(itemEl).parent().parent().attr("id");
4926
                            var type =  $(itemEl).find(".link_with_id").attr("data_type");
4927
                            var title = $(itemEl).find(".link_with_id").text();
4928
4929
                            let previousId = 0;
4930
                            if (0 !== newIndex) {
4931
                                previousId = $(itemEl).prev().attr("id");
4932
                            }
4933
                            var params = {
4934
                                "a": "add_lp_item",
4935
                                "id": id,
4936
                                "parent_id": parent_id,
4937
                                "previous_id": previousId,
4938
                                "type": type,
4939
                                "title" : title
4940
                            };
4941
                            console.log(params);
4942
                            $.ajax({
4943
                                type: "GET",
4944
                                url: "'.$ajax_url.'",
4945
                                data: params,
4946
                                success: function(itemId) {
4947
                                    $(itemEl).attr("id", itemId);
4948
                                    $(itemEl).attr("data-id", itemId);
4949
                                    let list = serialize(root);
4950
                                    let listInString = JSON.stringify(list);
4951
                                    if (typeof listInString === "undefined") {
4952
                                        listInString = "";
4953
                                    }
4954
                                    let order = "&a=update_lp_item_order&new_order=" + listInString;
4955
                                    $.get(
4956
                                        "'.$ajax_url.'",
4957
                                        order,
4958
                                        function(reponse) {
4959
                                            $("#message").html(reponse);
4960
                                            refreshTree();
4961
                                        }
4962
                                    );
4963
                                }
4964
                            });
4965
                        },
4966
                    });
4967
                });
4968
4969
            });
4970
        </script>';
4971
4972
        $content .= "
4973
        <script>
4974
            function confirmation(name) {
4975
                if (confirm('$sureToDelete ' + name)) {
4976
                    return true;
4977
                } else {
4978
                    return false;
4979
                }
4980
            }
4981
            function refreshTree() {
4982
                var params = '&a=get_lp_item_tree';
4983
                $.get(
4984
                    '".$ajax_url."',
4985
                    params,
4986
                    function(result) {
4987
                        $('#lp_item_list').html(result);
4988
                    }
4989
                );
4990
            }
4991
4992
            $(function () {
4993
                //$('.scrollbar-inner').scrollbar();
4994
                $('#subtab').on('click', 'a:first', function() {
4995
                    window.location.reload();
4996
                });
4997
                $('#subtab ').on('click', 'a:first', function () {
4998
                    window.location.reload();
4999
                });
5000
                expandColumnToggle('#hide_bar_template', {
5001
                    selector: '#lp_sidebar'
5002
                }, {
5003
                    selector: '#doc_form'
5004
                });
5005
5006
                $('.lp-btn-associate-forum').on('click', function (e) {
5007
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
5008
                    if (!associate) {
5009
                        e.preventDefault();
5010
                    }
5011
                });
5012
5013
                $('.lp-btn-dissociate-forum').on('click', function (e) {
5014
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
5015
                    if (!dissociate) {
5016
                        e.preventDefault();
5017
                    }
5018
                });
5019
5020
                // hide the current template list for new documment until it tab clicked
5021
                $('#frmModel').hide();
5022
            });
5023
5024
            // document template for new document tab handler
5025
            $(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
5026
                var id = e.target.id;
5027
                if (id == 'subtab2') {
5028
                    $('#frmModel').show();
5029
                } else {
5030
                    $('#frmModel').hide();
5031
                }
5032
            });
5033
5034
          function deleteItem(event) {
5035
          console.log($(event));
5036
            var id = $(event).attr('data-id');
5037
            var title = $(event).attr('data-title');
5038
            var params = '&a=delete_item&id=' + id;
5039
            if (confirmation(title)) {
5040
                $.get(
5041
                    '".$ajax_url."',
5042
                    params,
5043
                    function(result) {
5044
                        refreshTree();
5045
                    }
5046
                );
5047
            }
5048
        }
5049
        </script>";
5050
5051
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
5052
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
5053
5054
        $repo = Container::getDocumentRepository();
5055
        $document = $repo->find($documentId);
5056
        if ($document) {
5057
            // Show the template list
5058
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
5059
        }
5060
5061
        // Show the template list.
5062
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
5063
            // Show the template list.
5064
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
5065
        }
5066
5067
        return $content;
5068
    }
5069
5070
    /**
5071
     * @param bool  $updateAudio
5072
     * @param bool   $dropElement
5073
     *
5074
     * @return string
5075
     */
5076
    public function return_new_tree($updateAudio = false, $dropElement = false)
5077
    {
5078
        /*$result = $this->processBuildMenuElements($update_audio);
5079
        $list = '<ul id="lp_item_list">';
5080
        $tree = $this->print_recursive(
5081
            $result['elements'],
5082
            $result['default_data'],
5083
            $result['default_content']
5084
        );
5085
5086
        if (!empty($tree)) {
5087
            $list .= $tree;
5088
        } else {
5089
            if ($drop_element_here) {
5090
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
5091
            }
5092
        }
5093
        $list .= '</ul>';*/
5094
5095
        $list = $this->getBuildTree(false, $dropElement);
5096
5097
        $return = Display::panelCollapse(
5098
            $this->name,
5099
            $list,
5100
            'scorm-list',
5101
            null,
5102
            'scorm-list-accordion',
5103
            'scorm-list-collapse'
5104
        );
5105
5106
        if ($updateAudio) {
5107
            //$return = $result['return_audio'];
5108
        }
5109
5110
        return $return;
5111
    }
5112
5113
    public function getBuildTree($noWrapper = false, $dropElement = false)
5114
    {
5115
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5116
5117
        $upIcon = Display::return_icon(
5118
            'up.png',
5119
            get_lang('Up'),
5120
            [],
5121
            ICON_SIZE_TINY
5122
        );
5123
5124
        $disableUpIcon = Display::return_icon(
5125
            'up_na.png',
5126
            get_lang('Up'),
5127
            [],
5128
            ICON_SIZE_TINY
5129
        );
5130
5131
        $downIcon = Display::return_icon(
5132
            'down.png',
5133
            get_lang('Down'),
5134
            [],
5135
            ICON_SIZE_TINY
5136
        );
5137
5138
        $previewImage = Display::return_icon(
5139
            'preview_view.png',
5140
            get_lang('Preview'),
5141
            [],
5142
            ICON_SIZE_TINY
5143
        );
5144
        $lpItemRepo = Container::getLpItemRepository();
5145
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
5146
5147
        $options = [
5148
            'decorate' => true,
5149
            'rootOpen' => function($tree) use ($noWrapper) {
5150
                if ($tree[0]['lvl'] === 1) {
5151
                    if ($noWrapper) {
5152
                        return '';
5153
                    }
5154
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
5155
                }
5156
5157
                return '<ul class="list-group nested-sortable">';
5158
            },
5159
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
5160
                if ($tree[0]['lvl'] === 1) {
5161
                    if ($dropElement) {
5162
                        //return Display::return_message(get_lang('Drag and drop an element here'));
5163
                        //return $this->getDropElementHtml();
5164
                    }
5165
                    if ($noWrapper) {
5166
                        return '';
5167
                    }
5168
                }
5169
5170
                return '</ul>';
5171
            },
5172
            'childOpen' => function($child) {
5173
                $id = $child['iid'];
5174
                return '<li
5175
                    id="'.$id.'"
5176
                    data-id="'.$id.'"
5177
                    class=" flex flex-col list-group-item nested-'.$child['lvl'].'">';
5178
            },
5179
            'childClose' => '',
5180
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
5181
                $fullTitle = $node['title'];
5182
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
5183
                $itemId = $node['iid'];
5184
                $type = $node['itemType'];
5185
                $lpId = $this->get_id();
5186
5187
                $moveIcon = '';
5188
                if (TOOL_LP_FINAL_ITEM !== $type) {
5189
                    $moveIcon .= '<a class="moved" href="#">';
5190
                    $moveIcon .= Display::return_icon(
5191
                        'move_everywhere.png',
5192
                        get_lang('Move'),
5193
                        [],
5194
                        ICON_SIZE_TINY
5195
                    );
5196
                    $moveIcon .= '</a>';
5197
                }
5198
5199
                $iconName = str_replace(' ', '', $type);
5200
                $icon = Display::return_icon(
5201
                    'lp_'.$iconName.'.png',
5202
                    '',
5203
                    [],
5204
                    ICON_SIZE_TINY
5205
                );
5206
5207
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
5208
                $previewIcon = Display::url(
5209
                    $previewImage,
5210
                    $urlPreviewLink,
5211
                    [
5212
                        'target' => '_blank',
5213
                        'class' => 'btn btn-default',
5214
                        'data-title' => $title,
5215
                        'title' => $title,
5216
                    ]
5217
                );
5218
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
5219
5220
                $preRequisitiesIcon = Display::url(
5221
                    Display::return_icon(
5222
                        'accept.png',
5223
                        get_lang('Prerequisites'),
5224
                        [],
5225
                        ICON_SIZE_TINY
5226
                    ),
5227
                    $url.'&action=edit_item_prereq',
5228
                    ['class' => 'btn btn-default']
5229
                );
5230
5231
                //$editIcon = '';
5232
                $editIcon = '<a
5233
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
5234
                    class="btn btn-default"
5235
                    >';
5236
                $editIcon .= Display::return_icon(
5237
                    'edit.png',
5238
                    get_lang('Edit section description/name'),
5239
                    [],
5240
                    ICON_SIZE_TINY
5241
                );
5242
                $editIcon .= '</a>';
5243
                $orderIcons = '';
5244
                /*if ('final_item' !== $type) {
5245
                    $orderIcons = Display::url(
5246
                        $upIcon,
5247
                        'javascript:void(0)',
5248
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
5249
                    );
5250
                    $orderIcons .= Display::url(
5251
                        $downIcon,
5252
                        'javascript:void(0)',
5253
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
5254
                    );
5255
                }*/
5256
5257
                $deleteIcon = ' <a
5258
                    data-id = '.$itemId.'
5259
                    data-title = \''.addslashes($title).'\'
5260
                    href="javascript:void(0);"
5261
                    onclick="return deleteItem(this);"
5262
                    class="btn btn-default">';
5263
                $deleteIcon .= Display::return_icon(
5264
                    'delete.png',
5265
                    get_lang('Delete section'),
5266
                    [],
5267
                    ICON_SIZE_TINY
5268
                );
5269
                $deleteIcon .= '</a>';
5270
                $extra = '';
5271
5272
                if ('dir' === $type && empty($node['__children'])) {
5273
                    $level = $node['lvl'] + 1;
5274
                    $extra = '<ul class="list-group nested-sortable">
5275
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
5276
                              </ul>';
5277
                }
5278
                //return $title;
5279
                $buttons = Display::tag(
5280
                    'div',
5281
                    "<div class=\"btn-group btn-group-sm\">
5282
                                $editIcon
5283
                                $preRequisitiesIcon
5284
                                $orderIcons
5285
                                $deleteIcon
5286
                               </div>",
5287
                    ['class' => 'btn-toolbar button_actions']
5288
                );
5289
                //$buttons = '';
5290
                //$extra = '';
5291
                //Display::span($title, ['title' => $fullTitle])
5292
                //return $title.                $extra;
5293
                return
5294
                    "<div class='flex flex-row'> $moveIcon  $icon <div>$title </div></div>
5295
                    $extra
5296
                    $buttons
5297
                    "
5298
                    ;
5299
            },
5300
        ];
5301
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
5302
5303
        if (empty($tree) && $dropElement) {
5304
            return $this->getDropElementHtml($noWrapper);
5305
        }
5306
5307
        return $tree;
5308
    }
5309
5310
    public function getDropElementHtml($noWrapper = false)
5311
    {
5312
        $li = '<li class="list-group-item">'.
5313
            Display::return_message(get_lang('Drag and drop an element here')).
5314
            '</li>';
5315
        if ($noWrapper) {
5316
            return $li;
5317
        }
5318
5319
        return
5320
            '<ul id="lp_item_list" class="list-group nested-sortable">
5321
            '.$li.'
5322
            </ul>';
5323
    }
5324
5325
    /**
5326
     * This function builds the action menu.
5327
     *
5328
     * @param bool   $returnString           Optional
5329
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
5330
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
5331
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
5332
     * @param string $action
5333
     * @param array  $extraField
5334
     *
5335
     * @return string
5336
     */
5337
    public function build_action_menu(
5338
        $returnString = false,
5339
        $showRequirementButtons = true,
5340
        $isConfigPage = false,
5341
        $allowExpand = true,
5342
        $action = '',
5343
        $extraField = []
5344
    ) {
5345
        $actionsRight = '';
5346
        $lpId = $this->lp_id;
5347
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
5348
            $back = Display::url(
5349
                Display::return_icon(
5350
                    'back.png',
5351
                    get_lang('Back to learning paths'),
5352
                    '',
5353
                    ICON_SIZE_MEDIUM
5354
                ),
5355
                'lp_controller.php?'.api_get_cidreq()
5356
            );
5357
        } else {
5358
            $back = Display::url(
5359
                Display::return_icon(
5360
                    'back.png',
5361
                    get_lang('Back'),
5362
                    '',
5363
                    ICON_SIZE_MEDIUM
5364
                ),
5365
                $extraField['backTo']
5366
            );
5367
        }
5368
5369
        /*if ($backToBuild) {
5370
            $back = Display::url(
5371
                Display::return_icon(
5372
                    'back.png',
5373
                    get_lang('GoBack'),
5374
                    '',
5375
                    ICON_SIZE_MEDIUM
5376
                ),
5377
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
5378
            );
5379
        }*/
5380
5381
        $actionsLeft = $back;
5382
5383
        $actionsLeft .= Display::url(
5384
            Display::return_icon(
5385
                'preview_view.png',
5386
                get_lang('Preview'),
5387
                '',
5388
                ICON_SIZE_MEDIUM
5389
            ),
5390
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5391
                'action' => 'view',
5392
                'lp_id' => $lpId,
5393
                'isStudentView' => 'true',
5394
            ])
5395
        );
5396
5397
        /*$actionsLeft .= Display::url(
5398
            Display::return_icon(
5399
                'upload_audio.png',
5400
                get_lang('Add audio'),
5401
                '',
5402
                ICON_SIZE_MEDIUM
5403
            ),
5404
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5405
                'action' => 'admin_view',
5406
                'lp_id' => $lpId,
5407
                'updateaudio' => 'true',
5408
            ])
5409
        );*/
5410
5411
        $subscriptionSettings = self::getSubscriptionSettings();
5412
5413
        $request = api_request_uri();
5414
        if (false === strpos($request, 'edit')) {
5415
            $actionsLeft .= Display::url(
5416
                Display::return_icon(
5417
                    'settings.png',
5418
                    get_lang('Course settings'),
5419
                    '',
5420
                    ICON_SIZE_MEDIUM
5421
                ),
5422
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5423
                    'action' => 'edit',
5424
                    'lp_id' => $lpId,
5425
                ])
5426
            );
5427
        }
5428
5429
        if ((false === strpos($request, 'build') &&
5430
            false === strpos($request, 'add_item')) ||
5431
            in_array($action, ['add_audio'], true)
5432
        ) {
5433
            $actionsLeft .= Display::url(
5434
                Display::return_icon(
5435
                    'edit.png',
5436
                    get_lang('Edit'),
5437
                    '',
5438
                    ICON_SIZE_MEDIUM
5439
                ),
5440
                'lp_controller.php?'.http_build_query([
5441
                    'action' => 'build',
5442
                    'lp_id' => $lpId,
5443
                ]).'&'.api_get_cidreq()
5444
            );
5445
        }
5446
5447
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
5448
            if (1 == $this->subscribeUsers &&
5449
                $subscriptionSettings['allow_add_users_to_lp']) {
5450
                $actionsLeft .= Display::url(
5451
                    Display::return_icon(
5452
                        'user.png',
5453
                        get_lang('Subscribe users to learning path'),
5454
                        '',
5455
                        ICON_SIZE_MEDIUM
5456
                    ),
5457
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
5458
                );
5459
            }
5460
        }
5461
5462
        if ($allowExpand) {
5463
            /*$actionsLeft .= Display::url(
5464
                Display::return_icon(
5465
                    'expand.png',
5466
                    get_lang('Expand'),
5467
                    ['id' => 'expand'],
5468
                    ICON_SIZE_MEDIUM
5469
                ).
5470
                Display::return_icon(
5471
                    'contract.png',
5472
                    get_lang('Collapse'),
5473
                    ['id' => 'contract', 'class' => 'hide'],
5474
                    ICON_SIZE_MEDIUM
5475
                ),
5476
                '#',
5477
                ['role' => 'button', 'id' => 'hide_bar_template']
5478
            );*/
5479
        }
5480
5481
        if ($showRequirementButtons) {
5482
            $buttons = [
5483
                [
5484
                    'title' => get_lang('Set previous step as prerequisite for each step'),
5485
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5486
                        'action' => 'set_previous_step_as_prerequisite',
5487
                        'lp_id' => $lpId,
5488
                    ]),
5489
                ],
5490
                [
5491
                    'title' => get_lang('Clear all prerequisites'),
5492
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5493
                        'action' => 'clear_prerequisites',
5494
                        'lp_id' => $lpId,
5495
                    ]),
5496
                ],
5497
            ];
5498
            $actionsRight = Display::groupButtonWithDropDown(
5499
                get_lang('Prerequisites options'),
5500
                $buttons,
5501
                true
5502
            );
5503
        }
5504
5505
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
5506
            $actionsLeft .= Display::url(
5507
                Display::return_icon(
5508
                    'add-groups.png',
5509
                    get_lang('Author'),
5510
                    '',
5511
                    ICON_SIZE_MEDIUM
5512
                ),
5513
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5514
                    'action' => 'author_view',
5515
                    'lp_id' => $lpId,
5516
                ])
5517
            );
5518
        }
5519
5520
        $toolbar = Display::toolbarAction('actions-lp-controller', [$actionsLeft, $actionsRight]);
5521
5522
        if ($returnString) {
5523
            return $toolbar;
5524
        }
5525
5526
        echo $toolbar;
5527
    }
5528
5529
    /**
5530
     * Creates the default learning path folder.
5531
     *
5532
     * @param array $course
5533
     * @param int   $creatorId
5534
     *
5535
     * @return CDocument
5536
     */
5537
    public static function generate_learning_path_folder($course, $creatorId = 0)
5538
    {
5539
        // Creating learning_path folder
5540
        $dir = 'learning_path';
5541
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5542
5543
        return create_unexisting_directory(
5544
            $course,
5545
            $creatorId,
5546
            0,
5547
            null,
5548
            0,
5549
            '',
5550
            $dir,
5551
            get_lang('Learning paths'),
5552
            0
5553
        );
5554
    }
5555
5556
    /**
5557
     * @param array  $course
5558
     * @param string $lp_name
5559
     * @param int    $creatorId
5560
     *
5561
     * @return CDocument
5562
     */
5563
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
5564
    {
5565
        $filepath = '';
5566
        $dir = '/learning_path/';
5567
5568
        if (empty($lp_name)) {
5569
            $lp_name = $this->name;
5570
        }
5571
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5572
        $parent = self::generate_learning_path_folder($course, $creatorId);
5573
5574
        // Limits title size
5575
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
5576
        $dir = $dir.$title;
5577
5578
        // Creating LP folder
5579
        $documentId = null;
5580
        $folder = null;
5581
        if ($parent) {
5582
            $folder = create_unexisting_directory(
5583
                $course,
5584
                $creatorId,
5585
                0,
5586
                0,
5587
                0,
5588
                $filepath,
5589
                $dir,
5590
                $lp_name,
5591
                '',
5592
                false,
5593
                false,
5594
                $parent
5595
            );
5596
        }
5597
5598
        return $folder;
5599
    }
5600
5601
    /**
5602
     * Create a new document //still needs some finetuning.
5603
     *
5604
     * @param array  $courseInfo
5605
     * @param string $content
5606
     * @param string $title
5607
     * @param string $extension
5608
     * @param int    $parentId
5609
     * @param int    $creatorId  creator id
5610
     *
5611
     * @return int
5612
     */
5613
    public function create_document(
5614
        $courseInfo,
5615
        $content = '',
5616
        $title = '',
5617
        $extension = 'html',
5618
        $parentId = 0,
5619
        $creatorId = 0
5620
    ) {
5621
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
5622
        $sessionId = api_get_session_id();
5623
5624
        // Generates folder
5625
        $this->generate_lp_folder($courseInfo);
5626
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
5627
        // is already escaped twice when it gets here.
5628
        $originalTitle = !empty($title) ? $title : $_POST['title'];
5629
        if (!empty($title)) {
5630
            $title = api_replace_dangerous_char(stripslashes($title));
5631
        } else {
5632
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
5633
        }
5634
5635
        $title = disable_dangerous_file($title);
5636
        $filename = $title;
5637
        $tmp_filename = $filename;
5638
        /*$i = 0;
5639
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
5640
            $tmp_filename = $filename.'_'.++$i;
5641
        }*/
5642
        $filename = $tmp_filename.'.'.$extension;
5643
5644
        if ('html' === $extension) {
5645
            $content = stripslashes($content);
5646
            $content = str_replace(
5647
                api_get_path(WEB_COURSE_PATH),
5648
                api_get_path(REL_PATH).'courses/',
5649
                $content
5650
            );
5651
5652
            // Change the path of mp3 to absolute.
5653
            // The first regexp deals with :// urls.
5654
            /*$content = preg_replace(
5655
                "|(flashvars=\"file=)([^:/]+)/|",
5656
                "$1".api_get_path(
5657
                    REL_COURSE_PATH
5658
                ).$courseInfo['path'].'/document/',
5659
                $content
5660
            );*/
5661
            // The second regexp deals with audio/ urls.
5662
            /*$content = preg_replace(
5663
                "|(flashvars=\"file=)([^/]+)/|",
5664
                "$1".api_get_path(
5665
                    REL_COURSE_PATH
5666
                ).$courseInfo['path'].'/document/$2/',
5667
                $content
5668
            );*/
5669
            // For flv player: To prevent edition problem with firefox,
5670
            // we have to use a strange tip (don't blame me please).
5671
            $content = str_replace(
5672
                '</body>',
5673
                '<style type="text/css">body{}</style></body>',
5674
                $content
5675
            );
5676
        }
5677
5678
        $document = DocumentManager::addDocument(
5679
            $courseInfo,
5680
            null,
5681
            'file',
5682
            '',
5683
            $tmp_filename,
5684
            '',
5685
            0, //readonly
5686
            true,
5687
            null,
5688
            $sessionId,
5689
            $creatorId,
5690
            false,
5691
            $content,
5692
            $parentId
5693
        );
5694
5695
        $document_id = $document->getIid();
5696
        if ($document_id) {
5697
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
5698
            $new_title = $originalTitle;
5699
5700
            if ($new_comment || $new_title) {
5701
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
5702
                $ct = '';
5703
                if ($new_comment) {
5704
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
5705
                }
5706
                if ($new_title) {
5707
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
5708
                }
5709
5710
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
5711
                        WHERE iid = $document_id ";
5712
                Database::query($sql);
5713
            }
5714
        }
5715
5716
        return $document_id;
5717
    }
5718
5719
    /**
5720
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
5721
     */
5722
    public function edit_document()
5723
    {
5724
        $repo = Container::getDocumentRepository();
5725
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
5726
            $id = (int) $_REQUEST['document_id'];
5727
            /** @var CDocument $document */
5728
            $document = $repo->find($id);
5729
            if ($document->getResourceNode()->hasEditableTextContent()) {
5730
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
5731
                /*   $nodeRepo = Container::getDocumentRepository()->getResourceNodeRepository();
5732
                   $nodeRepo->update($document->getResourceNode());
5733
                   var_dump($document->getResourceNode()->getContent());
5734
                   exit;*/
5735
            }
5736
            $document->setTitle($_REQUEST['title']);
5737
            $repo->update($document);
5738
        }
5739
    }
5740
5741
    /**
5742
     * Displays the selected item, with a panel for manipulating the item.
5743
     *
5744
     * @param CLpItem $lpItem
5745
     * @param string  $msg
5746
     * @param bool    $show_actions
5747
     *
5748
     * @return string
5749
     */
5750
    public function display_item($lpItem, $msg = null, $show_actions = true)
5751
    {
5752
        $course_id = api_get_course_int_id();
5753
        $return = '';
5754
5755
        if (null === $lpItem) {
5756
            return '';
5757
        }
5758
        $item_id = $lpItem->getIid();
5759
        $itemType = $lpItem->getItemType();
5760
        $lpId = $lpItem->getLp()->getIid();
5761
        $path = $lpItem->getPath();
5762
5763
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
5764
5765
        // Prevents wrong parent selection for document, see Bug#1251.
5766
        if ('dir' !== $itemType) {
5767
            Session::write('parent_item_id', $lpItem->getParentItemId());
5768
        }
5769
5770
        if ($show_actions) {
5771
            $return .= $this->displayItemMenu($lpItem);
5772
        }
5773
        $return .= '<div style="padding:10px;">';
5774
5775
        if ('' != $msg) {
5776
            $return .= $msg;
5777
        }
5778
5779
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
5780
5781
        switch ($itemType) {
5782
            case TOOL_THREAD:
5783
                $link = $this->rl_get_resource_link_for_learnpath(
5784
                    $course_id,
5785
                    $lpId,
5786
                    $item_id,
5787
                    0
5788
                );
5789
                $return .= Display::url(
5790
                    get_lang('Go to thread'),
5791
                    $link,
5792
                    ['class' => 'btn btn-primary']
5793
                );
5794
                break;
5795
            case TOOL_FORUM:
5796
                $return .= Display::url(
5797
                    get_lang('Go to the forum'),
5798
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
5799
                    ['class' => 'btn btn-primary']
5800
                );
5801
                break;
5802
            case TOOL_QUIZ:
5803
                if (!empty($path)) {
5804
                    $exercise = new Exercise();
5805
                    $exercise->read($path);
5806
                    $return .= $exercise->description.'<br />';
5807
                    $return .= Display::url(
5808
                        get_lang('Go to exercise'),
5809
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
5810
                        ['class' => 'btn btn-primary']
5811
                    );
5812
                }
5813
                break;
5814
            case TOOL_LP_FINAL_ITEM:
5815
                $return .= $this->getSavedFinalItem();
5816
                break;
5817
            case TOOL_DOCUMENT:
5818
            case TOOL_READOUT_TEXT:
5819
                $repo = Container::getDocumentRepository();
5820
                /** @var CDocument $document */
5821
                $document = $repo->find($lpItem->getPath());
5822
                $return .= $this->display_document($document, true, true);
5823
                break;
5824
            case TOOL_HOTPOTATOES:
5825
                $return .= $this->display_document($document, false, true);
5826
                break;
5827
        }
5828
        $return .= '</div>';
5829
5830
        return $return;
5831
    }
5832
5833
    /**
5834
     * Shows the needed forms for editing a specific item.
5835
     *
5836
     * @param CLpItem $lpItem
5837
     *
5838
     * @throws Exception
5839
     *
5840
     *
5841
     * @return string
5842
     */
5843
    public function display_edit_item($lpItem, $excludeExtraFields = [])
5844
    {
5845
        $return = '';
5846
        if (empty($lpItem)) {
5847
            return '';
5848
        }
5849
        $itemType = $lpItem->getItemType();
5850
        $path = $lpItem->getPath();
5851
5852
        switch ($itemType) {
5853
            case 'dir':
5854
            case 'asset':
5855
            case 'sco':
5856
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
5857
                    $return .= $this->displayItemMenu($lpItem);
5858
                    $return .= $this->display_item_form($lpItem, 'edit');
5859
                } else {
5860
                    $return .= $this->display_item_form($lpItem, 'edit_item');
5861
                }
5862
                break;
5863
            case TOOL_LP_FINAL_ITEM:
5864
            case TOOL_DOCUMENT:
5865
            case TOOL_READOUT_TEXT:
5866
                $return .= $this->displayItemMenu($lpItem);
5867
                $return .= $this->displayDocumentForm('edit', $lpItem);
5868
                break;
5869
            case TOOL_LINK:
5870
                $link = null;
5871
                if (!empty($path)) {
5872
                    $repo = Container::getLinkRepository();
5873
                    $link = $repo->find($path);
5874
                }
5875
                $return .= $this->displayItemMenu($lpItem);
5876
                $return .= $this->display_link_form('edit', $lpItem, $link);
5877
5878
                break;
5879
            case TOOL_QUIZ:
5880
                if (!empty($path)) {
5881
                    $repo = Container::getQuizRepository();
5882
                    $resource = $repo->find($path);
5883
                }
5884
                $return .= $this->displayItemMenu($lpItem);
5885
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
5886
                break;
5887
            /*case TOOL_HOTPOTATOES:
5888
                $return .= $this->displayItemMenu($lpItem);
5889
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
5890
                break;*/
5891
            case TOOL_STUDENTPUBLICATION:
5892
                if (!empty($path)) {
5893
                    $repo = Container::getStudentPublicationRepository();
5894
                    $resource = $repo->find($path);
5895
                }
5896
                $return .= $this->displayItemMenu($lpItem);
5897
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
5898
                break;
5899
            case TOOL_FORUM:
5900
                if (!empty($path)) {
5901
                    $repo = Container::getForumRepository();
5902
                    $resource = $repo->find($path);
5903
                }
5904
                $return .= $this->displayItemMenu($lpItem);
5905
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
5906
                break;
5907
            case TOOL_THREAD:
5908
                if (!empty($path)) {
5909
                    $repo = Container::getForumPostRepository();
5910
                    $resource = $repo->find($path);
5911
                }
5912
                $return .= $this->displayItemMenu($lpItem);
5913
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
5914
                break;
5915
        }
5916
5917
        return $return;
5918
    }
5919
5920
    /**
5921
     * Function that displays a list with al the resources that
5922
     * could be added to the learning path.
5923
     *
5924
     * @throws Exception
5925
     */
5926
    public function displayResources(): string
5927
    {
5928
        // Get all the docs.
5929
        $documents = $this->get_documents(true);
5930
5931
        // Get all the exercises.
5932
        $exercises = $this->get_exercises();
5933
5934
        // Get all the links.
5935
        $links = $this->get_links();
5936
5937
        // Get all the student publications.
5938
        $works = $this->get_student_publications();
5939
5940
        // Get all the forums.
5941
        $forums = $this->get_forums();
5942
5943
        // Get the final item form (see BT#11048) .
5944
        $finish = $this->getFinalItemForm();
5945
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
5946
        $headers = [
5947
            Display::return_icon('folder_document.png', get_lang('Documents'), [], $size),
5948
            Display::return_icon('quiz.png', get_lang('Tests'), [], $size),
5949
            Display::return_icon('links.png', get_lang('Links'), [], $size),
5950
            Display::return_icon('works.png', get_lang('Assignments'), [], $size),
5951
            Display::return_icon('forum.png', get_lang('Forums'), [], $size),
5952
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], $size),
5953
            Display::return_icon('certificate.png', get_lang('Certificate'), [], $size),
5954
        ];
5955
        $content = '';
5956
        /*$content = Display::return_message(
5957
            get_lang('Click on the [Learner view] button to see your learning path'),
5958
            'normal'
5959
        );*/
5960
        $section = $this->displayNewSectionForm();
5961
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
5962
5963
        return Display::tabs(
5964
            $headers,
5965
            [
5966
                $documents,
5967
                $exercises,
5968
                $links,
5969
                $works,
5970
                $forums,
5971
                $section,
5972
                $finish,
5973
            ],
5974
            'resource_tab',
5975
            [],
5976
            [],
5977
            $selected
5978
        );
5979
    }
5980
5981
    /**
5982
     * Returns the extension of a document.
5983
     *
5984
     * @param string $filename
5985
     *
5986
     * @return string Extension (part after the last dot)
5987
     */
5988
    public function get_extension($filename)
5989
    {
5990
        $explode = explode('.', $filename);
5991
5992
        return $explode[count($explode) - 1];
5993
    }
5994
5995
    /**
5996
     * @return string
5997
     */
5998
    public function getCurrentBuildingModeURL()
5999
    {
6000
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
6001
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
6002
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
6003
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
6004
6005
        $currentUrl = api_get_self().'?'.api_get_cidreq().
6006
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
6007
6008
        return $currentUrl;
6009
    }
6010
6011
    /**
6012
     * Displays a document by id.
6013
     *
6014
     * @param CDocument $document
6015
     * @param bool      $show_title
6016
     * @param bool      $iframe
6017
     * @param bool      $edit_link
6018
     *
6019
     * @return string
6020
     */
6021
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
6022
    {
6023
        $return = '';
6024
        if (!$document) {
6025
            return '';
6026
        }
6027
6028
        $repo = Container::getDocumentRepository();
6029
6030
        // TODO: Add a path filter.
6031
        if ($iframe) {
6032
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
6033
            $url = $repo->getResourceFileUrl($document);
6034
6035
            $return .= '<iframe
6036
                id="learnpath_preview_frame"
6037
                frameborder="0"
6038
                height="400"
6039
                width="100%"
6040
                scrolling="auto"
6041
                src="'.$url.'"></iframe>';
6042
        } else {
6043
            $return = $repo->getResourceFileContent($document);
6044
        }
6045
6046
        return $return;
6047
    }
6048
6049
    /**
6050
     * Return HTML form to add/edit a link item.
6051
     *
6052
     * @param string  $action (add/edit)
6053
     * @param CLpItem $lpItem
6054
     * @param CLink   $link
6055
     *
6056
     * @throws Exception
6057
     *
6058
     *
6059
     * @return string HTML form
6060
     */
6061
    public function display_link_form($action, $lpItem, $link)
6062
    {
6063
        $item_url = '';
6064
        if ($link) {
6065
            $item_url = stripslashes($link->getUrl());
6066
        }
6067
        $form = new FormValidator(
6068
            'edit_link',
6069
            'POST',
6070
            $this->getCurrentBuildingModeURL()
6071
        );
6072
6073
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6074
6075
        $urlAttributes = ['class' => 'learnpath_item_form'];
6076
        $urlAttributes['disabled'] = 'disabled';
6077
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
6078
        $form->setDefault('url', $item_url);
6079
6080
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6081
6082
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6083
    }
6084
6085
    /**
6086
     * Return HTML form to add/edit a quiz.
6087
     *
6088
     * @param string  $action   Action (add/edit)
6089
     * @param CLpItem $lpItem   Item ID if already exists
6090
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
6091
     *
6092
     * @throws Exception
6093
     *
6094
     * @return string HTML form
6095
     */
6096
    public function display_quiz_form($action, $lpItem, $exercise)
6097
    {
6098
        $form = new FormValidator(
6099
            'quiz_form',
6100
            'POST',
6101
            $this->getCurrentBuildingModeURL()
6102
        );
6103
6104
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6105
6106
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6107
6108
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6109
    }
6110
6111
    /**
6112
     * Return the form to display the forum edit/add option.
6113
     *
6114
     * @param CLpItem $lpItem
6115
     *
6116
     * @throws Exception
6117
     *
6118
     * @return string HTML form
6119
     */
6120
    public function display_forum_form($action, $lpItem, $resource)
6121
    {
6122
        $form = new FormValidator(
6123
            'forum_form',
6124
            'POST',
6125
            $this->getCurrentBuildingModeURL()
6126
        );
6127
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6128
6129
        if ('add' === $action) {
6130
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
6131
        } else {
6132
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
6133
        }
6134
6135
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6136
    }
6137
6138
    /**
6139
     * Return HTML form to add/edit forum threads.
6140
     *
6141
     * @param string  $action
6142
     * @param CLpItem $lpItem
6143
     * @param string  $resource
6144
     *
6145
     * @throws Exception
6146
     *
6147
     * @return string HTML form
6148
     */
6149
    public function display_thread_form($action, $lpItem, $resource)
6150
    {
6151
        $form = new FormValidator(
6152
            'thread_form',
6153
            'POST',
6154
            $this->getCurrentBuildingModeURL()
6155
        );
6156
6157
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6158
6159
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6160
6161
        return $form->returnForm();
6162
    }
6163
6164
    /**
6165
     * Return the HTML form to display an item (generally a dir item).
6166
     *
6167
     * @param CLpItem $lpItem
6168
     * @param string  $action
6169
     *
6170
     * @throws Exception
6171
     *
6172
     *
6173
     * @return string HTML form
6174
     */
6175
    public function display_item_form(
6176
        $lpItem,
6177
        $action = 'add_item'
6178
    ) {
6179
        $item_type = $lpItem->getItemType();
6180
6181
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6182
6183
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6184
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6185
6186
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6187
6188
        return $form->returnForm();
6189
    }
6190
6191
    /**
6192
     * Return HTML form to add/edit a student publication (work).
6193
     *
6194
     * @param string              $action
6195
     * @param CStudentPublication $resource
6196
     *
6197
     * @throws Exception
6198
     *
6199
     * @return string HTML form
6200
     */
6201
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
6202
    {
6203
        $form = new FormValidator('frm_student_publication', 'post', '#');
6204
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6205
6206
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6207
6208
        $return = '<div class="sectioncomment">';
6209
        $return .= $form->returnForm();
6210
        $return .= '</div>';
6211
6212
        return $return;
6213
    }
6214
6215
    public function displayNewSectionForm()
6216
    {
6217
        $action = 'add_item';
6218
        $item_type = 'dir';
6219
6220
        $lpItem = new CLpItem();
6221
        $lpItem->setTitle('dir');
6222
        $lpItem->setItemType('dir');
6223
6224
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6225
6226
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6227
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
6228
6229
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6230
        $form->addElement('hidden', 'type', 'dir');
6231
6232
        return $form->returnForm();
6233
    }
6234
6235
    /**
6236
     * Returns the form to update or create a document.
6237
     *
6238
     * @param string  $action (add/edit)
6239
     * @param CLpItem $lpItem
6240
     *
6241
     *
6242
     * @throws Exception
6243
     *
6244
     * @return string HTML form
6245
     */
6246
    public function displayDocumentForm($action = 'add', $lpItem = null)
6247
    {
6248
        $courseInfo = api_get_course_info();
6249
6250
        $form = new FormValidator(
6251
            'form',
6252
            'POST',
6253
            $this->getCurrentBuildingModeURL(),
6254
            '',
6255
            ['enctype' => 'multipart/form-data']
6256
        );
6257
6258
        $data = $this->generate_lp_folder($courseInfo);
6259
6260
        if (null !== $lpItem) {
6261
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6262
        }
6263
6264
        switch ($action) {
6265
            case 'add':
6266
                $folders = DocumentManager::get_all_document_folders(
6267
                    $courseInfo,
6268
                    0,
6269
                    true
6270
                );
6271
                DocumentManager::build_directory_selector(
6272
                    $folders,
6273
                    '',
6274
                    [],
6275
                    true,
6276
                    $form,
6277
                    'directory_parent_id'
6278
                );
6279
6280
                if ($data) {
6281
                    $defaults['directory_parent_id'] = $data->getIid();
6282
                }
6283
6284
                break;
6285
        }
6286
6287
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6288
6289
        return $form->returnForm();
6290
    }
6291
6292
    /**
6293
     * @param array  $courseInfo
6294
     * @param string $content
6295
     * @param string $title
6296
     * @param int    $parentId
6297
     *
6298
     * @return int
6299
     */
6300
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
6301
    {
6302
        $creatorId = api_get_user_id();
6303
        $sessionId = api_get_session_id();
6304
6305
        // Generates folder
6306
        $result = $this->generate_lp_folder($courseInfo);
6307
        $dir = $result['dir'];
6308
6309
        if (empty($parentId) || '/' == $parentId) {
6310
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6311
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6312
6313
            if ('/' === $parentId) {
6314
                $dir = '/';
6315
            }
6316
6317
            // Please, do not modify this dirname formatting.
6318
            if (strstr($dir, '..')) {
6319
                $dir = '/';
6320
            }
6321
6322
            if (!empty($dir[0]) && '.' == $dir[0]) {
6323
                $dir = substr($dir, 1);
6324
            }
6325
            if (!empty($dir[0]) && '/' != $dir[0]) {
6326
                $dir = '/'.$dir;
6327
            }
6328
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6329
                $dir .= '/';
6330
            }
6331
        } else {
6332
            $parentInfo = DocumentManager::get_document_data_by_id(
6333
                $parentId,
6334
                $courseInfo['code']
6335
            );
6336
            if (!empty($parentInfo)) {
6337
                $dir = $parentInfo['path'].'/';
6338
            }
6339
        }
6340
6341
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6342
6343
        if (!is_dir($filepath)) {
6344
            $dir = '/';
6345
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6346
        }
6347
6348
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6349
6350
        if (!empty($title)) {
6351
            $title = api_replace_dangerous_char(stripslashes($title));
6352
        } else {
6353
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6354
        }
6355
6356
        $title = disable_dangerous_file($title);
6357
        $filename = $title;
6358
        $content = !empty($content) ? $content : $_POST['content_lp'];
6359
        $tmpFileName = $filename;
6360
6361
        $i = 0;
6362
        while (file_exists($filepath.$tmpFileName.'.html')) {
6363
            $tmpFileName = $filename.'_'.++$i;
6364
        }
6365
6366
        $filename = $tmpFileName.'.html';
6367
        $content = stripslashes($content);
6368
6369
        if (file_exists($filepath.$filename)) {
6370
            return 0;
6371
        }
6372
6373
        $putContent = file_put_contents($filepath.$filename, $content);
6374
6375
        if (false === $putContent) {
6376
            return 0;
6377
        }
6378
6379
        $fileSize = filesize($filepath.$filename);
6380
        $saveFilePath = $dir.$filename;
6381
6382
        $document = DocumentManager::addDocument(
6383
            $courseInfo,
6384
            $saveFilePath,
6385
            'file',
6386
            $fileSize,
6387
            $tmpFileName,
6388
            '',
6389
            0, //readonly
6390
            true,
6391
            null,
6392
            $sessionId,
6393
            $creatorId
6394
        );
6395
6396
        $documentId = $document->getId();
6397
6398
        if (!$document) {
6399
            return 0;
6400
        }
6401
6402
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6403
        $newTitle = $originalTitle;
6404
6405
        if ($newComment || $newTitle) {
6406
            $em = Database::getManager();
6407
6408
            if ($newComment) {
6409
                $document->setComment($newComment);
6410
            }
6411
6412
            if ($newTitle) {
6413
                $document->setTitle($newTitle);
6414
            }
6415
6416
            $em->persist($document);
6417
            $em->flush();
6418
        }
6419
6420
        return $documentId;
6421
    }
6422
6423
    /**
6424
     * Displays the menu for manipulating a step.
6425
     *
6426
     * @return string
6427
     */
6428
    public function displayItemMenu(CLpItem $lpItem)
6429
    {
6430
        $item_id = $lpItem->getIid();
6431
        $audio = $lpItem->getAudio();
6432
        $itemType = $lpItem->getItemType();
6433
        $path = $lpItem->getPath();
6434
6435
        $return = '';
6436
        $audio_player = null;
6437
        // We display an audio player if needed.
6438
        if (!empty($audio)) {
6439
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
6440
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
6441
                .'<audio src="'.$webAudioPath.'" controls>'
6442
                .'</div><br>';*/
6443
        }
6444
6445
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
6446
6447
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
6448
            $return .= Display::url(
6449
                Display::return_icon(
6450
                    'edit.png',
6451
                    get_lang('Edit'),
6452
                    [],
6453
                    ICON_SIZE_SMALL
6454
                ),
6455
                $url.'&action=edit_item&path_item='.$path
6456
            );
6457
6458
            /*$return .= Display::url(
6459
                Display::return_icon(
6460
                    'move.png',
6461
                    get_lang('Move'),
6462
                    [],
6463
                    ICON_SIZE_SMALL
6464
                ),
6465
                $url.'&action=move_item'
6466
            );*/
6467
        }
6468
6469
        // Commented for now as prerequisites cannot be added to chapters.
6470
        if ('dir' !== $itemType) {
6471
            $return .= Display::url(
6472
                Display::return_icon(
6473
                    'accept.png',
6474
                    get_lang('Prerequisites'),
6475
                    [],
6476
                    ICON_SIZE_SMALL
6477
                ),
6478
                $url.'&action=edit_item_prereq'
6479
            );
6480
        }
6481
        $return .= Display::url(
6482
            Display::return_icon(
6483
                'delete.png',
6484
                get_lang('Delete'),
6485
                [],
6486
                ICON_SIZE_SMALL
6487
            ),
6488
            $url.'&action=delete_item'
6489
        );
6490
6491
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
6492
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
6493
            if (empty($documentData)) {
6494
                // Try with iid
6495
                $table = Database::get_course_table(TABLE_DOCUMENT);
6496
                $sql = "SELECT path FROM $table
6497
                        WHERE
6498
                              c_id = ".api_get_course_int_id()." AND
6499
                              iid = ".$path." AND
6500
                              path NOT LIKE '%_DELETED_%'";
6501
                $result = Database::query($sql);
6502
                $documentData = Database::fetch_array($result);
6503
                if ($documentData) {
6504
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
6505
                }
6506
            }
6507
            if (isset($documentData['absolute_path_from_document'])) {
6508
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
6509
            }
6510
        }*/
6511
6512
        if (!empty($audio_player)) {
6513
            $return .= $audio_player;
6514
        }
6515
6516
        return Display::toolbarAction('lp_item', [$return]);
6517
    }
6518
6519
    /**
6520
     * Creates the javascript needed for filling up the checkboxes without page reload.
6521
     *
6522
     * @return string
6523
     */
6524
    public function get_js_dropdown_array()
6525
    {
6526
        $course_id = api_get_course_int_id();
6527
        $return = 'var child_name = new Array();'."\n";
6528
        $return .= 'var child_value = new Array();'."\n\n";
6529
        $return .= 'child_name[0] = new Array();'."\n";
6530
        $return .= 'child_value[0] = new Array();'."\n\n";
6531
6532
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6533
        $sql = "SELECT * FROM ".$tbl_lp_item."
6534
                WHERE
6535
                    lp_id = ".$this->lp_id." AND
6536
                    parent_item_id = 0
6537
                ORDER BY display_order ASC";
6538
        Database::query($sql);
6539
        $i = 0;
6540
6541
        $list = $this->getItemsForForm(true);
6542
6543
        foreach ($list as $row_zero) {
6544
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
6545
                if (TOOL_QUIZ == $row_zero['item_type']) {
6546
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
6547
                }
6548
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
6549
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
6550
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
6551
            }
6552
        }
6553
6554
        $return .= "\n";
6555
        $sql = "SELECT * FROM $tbl_lp_item
6556
                WHERE lp_id = ".$this->lp_id;
6557
        $res = Database::query($sql);
6558
        while ($row = Database::fetch_array($res)) {
6559
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
6560
                           WHERE
6561
                                parent_item_id = ".$row['iid']."
6562
                           ORDER BY display_order ASC";
6563
            $res_parent = Database::query($sql_parent);
6564
            $i = 0;
6565
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
6566
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
6567
6568
            while ($row_parent = Database::fetch_array($res_parent)) {
6569
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
6570
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
6571
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
6572
            }
6573
            $return .= "\n";
6574
        }
6575
6576
        $return .= "
6577
            function load_cbo(id) {
6578
                if (!id) {
6579
                    return false;
6580
                }
6581
6582
                var cbo = document.getElementById('previous');
6583
                for(var i = cbo.length - 1; i > 0; i--) {
6584
                    cbo.options[i] = null;
6585
                }
6586
6587
                var k=0;
6588
                for(var i = 1; i <= child_name[id].length; i++){
6589
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
6590
                    option.style.paddingLeft = '40px';
6591
                    cbo.options[i] = option;
6592
                    k = i;
6593
                }
6594
6595
                cbo.options[k].selected = true;
6596
                //$('#previous').selectpicker('refresh');
6597
            }";
6598
6599
        return $return;
6600
    }
6601
6602
    /**
6603
     * Display the form to allow moving an item.
6604
     *
6605
     * @param CLpItem $lpItem
6606
     *
6607
     * @throws Exception
6608
     *
6609
     *
6610
     * @return string HTML form
6611
     */
6612
    public function display_move_item($lpItem)
6613
    {
6614
        $return = '';
6615
        $path = $lpItem->getPath();
6616
6617
        if ($lpItem) {
6618
            $itemType = $lpItem->getItemType();
6619
            switch ($itemType) {
6620
                case 'dir':
6621
                case 'asset':
6622
                    $return .= $this->displayItemMenu($lpItem);
6623
                    $return .= $this->display_item_form(
6624
                        $lpItem,
6625
                        get_lang('Move the current section'),
6626
                        'move',
6627
                        $row
6628
                    );
6629
                    break;
6630
                case TOOL_DOCUMENT:
6631
                    $return .= $this->displayItemMenu($lpItem);
6632
                    $return .= $this->displayDocumentForm('move', $lpItem);
6633
                    break;
6634
                case TOOL_LINK:
6635
                    $link = null;
6636
                    if (!empty($path)) {
6637
                        $repo = Container::getLinkRepository();
6638
                        $link = $repo->find($path);
6639
                    }
6640
                    $return .= $this->displayItemMenu($lpItem);
6641
                    $return .= $this->display_link_form('move', $lpItem, $link);
6642
                    break;
6643
                case TOOL_HOTPOTATOES:
6644
                    $return .= $this->displayItemMenu($lpItem);
6645
                    $return .= $this->display_link_form('move', $lpItem, $row);
6646
                    break;
6647
                case TOOL_QUIZ:
6648
                    $return .= $this->displayItemMenu($lpItem);
6649
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
6650
                    break;
6651
                case TOOL_STUDENTPUBLICATION:
6652
                    $return .= $this->displayItemMenu($lpItem);
6653
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
6654
                    break;
6655
                case TOOL_FORUM:
6656
                    $return .= $this->displayItemMenu($lpItem);
6657
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6658
                    break;
6659
                case TOOL_THREAD:
6660
                    $return .= $this->displayItemMenu($lpItem);
6661
                    $return .= $this->display_forum_form('move', $lpItem, $row);
6662
                    break;
6663
            }
6664
        }
6665
6666
        return $return;
6667
    }
6668
6669
    /**
6670
     * Return HTML form to allow prerequisites selection.
6671
     *
6672
     * @todo use FormValidator
6673
     *
6674
     * @return string HTML form
6675
     */
6676
    public function display_item_prerequisites_form(CLpItem $lpItem)
6677
    {
6678
        $course_id = api_get_course_int_id();
6679
        $preRequisiteId = $lpItem->getPrerequisite();
6680
        $itemId = $lpItem->getIid();
6681
6682
        $return = Display::page_header(get_lang('Add/edit prerequisites').' '.$lpItem->getTitle());
6683
6684
        $return .= '<form method="POST">';
6685
        $return .= '<div class="table-responsive">';
6686
        $return .= '<table class="table table-hover">';
6687
        $return .= '<thead>';
6688
        $return .= '<tr>';
6689
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
6690
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
6691
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
6692
        $return .= '</tr>';
6693
        $return .= '</thead>';
6694
6695
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
6696
        $return .= '<tbody>';
6697
        $return .= '<tr>';
6698
        $return .= '<td colspan="3">';
6699
        $return .= '<div class="radio learnpath"><label for="idnone">';
6700
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
6701
        $return .= get_lang('none').'</label>';
6702
        $return .= '</div>';
6703
        $return .= '</tr>';
6704
6705
        // @todo use entitites
6706
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6707
        $sql = "SELECT * FROM $tbl_lp_item
6708
                WHERE lp_id = ".$this->lp_id;
6709
        $result = Database::query($sql);
6710
6711
        $selectedMinScore = [];
6712
        $selectedMaxScore = [];
6713
        $masteryScore = [];
6714
        while ($row = Database::fetch_array($result)) {
6715
            if ($row['iid'] == $itemId) {
6716
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
6717
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
6718
            }
6719
            $masteryScore[$row['iid']] = $row['mastery_score'];
6720
        }
6721
6722
        $displayOrder = $lpItem->getDisplayOrder();
6723
        $lpItemRepo = Container::getLpItemRepository();
6724
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
6725
        $em = Database::getManager();
6726
6727
        $currentItemId = $itemId;
6728
        $options = [
6729
            'decorate' => true,
6730
            'rootOpen' => function() {
6731
                return '';
6732
            },
6733
            'rootClose' => function() {
6734
                return '';
6735
            },
6736
            'childOpen' => function() {
6737
                return '';
6738
            },
6739
            'childClose' => '',
6740
            'nodeDecorator' => function ($item) use (
6741
                $currentItemId,
6742
                $preRequisiteId,
6743
                $course_id,
6744
                $selectedMaxScore,
6745
                $selectedMinScore,
6746
                $displayOrder,
6747
                $lpItemRepo,
6748
                $em
6749
            ) {
6750
                $mainUrl = '';
6751
                $fullTitle = $item['title'];
6752
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
6753
                $itemId = $item['iid'];
6754
                $type = $item['itemType'];
6755
                $lpId = $this->get_id();
6756
                $iconName = str_replace(' ', '', $type);
6757
                $icon = Display::return_icon(
6758
                    'lp_'.$iconName.'.png',
6759
                    '',
6760
                    [],
6761
                    ICON_SIZE_TINY
6762
                );
6763
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
6764
6765
                if ($itemId == $currentItemId) {
6766
                    return '';
6767
                }
6768
6769
                if ($displayOrder < $item['displayOrder']) {
6770
                    return '';
6771
                }
6772
6773
                $selectedMaxScoreValue = isset($selectedMaxScore[$itemId]) ? $selectedMaxScore[$itemId] : $item['maxScore'];
6774
                $selectedMinScoreValue = $selectedMinScore[$itemId] ?? 0;
6775
                $masteryScoreAsMinValue = $masteryScore[$itemId] ?? 0;
6776
6777
                $return = '<tr>';
6778
                $return .= '<td '.((TOOL_QUIZ != $type && TOOL_HOTPOTATOES != $type) ? ' colspan="3"' : '').'>';
6779
                $return .= '<div style="margin-left:'.($item['lvl'] * 20).'px;" class="radio learnpath">';
6780
                $return .= '<label for="id'.$itemId.'">';
6781
6782
                $checked = '';
6783
                if (null !== $preRequisiteId) {
6784
                    $checked = in_array($preRequisiteId, [$itemId, $item['ref']]) ? ' checked="checked" ' : '';
6785
                }
6786
6787
                $disabled = 'dir' === $type ? ' disabled="disabled" ' : '';
6788
6789
                $return .= '<input
6790
                    '.$checked.' '.$disabled.'
6791
                    id="id'.$itemId.'"
6792
                    name="prerequisites"
6793
                    type="radio"
6794
                    value="'.$itemId.'" />';
6795
6796
                $return .= $icon.'&nbsp;&nbsp;'.$item['title'].'</label>';
6797
                $return .= '</div>';
6798
                $return .= '</td>';
6799
6800
                if (TOOL_QUIZ == $type) {
6801
                    // lets update max_score Tests information depending of the Tests Advanced properties
6802
                    $exercise = new Exercise($course_id);
6803
                    /** @var CLpItem $itemEntity */
6804
                    $itemEntity = $lpItemRepo->find($itemId);
6805
                    $exercise->read($item['path']);
6806
                    $itemEntity->setMaxScore($exercise->get_max_score());
6807
                    $em->persist($itemEntity);
6808
                    $em->flush($itemEntity);
6809
6810
                    $item['maxScore'] = $exercise->get_max_score();
6811
6812
                    if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
6813
                        // Backwards compatibility with 1.9.x use mastery_score as min value
6814
                        $selectedMinScoreValue = $masteryScoreAsMinValue;
6815
                    }
6816
                    $return .= '<td>';
6817
                    $return .= '<input
6818
                        class="form-control"
6819
                        size="4" maxlength="3"
6820
                        name="min_'.$itemId.'"
6821
                        type="number"
6822
                        min="0"
6823
                        step="any"
6824
                        max="'.$item['maxScore'].'"
6825
                        value="'.$selectedMinScoreValue.'"
6826
                    />';
6827
                    $return .= '</td>';
6828
                    $return .= '<td>';
6829
                    $return .= '<input
6830
                        class="form-control"
6831
                        size="4"
6832
                        maxlength="3"
6833
                        name="max_'.$itemId.'"
6834
                        type="number"
6835
                        min="0"
6836
                        step="any"
6837
                        max="'.$item['maxScore'].'"
6838
                        value="'.$selectedMaxScoreValue.'"
6839
                    />';
6840
                        $return .= '</td>';
6841
                    }
6842
6843
                if (TOOL_HOTPOTATOES == $type) {
6844
                    $return .= '<td>';
6845
                    $return .= '<input
6846
                        size="4"
6847
                        maxlength="3"
6848
                        name="min_'.$itemId.'"
6849
                        type="number"
6850
                        min="0"
6851
                        step="any"
6852
                        max="'.$item['maxScore'].'"
6853
                        value="'.$selectedMinScoreValue.'"
6854
                    />';
6855
                        $return .= '</td>';
6856
                        $return .= '<td>';
6857
                        $return .= '<input
6858
                        size="4"
6859
                        maxlength="3"
6860
                        name="max_'.$itemId.'"
6861
                        type="number"
6862
                        min="0"
6863
                        step="any"
6864
                        max="'.$item['maxScore'].'"
6865
                        value="'.$selectedMaxScoreValue.'"
6866
                    />';
6867
                    $return .= '</td>';
6868
                }
6869
                $return .= '</tr>';
6870
6871
                return $return;
6872
            },
6873
        ];
6874
6875
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
6876
        $return .= $tree;
6877
        $return .= '</tbody>';
6878
        $return .= '</table>';
6879
        $return .= '</div>';
6880
        $return .= '<div class="form-group">';
6881
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
6882
            get_lang('Save prerequisites settings').'</button>';
6883
        $return .= '</form>';
6884
6885
        return $return;
6886
    }
6887
6888
    /**
6889
     * Return HTML list to allow prerequisites selection for lp.
6890
     */
6891
    public function display_lp_prerequisites_list(FormValidator $form)
6892
    {
6893
        $lp_id = $this->lp_id;
6894
        $prerequisiteId = $this->entity->getPrerequisite();
6895
6896
        $repo = Container::getLpRepository();
6897
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
6898
        /** @var CLp[] $lps */
6899
        $lps = $qb->getQuery()->getResult();
6900
6901
        //$session_id = api_get_session_id();
6902
        /*$session_condition = api_get_session_condition($session_id, true, true);
6903
        $sql = "SELECT * FROM $tbl_lp
6904
                WHERE c_id = $course_id $session_condition
6905
                ORDER BY display_order ";
6906
        $rs = Database::query($sql);*/
6907
6908
        $items = [get_lang('none')];
6909
        foreach ($lps as $lp) {
6910
            $myLpId = $lp->getIid();
6911
            if ($myLpId == $lp_id) {
6912
                continue;
6913
            }
6914
            $items[$myLpId] = $lp->getName();
6915
            /*$return .= '<option
6916
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
6917
                $lp->getName().
6918
                '</option>';*/
6919
        }
6920
6921
        $select = $form->addSelect('prerequisites', get_lang('Prerequisites'), $items);
6922
        $select->setSelected($prerequisiteId);
6923
    }
6924
6925
    /**
6926
     * Creates a list with all the documents in it.
6927
     *
6928
     * @param bool $showInvisibleFiles
6929
     *
6930
     * @throws Exception
6931
     *
6932
     *
6933
     * @return string
6934
     */
6935
    public function get_documents($showInvisibleFiles = false)
6936
    {
6937
        $course_info = api_get_course_info();
6938
        $sessionId = api_get_session_id();
6939
        $documentTree = DocumentManager::get_document_preview(
6940
            $course_info,
6941
            $this->lp_id,
6942
            null,
6943
            $sessionId,
6944
            true,
6945
            null,
6946
            null,
6947
            $showInvisibleFiles,
6948
            true
6949
        );
6950
6951
        $form = new FormValidator(
6952
            'form_upload',
6953
            'POST',
6954
            $this->getCurrentBuildingModeURL(),
6955
            '',
6956
            ['enctype' => 'multipart/form-data']
6957
        );
6958
6959
        $folders = DocumentManager::get_all_document_folders(
6960
            api_get_course_info(),
6961
            0,
6962
            true
6963
        );
6964
6965
        $folder = $this->generate_lp_folder(api_get_course_info());
6966
6967
        DocumentManager::build_directory_selector(
6968
            $folders,
6969
            $folder->getIid(),
6970
            [],
6971
            true,
6972
            $form,
6973
            'directory_parent_id'
6974
        );
6975
6976
        $group = [
6977
            $form->createElement(
6978
                'radio',
6979
                'if_exists',
6980
                get_lang('If file exists:'),
6981
                get_lang('Do nothing'),
6982
                'nothing'
6983
            ),
6984
            $form->createElement(
6985
                'radio',
6986
                'if_exists',
6987
                null,
6988
                get_lang('Overwrite the existing file'),
6989
                'overwrite'
6990
            ),
6991
            $form->createElement(
6992
                'radio',
6993
                'if_exists',
6994
                null,
6995
                get_lang('Rename the uploaded file if it exists'),
6996
                'rename'
6997
            ),
6998
        ];
6999
        $form->addGroup($group, null, get_lang('If file exists:'));
7000
7001
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7002
        $defaultFileExistsOption = 'rename';
7003
        if (!empty($fileExistsOption)) {
7004
            $defaultFileExistsOption = $fileExistsOption;
7005
        }
7006
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7007
7008
        // Check box options
7009
        $form->addElement(
7010
            'checkbox',
7011
            'unzip',
7012
            get_lang('Options'),
7013
            get_lang('Uncompress zip')
7014
        );
7015
7016
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7017
        $form->addMultipleUpload($url);
7018
7019
        $lpItem = new CLpItem();
7020
        $lpItem->setTitle('');
7021
        $lpItem->setItemType(TOOL_DOCUMENT);
7022
        $new = $this->displayDocumentForm('add', $lpItem);
7023
7024
        /*$lpItem = new CLpItem();
7025
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7026
        $frmReadOutText = $this->displayDocumentForm('add');*/
7027
7028
        $headers = [
7029
            get_lang('Files'),
7030
            get_lang('Create a new document'),
7031
            //get_lang('Create read-out text'),
7032
            get_lang('Upload'),
7033
        ];
7034
7035
        return Display::tabs(
7036
            $headers,
7037
            [$documentTree, $new, $form->returnForm()],
7038
            'subtab'
7039
        );
7040
    }
7041
7042
    /**
7043
     * Creates a list with all the exercises (quiz) in it.
7044
     *
7045
     * @return string
7046
     */
7047
    public function get_exercises()
7048
    {
7049
        $course_id = api_get_course_int_id();
7050
        $session_id = api_get_session_id();
7051
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7052
7053
        //$activeCondition = ' active <> -1 ';
7054
        $active = 2;
7055
        if ($setting) {
7056
            $active = 1;
7057
            //$activeCondition = ' active = 1 ';
7058
        }
7059
7060
        $categoryCondition = '';
7061
7062
        $keyword = $_REQUEST['keyword'] ?? null;
7063
        $categoryId = $_REQUEST['category_id'] ?? null;
7064
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
7065
            $categoryCondition = " AND exercise_category_id = $categoryId ";
7066
        }
7067
7068
        $keywordCondition = '';
7069
7070
        if (!empty($keyword)) {
7071
            $keyword = Database::escape_string($keyword);
7072
            $keywordCondition = " AND title LIKE '%$keyword%' ";
7073
        }
7074
        */
7075
        $course = api_get_course_entity($course_id);
7076
        $session = api_get_session_entity($session_id);
7077
7078
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
7079
        /** @var CQuiz[] $exercises */
7080
        $exercises = $qb->getQuery()->getResult();
7081
7082
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
7083
                     WHERE
7084
                            c_id = $course_id AND
7085
                            $activeCondition
7086
                            $condition_session
7087
                            $categoryCondition
7088
                            $keywordCondition
7089
                     ORDER BY title ASC";
7090
        $res_quiz = Database::query($sql_quiz);*/
7091
7092
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
7093
7094
        // Create a search-box
7095
        /*$form = new FormValidator('search_simple', 'get', $currentUrl);
7096
        $form->addHidden('action', 'add_item');
7097
        $form->addHidden('type', 'step');
7098
        $form->addHidden('lp_id', $this->lp_id);
7099
        $form->addHidden('lp_build_selected', '2');
7100
7101
        $form->addCourseHiddenParams();
7102
        $form->addText(
7103
            'keyword',
7104
            get_lang('Search'),
7105
            false,
7106
            [
7107
                'aria-label' => get_lang('Search'),
7108
            ]
7109
        );
7110
7111
        if (api_get_configuration_value('allow_exercise_categories')) {
7112
            $manager = new ExerciseCategoryManager();
7113
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
7114
            if (!empty($options)) {
7115
                $form->addSelect(
7116
                    'category_id',
7117
                    get_lang('Category'),
7118
                    $options,
7119
                    ['placeholder' => get_lang('Please select an option')]
7120
                );
7121
            }
7122
        }
7123
7124
        $form->addButtonSearch(get_lang('Search'));
7125
        $return = $form->returnForm();*/
7126
7127
        $return = '<ul class = "list-group lp_resource">';
7128
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7129
        $return .= Display::return_icon('new_exercice.png');
7130
        $return .= '<a
7131
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
7132
            get_lang('New test').'</a>';
7133
        $return .= '</li>';
7134
7135
        $previewIcon = Display::return_icon(
7136
            'preview_view.png',
7137
            get_lang('Preview')
7138
        );
7139
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
7140
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7141
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
7142
        foreach ($exercises as $exercise) {
7143
            $exerciseId = $exercise->getIid();
7144
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
7145
            $visibility = $exercise->isVisible($course, $session);
7146
7147
            $link = Display::url(
7148
                $previewIcon,
7149
                $exerciseUrl.'&exerciseId='.$exerciseId,
7150
                ['target' => '_blank']
7151
            );
7152
            $return .= '<li
7153
                class="list-group-item lp_resource_element"
7154
                id="'.$exerciseId.'"
7155
                data-id="'.$exerciseId.'"
7156
                title="'.$title.'">';
7157
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
7158
            $return .= $quizIcon;
7159
            $sessionStar = '';
7160
            /*$sessionStar = api_get_session_image(
7161
                $row_quiz['session_id'],
7162
                $userInfo['status']
7163
            );*/
7164
            $return .= Display::url(
7165
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
7166
                api_get_self().'?'.
7167
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
7168
                [
7169
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
7170
                    'data_type' => 'quiz',
7171
                    'data-id' => $exerciseId,
7172
                ]
7173
            );
7174
            $return .= '</li>';
7175
        }
7176
7177
        $return .= '</ul>';
7178
7179
        return $return;
7180
    }
7181
7182
    /**
7183
     * Creates a list with all the links in it.
7184
     *
7185
     * @return string
7186
     */
7187
    public function get_links()
7188
    {
7189
        $sessionId = api_get_session_id();
7190
        $repo = Container::getLinkRepository();
7191
7192
        $course = api_get_course_entity();
7193
        $session = api_get_session_entity($sessionId);
7194
        $qb = $repo->getResourcesByCourse($course, $session);
7195
        /** @var CLink[] $links */
7196
        $links = $qb->getQuery()->getResult();
7197
7198
        $selfUrl = api_get_self();
7199
        $courseIdReq = api_get_cidreq();
7200
        $userInfo = api_get_user_info();
7201
7202
        $moveEverywhereIcon = Display::return_icon(
7203
            'move_everywhere.png',
7204
            get_lang('Move'),
7205
            [],
7206
            ICON_SIZE_TINY
7207
        );
7208
7209
        $categorizedLinks = [];
7210
        $categories = [];
7211
7212
        foreach ($links as $link) {
7213
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
7214
            if (empty($categoryId)) {
7215
                $categories[0] = get_lang('Uncategorized');
7216
            } else {
7217
                $category = $link->getCategory();
7218
                $categories[$categoryId] = $category->getCategoryTitle();
7219
            }
7220
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
7221
        }
7222
7223
        $linksHtmlCode =
7224
            '<script>
7225
            function toggle_tool(tool, id) {
7226
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
7227
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
7228
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7229
                } else {
7230
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
7231
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7232
                }
7233
            }
7234
        </script>
7235
7236
        <ul class="list-group lp_resource">
7237
            <li class="list-group-item lp_resource_element disable_drag ">
7238
                '.Display::return_icon('linksnew.gif').'
7239
                <a
7240
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
7241
                title="'.get_lang('Add a link').'">'.
7242
                get_lang('Add a link').'
7243
                </a>
7244
            </li>';
7245
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
7246
        foreach ($categorizedLinks as $categoryId => $links) {
7247
            $linkNodes = null;
7248
            /** @var CLink $link */
7249
            foreach ($links as $key => $link) {
7250
                $title = $link->getTitle();
7251
                $id = $link->getIid();
7252
                $linkUrl = Display::url(
7253
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7254
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
7255
                    ['target' => '_blank']
7256
                );
7257
7258
                if ($link->isVisible($course, $session)) {
7259
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
7260
                    $sessionStar = '';
7261
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
7262
                    $link = Display::url(
7263
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
7264
                        $url,
7265
                        [
7266
                            'class' => 'moved link_with_id',
7267
                            'data-id' => $key,
7268
                            'data_type' => TOOL_LINK,
7269
                            'title' => $title,
7270
                        ]
7271
                    );
7272
                    $linkNodes .=
7273
                        "<li
7274
                            class='list-group-item lp_resource_element'
7275
                            id= $id
7276
                            data-id= $id
7277
                            >
7278
                         <a class='moved' href='#'>
7279
                            $moveEverywhereIcon
7280
                        </a>
7281
                        $linkIcon $link
7282
                        </li>";
7283
                }
7284
            }
7285
            $linksHtmlCode .=
7286
                '<li class="list-group-item disable_drag">
7287
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
7288
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
7289
                        align="absbottom" />
7290
                    </a>
7291
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
7292
                </li>
7293
            '.
7294
                $linkNodes.
7295
            '';
7296
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
7297
        }
7298
        $linksHtmlCode .= '</ul>';
7299
7300
        return $linksHtmlCode;
7301
    }
7302
7303
    /**
7304
     * Creates a list with all the student publications in it.
7305
     *
7306
     * @return string
7307
     */
7308
    public function get_student_publications()
7309
    {
7310
        $return = '<ul class="list-group lp_resource">';
7311
        $return .= '<li class="list-group-item lp_resource_element">';
7312
        $works = getWorkListTeacher(0, 100, null, null, null);
7313
        if (!empty($works)) {
7314
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
7315
            foreach ($works as $work) {
7316
                $workId = $work['iid'];
7317
                $link = Display::url(
7318
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7319
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$workId,
7320
                    ['target' => '_blank']
7321
                );
7322
7323
                $return .= '<li
7324
                    class="list-group-item lp_resource_element"
7325
                    id="'.$workId.'"
7326
                    data-id="'.$workId.'"
7327
                    >';
7328
                $return .= '<a class="moved" href="#">';
7329
                $return .= Display::return_icon(
7330
                    'move_everywhere.png',
7331
                    get_lang('Move'),
7332
                    [],
7333
                    ICON_SIZE_TINY
7334
                );
7335
                $return .= '</a> ';
7336
7337
                $return .= $icon;
7338
                $return .= Display::url(
7339
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
7340
                    api_get_self().'?'.
7341
                    api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
7342
                    [
7343
                        'class' => 'moved link_with_id',
7344
                        'data-id' => $work['iid'],
7345
                        'data_type' => TOOL_STUDENTPUBLICATION,
7346
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
7347
                    ]
7348
                );
7349
                $return .= '</li>';
7350
            }
7351
        }
7352
7353
        $return .= '</ul>';
7354
7355
        return $return;
7356
    }
7357
7358
    /**
7359
     * Creates a list with all the forums in it.
7360
     *
7361
     * @return string
7362
     */
7363
    public function get_forums()
7364
    {
7365
        $forumCategories = get_forum_categories();
7366
        $forumsInNoCategory = get_forums_in_category(0);
7367
        if (!empty($forumsInNoCategory)) {
7368
            $forumCategories = array_merge(
7369
                $forumCategories,
7370
                [
7371
                    [
7372
                        'cat_id' => 0,
7373
                        'session_id' => 0,
7374
                        'visibility' => 1,
7375
                        'cat_comment' => null,
7376
                    ],
7377
                ]
7378
            );
7379
        }
7380
7381
        $a_forums = [];
7382
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7383
        $sessionEntity = api_get_session_entity(api_get_session_id());
7384
7385
        foreach ($forumCategories as $forumCategory) {
7386
            // The forums in this category.
7387
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
7388
            if (!empty($forumsInCategory)) {
7389
                foreach ($forumsInCategory as $forum) {
7390
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
7391
                        $a_forums[] = $forum;
7392
                    }
7393
                }
7394
            }
7395
        }
7396
7397
        $return = '<ul class="list-group lp_resource">';
7398
7399
        // First add link
7400
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7401
        $return .= Display::return_icon('new_forum.png');
7402
        $return .= Display::url(
7403
            get_lang('Create a new forum'),
7404
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
7405
                'action' => 'add',
7406
                'content' => 'forum',
7407
                'lp_id' => $this->lp_id,
7408
            ]),
7409
            ['title' => get_lang('Create a new forum')]
7410
        );
7411
        $return .= '</li>';
7412
7413
        $return .= '<script>
7414
            function toggle_forum(forum_id) {
7415
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
7416
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
7417
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7418
                } else {
7419
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
7420
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7421
                }
7422
            }
7423
        </script>';
7424
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7425
        foreach ($a_forums as $forum) {
7426
            $forumId = $forum->getIid();
7427
            $title = Security::remove_XSS($forum->getForumTitle());
7428
            $link = Display::url(
7429
                Display::return_icon('preview_view.png', get_lang('Preview')),
7430
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
7431
                ['target' => '_blank']
7432
            );
7433
7434
            $return .= '<li
7435
                    class="list-group-item lp_resource_element"
7436
                    id="'.$forumId.'"
7437
                    data-id="'.$forumId.'"
7438
                    >';
7439
            $return .= '<a class="moved" href="#">';
7440
            $return .= $moveIcon;
7441
            $return .= ' </a>';
7442
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
7443
7444
            $moveLink = Display::url(
7445
                $title.' '.$link,
7446
                api_get_self().'?'.
7447
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
7448
                [
7449
                    'class' => 'moved link_with_id',
7450
                    'data-id' => $forumId,
7451
                    'data_type' => TOOL_FORUM,
7452
                    'title' => $title,
7453
                    'style' => 'vertical-align:middle',
7454
                ]
7455
            );
7456
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
7457
                            <img
7458
                                src="'.Display::returnIconPath('add.png').'"
7459
                                id="forum_'.$forumId.'_opener" align="absbottom"
7460
                             />
7461
                        </a>
7462
                        '.$moveLink;
7463
            $return .= '</li>';
7464
7465
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
7466
            $threads = get_threads($forumId);
7467
            if (is_array($threads)) {
7468
                foreach ($threads as $thread) {
7469
                    $threadId = $thread->getIid();
7470
                    $link = Display::url(
7471
                        Display::return_icon('preview_view.png', get_lang('Preview')),
7472
                        api_get_path(WEB_CODE_PATH).
7473
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
7474
                        ['target' => '_blank']
7475
                    );
7476
7477
                    $return .= '<li
7478
                        class="list-group-item lp_resource_element"
7479
                      id="'.$threadId.'"
7480
                        data-id="'.$threadId.'"
7481
                    >';
7482
                    $return .= '&nbsp;<a class="moved" href="#">';
7483
                    $return .= $moveIcon;
7484
                    $return .= ' </a>';
7485
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
7486
                    $return .= '<a
7487
                        class="moved link_with_id"
7488
                        data-id="'.$threadId.'"
7489
                        data_type="'.TOOL_THREAD.'"
7490
                        title="'.$thread->getThreadTitle().'"
7491
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
7492
                        >'.
7493
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
7494
                    $return .= '</li>';
7495
                }
7496
            }
7497
            $return .= '</div>';
7498
        }
7499
        $return .= '</ul>';
7500
7501
        return $return;
7502
    }
7503
7504
    /**
7505
     * // TODO: The output encoding should be equal to the system encoding.
7506
     *
7507
     * Exports the learning path as a SCORM package. This is the main function that
7508
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
7509
     * whole thing and returns the zip.
7510
     *
7511
     * This method needs to be called in PHP5, as it will fail with non-adequate
7512
     * XML package (like the ones for PHP4), and it is *not* a static method, so
7513
     * you need to call it on a learnpath object.
7514
     *
7515
     * @TODO The method might be redefined later on in the scorm class itself to avoid
7516
     * creating a SCORM structure if there is one already. However, if the initial SCORM
7517
     * path has been modified, it should use the generic method here below.
7518
     *
7519
     * @return string Returns the zip package string, or null if error
7520
     */
7521
    public function scormExport()
7522
    {
7523
        ScormExport::export($this);
7524
    }
7525
7526
    public function scorm_export_to_pdf($lpId)
7527
    {
7528
        ScormExport::exportToPdf($lpId, api_get_course_info());
7529
    }
7530
7531
    /**
7532
     * Temp function to be moved in main_api or the best place around for this.
7533
     * Creates a file path if it doesn't exist.
7534
     *
7535
     * @param string $path
7536
     */
7537
    public function create_path($path)
7538
    {
7539
        $path_bits = explode('/', dirname($path));
7540
7541
        // IS_WINDOWS_OS has been defined in main_api.lib.php
7542
        $path_built = IS_WINDOWS_OS ? '' : '/';
7543
        foreach ($path_bits as $bit) {
7544
            if (!empty($bit)) {
7545
                $new_path = $path_built.$bit;
7546
                if (is_dir($new_path)) {
7547
                    $path_built = $new_path.'/';
7548
                } else {
7549
                    mkdir($new_path, api_get_permissions_for_new_directories());
7550
                    $path_built = $new_path.'/';
7551
                }
7552
            }
7553
        }
7554
    }
7555
7556
    /**
7557
     * @param int    $lp_id
7558
     * @param string $status
7559
     */
7560
    public function set_autolaunch($lp_id, $status)
7561
    {
7562
        $course_id = api_get_course_int_id();
7563
        $lp_id = (int) $lp_id;
7564
        $status = (int) $status;
7565
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
7566
7567
        // Setting everything to autolaunch = 0
7568
        $attributes['autolaunch'] = 0;
7569
        $where = [
7570
            'session_id = ? AND c_id = ? ' => [
7571
                api_get_session_id(),
7572
                $course_id,
7573
            ],
7574
        ];
7575
        Database::update($lp_table, $attributes, $where);
7576
        if (1 == $status) {
7577
            //Setting my lp_id to autolaunch = 1
7578
            $attributes['autolaunch'] = 1;
7579
            $where = [
7580
                'iid = ? AND session_id = ? AND c_id = ?' => [
7581
                    $lp_id,
7582
                    api_get_session_id(),
7583
                    $course_id,
7584
                ],
7585
            ];
7586
            Database::update($lp_table, $attributes, $where);
7587
        }
7588
    }
7589
7590
    /**
7591
     * Gets previous_item_id for the next element of the lp_item table.
7592
     *
7593
     * @author Isaac flores paz
7594
     *
7595
     * @return int Previous item ID
7596
     */
7597
    public function select_previous_item_id()
7598
    {
7599
        $course_id = api_get_course_int_id();
7600
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7601
7602
        // Get the max order of the items
7603
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
7604
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7605
        $rs_max_order = Database::query($sql);
7606
        $row_max_order = Database::fetch_object($rs_max_order);
7607
        $max_order = $row_max_order->display_order;
7608
        // Get the previous item ID
7609
        $sql = "SELECT iid as previous FROM $table_lp_item
7610
                WHERE
7611
                    c_id = $course_id AND
7612
                    lp_id = ".$this->lp_id." AND
7613
                    display_order = '$max_order' ";
7614
        $rs_max = Database::query($sql);
7615
        $row_max = Database::fetch_object($rs_max);
7616
7617
        // Return the previous item ID
7618
        return $row_max->previous;
7619
    }
7620
7621
    /**
7622
     * Copies an LP.
7623
     */
7624
    public function copy()
7625
    {
7626
        // Course builder
7627
        $cb = new CourseBuilder();
7628
7629
        //Setting tools that will be copied
7630
        $cb->set_tools_to_build(['learnpaths']);
7631
7632
        //Setting elements that will be copied
7633
        $cb->set_tools_specific_id_list(
7634
            ['learnpaths' => [$this->lp_id]]
7635
        );
7636
7637
        $course = $cb->build();
7638
7639
        //Course restorer
7640
        $course_restorer = new CourseRestorer($course);
7641
        $course_restorer->set_add_text_in_items(true);
7642
        $course_restorer->set_tool_copy_settings(
7643
            ['learnpaths' => ['reset_dates' => true]]
7644
        );
7645
        $course_restorer->restore(
7646
            api_get_course_id(),
7647
            api_get_session_id(),
7648
            false,
7649
            false
7650
        );
7651
    }
7652
7653
    /**
7654
     * Verify document size.
7655
     *
7656
     * @param string $s
7657
     *
7658
     * @return bool
7659
     */
7660
    public static function verify_document_size($s)
7661
    {
7662
        $post_max = ini_get('post_max_size');
7663
        if ('M' == substr($post_max, -1, 1)) {
7664
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
7665
        } elseif ('G' == substr($post_max, -1, 1)) {
7666
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
7667
        }
7668
        $upl_max = ini_get('upload_max_filesize');
7669
        if ('M' == substr($upl_max, -1, 1)) {
7670
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
7671
        } elseif ('G' == substr($upl_max, -1, 1)) {
7672
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
7673
        }
7674
7675
        $repo = Container::getDocumentRepository();
7676
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
7677
7678
        $course_max_space = DocumentManager::get_course_quota();
7679
        $total_size = filesize($s) + $documents_total_space;
7680
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
7681
            return true;
7682
        }
7683
7684
        return false;
7685
    }
7686
7687
    /**
7688
     * Clear LP prerequisites.
7689
     */
7690
    public function clearPrerequisites()
7691
    {
7692
        $course_id = $this->get_course_int_id();
7693
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7694
        $lp_id = $this->get_id();
7695
        // Cleaning prerequisites
7696
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
7697
                WHERE lp_id = $lp_id";
7698
        Database::query($sql);
7699
7700
        // Cleaning mastery score for exercises
7701
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
7702
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
7703
        Database::query($sql);
7704
    }
7705
7706
    public function set_previous_step_as_prerequisite_for_all_items()
7707
    {
7708
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7709
        $course_id = $this->get_course_int_id();
7710
        $lp_id = $this->get_id();
7711
7712
        if (!empty($this->items)) {
7713
            $previous_item_id = null;
7714
            $previous_item_max = 0;
7715
            $previous_item_type = null;
7716
            $last_item_not_dir = null;
7717
            $last_item_not_dir_type = null;
7718
            $last_item_not_dir_max = null;
7719
7720
            foreach ($this->ordered_items as $itemId) {
7721
                $item = $this->getItem($itemId);
7722
                // if there was a previous item... (otherwise jump to set it)
7723
                if (!empty($previous_item_id)) {
7724
                    $current_item_id = $item->get_id(); //save current id
7725
                    if ('dir' != $item->get_type()) {
7726
                        // Current item is not a folder, so it qualifies to get a prerequisites
7727
                        if ('quiz' == $last_item_not_dir_type) {
7728
                            // if previous is quiz, mark its max score as default score to be achieved
7729
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
7730
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
7731
                            Database::query($sql);
7732
                        }
7733
                        // now simply update the prerequisite to set it to the last non-chapter item
7734
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
7735
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
7736
                        Database::query($sql);
7737
                        // record item as 'non-chapter' reference
7738
                        $last_item_not_dir = $item->get_id();
7739
                        $last_item_not_dir_type = $item->get_type();
7740
                        $last_item_not_dir_max = $item->get_max();
7741
                    }
7742
                } else {
7743
                    if ('dir' != $item->get_type()) {
7744
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
7745
                        $last_item_not_dir = $item->get_id();
7746
                        $last_item_not_dir_type = $item->get_type();
7747
                        $last_item_not_dir_max = $item->get_max();
7748
                    }
7749
                }
7750
                // Saving the item as "previous item" for the next loop
7751
                $previous_item_id = $item->get_id();
7752
                $previous_item_max = $item->get_max();
7753
                $previous_item_type = $item->get_type();
7754
            }
7755
        }
7756
    }
7757
7758
    /**
7759
     * @param array $params
7760
     *
7761
     * @return int
7762
     */
7763
    public static function createCategory($params)
7764
    {
7765
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7766
7767
        $item = new CLpCategory();
7768
        $item
7769
            ->setName($params['name'])
7770
            ->setParent($courseEntity)
7771
            ->addCourseLink($courseEntity, api_get_session_entity())
7772
        ;
7773
7774
        $repo = Container::getLpCategoryRepository();
7775
        $repo->create($item);
7776
7777
        return $item->getIid();
7778
    }
7779
7780
    /**
7781
     * @param array $params
7782
     */
7783
    public static function updateCategory($params)
7784
    {
7785
        $em = Database::getManager();
7786
        /** @var CLpCategory $item */
7787
        $item = $em->find(CLpCategory::class, $params['id']);
7788
        if ($item) {
7789
            $item->setName($params['name']);
7790
            $em->persist($item);
7791
            $em->flush();
7792
        }
7793
    }
7794
7795
    /**
7796
     * @param int $id
7797
     */
7798
    public static function moveUpCategory($id)
7799
    {
7800
        $id = (int) $id;
7801
        $em = Database::getManager();
7802
        /** @var CLpCategory $item */
7803
        $item = $em->find(CLpCategory::class, $id);
7804
        if ($item) {
7805
            $position = $item->getPosition() - 1;
7806
            $item->setPosition($position);
7807
            $em->persist($item);
7808
            $em->flush();
7809
        }
7810
    }
7811
7812
    /**
7813
     * @param int $id
7814
     */
7815
    public static function moveDownCategory($id)
7816
    {
7817
        $id = (int) $id;
7818
        $em = Database::getManager();
7819
        /** @var CLpCategory $item */
7820
        $item = $em->find(CLpCategory::class, $id);
7821
        if ($item) {
7822
            $position = $item->getPosition() + 1;
7823
            $item->setPosition($position);
7824
            $em->persist($item);
7825
            $em->flush();
7826
        }
7827
    }
7828
7829
    /**
7830
     * @param int $courseId
7831
     *
7832
     * @return int
7833
     */
7834
    public static function getCountCategories($courseId)
7835
    {
7836
        if (empty($courseId)) {
7837
            return 0;
7838
        }
7839
        $repo = Container::getLpCategoryRepository();
7840
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7841
        $qb->addSelect('count(resource)');
7842
7843
        return (int) $qb->getQuery()->getSingleScalarResult();
7844
    }
7845
7846
    /**
7847
     * @param int $courseId
7848
     *
7849
     * @return CLpCategory[]
7850
     */
7851
    public static function getCategories($courseId)
7852
    {
7853
        // Using doctrine extensions
7854
        $repo = Container::getLpCategoryRepository();
7855
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7856
7857
        return $qb->getQuery()->getResult();
7858
    }
7859
7860
    public static function getCategorySessionId($id)
7861
    {
7862
        if (false === api_get_configuration_value('allow_session_lp_category')) {
7863
            return 0;
7864
        }
7865
7866
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
7867
        $id = (int) $id;
7868
7869
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
7870
        $result = Database::query($sql);
7871
        $result = Database::fetch_array($result, 'ASSOC');
7872
7873
        if ($result) {
7874
            return (int) $result['session_id'];
7875
        }
7876
7877
        return 0;
7878
    }
7879
7880
    /**
7881
     * @param int $id
7882
     */
7883
    public static function deleteCategory($id): bool
7884
    {
7885
        $repo = Container::getLpCategoryRepository();
7886
        /** @var CLpCategory $category */
7887
        $category = $repo->find($id);
7888
        if ($category) {
7889
            $em = Database::getManager();
7890
            $lps = $category->getLps();
7891
7892
            foreach ($lps as $lp) {
7893
                $lp->setCategory(null);
7894
                $em->persist($lp);
7895
            }
7896
7897
            // Removing category.
7898
            $em->remove($category);
7899
            $em->flush();
7900
7901
            return true;
7902
        }
7903
7904
        return false;
7905
    }
7906
7907
    /**
7908
     * @param int  $courseId
7909
     * @param bool $addSelectOption
7910
     *
7911
     * @return array
7912
     */
7913
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
7914
    {
7915
        $repo = Container::getLpCategoryRepository();
7916
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
7917
        $items = $qb->getQuery()->getResult();
7918
7919
        $cats = [];
7920
        if ($addSelectOption) {
7921
            $cats = [get_lang('Select a category')];
7922
        }
7923
7924
        if (!empty($items)) {
7925
            foreach ($items as $cat) {
7926
                $cats[$cat->getIid()] = $cat->getName();
7927
            }
7928
        }
7929
7930
        return $cats;
7931
    }
7932
7933
    /**
7934
     * @param string $courseCode
7935
     * @param int    $lpId
7936
     * @param int    $user_id
7937
     *
7938
     * @return learnpath
7939
     */
7940
    public static function getLpFromSession($courseCode, $lpId, $user_id)
7941
    {
7942
        $debug = 0;
7943
        $learnPath = null;
7944
        $lpObject = Session::read('lpobject');
7945
7946
        $repo = Container::getLpRepository();
7947
        $lp = $repo->find($lpId);
7948
        if (null !== $lpObject) {
7949
            /** @var learnpath $learnPath */
7950
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
7951
            $learnPath->entity = $lp;
7952
            if ($debug) {
7953
                error_log('getLpFromSession: unserialize');
7954
                error_log('------getLpFromSession------');
7955
                error_log('------unserialize------');
7956
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7957
                error_log("api_get_sessionid: ".api_get_session_id());
7958
            }
7959
        }
7960
7961
        if (!is_object($learnPath)) {
7962
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
7963
            if ($debug) {
7964
                error_log('------getLpFromSession------');
7965
                error_log('getLpFromSession: create new learnpath');
7966
                error_log("create new LP with $courseCode - $lpId - $user_id");
7967
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
7968
                error_log("api_get_sessionid: ".api_get_session_id());
7969
            }
7970
        }
7971
7972
        return $learnPath;
7973
    }
7974
7975
    /**
7976
     * @param int $itemId
7977
     *
7978
     * @return learnpathItem|false
7979
     */
7980
    public function getItem($itemId)
7981
    {
7982
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
7983
            return $this->items[$itemId];
7984
        }
7985
7986
        return false;
7987
    }
7988
7989
    /**
7990
     * @return int
7991
     */
7992
    public function getCurrentAttempt()
7993
    {
7994
        $attempt = $this->getItem($this->get_current_item_id());
7995
        if ($attempt) {
7996
            return $attempt->get_attempt_id();
7997
        }
7998
7999
        return 0;
8000
    }
8001
8002
    /**
8003
     * @return int
8004
     */
8005
    public function getCategoryId()
8006
    {
8007
        return (int) $this->categoryId;
8008
    }
8009
8010
    /**
8011
     * Get whether this is a learning path with the possibility to subscribe
8012
     * users or not.
8013
     *
8014
     * @return int
8015
     */
8016
    public function getSubscribeUsers()
8017
    {
8018
        return $this->subscribeUsers;
8019
    }
8020
8021
    /**
8022
     * Calculate the count of stars for a user in this LP
8023
     * This calculation is based on the following rules:
8024
     * - the student gets one star when he gets to 50% of the learning path
8025
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
8026
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
8027
     * - the student gets the final star when the score for the *last* test is >= 80%.
8028
     *
8029
     * @param int $sessionId Optional. The session ID
8030
     *
8031
     * @return int The count of stars
8032
     */
8033
    public function getCalculateStars($sessionId = 0)
8034
    {
8035
        $stars = 0;
8036
        $progress = self::getProgress(
8037
            $this->lp_id,
8038
            $this->user_id,
8039
            $this->course_int_id,
8040
            $sessionId
8041
        );
8042
8043
        if ($progress >= 50) {
8044
            $stars++;
8045
        }
8046
8047
        // Calculate stars chapters evaluation
8048
        $exercisesItems = $this->getExercisesItems();
8049
8050
        if (!empty($exercisesItems)) {
8051
            $totalResult = 0;
8052
8053
            foreach ($exercisesItems as $exerciseItem) {
8054
                $exerciseResultInfo = Event::getExerciseResultsByUser(
8055
                    $this->user_id,
8056
                    $exerciseItem->path,
8057
                    $this->course_int_id,
8058
                    $sessionId,
8059
                    $this->lp_id,
8060
                    $exerciseItem->db_id
8061
                );
8062
8063
                $exerciseResultInfo = end($exerciseResultInfo);
8064
8065
                if (!$exerciseResultInfo) {
8066
                    continue;
8067
                }
8068
8069
                if (!empty($exerciseResultInfo['max_score'])) {
8070
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
8071
                } else {
8072
                    $exerciseResult = 0;
8073
                }
8074
                $totalResult += $exerciseResult;
8075
            }
8076
8077
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
8078
8079
            if ($totalExerciseAverage >= 50) {
8080
                $stars++;
8081
            }
8082
8083
            if ($totalExerciseAverage >= 80) {
8084
                $stars++;
8085
            }
8086
        }
8087
8088
        // Calculate star for final evaluation
8089
        $finalEvaluationItem = $this->getFinalEvaluationItem();
8090
8091
        if (!empty($finalEvaluationItem)) {
8092
            $evaluationResultInfo = Event::getExerciseResultsByUser(
8093
                $this->user_id,
8094
                $finalEvaluationItem->path,
8095
                $this->course_int_id,
8096
                $sessionId,
8097
                $this->lp_id,
8098
                $finalEvaluationItem->db_id
8099
            );
8100
8101
            $evaluationResultInfo = end($evaluationResultInfo);
8102
8103
            if ($evaluationResultInfo) {
8104
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
8105
                if ($evaluationResult >= 80) {
8106
                    $stars++;
8107
                }
8108
            }
8109
        }
8110
8111
        return $stars;
8112
    }
8113
8114
    /**
8115
     * Get the items of exercise type.
8116
     *
8117
     * @return array The items. Otherwise return false
8118
     */
8119
    public function getExercisesItems()
8120
    {
8121
        $exercises = [];
8122
        foreach ($this->items as $item) {
8123
            if ('quiz' !== $item->type) {
8124
                continue;
8125
            }
8126
            $exercises[] = $item;
8127
        }
8128
8129
        array_pop($exercises);
8130
8131
        return $exercises;
8132
    }
8133
8134
    /**
8135
     * Get the item of exercise type (evaluation type).
8136
     *
8137
     * @return array The final evaluation. Otherwise return false
8138
     */
8139
    public function getFinalEvaluationItem()
8140
    {
8141
        $exercises = [];
8142
        foreach ($this->items as $item) {
8143
            if (TOOL_QUIZ !== $item->type) {
8144
                continue;
8145
            }
8146
8147
            $exercises[] = $item;
8148
        }
8149
8150
        return array_pop($exercises);
8151
    }
8152
8153
    /**
8154
     * Calculate the total points achieved for the current user in this learning path.
8155
     *
8156
     * @param int $sessionId Optional. The session Id
8157
     *
8158
     * @return int
8159
     */
8160
    public function getCalculateScore($sessionId = 0)
8161
    {
8162
        // Calculate stars chapters evaluation
8163
        $exercisesItems = $this->getExercisesItems();
8164
        $finalEvaluationItem = $this->getFinalEvaluationItem();
8165
        $totalExercisesResult = 0;
8166
        $totalEvaluationResult = 0;
8167
8168
        if (false !== $exercisesItems) {
8169
            foreach ($exercisesItems as $exerciseItem) {
8170
                $exerciseResultInfo = Event::getExerciseResultsByUser(
8171
                    $this->user_id,
8172
                    $exerciseItem->path,
8173
                    $this->course_int_id,
8174
                    $sessionId,
8175
                    $this->lp_id,
8176
                    $exerciseItem->db_id
8177
                );
8178
8179
                $exerciseResultInfo = end($exerciseResultInfo);
8180
8181
                if (!$exerciseResultInfo) {
8182
                    continue;
8183
                }
8184
8185
                $totalExercisesResult += $exerciseResultInfo['score'];
8186
            }
8187
        }
8188
8189
        if (!empty($finalEvaluationItem)) {
8190
            $evaluationResultInfo = Event::getExerciseResultsByUser(
8191
                $this->user_id,
8192
                $finalEvaluationItem->path,
8193
                $this->course_int_id,
8194
                $sessionId,
8195
                $this->lp_id,
8196
                $finalEvaluationItem->db_id
8197
            );
8198
8199
            $evaluationResultInfo = end($evaluationResultInfo);
8200
8201
            if ($evaluationResultInfo) {
8202
                $totalEvaluationResult += $evaluationResultInfo['score'];
8203
            }
8204
        }
8205
8206
        return $totalExercisesResult + $totalEvaluationResult;
8207
    }
8208
8209
    /**
8210
     * Check if URL is not allowed to be show in a iframe.
8211
     *
8212
     * @param string $src
8213
     *
8214
     * @return string
8215
     */
8216
    public function fixBlockedLinks($src)
8217
    {
8218
        $urlInfo = parse_url($src);
8219
8220
        $platformProtocol = 'https';
8221
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
8222
            $platformProtocol = 'http';
8223
        }
8224
8225
        $protocolFixApplied = false;
8226
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
8227
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
8228
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
8229
8230
        if ($platformProtocol != $scheme) {
8231
            Session::write('x_frame_source', $src);
8232
            $src = 'blank.php?error=x_frames_options';
8233
            $protocolFixApplied = true;
8234
        }
8235
8236
        if (false == $protocolFixApplied) {
8237
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
8238
                // Check X-Frame-Options
8239
                $ch = curl_init();
8240
                $options = [
8241
                    CURLOPT_URL => $src,
8242
                    CURLOPT_RETURNTRANSFER => true,
8243
                    CURLOPT_HEADER => true,
8244
                    CURLOPT_FOLLOWLOCATION => true,
8245
                    CURLOPT_ENCODING => "",
8246
                    CURLOPT_AUTOREFERER => true,
8247
                    CURLOPT_CONNECTTIMEOUT => 120,
8248
                    CURLOPT_TIMEOUT => 120,
8249
                    CURLOPT_MAXREDIRS => 10,
8250
                ];
8251
8252
                $proxySettings = api_get_configuration_value('proxy_settings');
8253
                if (!empty($proxySettings) &&
8254
                    isset($proxySettings['curl_setopt_array'])
8255
                ) {
8256
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
8257
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
8258
                }
8259
8260
                curl_setopt_array($ch, $options);
8261
                $response = curl_exec($ch);
8262
                $httpCode = curl_getinfo($ch);
8263
                $headers = substr($response, 0, $httpCode['header_size']);
8264
8265
                $error = false;
8266
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
8267
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
8268
                ) {
8269
                    $error = true;
8270
                }
8271
8272
                if ($error) {
8273
                    Session::write('x_frame_source', $src);
8274
                    $src = 'blank.php?error=x_frames_options';
8275
                }
8276
            }
8277
        }
8278
8279
        return $src;
8280
    }
8281
8282
    /**
8283
     * Check if this LP has a created forum in the basis course.
8284
     *
8285
     * @deprecated
8286
     *
8287
     * @return bool
8288
     */
8289
    public function lpHasForum()
8290
    {
8291
        $forumTable = Database::get_course_table(TABLE_FORUM);
8292
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
8293
8294
        $fakeFrom = "
8295
            $forumTable f
8296
            INNER JOIN $itemProperty ip
8297
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
8298
        ";
8299
8300
        $resultData = Database::select(
8301
            'COUNT(f.iid) AS qty',
8302
            $fakeFrom,
8303
            [
8304
                'where' => [
8305
                    'ip.visibility != ? AND ' => 2,
8306
                    'ip.tool = ? AND ' => TOOL_FORUM,
8307
                    'f.c_id = ? AND ' => intval($this->course_int_id),
8308
                    'f.lp_id = ?' => intval($this->lp_id),
8309
                ],
8310
            ],
8311
            'first'
8312
        );
8313
8314
        return $resultData['qty'] > 0;
8315
    }
8316
8317
    /**
8318
     * Get the forum for this learning path.
8319
     *
8320
     * @param int $sessionId
8321
     *
8322
     * @return array
8323
     */
8324
    public function getForum($sessionId = 0)
8325
    {
8326
        $repo = Container::getForumRepository();
8327
8328
        $course = api_get_course_entity();
8329
        $session = api_get_session_entity($sessionId);
8330
        $qb = $repo->getResourcesByCourse($course, $session);
8331
8332
        return $qb->getQuery()->getResult();
8333
    }
8334
8335
    /**
8336
     * Create a forum for this learning path.
8337
     *
8338
     * @return int The forum ID if was created. Otherwise return false
8339
     */
8340
    public function createForum(CForumCategory $forumCategory)
8341
    {
8342
        return store_forum(
8343
            [
8344
                'lp_id' => $this->lp_id,
8345
                'forum_title' => $this->name,
8346
                'forum_comment' => null,
8347
                'forum_category' => $forumCategory->getIid(),
8348
                'students_can_edit_group' => ['students_can_edit' => 0],
8349
                'allow_new_threads_group' => ['allow_new_threads' => 0],
8350
                'default_view_type_group' => ['default_view_type' => 'flat'],
8351
                'group_forum' => 0,
8352
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
8353
            ],
8354
            [],
8355
            true
8356
        );
8357
    }
8358
8359
    /**
8360
     * Get the LP Final Item form.
8361
     *
8362
     * @throws Exception
8363
     *
8364
     *
8365
     * @return string
8366
     */
8367
    public function getFinalItemForm()
8368
    {
8369
        $finalItem = $this->getFinalItem();
8370
        $title = '';
8371
8372
        if ($finalItem) {
8373
            $title = $finalItem->get_title();
8374
            $buttonText = get_lang('Save');
8375
            $content = $this->getSavedFinalItem();
8376
        } else {
8377
            $buttonText = get_lang('Add this document to the course');
8378
            $content = $this->getFinalItemTemplate();
8379
        }
8380
8381
        $editorConfig = [
8382
            'ToolbarSet' => 'LearningPathDocuments',
8383
            'Width' => '100%',
8384
            'Height' => '500',
8385
            'FullPage' => true,
8386
//            'CreateDocumentDir' => $relative_prefix,
8387
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8388
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8389
        ];
8390
8391
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
8392
            'type' => 'document',
8393
            'lp_id' => $this->lp_id,
8394
        ]);
8395
8396
        $form = new FormValidator('final_item', 'POST', $url);
8397
        $form->addText('title', get_lang('Title'));
8398
        $form->addButtonSave($buttonText);
8399
        $form->addHtml(
8400
            Display::return_message(
8401
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
8402
                'normal',
8403
                false
8404
            )
8405
        );
8406
8407
        $renderer = $form->defaultRenderer();
8408
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
8409
8410
        $form->addHtmlEditor(
8411
            'content_lp_certificate',
8412
            null,
8413
            true,
8414
            false,
8415
            $editorConfig
8416
        );
8417
        $form->addHidden('action', 'add_final_item');
8418
        $form->addHidden('path', Session::read('pathItem'));
8419
        $form->addHidden('previous', $this->get_last());
8420
        $form->setDefaults(
8421
            ['title' => $title, 'content_lp_certificate' => $content]
8422
        );
8423
8424
        if ($form->validate()) {
8425
            $values = $form->exportValues();
8426
            $lastItemId = $this->getLastInFirstLevel();
8427
8428
            if (!$finalItem) {
8429
                $documentId = $this->create_document(
8430
                    $this->course_info,
8431
                    $values['content_lp_certificate'],
8432
                    $values['title']
8433
                );
8434
                $this->add_item(
8435
                    0,
8436
                    $lastItemId,
8437
                    'final_item',
8438
                    $documentId,
8439
                    $values['title'],
8440
                    ''
8441
                );
8442
8443
                Display::addFlash(
8444
                    Display::return_message(get_lang('Added'))
8445
                );
8446
            } else {
8447
                $this->edit_document($this->course_info);
8448
            }
8449
        }
8450
8451
        return $form->returnForm();
8452
    }
8453
8454
    /**
8455
     * Check if the current lp item is first, both, last or none from lp list.
8456
     *
8457
     * @param int $currentItemId
8458
     *
8459
     * @return string
8460
     */
8461
    public function isFirstOrLastItem($currentItemId)
8462
    {
8463
        $lpItemId = [];
8464
        $typeListNotToVerify = self::getChapterTypes();
8465
8466
        // Using get_toc() function instead $this->items because returns the correct order of the items
8467
        foreach ($this->get_toc() as $item) {
8468
            if (!in_array($item['type'], $typeListNotToVerify)) {
8469
                $lpItemId[] = $item['id'];
8470
            }
8471
        }
8472
8473
        $lastLpItemIndex = count($lpItemId) - 1;
8474
        $position = array_search($currentItemId, $lpItemId);
8475
8476
        switch ($position) {
8477
            case 0:
8478
                if (!$lastLpItemIndex) {
8479
                    $answer = 'both';
8480
                    break;
8481
                }
8482
8483
                $answer = 'first';
8484
                break;
8485
            case $lastLpItemIndex:
8486
                $answer = 'last';
8487
                break;
8488
            default:
8489
                $answer = 'none';
8490
        }
8491
8492
        return $answer;
8493
    }
8494
8495
    /**
8496
     * Get whether this is a learning path with the accumulated SCORM time or not.
8497
     *
8498
     * @return int
8499
     */
8500
    public function getAccumulateScormTime()
8501
    {
8502
        return $this->accumulateScormTime;
8503
    }
8504
8505
    /**
8506
     * Returns an HTML-formatted link to a resource, to incorporate directly into
8507
     * the new learning path tool.
8508
     *
8509
     * The function is a big switch on tool type.
8510
     * In each case, we query the corresponding table for information and build the link
8511
     * with that information.
8512
     *
8513
     * @author Yannick Warnier <[email protected]> - rebranding based on
8514
     * previous work (display_addedresource_link_in_learnpath())
8515
     *
8516
     * @param int $course_id      Course code
8517
     * @param int $learningPathId The learning path ID (in lp table)
8518
     * @param int $id_in_path     the unique index in the items table
8519
     * @param int $lpViewId
8520
     *
8521
     * @return string
8522
     */
8523
    public static function rl_get_resource_link_for_learnpath(
8524
        $course_id,
8525
        $learningPathId,
8526
        $id_in_path,
8527
        $lpViewId
8528
    ) {
8529
        $session_id = api_get_session_id();
8530
8531
        $learningPathId = (int) $learningPathId;
8532
        $id_in_path = (int) $id_in_path;
8533
        $lpViewId = (int) $lpViewId;
8534
8535
        $em = Database::getManager();
8536
        $lpItemRepo = $em->getRepository(CLpItem::class);
8537
8538
        /** @var CLpItem $rowItem */
8539
        $rowItem = $lpItemRepo->findOneBy([
8540
            'lp' => $learningPathId,
8541
            'iid' => $id_in_path,
8542
        ]);
8543
        $type = $rowItem->getItemType();
8544
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
8545
        $main_dir_path = api_get_path(WEB_CODE_PATH);
8546
        $link = '';
8547
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
8548
8549
        switch ($type) {
8550
            case 'dir':
8551
                return $main_dir_path.'lp/blank.php';
8552
            case TOOL_CALENDAR_EVENT:
8553
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
8554
            case TOOL_ANNOUNCEMENT:
8555
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
8556
            case TOOL_LINK:
8557
                $linkInfo = Link::getLinkInfo($id);
8558
                if (isset($linkInfo['url'])) {
8559
                    return $linkInfo['url'];
8560
                }
8561
8562
                return '';
8563
            case TOOL_QUIZ:
8564
                if (empty($id)) {
8565
                    return '';
8566
                }
8567
8568
                // Get the lp_item_view with the highest view_count.
8569
                $learnpathItemViewResult = $em
8570
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
8571
                    ->findBy(
8572
                        ['item' => $rowItem->getIid(), 'view' => $lpViewId],
8573
                        ['viewCount' => 'DESC'],
8574
                        1
8575
                    );
8576
                /** @var CLpItemView $learnpathItemViewData */
8577
                $learnpathItemViewData = current($learnpathItemViewResult);
8578
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
8579
8580
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
8581
                    .http_build_query([
8582
                        'lp_init' => 1,
8583
                        'learnpath_item_view_id' => $learnpathItemViewId,
8584
                        'learnpath_id' => $learningPathId,
8585
                        'learnpath_item_id' => $id_in_path,
8586
                        'exerciseId' => $id,
8587
                    ]);
8588
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
8589
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
8590
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
8591
                $myrow = Database::fetch_array($result);
8592
                $path = $myrow['path'];
8593
8594
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
8595
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
8596
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
8597
            case TOOL_FORUM:
8598
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
8599
            case TOOL_THREAD:
8600
                // forum post
8601
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
8602
                if (empty($id)) {
8603
                    return '';
8604
                }
8605
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
8606
                $result = Database::query($sql);
8607
                $myrow = Database::fetch_array($result);
8608
8609
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
8610
                    .$extraParams;
8611
            case TOOL_POST:
8612
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8613
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
8614
                $myrow = Database::fetch_array($result);
8615
8616
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
8617
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
8618
            case TOOL_READOUT_TEXT:
8619
                return api_get_path(WEB_CODE_PATH).
8620
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
8621
            case TOOL_DOCUMENT:
8622
                $repo = Container::getDocumentRepository();
8623
                $document = $repo->find($rowItem->getPath());
8624
                if ($document) {
8625
                    $params = [
8626
                        'cid' => $course_id,
8627
                        'sid' => $session_id,
8628
                    ];
8629
8630
                    return $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
8631
                }
8632
8633
                return null;
8634
8635
                $documentPathInfo = pathinfo($document->getPath());
0 ignored issues
show
Unused Code introduced by
$documentPathInfo = path...o($document->getPath()) 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...
8636
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
8637
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
8638
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
8639
8640
                $openmethod = 2;
8641
                $officedoc = false;
8642
                Session::write('openmethod', $openmethod);
8643
                Session::write('officedoc', $officedoc);
8644
8645
                if ($showDirectUrl) {
8646
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
8647
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
8648
                        if (Link::isPdfLink($file)) {
8649
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
8650
8651
                            return $pdfUrl;
8652
                        }
8653
                    }
8654
8655
                    return $file;
8656
                }
8657
8658
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
8659
            case TOOL_LP_FINAL_ITEM:
8660
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
8661
                    .$extraParams;
8662
            case 'assignments':
8663
                return $main_dir_path.'work/work.php?'.$extraParams;
8664
            case TOOL_DROPBOX:
8665
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
8666
            case 'introduction_text': //DEPRECATED
8667
                return '';
8668
            case TOOL_COURSE_DESCRIPTION:
8669
                return $main_dir_path.'course_description?'.$extraParams;
8670
            case TOOL_GROUP:
8671
                return $main_dir_path.'group/group.php?'.$extraParams;
8672
            case TOOL_USER:
8673
                return $main_dir_path.'user/user.php?'.$extraParams;
8674
            case TOOL_STUDENTPUBLICATION:
8675
                if (!empty($rowItem->getPath())) {
8676
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
8677
                }
8678
8679
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
8680
        }
8681
8682
        return $link;
8683
    }
8684
8685
    /**
8686
     * Gets the name of a resource (generally used in learnpath when no name is provided).
8687
     *
8688
     * @author Yannick Warnier <[email protected]>
8689
     *
8690
     * @param string $course_code    Course code
8691
     * @param int    $learningPathId
8692
     * @param int    $id_in_path     The resource ID
8693
     *
8694
     * @return string
8695
     */
8696
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
8697
    {
8698
        $_course = api_get_course_info($course_code);
8699
        if (empty($_course)) {
8700
            return '';
8701
        }
8702
        $course_id = $_course['real_id'];
8703
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8704
        $learningPathId = (int) $learningPathId;
8705
        $id_in_path = (int) $id_in_path;
8706
8707
        $sql = "SELECT item_type, title, ref
8708
                FROM $tbl_lp_item
8709
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
8710
        $res_item = Database::query($sql);
8711
8712
        if (Database::num_rows($res_item) < 1) {
8713
            return '';
8714
        }
8715
        $row_item = Database::fetch_array($res_item);
8716
        $type = strtolower($row_item['item_type']);
8717
        $id = $row_item['ref'];
8718
        $output = '';
8719
8720
        switch ($type) {
8721
            case TOOL_CALENDAR_EVENT:
8722
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
8723
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
8724
                $myrow = Database::fetch_array($result);
8725
                $output = $myrow['title'];
8726
                break;
8727
            case TOOL_ANNOUNCEMENT:
8728
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
8729
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
8730
                $myrow = Database::fetch_array($result);
8731
                $output = $myrow['title'];
8732
                break;
8733
            case TOOL_LINK:
8734
                // Doesn't take $target into account.
8735
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
8736
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
8737
                $myrow = Database::fetch_array($result);
8738
                $output = $myrow['title'];
8739
                break;
8740
            case TOOL_QUIZ:
8741
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
8742
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
8743
                $myrow = Database::fetch_array($result);
8744
                $output = $myrow['title'];
8745
                break;
8746
            case TOOL_FORUM:
8747
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
8748
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
8749
                $myrow = Database::fetch_array($result);
8750
                $output = $myrow['forum_name'];
8751
                break;
8752
            case TOOL_THREAD:
8753
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8754
                // Grabbing the title of the post.
8755
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
8756
                $result_title = Database::query($sql_title);
8757
                $myrow_title = Database::fetch_array($result_title);
8758
                $output = $myrow_title['post_title'];
8759
                break;
8760
            case TOOL_POST:
8761
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
8762
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
8763
                $result = Database::query($sql);
8764
                $post = Database::fetch_array($result);
8765
                $output = $post['post_title'];
8766
                break;
8767
            case 'dir':
8768
            case TOOL_DOCUMENT:
8769
                $title = $row_item['title'];
8770
                $output = '-';
8771
                if (!empty($title)) {
8772
                    $output = $title;
8773
                }
8774
                break;
8775
            case 'hotpotatoes':
8776
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8777
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
8778
                $myrow = Database::fetch_array($result);
8779
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
8780
                $last = count($pathname) - 1; // Making a correct name for the link.
8781
                $filename = $pathname[$last]; // Making a correct name for the link.
8782
                $myrow['path'] = rawurlencode($myrow['path']);
8783
                $output = $filename;
8784
                break;
8785
        }
8786
8787
        return stripslashes($output);
8788
    }
8789
8790
    /**
8791
     * Get the parent names for the current item.
8792
     *
8793
     * @param int $newItemId Optional. The item ID
8794
     *
8795
     * @return array
8796
     */
8797
    public function getCurrentItemParentNames($newItemId = 0)
8798
    {
8799
        $newItemId = $newItemId ?: $this->get_current_item_id();
8800
        $return = [];
8801
        $item = $this->getItem($newItemId);
8802
        $parent = $this->getItem($item->get_parent());
8803
8804
        while ($parent) {
8805
            $return[] = $parent->get_title();
8806
            $parent = $this->getItem($parent->get_parent());
8807
        }
8808
8809
        return array_reverse($return);
8810
    }
8811
8812
    /**
8813
     * Reads and process "lp_subscription_settings" setting.
8814
     *
8815
     * @return array
8816
     */
8817
    public static function getSubscriptionSettings()
8818
    {
8819
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
8820
        if (empty($subscriptionSettings)) {
8821
            // By default allow both settings
8822
            $subscriptionSettings = [
8823
                'allow_add_users_to_lp' => true,
8824
                'allow_add_users_to_lp_category' => true,
8825
            ];
8826
        } else {
8827
            $subscriptionSettings = $subscriptionSettings['options'];
8828
        }
8829
8830
        return $subscriptionSettings;
8831
    }
8832
8833
    /**
8834
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
8835
     */
8836
    public function exportToCourseBuildFormat()
8837
    {
8838
        if (!api_is_allowed_to_edit()) {
8839
            return false;
8840
        }
8841
8842
        $courseBuilder = new CourseBuilder();
8843
        $itemList = [];
8844
        /** @var learnpathItem $item */
8845
        foreach ($this->items as $item) {
8846
            $itemList[$item->get_type()][] = $item->get_path();
8847
        }
8848
8849
        if (empty($itemList)) {
8850
            return false;
8851
        }
8852
8853
        if (isset($itemList['document'])) {
8854
            // Get parents
8855
            foreach ($itemList['document'] as $documentId) {
8856
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
8857
                if (!empty($documentInfo['parents'])) {
8858
                    foreach ($documentInfo['parents'] as $parentInfo) {
8859
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
8860
                            continue;
8861
                        }
8862
                        $itemList['document'][] = $parentInfo['iid'];
8863
                    }
8864
                }
8865
            }
8866
8867
            $courseInfo = api_get_course_info();
8868
            foreach ($itemList['document'] as $documentId) {
8869
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
8870
                $items = DocumentManager::get_resources_from_source_html(
8871
                    $documentInfo['absolute_path'],
8872
                    true,
8873
                    TOOL_DOCUMENT
8874
                );
8875
8876
                if (!empty($items)) {
8877
                    foreach ($items as $item) {
8878
                        // Get information about source url
8879
                        $url = $item[0]; // url
8880
                        $scope = $item[1]; // scope (local, remote)
8881
                        $type = $item[2]; // type (rel, abs, url)
8882
8883
                        $origParseUrl = parse_url($url);
8884
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
8885
8886
                        if ('local' === $scope) {
8887
                            if ('abs' === $type || 'rel' === $type) {
8888
                                $documentFile = strstr($realOrigPath, 'document');
8889
                                if (false !== strpos($realOrigPath, $documentFile)) {
8890
                                    $documentFile = str_replace('document', '', $documentFile);
8891
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
8892
                                    // Document found! Add it to the list
8893
                                    if ($itemDocumentId) {
8894
                                        $itemList['document'][] = $itemDocumentId;
8895
                                    }
8896
                                }
8897
                            }
8898
                        }
8899
                    }
8900
                }
8901
            }
8902
8903
            $courseBuilder->build_documents(
8904
                api_get_session_id(),
8905
                $this->get_course_int_id(),
8906
                true,
8907
                $itemList['document']
8908
            );
8909
        }
8910
8911
        if (isset($itemList['quiz'])) {
8912
            $courseBuilder->build_quizzes(
8913
                api_get_session_id(),
8914
                $this->get_course_int_id(),
8915
                true,
8916
                $itemList['quiz']
8917
            );
8918
        }
8919
8920
        /*if (!empty($itemList['thread'])) {
8921
            $postList = [];
8922
            foreach ($itemList['thread'] as $postId) {
8923
                $post = get_post_information($postId);
8924
                if ($post) {
8925
                    if (!isset($itemList['forum'])) {
8926
                        $itemList['forum'] = [];
8927
                    }
8928
                    $itemList['forum'][] = $post['forum_id'];
8929
                    $postList[] = $postId;
8930
                }
8931
            }
8932
8933
            if (!empty($postList)) {
8934
                $courseBuilder->build_forum_posts(
8935
                    $this->get_course_int_id(),
8936
                    null,
8937
                    null,
8938
                    $postList
8939
                );
8940
            }
8941
        }*/
8942
8943
        if (!empty($itemList['thread'])) {
8944
            $threadList = [];
8945
            $em = Database::getManager();
8946
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
8947
            foreach ($itemList['thread'] as $threadId) {
8948
                /** @var CForumThread $thread */
8949
                $thread = $repo->find($threadId);
8950
                if ($thread) {
8951
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
8952
                    $threadList[] = $thread->getIid();
8953
                }
8954
            }
8955
8956
            if (!empty($threadList)) {
8957
                $courseBuilder->build_forum_topics(
8958
                    api_get_session_id(),
8959
                    $this->get_course_int_id(),
8960
                    null,
8961
                    $threadList
8962
                );
8963
            }
8964
        }
8965
8966
        $forumCategoryList = [];
8967
        if (isset($itemList['forum'])) {
8968
            foreach ($itemList['forum'] as $forumId) {
8969
                $forumInfo = get_forums($forumId);
8970
                $forumCategoryList[] = $forumInfo['forum_category'];
8971
            }
8972
        }
8973
8974
        if (!empty($forumCategoryList)) {
8975
            $courseBuilder->build_forum_category(
8976
                api_get_session_id(),
8977
                $this->get_course_int_id(),
8978
                true,
8979
                $forumCategoryList
8980
            );
8981
        }
8982
8983
        if (!empty($itemList['forum'])) {
8984
            $courseBuilder->build_forums(
8985
                api_get_session_id(),
8986
                $this->get_course_int_id(),
8987
                true,
8988
                $itemList['forum']
8989
            );
8990
        }
8991
8992
        if (isset($itemList['link'])) {
8993
            $courseBuilder->build_links(
8994
                api_get_session_id(),
8995
                $this->get_course_int_id(),
8996
                true,
8997
                $itemList['link']
8998
            );
8999
        }
9000
9001
        if (!empty($itemList['student_publication'])) {
9002
            $courseBuilder->build_works(
9003
                api_get_session_id(),
9004
                $this->get_course_int_id(),
9005
                true,
9006
                $itemList['student_publication']
9007
            );
9008
        }
9009
9010
        $courseBuilder->build_learnpaths(
9011
            api_get_session_id(),
9012
            $this->get_course_int_id(),
9013
            true,
9014
            [$this->get_id()],
9015
            false
9016
        );
9017
9018
        $courseBuilder->restoreDocumentsFromList();
9019
9020
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
9021
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
9022
        $result = DocumentManager::file_send_for_download(
9023
            $zipPath,
9024
            true,
9025
            $this->get_name().'.zip'
9026
        );
9027
9028
        if ($result) {
9029
            api_not_allowed();
9030
        }
9031
9032
        return true;
9033
    }
9034
9035
    /**
9036
     * Get whether this is a learning path with the accumulated work time or not.
9037
     *
9038
     * @return int
9039
     */
9040
    public function getAccumulateWorkTime()
9041
    {
9042
        return (int) $this->accumulateWorkTime;
9043
    }
9044
9045
    /**
9046
     * Get whether this is a learning path with the accumulated work time or not.
9047
     *
9048
     * @return int
9049
     */
9050
    public function getAccumulateWorkTimeTotalCourse()
9051
    {
9052
        $table = Database::get_course_table(TABLE_LP_MAIN);
9053
        $sql = "SELECT SUM(accumulate_work_time) AS total
9054
                FROM $table
9055
                WHERE c_id = ".$this->course_int_id;
9056
        $result = Database::query($sql);
9057
        $row = Database::fetch_array($result);
9058
9059
        return (int) $row['total'];
9060
    }
9061
9062
    /**
9063
     * @param int $lpId
9064
     * @param int $courseId
9065
     *
9066
     * @return mixed
9067
     */
9068
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
9069
    {
9070
        $lpId = (int) $lpId;
9071
        $table = Database::get_course_table(TABLE_LP_MAIN);
9072
        $sql = "SELECT accumulate_work_time
9073
                FROM $table
9074
                WHERE iid = $lpId";
9075
        $result = Database::query($sql);
9076
        $row = Database::fetch_array($result);
9077
9078
        return $row['accumulate_work_time'];
9079
    }
9080
9081
    /**
9082
     * @param int $courseId
9083
     *
9084
     * @return int
9085
     */
9086
    public static function getAccumulateWorkTimeTotal($courseId)
9087
    {
9088
        $table = Database::get_course_table(TABLE_LP_MAIN);
9089
        $courseId = (int) $courseId;
9090
        $sql = "SELECT SUM(accumulate_work_time) AS total
9091
                FROM $table
9092
                WHERE c_id = $courseId";
9093
        $result = Database::query($sql);
9094
        $row = Database::fetch_array($result);
9095
9096
        return (int) $row['total'];
9097
    }
9098
9099
    /**
9100
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
9101
     * and put the images in.
9102
     *
9103
     * @return array
9104
     */
9105
    public static function getIconSelect()
9106
    {
9107
        $theme = api_get_visual_theme();
9108
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
9109
        $icons = ['' => get_lang('Please select an option')];
9110
9111
        if (is_dir($path)) {
9112
            $finder = new Finder();
9113
            $finder->files()->in($path);
9114
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
9115
            /** @var SplFileInfo $file */
9116
            foreach ($finder as $file) {
9117
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
9118
                    $icons[$file->getFilename()] = $file->getFilename();
9119
                }
9120
            }
9121
        }
9122
9123
        return $icons;
9124
    }
9125
9126
    /**
9127
     * @param int $lpId
9128
     *
9129
     * @return string
9130
     */
9131
    public static function getSelectedIcon($lpId)
9132
    {
9133
        $extraFieldValue = new ExtraFieldValue('lp');
9134
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
9135
        $icon = '';
9136
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
9137
            $icon = $lpIcon['value'];
9138
        }
9139
9140
        return $icon;
9141
    }
9142
9143
    /**
9144
     * @param int $lpId
9145
     *
9146
     * @return string
9147
     */
9148
    public static function getSelectedIconHtml($lpId)
9149
    {
9150
        $icon = self::getSelectedIcon($lpId);
9151
9152
        if (empty($icon)) {
9153
            return '';
9154
        }
9155
9156
        $theme = api_get_visual_theme();
9157
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
9158
9159
        return Display::img($path);
9160
    }
9161
9162
    /**
9163
     * @param string $value
9164
     *
9165
     * @return string
9166
     */
9167
    public function cleanItemTitle($value)
9168
    {
9169
        $value = Security::remove_XSS(strip_tags($value));
9170
9171
        return $value;
9172
    }
9173
9174
    public function setItemTitle(FormValidator $form)
9175
    {
9176
        if (api_get_configuration_value('save_titles_as_html')) {
9177
            $form->addHtmlEditor(
9178
                'title',
9179
                get_lang('Title'),
9180
                true,
9181
                false,
9182
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
9183
            );
9184
        } else {
9185
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
9186
            $form->applyFilter('title', 'trim');
9187
            $form->applyFilter('title', 'html_filter');
9188
        }
9189
    }
9190
9191
    /**
9192
     * @return array
9193
     */
9194
    public function getItemsForForm($addParentCondition = false)
9195
    {
9196
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9197
9198
        $sql = "SELECT * FROM $tbl_lp_item
9199
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
9200
9201
        if ($addParentCondition) {
9202
            $sql .= ' AND parent_item_id IS NULL ';
9203
        }
9204
        $sql .= ' ORDER BY display_order ASC';
9205
9206
        $result = Database::query($sql);
9207
        $arrLP = [];
9208
        while ($row = Database::fetch_array($result)) {
9209
            $arrLP[] = [
9210
                'iid' => $row['iid'],
9211
                'id' => $row['iid'],
9212
                'item_type' => $row['item_type'],
9213
                'title' => $this->cleanItemTitle($row['title']),
9214
                'title_raw' => $row['title'],
9215
                'path' => $row['path'],
9216
                'description' => Security::remove_XSS($row['description']),
9217
                'parent_item_id' => $row['parent_item_id'],
9218
                'previous_item_id' => $row['previous_item_id'],
9219
                'next_item_id' => $row['next_item_id'],
9220
                'display_order' => $row['display_order'],
9221
                'max_score' => $row['max_score'],
9222
                'min_score' => $row['min_score'],
9223
                'mastery_score' => $row['mastery_score'],
9224
                'prerequisite' => $row['prerequisite'],
9225
                'max_time_allowed' => $row['max_time_allowed'],
9226
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9227
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9228
            ];
9229
        }
9230
9231
        return $arrLP;
9232
    }
9233
9234
    /**
9235
     * Gets whether this SCORM learning path has been marked to use the score
9236
     * as progress. Takes into account whether the learnpath matches (SCORM
9237
     * content + less than 2 items).
9238
     *
9239
     * @return bool True if the score should be used as progress, false otherwise
9240
     */
9241
    public function getUseScoreAsProgress()
9242
    {
9243
        // If not a SCORM, we don't care about the setting
9244
        if (2 != $this->get_type()) {
9245
            return false;
9246
        }
9247
        // If more than one step in the SCORM, we don't care about the setting
9248
        if ($this->get_total_items_count() > 1) {
9249
            return false;
9250
        }
9251
        $extraFieldValue = new ExtraFieldValue('lp');
9252
        $doUseScore = false;
9253
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
9254
            $this->get_id(),
9255
            'use_score_as_progress'
9256
        );
9257
        if (!empty($useScore) && isset($useScore['value'])) {
9258
            $doUseScore = $useScore['value'];
9259
        }
9260
9261
        return $doUseScore;
9262
    }
9263
9264
    /**
9265
     * Get the user identifier (user_id or username
9266
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
9267
     *
9268
     * @return string User ID or username, depending on configuration setting
9269
     */
9270
    public static function getUserIdentifierForExternalServices()
9271
    {
9272
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
9273
            return api_get_user_info(api_get_user_id())['username'];
9274
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
9275
            $extraFieldValue = new ExtraFieldValue('user');
9276
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
9277
                api_get_user_id(),
9278
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
9279
            );
9280
9281
            return $extrafield['value'];
9282
        } else {
9283
            return api_get_user_id();
9284
        }
9285
    }
9286
9287
    /**
9288
     * Save the new order for learning path items.
9289
     *
9290
     * @param array $orderList A associative array with id and parent_id keys.
9291
     */
9292
    public static function sortItemByOrderList(int $lpId, array $orderList = [])
9293
    {
9294
        if (empty($orderList)) {
9295
            return true;
9296
        }
9297
        //echo '<pre>';        var_dump($orderList);
9298
        $lpItemRepo = Container::getLpItemRepository();
9299
        $rootParent = $lpItemRepo->getItemRoot($lpId);
9300
9301
        /*$previous = 2;
9302
        $next = 0;
9303
        $rootParent->setPreviousItemId(1);
9304
        $last = $this->updateList($orderList, $rootParent, $previous, $next);
9305
        $rootParent->setNextItemId($last + 1);
9306
*/
9307
        $em = Database::getManager();
9308
        //echo '<pre>';
9309
        //var_dump($orderList);
9310
//        $em->persist($rootParent);
9311
9312
        /*$node = new \Tree\Node\Node($rootParent->getIid());
9313
        $parentList[$rootParent->getIid()] = $node;
9314
9315
        foreach ($orderList as $item) {
9316
            $itemId = $item->id ?? 0;
9317
            if (empty($itemId)) {
9318
                continue;
9319
            }
9320
            $parentId = $item->parent_id ?? 0;
9321
9322
            if (empty($parentId)) {
9323
                $parent = $rootParent;
9324
                $parentId = $rootParent->getIid();
9325
            } else {
9326
                //$parent = $lpItemRepo->find($parentId);
9327
            }
9328
            //$parentList[$itemId] = ;
9329
9330
            $child = new \Tree\Node\Node($itemId);
9331
            $parentList[$itemId] = $child;
9332
            $parentList[$parentId]->addChild($child);
9333
        }
9334
9335
        $builder = new Tree\Builder\NodeBuilder;
9336
        echo '<pre>';
9337
        foreach ($node->getChildren() as $child) {
9338
            var_dump($child->getValue().'-'.$child->getDepth().'-'.$child->getSize());
9339
9340
            var_dump("---");
9341
            foreach ($child->getNeighbors() as $neighbor) {
9342
9343
                var_dump($neighbor->getValue(), $child->getSize(), $neighbor->getDepth());
9344
            }
9345
        }
9346
exit;*/
9347
9348
        $counter = 2;
9349
        /*$rootParent->setPreviousItemId(1);
9350
        $rootParent->setDisplayOrder(0);
9351
        $rootParent->setLaunchData(1);*/
9352
        //echo '<pre>';
9353
        $rootParent->setDisplayOrder(1);
9354
        //$rootParent->setNextItemId(null);
9355
        //$rootParent->setPreviousItemId(null);
9356
        foreach ($orderList as $item) {
9357
            $itemId  = $item->id ?? 0;
9358
            if (empty($itemId)) {
9359
                continue;
9360
            }
9361
            $parentId = $item->parent_id ?? 0;
9362
            $parent = $rootParent;
9363
            if (!empty($parentId)) {
9364
                $parentExists = $lpItemRepo->find($parentId);
9365
                if (null !== $parentExists) {
9366
                    $parent = $parentExists;
9367
                }
9368
            }
9369
9370
            if (isset($parentOrder[$parent->getIid()])) {
9371
                $parentOrder[$parent->getIid()]++;
9372
            } else {
9373
                $parentOrder[$parent->getIid()] = 0;
9374
            }
9375
            /** @var CLpItem $item */
9376
            $item = $lpItemRepo->find($itemId);
9377
9378
            $previousId = 2;
9379
            if (isset($previous[$counter - 1])) {
9380
                $previousId = $previous[$counter - 1];
9381
            }
9382
            $previous[$counter] = $counter + 2;
9383
9384
            $item->setParent($parent);
9385
            //$item->setPreviousItemId($previousId);
9386
            //$item->setNextItemId($counter + 1);
9387
            //$item->setDisplayOrder($parentOrder[$parent->getIid()]);
9388
            //var_dump($item->getIid().'-'.$counter);
9389
            //$item->setPreviousItemId(null);
9390
            //$item->setNextItemId(null);
9391
            //$item->setPreviousItemId($previousId);
9392
            $item->setDisplayOrder($counter);
9393
            //var_dump($parent->getIid(), $parent->getTitle());
9394
//            $lpItemRepo->persistAsLastChildOf($item, $parent);
9395
            //$lpItemRepo->persistAs();
9396
            //$lpItemRepo->persistAsFirstChildOf($item, $parent);
9397
            //$em->flush();
9398
            //$lpItemRepo->moveDown($item, true);
9399
            $em->persist($item);
9400
            $counter++;
9401
        }
9402
9403
        //$rootParent->setNextItemId($counter+1);
9404
        $em->persist($rootParent);
9405
        $em->flush();
9406
        //var_dump($lpItemRepo->verify());
9407
        //$lpItemRepo->reorder($rootParent, 'displayOrder', 'ASC', false);
9408
        //$lpItemRepo->reorder($rootParent);
9409
        //$em->flush();
9410
        //var_dump($lpItemRepo->verify());
9411
        $lpItemRepo->recoverNode($rootParent, 'displayOrder');
9412
        $em->flush();
9413
        //var_dump($lpItemRepo->verify());
9414
9415
        return true;
9416
        /*
9417
        $lpItemRepo->recoverNode($rootParent);
9418
        var_dump($lpItemRepo->verify());
9419
9420
        $em->flush();
9421
        exit;*/
9422
    }
9423
9424
    /**
9425
     * Get the depth level of LP item.
9426
     *
9427
     * @param array $items
9428
     * @param int   $currentItemId
9429
     *
9430
     * @return int
9431
     */
9432
    private static function get_level_for_item($items, $currentItemId)
9433
    {
9434
        $parentItemId = 0;
9435
        if (isset($items[$currentItemId])) {
9436
            $parentItemId = $items[$currentItemId]->parent;
9437
        }
9438
9439
        if (0 == $parentItemId) {
9440
            return 0;
9441
        }
9442
9443
        return self::get_level_for_item($items, $parentItemId) + 1;
9444
    }
9445
9446
    /**
9447
     * Generate the link for a learnpath category as course tool.
9448
     *
9449
     * @param int $categoryId
9450
     *
9451
     * @return string
9452
     */
9453
    private static function getCategoryLinkForTool($categoryId)
9454
    {
9455
        $categoryId = (int) $categoryId;
9456
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
9457
            .http_build_query(
9458
                [
9459
                    'action' => 'view_category',
9460
                    'id' => $categoryId,
9461
                ]
9462
            );
9463
    }
9464
9465
    /**
9466
     * Return the scorm item type object with spaces replaced with _
9467
     * The return result is use to build a css classname like scorm_type_$return.
9468
     *
9469
     * @param $in_type
9470
     *
9471
     * @return mixed
9472
     */
9473
    private static function format_scorm_type_item($in_type)
9474
    {
9475
        return str_replace(' ', '_', $in_type);
9476
    }
9477
9478
    /**
9479
     * Check and obtain the lp final item if exist.
9480
     *
9481
     * @return learnpathItem
9482
     */
9483
    private function getFinalItem()
9484
    {
9485
        if (empty($this->items)) {
9486
            return null;
9487
        }
9488
9489
        foreach ($this->items as $item) {
9490
            if ('final_item' !== $item->type) {
9491
                continue;
9492
            }
9493
9494
            return $item;
9495
        }
9496
    }
9497
9498
    /**
9499
     * Get the LP Final Item Template.
9500
     *
9501
     * @return string
9502
     */
9503
    private function getFinalItemTemplate()
9504
    {
9505
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
9506
    }
9507
9508
    /**
9509
     * Get the LP Final Item Url.
9510
     *
9511
     * @return string
9512
     */
9513
    private function getSavedFinalItem()
9514
    {
9515
        $finalItem = $this->getFinalItem();
9516
9517
        $repo = Container::getDocumentRepository();
9518
        /** @var CDocument $document */
9519
        $document = $repo->find($finalItem->path);
9520
9521
        if ($document && $document->getResourceNode()->hasResourceFile()) {
9522
            return $repo->getResourceFileContent($document);
9523
        }
9524
9525
        return '';
9526
    }
9527
}
9528