Passed
Push — master ( 75cb6d...4670ec )
by Julito
11:34
created

learnpath::start_current_item()   C

Complexity

Conditions 16
Paths 52

Size

Total Lines 42
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 29
nc 52
nop 1
dl 0
loc 42
rs 5.5666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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