Passed
Push — master ( 918d95...ccaa94 )
by Julito
07:44
created

learnpath::createReadOutText()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 121
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 73
nc 54080
nop 4
dl 0
loc 121
rs 0
c 0
b 0
f 0

How to fix   Long Method    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\Filesystem\Filesystem;
28
use Symfony\Component\Finder\Finder;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
31
/**
32
 * Class learnpath
33
 * This class defines the parent attributes and methods for Chamilo learnpaths
34
 * and SCORM learnpaths. It is used by the scorm class.
35
 *
36
 * @todo decouple class
37
 *
38
 * @author  Yannick Warnier <[email protected]>
39
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
40
 */
41
class learnpath
42
{
43
    public const MAX_LP_ITEM_TITLE_LENGTH = 36;
44
    public const STATUS_CSS_CLASS_NAME = [
45
        'not attempted' => 'scorm_not_attempted',
46
        'incomplete' => 'scorm_not_attempted',
47
        'failed' => 'scorm_failed',
48
        'completed' => 'scorm_completed',
49
        'passed' => 'scorm_completed',
50
        'succeeded' => 'scorm_completed',
51
        'browsed' => 'scorm_completed',
52
    ];
53
54
    public $attempt = 0; // The number for the current ID view.
55
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
56
    public $current; // Id of the current item the user is viewing.
57
    public $current_score; // The score of the current item.
58
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
59
    public $current_time_stop; // The time the user closed this resource.
60
    public $default_status = 'not attempted';
61
    public $encoding = 'UTF-8';
62
    public $error = '';
63
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
64
    public $index; // The index of the active learnpath_item in $ordered_items array.
65
    /** @var learnpathItem[] */
66
    public $items = [];
67
    public $last; // item_id of last item viewed in the learning path.
68
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
69
    public $license; // Which license this course has been given - not used yet on 20060522.
70
    public $lp_id; // DB iid for this learnpath.
71
    public $lp_view_id; // DB ID for lp_view
72
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
73
    public $message = '';
74
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
75
    public $name; // Learnpath name (they generally have one).
76
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
77
    public $path = ''; // Path inside the scorm directory (if scorm).
78
    public $theme; // The current theme of the learning path.
79
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
80
    public $accumulateWorkTime; // The min time of learnpath
81
82
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
83
    public $prevent_reinit = 1;
84
85
    // Describes the mode of progress bar display.
86
    public $seriousgame_mode = 0;
87
    public $progress_bar_mode = '%';
88
89
    // Percentage progress as saved in the db.
90
    public $progress_db = 0;
91
    public $proximity; // Wether the content is distant or local or unknown.
92
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
93
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
94
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
95
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
96
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
97
    public $user_id; //ID of the user that is viewing/using the course
98
    public $update_queue = [];
99
    public $scorm_debug = 0;
100
    public $arrMenu = []; // Array for the menu items.
101
    public $debug = 0; // Logging level.
102
    public $lp_session_id = 0;
103
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
104
    public $prerequisite = 0;
105
    public $use_max_score = 1; // 1 or 0
106
    public $subscribeUsers = 0; // Subscribe users or not
107
    public $created_on = '';
108
    public $modified_on = '';
109
    public $publicated_on = '';
110
    public $expired_on = '';
111
    public $ref;
112
    public $course_int_id;
113
    public $course_info;
114
    public $categoryId;
115
    public $scormUrl;
116
    public $entity;
117
118
    public function __construct(CLp $entity = null, $course_info, $user_id)
119
    {
120
        $debug = $this->debug;
121
        $user_id = (int) $user_id;
122
        $this->encoding = api_get_system_encoding();
123
        $lp_id = 0;
124
        if (null !== $entity) {
125
            $lp_id = (int) $entity->getIid();
126
        }
127
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
128
        $course_id = (int) $course_info['real_id'];
129
        $this->course_info = $course_info;
130
        $this->set_course_int_id($course_id);
131
        if (empty($lp_id) || empty($course_id)) {
132
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
133
        } else {
134
            $this->entity = $entity;
135
            $this->lp_id = $lp_id;
136
            $this->type = $entity->getLpType();
137
            $this->name = stripslashes($entity->getName());
138
            $this->proximity = $entity->getContentLocal();
139
            $this->theme = $entity->getTheme();
140
            $this->maker = $entity->getContentLocal();
141
            $this->prevent_reinit = $entity->getPreventReinit();
142
            $this->seriousgame_mode = $entity->getSeriousgameMode();
143
            $this->license = $entity->getContentLicense();
144
            $this->scorm_debug = $entity->getDebug();
145
            $this->js_lib = $entity->getJsLib();
146
            $this->path = $entity->getPath();
147
            $this->author = $entity->getAuthor();
148
            $this->hide_toc_frame = $entity->getHideTocFrame();
149
            //$this->lp_session_id = $entity->getSessionId();
150
            $this->use_max_score = $entity->getUseMaxScore();
151
            $this->subscribeUsers = $entity->getSubscribeUsers();
152
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
153
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
154
            $this->ref = $entity->getRef();
155
            $this->categoryId = 0;
156
            if ($entity->getCategory()) {
157
                $this->categoryId = $entity->getCategory()->getIid();
158
            }
159
160
            if ($entity->hasAsset()) {
161
                $asset = $entity->getAsset();
162
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/';
163
            }
164
165
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
166
167
            if (!empty($entity->getPublicatedOn())) {
168
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
169
            }
170
171
            if (!empty($entity->getExpiredOn())) {
172
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
173
            }
174
            if (2 == $this->type) {
175
                if (1 == $entity->getForceCommit()) {
176
                    $this->force_commit = true;
177
                }
178
            }
179
            $this->mode = $entity->getDefaultViewMod();
180
181
            // Check user ID.
182
            if (empty($user_id)) {
183
                $this->error = 'User ID is empty';
184
            } else {
185
                $this->user_id = $user_id;
186
            }
187
188
            // End of variables checking.
189
            $session_id = api_get_session_id();
190
            //  Get the session condition for learning paths of the base + session.
191
            $session = api_get_session_condition($session_id);
192
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
193
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
194
195
            // Selecting by view_count descending allows to get the highest view_count first.
196
            $sql = "SELECT * FROM $lp_table
197
                    WHERE
198
                        c_id = $course_id AND
199
                        lp_id = $lp_id AND
200
                        user_id = $user_id
201
                        $session
202
                    ORDER BY view_count DESC";
203
            $res = Database::query($sql);
204
205
            if (Database::num_rows($res) > 0) {
206
                $row = Database::fetch_array($res);
207
                $this->attempt = $row['view_count'];
208
                $this->lp_view_id = $row['iid'];
209
                $this->last_item_seen = $row['last_item'];
210
                $this->progress_db = $row['progress'];
211
                $this->lp_view_session_id = $row['session_id'];
212
            } elseif (!api_is_invitee()) {
213
                $this->attempt = 1;
214
                $params = [
215
                    'c_id' => $course_id,
216
                    'lp_id' => $lp_id,
217
                    'user_id' => $user_id,
218
                    'view_count' => 1,
219
                    //'session_id' => $session_id,
220
                    'last_item' => 0,
221
                ];
222
                if (!empty($session_id)) {
223
                    $params['session_id'] = $session_id;
224
                }
225
                $this->last_item_seen = 0;
226
                $this->lp_view_session_id = $session_id;
227
                $this->lp_view_id = Database::insert($lp_table, $params);
228
            }
229
230
            $criteria = Criteria::create()
231
                ->orderBy(
232
                    [
233
                        'parent' => Criteria::ASC,
234
                        'displayOrder' => Criteria::ASC,
235
                    ]
236
                );
237
            $items = $this->entity->getItems()->matching($criteria);
238
            $lp_item_id_list = [];
239
            foreach ($items as $item) {
240
                $itemId = $item->getIid();
241
                $lp_item_id_list[] = $itemId;
242
                switch ($this->type) {
243
                    case 3: //aicc
244
                        $oItem = new aiccItem('db', $itemId, $course_id);
245
                        if (is_object($oItem)) {
246
                            $my_item_id = $oItem->get_id();
247
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
248
                            $oItem->set_prevent_reinit($this->prevent_reinit);
249
                            // Don't use reference here as the next loop will make the pointed object change.
250
                            $this->items[$my_item_id] = $oItem;
251
                            $this->refs_list[$oItem->ref] = $my_item_id;
252
                        }
253
                        break;
254
                    case 2:
255
                        $oItem = new scormItem('db', $itemId, $course_id);
256
                        if (is_object($oItem)) {
257
                            $my_item_id = $oItem->get_id();
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[$my_item_id] = $oItem;
262
                            $this->refs_list[$oItem->ref] = $my_item_id;
263
                        }
264
                        break;
265
                    case 1:
266
                    default:
267
                        $oItem = new learnpathItem($itemId, $course_id, $item);
268
                        if (is_object($oItem)) {
269
                            $my_item_id = $oItem->get_id();
270
                            // Moved down to when we are sure the item_view exists.
271
                            //$oItem->set_lp_view($this->lp_view_id);
272
                            $oItem->set_prevent_reinit($this->prevent_reinit);
273
                            // Don't use reference here as the next loop will make the pointed object change.
274
                            $this->items[$my_item_id] = $oItem;
275
                            $this->refs_list[$my_item_id] = $my_item_id;
276
                        }
277
                        break;
278
                }
279
280
                // Setting the object level with variable $this->items[$i][parent]
281
                foreach ($this->items as $itemLPObject) {
282
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
283
                    $itemLPObject->level = $level;
284
                }
285
286
                // Setting the view in the item object.
287
                if (is_object($this->items[$itemId])) {
288
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
289
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
290
                        $this->items[$itemId]->current_start_time = 0;
291
                        $this->items[$itemId]->current_stop_time = 0;
292
                    }
293
                }
294
            }
295
296
            if (!empty($lp_item_id_list)) {
297
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
298
                if (!empty($lp_item_id_list_to_string)) {
299
                    // Get last viewing vars.
300
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
301
                    // This query should only return one or zero result.
302
                    $sql = "SELECT lp_item_id, status
303
                            FROM $itemViewTable
304
                            WHERE
305
                                lp_view_id = ".$this->get_view_id()." AND
306
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
307
                            ORDER BY view_count DESC ";
308
                    $status_list = [];
309
                    $res = Database::query($sql);
310
                    while ($row = Database:: fetch_array($res)) {
311
                        $status_list[$row['lp_item_id']] = $row['status'];
312
                    }
313
314
                    foreach ($lp_item_id_list as $item_id) {
315
                        if (isset($status_list[$item_id])) {
316
                            $status = $status_list[$item_id];
317
                            if (is_object($this->items[$item_id])) {
318
                                $this->items[$item_id]->set_status($status);
319
                                if (empty($status)) {
320
                                    $this->items[$item_id]->set_status(
321
                                        $this->default_status
322
                                    );
323
                                }
324
                            }
325
                        } else {
326
                            if (!api_is_invitee()) {
327
                                if (is_object($this->items[$item_id])) {
328
                                    $this->items[$item_id]->set_status(
329
                                        $this->default_status
330
                                    );
331
                                }
332
333
                                if (!empty($this->lp_view_id)) {
334
                                    // Add that row to the lp_item_view table so that
335
                                    // we have something to show in the stats page.
336
                                    $params = [
337
                                        'lp_item_id' => $item_id,
338
                                        'lp_view_id' => $this->lp_view_id,
339
                                        'view_count' => 1,
340
                                        'status' => 'not attempted',
341
                                        'start_time' => time(),
342
                                        'total_time' => 0,
343
                                        'score' => 0,
344
                                    ];
345
                                    Database::insert($itemViewTable, $params);
346
347
                                    $this->items[$item_id]->set_lp_view(
348
                                        $this->lp_view_id,
349
                                        $course_id
350
                                    );
351
                                }
352
                            }
353
                        }
354
                    }
355
                }
356
            }
357
358
            $this->ordered_items = self::get_flat_ordered_items_list($this->entity, null);
359
            $this->max_ordered_items = 0;
360
            foreach ($this->ordered_items as $index => $dummy) {
361
                if ($index > $this->max_ordered_items && !empty($dummy)) {
362
                    $this->max_ordered_items = $index;
363
                }
364
            }
365
            // TODO: Define the current item better.
366
            $this->first();
367
            if ($debug) {
368
                error_log('lp_view_session_id '.$this->lp_view_session_id);
369
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
370
            }
371
        }
372
    }
373
374
    /**
375
     * @return string
376
     */
377
    public function getCourseCode()
378
    {
379
        return $this->cc;
380
    }
381
382
    /**
383
     * @return int
384
     */
385
    public function get_course_int_id()
386
    {
387
        return $this->course_int_id ?? api_get_course_int_id();
388
    }
389
390
    /**
391
     * @param $course_id
392
     *
393
     * @return int
394
     */
395
    public function set_course_int_id($course_id)
396
    {
397
        return $this->course_int_id = (int) $course_id;
398
    }
399
400
    /**
401
     * Function rewritten based on old_add_item() from Yannick Warnier.
402
     * Due the fact that users can decide where the item should come, I had to overlook this function and
403
     * I found it better to rewrite it. Old function is still available.
404
     * Added also the possibility to add a description.
405
     *
406
     * @param CLpItem $parent
407
     * @param int     $previousId
408
     * @param string  $type
409
     * @param int     $id resource ID (ref)
410
     * @param string  $title
411
     * @param string  $description
412
     * @param int     $prerequisites
413
     * @param int     $max_time_allowed
414
     * @param int     $userId
415
     *
416
     * @return int
417
     */
418
    public function add_item(
419
        ?CLpItem $parent,
420
        $previousId,
421
        $type,
422
        $id,
423
        $title,
424
        $description = '',
425
        $prerequisites = 0,
426
        $max_time_allowed = 0
427
    ) {
428
        $type = empty($type) ? 'dir' : $type;
429
        $course_id = $this->course_info['real_id'];
430
        if (empty($course_id)) {
431
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
432
            $this->course_info = api_get_course_info($this->cc);
433
            $course_id = $this->course_info['real_id'];
434
        }
435
        $id = (int) $id;
436
        $max_time_allowed = (int) $max_time_allowed;
437
        if (empty($max_time_allowed)) {
438
            $max_time_allowed = 0;
439
        }
440
        $lpId = $this->get_id();
441
        $max_score = 100;
442
        if ('quiz' === $type && $id) {
443
            $sql = 'SELECT SUM(ponderation)
444
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
445
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
446
                    ON q.iid = quiz_rel_question.question_id
447
                    WHERE
448
                        quiz_rel_question.quiz_id = '.$id;
449
            $rsQuiz = Database::query($sql);
450
            $max_score = Database::result($rsQuiz, 0, 0);
451
452
            // Disabling the exercise if we add it inside a LP
453
            $exercise = new Exercise($course_id);
454
            $exercise->read($id);
455
            $exercise->disable();
456
            $exercise->save();
457
            $title = $exercise->get_formated_title();
458
        }
459
460
        $lpItem = new CLpItem();
461
        $lpItem
462
            ->setTitle($title)
463
            ->setDescription($description)
464
            ->setPath($id)
465
            ->setLp($this->entity)
466
            ->setItemType($type)
467
            ->setMaxScore($max_score)
468
            ->setMaxTimeAllowed($max_time_allowed)
469
            ->setPrerequisite($prerequisites)
470
            //->setDisplayOrder($display_order + 1)
471
            //->setNextItemId((int) $next)
472
            //->setPreviousItemId($previous)
473
        ;
474
475
        if (!empty($parent))  {
476
            $lpItem->setParent($parent);
477
        }
478
        $em = Database::getManager();
479
        $em->persist($lpItem);
480
        $em->flush();
481
482
        $new_item_id = $lpItem->getIid();
483
        if ($new_item_id) {
484
            // @todo fix upload audio.
485
            // Upload audio.
486
            /*if (!empty($_FILES['mp3']['name'])) {
487
                // Create the audio folder if it does not exist yet.
488
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
489
                if (!is_dir($filepath.'audio')) {
490
                    mkdir(
491
                        $filepath.'audio',
492
                        api_get_permissions_for_new_directories()
493
                    );
494
                    DocumentManager::addDocument(
495
                        $_course,
496
                        '/audio',
497
                        'folder',
498
                        0,
499
                        'audio',
500
                        '',
501
                        0,
502
                        true,
503
                        null,
504
                        $sessionId,
505
                        $userId
506
                    );
507
                }
508
509
                $file_path = handle_uploaded_document(
510
                    $_course,
511
                    $_FILES['mp3'],
512
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
513
                    '/audio',
514
                    $userId,
515
                    '',
516
                    '',
517
                    '',
518
                    '',
519
                    false
520
                );
521
522
                // Getting the filename only.
523
                $file_components = explode('/', $file_path);
524
                $file = $file_components[count($file_components) - 1];
525
526
                // Store the mp3 file in the lp_item table.
527
                $sql = "UPDATE $tbl_lp_item SET
528
                          audio = '".Database::escape_string($file)."'
529
                        WHERE iid = '".intval($new_item_id)."'";
530
                Database::query($sql);
531
            }*/
532
        }
533
534
        return $new_item_id;
535
    }
536
537
    /**
538
     * Static admin function allowing addition of a learnpath to a course.
539
     *
540
     * @param string $courseCode
541
     * @param string $name
542
     * @param string $description
543
     * @param string $learnpath
544
     * @param string $origin
545
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
546
     * @param string $publicated_on
547
     * @param string $expired_on
548
     * @param int    $categoryId
549
     * @param int    $userId
550
     *
551
     * @return CLp
552
     */
553
    public static function add_lp(
554
        $courseCode,
555
        $name,
556
        $description = '',
557
        $learnpath = 'guess',
558
        $origin = 'zip',
559
        $zipname = '',
560
        $publicated_on = '',
561
        $expired_on = '',
562
        $categoryId = 0,
563
        $userId = 0
564
    ) {
565
        global $charset;
566
567
        if (!empty($courseCode)) {
568
            $courseInfo = api_get_course_info($courseCode);
569
            $course_id = $courseInfo['real_id'];
570
        } else {
571
            $course_id = api_get_course_int_id();
572
            $courseInfo = api_get_course_info();
573
        }
574
575
        $categoryId = (int) $categoryId;
576
577
        if (empty($publicated_on)) {
578
            $publicated_on = null;
579
        } else {
580
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
581
        }
582
583
        if (empty($expired_on)) {
584
            $expired_on = null;
585
        } else {
586
            $expired_on = api_get_utc_datetime($expired_on, true, true);
587
        }
588
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
        while (Database::num_rows($res_name)) {
594
            // There is already one such name, update the current one a bit.
595
            $i++;
596
            $name = $name.' - '.$i;
597
            $check_name = "SELECT * FROM $tbl_lp
598
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
599
            $res_name = Database::query($check_name);
600
        }*/
601
        // New name does not exist yet; keep it.
602
        // Escape description.
603
        // Kevin: added htmlentities().
604
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
605
        $type = 1;
606
        switch ($learnpath) {
607
            case 'guess':
608
            case 'aicc':
609
                break;
610
            case 'dokeos':
611
            case 'chamilo':
612
                $type = 1;
613
                break;
614
        }
615
616
        $id = null;
617
        $sessionEntity = api_get_session_entity();
618
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
619
        $lp = null;
620
        switch ($origin) {
621
            case 'zip':
622
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
623
                break;
624
            case 'manual':
625
            default:
626
                /*$get_max = "SELECT MAX(display_order)
627
                            FROM $tbl_lp WHERE c_id = $course_id";
628
                $res_max = Database::query($get_max);
629
                if (Database::num_rows($res_max) < 1) {
630
                    $dsp = 1;
631
                } else {
632
                    $row = Database::fetch_array($res_max);
633
                    $dsp = $row[0] + 1;
634
                }*/
635
636
                $dsp = 1;
637
                $category = null;
638
                if (!empty($categoryId)) {
639
                    $category = Container::getLpCategoryRepository()->find($categoryId);
640
                }
641
642
                $lp = new CLp();
643
                $lp
644
                    ->setLpType($type)
645
                    ->setName($name)
646
                    ->setDescription($description)
647
                    ->setDisplayOrder($dsp)
648
                    ->setCategory($category)
649
                    ->setPublicatedOn($publicated_on)
650
                    ->setExpiredOn($expired_on)
651
                    ->setParent($courseEntity)
652
                    ->addCourseLink($courseEntity, $sessionEntity)
653
                ;
654
655
                $em = Database::getManager();
656
                $em->persist($lp);
657
                $em->flush();
658
                break;
659
        }
660
661
        return $lp;
662
    }
663
664
    /**
665
     * Auto completes the parents of an item in case it's been completed or passed.
666
     *
667
     * @param int $item Optional ID of the item from which to look for parents
668
     */
669
    public function autocomplete_parents($item)
670
    {
671
        $debug = $this->debug;
672
673
        if (empty($item)) {
674
            $item = $this->current;
675
        }
676
677
        $currentItem = $this->getItem($item);
678
        if ($currentItem) {
679
            $parent_id = $currentItem->get_parent();
680
            $parent = $this->getItem($parent_id);
681
            if ($parent) {
682
                // if $item points to an object and there is a parent.
683
                if ($debug) {
684
                    error_log(
685
                        'Autocompleting parent of item '.$item.' '.
686
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
687
                        0
688
                    );
689
                }
690
691
                // New experiment including failed and browsed in completed status.
692
                //$current_status = $currentItem->get_status();
693
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
694
                // Fixes chapter auto complete
695
                if (true) {
696
                    // If the current item is completed or passes or succeeded.
697
                    $updateParentStatus = true;
698
                    if ($debug) {
699
                        error_log('Status of current item is alright');
700
                    }
701
702
                    foreach ($parent->get_children() as $childItemId) {
703
                        $childItem = $this->getItem($childItemId);
704
705
                        // If children was not set try to get the info
706
                        if (empty($childItem->db_item_view_id)) {
707
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
708
                        }
709
710
                        // Check all his brothers (parent's children) for completion status.
711
                        if ($childItemId != $item) {
712
                            if ($debug) {
713
                                error_log(
714
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
715
                                    0
716
                                );
717
                            }
718
                            // Trying completing parents of failed and browsed items as well.
719
                            if ($childItem->status_is(
720
                                [
721
                                    'completed',
722
                                    'passed',
723
                                    'succeeded',
724
                                    'browsed',
725
                                    'failed',
726
                                ]
727
                            )
728
                            ) {
729
                                // Keep completion status to true.
730
                                continue;
731
                            } else {
732
                                if ($debug > 2) {
733
                                    error_log(
734
                                        '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,
735
                                        0
736
                                    );
737
                                }
738
                                $updateParentStatus = false;
739
                                break;
740
                            }
741
                        }
742
                    }
743
744
                    if ($updateParentStatus) {
745
                        // If all the children were completed:
746
                        $parent->set_status('completed');
747
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
748
                        // Force the status to "completed"
749
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
750
                        $this->update_queue[$parent->get_id()] = 'completed';
751
                        if ($debug) {
752
                            error_log(
753
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
754
                                print_r($this->update_queue, 1),
755
                                0
756
                            );
757
                        }
758
                        // Recursive call.
759
                        $this->autocomplete_parents($parent->get_id());
760
                    }
761
                }
762
            } else {
763
                if ($debug) {
764
                    error_log("Parent #$parent_id does not exists");
765
                }
766
            }
767
        } else {
768
            if ($debug) {
769
                error_log("#$item is an item that doesn't have parents");
770
            }
771
        }
772
    }
773
774
    /**
775
     * Closes the current resource.
776
     *
777
     * Stops the timer
778
     * Saves into the database if required
779
     * Clears the current resource data from this object
780
     *
781
     * @return bool True on success, false on failure
782
     */
783
    public function close()
784
    {
785
        if (empty($this->lp_id)) {
786
            $this->error = 'Trying to close this learnpath but no ID is set';
787
788
            return false;
789
        }
790
        $this->current_time_stop = time();
791
        $this->ordered_items = [];
792
        $this->index = 0;
793
        unset($this->lp_id);
794
        //unset other stuff
795
        return true;
796
    }
797
798
    /**
799
     * Static admin function allowing removal of a learnpath.
800
     *
801
     * @param array  $courseInfo
802
     * @param int    $id         Learnpath ID
803
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
804
     *
805
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
806
     */
807
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
808
    {
809
        $course_id = api_get_course_int_id();
810
        if (!empty($courseInfo)) {
811
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
812
        }
813
814
        // TODO: Implement a way of getting this to work when the current object is not set.
815
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
816
        // If an ID is specifically given and the current LP is not the same, prevent delete.
817
        if (!empty($id) && ($id != $this->lp_id)) {
818
            return false;
819
        }
820
821
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
822
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
823
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
824
825
        // Delete lp item id.
826
        foreach ($this->items as $lpItemId => $dummy) {
827
            $sql = "DELETE FROM $lp_item_view
828
                    WHERE lp_item_id = '".$lpItemId."'";
829
            Database::query($sql);
830
        }
831
832
        // Proposed by Christophe (nickname: clefevre)
833
        $sql = "DELETE FROM $lp_item
834
                WHERE lp_id = ".$this->lp_id;
835
        Database::query($sql);
836
837
        $sql = "DELETE FROM $lp_view
838
                WHERE lp_id = ".$this->lp_id;
839
        Database::query($sql);
840
841
        //self::toggleVisibility($this->lp_id, 0);
842
843
        /*if (2 == $this->type || 3 == $this->type) {
844
            // This is a scorm learning path, delete the files as well.
845
            $sql = "SELECT path FROM $lp
846
                    WHERE iid = ".$this->lp_id;
847
            $res = Database::query($sql);
848
            if (Database::num_rows($res) > 0) {
849
                $row = Database::fetch_array($res);
850
                $path = $row['path'];
851
                $sql = "SELECT iid FROM $lp
852
                        WHERE
853
                            c_id = $course_id AND
854
                            path = '$path' AND
855
                            iid != ".$this->lp_id;
856
                $res = Database::query($sql);
857
                if (Database::num_rows($res) > 0) {
858
                    // Another learning path uses this directory, so don't delete it.
859
                    if ($this->debug > 2) {
860
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
861
                    }
862
                } else {
863
                    // No other LP uses that directory, delete it.
864
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
865
                    // The absolute system path for this course.
866
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
867
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
868
                        if ($this->debug > 2) {
869
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
870
                        }
871
                        // Proposed by Christophe (clefevre).
872
                        if (0 == strcmp(substr($path, -2), "/.")) {
873
                            $path = substr($path, 0, -1); // Remove "." at the end.
874
                        }
875
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
876
                        rmdirr($course_scorm_dir.$path);
877
                    }
878
                }
879
            }
880
        }*/
881
882
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
883
        $sql = "DELETE FROM $table
884
                WHERE
885
                    lp_id = {$this->lp_id}";
886
        Database::query($sql);
887
888
        $repo = Container::getLpRepository();
889
        $lp = $repo->find($this->lp_id);
890
        Database::getManager()->remove($lp);
891
        Database::getManager()->flush();
892
893
        // Updates the display order of all lps.
894
        $this->update_display_order();
895
896
        $link_info = GradebookUtils::isResourceInCourseGradebook(
897
            api_get_course_id(),
898
            4,
899
            $id,
900
            api_get_session_id()
901
        );
902
903
        if (false !== $link_info) {
904
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
905
        }
906
907
        if ('true' === api_get_setting('search_enabled')) {
908
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
909
        }
910
    }
911
912
    /**
913
     * Removes all the children of one item - dangerous!
914
     *
915
     * @param int $id Element ID of which children have to be removed
916
     *
917
     * @return int Total number of children removed
918
     */
919
    public function delete_children_items($id)
920
    {
921
        $course_id = $this->course_info['real_id'];
922
923
        $num = 0;
924
        $id = (int) $id;
925
        if (empty($id) || empty($course_id)) {
926
            return false;
927
        }
928
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
929
        $sql = "SELECT * FROM $lp_item
930
                WHERE parent_item_id = $id";
931
        $res = Database::query($sql);
932
        while ($row = Database::fetch_array($res)) {
933
            $num += $this->delete_children_items($row['iid']);
934
            $sql = "DELETE FROM $lp_item
935
                    WHERE iid = ".$row['iid'];
936
            Database::query($sql);
937
            $num++;
938
        }
939
940
        return $num;
941
    }
942
943
    /**
944
     * Removes an item from the current learnpath.
945
     *
946
     * @param int $id Elem ID (0 if first)
947
     *
948
     * @return int Number of elements moved
949
     *
950
     * @todo implement resource removal
951
     */
952
    public function delete_item($id)
953
    {
954
        $course_id = api_get_course_int_id();
955
        $id = (int) $id;
956
        // TODO: Implement the resource removal.
957
        if (empty($id) || empty($course_id)) {
958
            return false;
959
        }
960
961
        $repo = Container::getLpItemRepository();
962
        $item = $repo->find($id);
963
        if (null === $item) {
964
            return false;
965
        }
966
967
        $em = Database::getManager();
968
        $repo->removeFromTree($item);
969
        $em->flush();
970
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
971
972
        //Removing prerequisites since the item will not longer exist
973
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
974
                    WHERE prerequisite = '$id'";
975
        Database::query($sql_all);
976
977
        $sql = "UPDATE $lp_item
978
                SET previous_item_id = ".$this->getLastInFirstLevel()."
979
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
980
        Database::query($sql);
981
982
        // Remove from search engine if enabled.
983
        if ('true' === api_get_setting('search_enabled')) {
984
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
985
            $sql = 'SELECT * FROM %s
986
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
987
                    LIMIT 1';
988
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
989
            $res = Database::query($sql);
990
            if (Database::num_rows($res) > 0) {
991
                $row2 = Database::fetch_array($res);
992
                $di = new ChamiloIndexer();
993
                $di->remove_document($row2['search_did']);
994
            }
995
            $sql = 'DELETE FROM %s
996
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
997
                    LIMIT 1';
998
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
999
            Database::query($sql);
1000
        }
1001
    }
1002
1003
    /**
1004
     * Updates an item's content in place.
1005
     *
1006
     * @param int    $id               Element ID
1007
     * @param int    $parent           Parent item ID
1008
     * @param int    $previous         Previous item ID
1009
     * @param string $title            Item title
1010
     * @param string $description      Item description
1011
     * @param string $prerequisites    Prerequisites (optional)
1012
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1013
     * @param int    $max_time_allowed
1014
     * @param string $url
1015
     *
1016
     * @return bool True on success, false on error
1017
     */
1018
    public function edit_item(
1019
        $id,
1020
        $parent,
1021
        $previous,
1022
        $title,
1023
        $description,
1024
        $prerequisites = '0',
1025
        $audio = [],
1026
        $max_time_allowed = 0,
1027
        $url = ''
1028
    ) {
1029
        $_course = api_get_course_info();
1030
        $id = (int) $id;
1031
1032
        if (empty($id) || empty($_course)) {
1033
            return false;
1034
        }
1035
        $repo = Container::getLpItemRepository();
1036
        /** @var CLpItem $item */
1037
        $item = $repo->find($id);
1038
        if (null === $item) {
1039
            return false;
1040
        }
1041
1042
        $item
1043
            ->setTitle($title)
1044
            ->setDescription($description)
1045
            ->setPrerequisite($prerequisites)
1046
            ->setMaxTimeAllowed((int) $max_time_allowed)
1047
        ;
1048
1049
        $em = Database::getManager();
1050
        if (!empty($parent)) {
1051
            $parent = $repo->find($parent);
1052
            $item->setParent($parent);
1053
        } else {
1054
            $item->setParent(null);
1055
        }
1056
1057
        if (!empty($previous)) {
1058
            $previous = $repo->find($previous);
1059
            $repo->persistAsNextSiblingOf( $item, $previous);
1060
            //$repo->reorder($item, 'parent');
1061
            //$repo->persistAsNextSiblingOf()
1062
        } else {
1063
            //$previous = $repo->find($this->get_first_item_id());
1064
            //$repo->persistAsPrevSibling($previous);
1065
            //$repo->persistAsPrevSiblingOf()
1066
            $em->persist($item);
1067
        }
1068
1069
        $em->flush();
1070
        /*$repo->verify();
1071
        $repo->recover();*/
1072
        $audio_update_sql = '';
1073
        // @todo implement audio upload.
1074
        /*if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1075
            // Create the audio folder if it does not exist yet.
1076
            //$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1077
            if (!is_dir($filepath.'audio')) {
1078
                //mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1079
                $audio_id = DocumentManager::addDocument(
1080
                    $_course,
1081
                    '/audio',
1082
                    'folder',
1083
                    0,
1084
                    'audio'
1085
                );
1086
            }
1087
1088
            // Upload file in documents.
1089
            $pi = pathinfo($audio['name']);
1090
            if ('mp3' === $pi['extension']) {
1091
                $c_det = api_get_course_info($this->cc);
1092
                //$bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1093
                $path = handle_uploaded_document(
1094
                    $c_det,
1095
                    $audio,
1096
                    $bp,
1097
                    '/audio',
1098
                    api_get_user_id(),
1099
                    0,
1100
                    null,
1101
                    0,
1102
                    'rename',
1103
                    false,
1104
                    0
1105
                );
1106
                $path = substr($path, 7);
1107
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1108
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1109
            }
1110
        }*/
1111
1112
        if ('link' === $item->getItemType()) {
1113
            $link = new Link();
1114
            $linkId = $item->getPath();
1115
            $link->updateLink($linkId, $url);
1116
        }
1117
    }
1118
1119
    /**
1120
     * Updates an item's prereq in place.
1121
     *
1122
     * @param int    $id              Element ID
1123
     * @param string $prerequisite_id Prerequisite Element ID
1124
     * @param int    $minScore        Prerequisite min score
1125
     * @param int    $maxScore        Prerequisite max score
1126
     *
1127
     * @return bool True on success, false on error
1128
     */
1129
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1130
    {
1131
        $id = (int) $id;
1132
1133
        if (empty($id)) {
1134
            return false;
1135
        }
1136
        $prerequisite_id = (int) $prerequisite_id;
1137
1138
        if (empty($minScore) || $minScore < 0) {
1139
            $minScore = 0;
1140
        }
1141
1142
        if (empty($maxScore) || $maxScore < 0) {
1143
            $maxScore = 100;
1144
        }
1145
1146
        $minScore = (float) $minScore;
1147
        $maxScore = (float) $maxScore;
1148
1149
        if (empty($prerequisite_id)) {
1150
            $prerequisite_id = 'NULL';
1151
            $minScore = 0;
1152
            $maxScore = 100;
1153
        }
1154
1155
        $table = Database::get_course_table(TABLE_LP_ITEM);
1156
        $sql = " UPDATE $table
1157
                 SET
1158
                    prerequisite = $prerequisite_id ,
1159
                    prerequisite_min_score = $minScore ,
1160
                    prerequisite_max_score = $maxScore
1161
                 WHERE iid = $id";
1162
        Database::query($sql);
1163
1164
        return true;
1165
    }
1166
1167
    /**
1168
     * Get the specific prefix index terms of this learning path.
1169
     *
1170
     * @param string $prefix
1171
     *
1172
     * @return array Array of terms
1173
     */
1174
    public function get_common_index_terms_by_prefix($prefix)
1175
    {
1176
        $terms = get_specific_field_values_list_by_prefix(
1177
            $prefix,
1178
            $this->cc,
1179
            TOOL_LEARNPATH,
1180
            $this->lp_id
1181
        );
1182
        $prefix_terms = [];
1183
        if (!empty($terms)) {
1184
            foreach ($terms as $term) {
1185
                $prefix_terms[] = $term['value'];
1186
            }
1187
        }
1188
1189
        return $prefix_terms;
1190
    }
1191
1192
    /**
1193
     * Gets the number of items currently completed.
1194
     *
1195
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1196
     *
1197
     * @return int The number of items currently completed
1198
     */
1199
    public function get_complete_items_count($failedStatusException = false)
1200
    {
1201
        $i = 0;
1202
        $completedStatusList = [
1203
            'completed',
1204
            'passed',
1205
            'succeeded',
1206
            'browsed',
1207
        ];
1208
1209
        if (!$failedStatusException) {
1210
            $completedStatusList[] = 'failed';
1211
        }
1212
1213
        foreach ($this->items as $id => $dummy) {
1214
            // Trying failed and browsed considered "progressed" as well.
1215
            if ($this->items[$id]->status_is($completedStatusList) &&
1216
                'dir' !== $this->items[$id]->get_type()
1217
            ) {
1218
                $i++;
1219
            }
1220
        }
1221
1222
        return $i;
1223
    }
1224
1225
    /**
1226
     * Gets the current item ID.
1227
     *
1228
     * @return int The current learnpath item id
1229
     */
1230
    public function get_current_item_id()
1231
    {
1232
        $current = 0;
1233
        if (!empty($this->current)) {
1234
            $current = (int) $this->current;
1235
        }
1236
1237
        return $current;
1238
    }
1239
1240
    /**
1241
     * Force to get the first learnpath item id.
1242
     *
1243
     * @return int The current learnpath item id
1244
     */
1245
    public function get_first_item_id()
1246
    {
1247
        $current = 0;
1248
        if (is_array($this->ordered_items)) {
1249
            $current = $this->ordered_items[0];
1250
        }
1251
1252
        return $current;
1253
    }
1254
1255
    /**
1256
     * Gets the total number of items available for viewing in this SCORM.
1257
     *
1258
     * @return int The total number of items
1259
     */
1260
    public function get_total_items_count()
1261
    {
1262
        return count($this->items);
1263
    }
1264
1265
    /**
1266
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1267
     *
1268
     * @return int The total no-chapters number of items
1269
     */
1270
    public function getTotalItemsCountWithoutDirs()
1271
    {
1272
        $total = 0;
1273
        $typeListNotToCount = self::getChapterTypes();
1274
        foreach ($this->items as $temp2) {
1275
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1276
                $total++;
1277
            }
1278
        }
1279
1280
        return $total;
1281
    }
1282
1283
    /**
1284
     *  Sets the first element URL.
1285
     */
1286
    public function first()
1287
    {
1288
        if ($this->debug > 0) {
1289
            error_log('In learnpath::first()', 0);
1290
            error_log('$this->last_item_seen '.$this->last_item_seen);
1291
        }
1292
1293
        // Test if the last_item_seen exists and is not a dir.
1294
        if (0 == count($this->ordered_items)) {
1295
            $this->index = 0;
1296
        }
1297
1298
        if (!empty($this->last_item_seen) &&
1299
            !empty($this->items[$this->last_item_seen]) &&
1300
            'dir' != $this->items[$this->last_item_seen]->get_type()
1301
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1302
            //&& !$this->items[$this->last_item_seen]->is_done()
1303
        ) {
1304
            if ($this->debug > 2) {
1305
                error_log(
1306
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1307
                    $this->items[$this->last_item_seen]->get_type()
1308
                );
1309
            }
1310
            $index = -1;
1311
            foreach ($this->ordered_items as $myindex => $item_id) {
1312
                if ($item_id == $this->last_item_seen) {
1313
                    $index = $myindex;
1314
                    break;
1315
                }
1316
            }
1317
            if (-1 == $index) {
1318
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1319
                if ($this->debug > 2) {
1320
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1321
                }
1322
1323
                return false;
1324
            } else {
1325
                $this->last = $this->last_item_seen;
1326
                $this->current = $this->last_item_seen;
1327
                $this->index = $index;
1328
            }
1329
        } else {
1330
            if ($this->debug > 2) {
1331
                error_log('In learnpath::first() - No last item seen', 0);
1332
            }
1333
            $index = 0;
1334
            // Loop through all ordered items and stop at the first item that is
1335
            // not a directory *and* that has not been completed yet.
1336
            while (!empty($this->ordered_items[$index]) &&
1337
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1338
                (
1339
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1340
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1341
                ) && $index < $this->max_ordered_items) {
1342
                $index++;
1343
            }
1344
1345
            $this->last = $this->current;
1346
            // current is
1347
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1348
            $this->index = $index;
1349
            if ($this->debug > 2) {
1350
                error_log('$index '.$index);
1351
                error_log('In learnpath::first() - No last item seen');
1352
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1353
            }
1354
        }
1355
        if ($this->debug > 2) {
1356
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1357
        }
1358
    }
1359
1360
    /**
1361
     * Gets the js library from the database.
1362
     *
1363
     * @return string The name of the javascript library to be used
1364
     */
1365
    public function get_js_lib()
1366
    {
1367
        $lib = '';
1368
        if (!empty($this->js_lib)) {
1369
            $lib = $this->js_lib;
1370
        }
1371
1372
        return $lib;
1373
    }
1374
1375
    /**
1376
     * Gets the learnpath database ID.
1377
     *
1378
     * @return int Learnpath ID in the lp table
1379
     */
1380
    public function get_id()
1381
    {
1382
        if (!empty($this->lp_id)) {
1383
            return (int) $this->lp_id;
1384
        }
1385
1386
        return 0;
1387
    }
1388
1389
    /**
1390
     * Gets the last element URL.
1391
     *
1392
     * @return string URL to load into the viewer
1393
     */
1394
    public function get_last()
1395
    {
1396
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1397
        if (count($this->ordered_items) > 0) {
1398
            $this->index = count($this->ordered_items) - 1;
1399
1400
            return $this->ordered_items[$this->index];
1401
        }
1402
1403
        return false;
1404
    }
1405
1406
    /**
1407
     * Get the last element in the first level.
1408
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1409
     *
1410
     * @return mixed
1411
     */
1412
    public function getLastInFirstLevel()
1413
    {
1414
        try {
1415
            $lastId = Database::getManager()
1416
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1417
                WHERE i.lp = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1418
                ->setMaxResults(1)
1419
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1420
                ->getSingleScalarResult();
1421
1422
            return $lastId;
1423
        } catch (Exception $exception) {
1424
            return 0;
1425
        }
1426
    }
1427
1428
    /**
1429
     * Gets the navigation bar for the learnpath display screen.
1430
     *
1431
     * @param string $barId
1432
     *
1433
     * @return string The HTML string to use as a navigation bar
1434
     */
1435
    public function get_navigation_bar($barId = '')
1436
    {
1437
        if (empty($barId)) {
1438
            $barId = 'control-top';
1439
        }
1440
        $lpId = $this->lp_id;
1441
        $mycurrentitemid = $this->get_current_item_id();
1442
        $reportingText = get_lang('Reporting');
1443
        $previousText = get_lang('Previous');
1444
        $nextText = get_lang('Next');
1445
        $fullScreenText = get_lang('Back to normal screen');
1446
1447
        $settings = api_get_configuration_value('lp_view_settings');
1448
        $display = $settings['display'] ?? false;
1449
        $reportingIcon = '
1450
            <a class="icon-toolbar"
1451
                id="stats_link"
1452
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1453
                onclick="window.parent.API.save_asset(); return true;"
1454
                target="content_name" title="'.$reportingText.'">
1455
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1456
            </a>';
1457
1458
        if (!empty($display)) {
1459
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1460
            if (false === $showReporting) {
1461
                $reportingIcon = '';
1462
            }
1463
        }
1464
1465
        $hideArrows = false;
1466
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1467
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1468
        }
1469
1470
        $previousIcon = '';
1471
        $nextIcon = '';
1472
        if (false === $hideArrows) {
1473
            $previousIcon = '
1474
                <a class="icon-toolbar" id="scorm-previous" href="#"
1475
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1476
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1477
                </a>';
1478
1479
            $nextIcon = '
1480
                <a class="icon-toolbar" id="scorm-next" href="#"
1481
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1482
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1483
                </a>';
1484
        }
1485
1486
        if ('fullscreen' === $this->mode) {
1487
            $navbar = '
1488
                  <span id="'.$barId.'" class="buttons">
1489
                    '.$reportingIcon.'
1490
                    '.$previousIcon.'
1491
                    '.$nextIcon.'
1492
                    <a class="icon-toolbar" id="view-embedded"
1493
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1494
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1495
                    </a>
1496
                  </span>';
1497
        } else {
1498
            $navbar = '
1499
                 <span id="'.$barId.'" class="buttons text-right">
1500
                    '.$reportingIcon.'
1501
                    '.$previousIcon.'
1502
                    '.$nextIcon.'
1503
                </span>';
1504
        }
1505
1506
        return $navbar;
1507
    }
1508
1509
    /**
1510
     * Gets the next resource in queue (url).
1511
     *
1512
     * @return string URL to load into the viewer
1513
     */
1514
    public function get_next_index()
1515
    {
1516
        // TODO
1517
        $index = $this->index;
1518
        $index++;
1519
        while (
1520
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1521
            $index < $this->max_ordered_items
1522
        ) {
1523
            $index++;
1524
            if ($index == $this->max_ordered_items) {
1525
                if ('dir' === $this->items[$this->ordered_items[$index]]->get_type()) {
1526
                    return $this->index;
1527
                }
1528
1529
                return $index;
1530
            }
1531
        }
1532
        if (empty($this->ordered_items[$index])) {
1533
            return $this->index;
1534
        }
1535
1536
        return $index;
1537
    }
1538
1539
    /**
1540
     * Gets item_id for the next element.
1541
     *
1542
     * @return int Next item (DB) ID
1543
     */
1544
    public function get_next_item_id()
1545
    {
1546
        $new_index = $this->get_next_index();
1547
        if (!empty($new_index)) {
1548
            if (isset($this->ordered_items[$new_index])) {
1549
                return $this->ordered_items[$new_index];
1550
            }
1551
        }
1552
1553
        return 0;
1554
    }
1555
1556
    /**
1557
     * Returns the package type ('scorm','aicc','scorm2004','ppt'...).
1558
     *
1559
     * Generally, the package provided is in the form of a zip file, so the function
1560
     * has been written to test a zip file. If not a zip, the function will return the
1561
     * default return value: ''
1562
     *
1563
     * @param string $filePath the path to the file
1564
     * @param string $file_name the original name of the file
1565
     *
1566
     * @return string 'scorm','aicc','scorm2004','error-empty-package'
1567
     *                if the package is empty, or '' if the package cannot be recognized
1568
     */
1569
    public static function getPackageType($filePath, $file_name)
1570
    {
1571
        // Get name of the zip file without the extension.
1572
        $file_info = pathinfo($file_name);
1573
        $extension = $file_info['extension']; // Extension only.
1574
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1575
                'dll',
1576
                'exe',
1577
            ])) {
1578
            return 'oogie';
1579
        }
1580
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1581
                'dll',
1582
                'exe',
1583
            ])) {
1584
            return 'woogie';
1585
        }
1586
1587
        $zipFile = new ZipFile();
1588
        $zipFile->openFile($filePath);
1589
        $zipContentArray = $zipFile->getEntries();
1590
        $package_type = '';
1591
        $manifest = '';
1592
        $aicc_match_crs = 0;
1593
        $aicc_match_au = 0;
1594
        $aicc_match_des = 0;
1595
        $aicc_match_cst = 0;
1596
        $countItems = 0;
1597
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1598
        if ($zipContentArray) {
1599
            $countItems = count($zipContentArray);
1600
            if ($countItems > 0) {
1601
                foreach ($zipContentArray as $thisContent) {
1602
                    $fileName = basename($thisContent->getName());
1603
                    if (preg_match('~.(php.*|phtml)$~i', $fileName)) {
1604
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1605
                    } elseif (false !== stristr($fileName, 'imsmanifest.xml')) {
1606
                        $manifest = $fileName; // Just the relative directory inside scorm/
1607
                        $package_type = 'scorm';
1608
                        break; // Exit the foreach loop.
1609
                    } elseif (
1610
                        preg_match('/aicc\//i', $fileName) ||
1611
                        in_array(
1612
                            strtolower(pathinfo($fileName, PATHINFO_EXTENSION)),
1613
                            ['crs', 'au', 'des', 'cst']
1614
                        )
1615
                    ) {
1616
                        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
1617
                        switch ($ext) {
1618
                            case 'crs':
1619
                                $aicc_match_crs = 1;
1620
                                break;
1621
                            case 'au':
1622
                                $aicc_match_au = 1;
1623
                                break;
1624
                            case 'des':
1625
                                $aicc_match_des = 1;
1626
                                break;
1627
                            case 'cst':
1628
                                $aicc_match_cst = 1;
1629
                                break;
1630
                            default:
1631
                                break;
1632
                        }
1633
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1634
                    } else {
1635
                        $package_type = '';
1636
                    }
1637
                }
1638
            }
1639
        }
1640
1641
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1642
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1643
            $package_type = 'aicc';
1644
        }
1645
1646
        // Try with chamilo course builder
1647
        if (empty($package_type)) {
1648
            // Sometimes users will try to upload an empty zip, or a zip with
1649
            // only a folder. Catch that and make the calling function aware.
1650
            // If the single file was the imsmanifest.xml, then $package_type
1651
            // would be 'scorm' and we wouldn't be here.
1652
            if ($countItems < 2) {
1653
                return 'error-empty-package';
1654
            }
1655
            $package_type = 'chamilo';
1656
        }
1657
1658
        return $package_type;
1659
    }
1660
1661
    /**
1662
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1663
     *
1664
     * @return string URL to load into the viewer
1665
     */
1666
    public function get_previous_index()
1667
    {
1668
        $index = $this->index;
1669
        if (isset($this->ordered_items[$index - 1])) {
1670
            $index--;
1671
            while (isset($this->ordered_items[$index]) &&
1672
                ('dir' === $this->items[$this->ordered_items[$index]]->get_type())
1673
            ) {
1674
                $index--;
1675
                if ($index < 0) {
1676
                    return $this->index;
1677
                }
1678
            }
1679
        }
1680
1681
        return $index;
1682
    }
1683
1684
    /**
1685
     * Gets item_id for the next element.
1686
     *
1687
     * @return int Previous item (DB) ID
1688
     */
1689
    public function get_previous_item_id()
1690
    {
1691
        $index = $this->get_previous_index();
1692
1693
        return $this->ordered_items[$index];
1694
    }
1695
1696
    /**
1697
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1698
     *
1699
     * @param int    $lpItemId
1700
     * @param string $autostart
1701
     *
1702
     * @return string The mediaplayer HTML
1703
     */
1704
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1705
    {
1706
        $courseInfo = api_get_course_info();
1707
        $lpItemId = (int) $lpItemId;
1708
1709
        if (empty($courseInfo) || empty($lpItemId)) {
1710
            return '';
1711
        }
1712
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1713
1714
        if (empty($item)) {
1715
            return '';
1716
        }
1717
1718
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1719
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1720
        $itemViewId = (int) $item->db_item_view_id;
1721
1722
        // Getting all the information about the item.
1723
        $sql = "SELECT lp_view.status
1724
                FROM $tbl_lp_item as lpi
1725
                INNER JOIN $tbl_lp_item_view as lp_view
1726
                ON (lpi.iid = lp_view.lp_item_id)
1727
                WHERE
1728
                    lp_view.iid = $itemViewId AND
1729
                    lpi.iid = $lpItemId
1730
                ";
1731
        $result = Database::query($sql);
1732
        $row = Database::fetch_assoc($result);
1733
        $output = '';
1734
        $audio = $item->audio;
1735
1736
        if (!empty($audio)) {
1737
            $list = $_SESSION['oLP']->get_toc();
1738
1739
            switch ($item->get_type()) {
1740
                case 'quiz':
1741
                    $type_quiz = false;
1742
                    foreach ($list as $toc) {
1743
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1744
                            $type_quiz = true;
1745
                        }
1746
                    }
1747
1748
                    if ($type_quiz) {
1749
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1750
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1751
                        } else {
1752
                            $autostart_audio = $autostart;
1753
                        }
1754
                    }
1755
                    break;
1756
                case TOOL_READOUT_TEXT:
1757
                    $autostart_audio = 'false';
1758
                    break;
1759
                default:
1760
                    $autostart_audio = 'true';
1761
            }
1762
1763
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
1764
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
1765
1766
            $player = Display::getMediaPlayer(
1767
                $file,
1768
                [
1769
                    'id' => 'lp_audio_media_player',
1770
                    'url' => $url,
1771
                    'autoplay' => $autostart_audio,
1772
                    'width' => '100%',
1773
                ]
1774
            );
1775
1776
            // The mp3 player.
1777
            $output = '<div id="container">';
1778
            $output .= $player;
1779
            $output .= '</div>';
1780
        }
1781
1782
        return $output;
1783
    }
1784
1785
    /**
1786
     * @param int    $studentId
1787
     * @param int    $prerequisite
1788
     * @param Course $course
1789
     * @param int    $sessionId
1790
     *
1791
     * @return bool
1792
     */
1793
    public static function isBlockedByPrerequisite(
1794
        $studentId,
1795
        $prerequisite,
1796
        Course $course,
1797
        $sessionId
1798
    ) {
1799
        if (null === $course) {
1800
            return false;
1801
        }
1802
1803
        $courseId = $course->getId();
1804
1805
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
1806
        if ($allow) {
1807
            if (api_is_allowed_to_edit() ||
1808
                api_is_platform_admin(true) ||
1809
                api_is_drh() ||
1810
                api_is_coach($sessionId, $courseId, false)
1811
            ) {
1812
                return false;
1813
            }
1814
        }
1815
1816
        $isBlocked = false;
1817
        if (!empty($prerequisite)) {
1818
            $progress = self::getProgress(
1819
                $prerequisite,
1820
                $studentId,
1821
                $courseId,
1822
                $sessionId
1823
            );
1824
            if ($progress < 100) {
1825
                $isBlocked = true;
1826
            }
1827
1828
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
1829
                // Block if it does not exceed minimum time
1830
                // Minimum time (in minutes) to pass the learning path
1831
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
1832
1833
                if ($accumulateWorkTime > 0) {
1834
                    // Total time in course (sum of times in learning paths from course)
1835
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
1836
1837
                    // Connect with the plugin_licences_course_session table
1838
                    // which indicates what percentage of the time applies
1839
                    // Minimum connection percentage
1840
                    $perc = 100;
1841
                    // Time from the course
1842
                    $tc = $accumulateWorkTimeTotal;
1843
1844
                    // Percentage of the learning paths
1845
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
1846
                    // Minimum time for each learning path
1847
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
1848
1849
                    // Spent time (in seconds) so far in the learning path
1850
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
1851
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
1852
1853
                    if ($lpTime < ($accumulateWorkTime * 60)) {
1854
                        $isBlocked = true;
1855
                    }
1856
                }
1857
            }
1858
        }
1859
1860
        return $isBlocked;
1861
    }
1862
1863
    /**
1864
     * Checks if the learning path is visible for student after the progress
1865
     * of its prerequisite is completed, considering the time availability and
1866
     * the LP visibility.
1867
     */
1868
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, SessionEntity $session = null): bool
1869
    {
1870
        if (null === $course) {
1871
            return false;
1872
        }
1873
1874
        $sessionId = $session ? $session->getId() : 0;
1875
        $courseId = $course->getId();
1876
        $visibility = $lp->isVisible($course, $session);
1877
1878
        // If the item was deleted.
1879
        if (false === $visibility) {
1880
            return false;
1881
        }
1882
1883
        $now = time();
1884
        if ($lp->hasCategory()) {
1885
            $category = $lp->getCategory();
1886
1887
            if (false === self::categoryIsVisibleForStudent(
1888
                    $category,
1889
                    api_get_user_entity($student_id),
1890
                    $course,
1891
                    $session
1892
                )) {
1893
                return false;
1894
            }
1895
1896
            $prerequisite = $lp->getPrerequisite();
1897
            $is_visible = true;
1898
1899
            $isBlocked = self::isBlockedByPrerequisite(
1900
                $student_id,
1901
                $prerequisite,
1902
                $course,
1903
                $sessionId
1904
            );
1905
1906
            if ($isBlocked) {
1907
                $is_visible = false;
1908
            }
1909
1910
            // Also check the time availability of the LP
1911
            if ($is_visible) {
1912
                // Adding visibility restrictions
1913
                if (null !== $lp->getPublicatedOn()) {
1914
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
1915
                        $is_visible = false;
1916
                    }
1917
                }
1918
                // Blocking empty start times see BT#2800
1919
                global $_custom;
1920
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
1921
                    $_custom['lps_hidden_when_no_start_date']
1922
                ) {
1923
                    if (null !== $lp->getPublicatedOn()) {
1924
                        $is_visible = false;
1925
                    }
1926
                }
1927
1928
                if (null !== $lp->getExpiredOn()) {
1929
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
1930
                        $is_visible = false;
1931
                    }
1932
                }
1933
            }
1934
1935
            if ($is_visible) {
1936
                $subscriptionSettings = self::getSubscriptionSettings();
1937
1938
                // Check if the subscription users/group to a LP is ON
1939
                if (1 == $lp->getSubscribeUsers() &&
1940
                    true === $subscriptionSettings['allow_add_users_to_lp']
1941
                ) {
1942
                    // Try group
1943
                    $is_visible = false;
1944
                    // Checking only the user visibility
1945
                    // @todo fix visibility
1946
                    $userVisibility = 1;
1947
                    if (1 == $userVisibility) {
1948
                        $is_visible = true;
1949
                    } else {
1950
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
1951
                        if (!empty($userGroups)) {
1952
                            foreach ($userGroups as $groupInfo) {
1953
                                $groupId = $groupInfo['iid'];
1954
                                // @todo fix visibility.
1955
                                $userVisibility = 1;
1956
                                if (1 == $userVisibility) {
1957
                                    $is_visible = true;
1958
                                    break;
1959
                                }
1960
                            }
1961
                        }
1962
                    }
1963
                }
1964
            }
1965
1966
            return $is_visible;
1967
        }
1968
1969
        return true;
1970
    }
1971
1972
    /**
1973
     * @param int $lpId
1974
     * @param int $userId
1975
     * @param int $courseId
1976
     * @param int $sessionId
1977
     *
1978
     * @return int
1979
     */
1980
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
1981
    {
1982
        $lpId = (int) $lpId;
1983
        $userId = (int) $userId;
1984
        $courseId = (int) $courseId;
1985
        $sessionId = (int) $sessionId;
1986
1987
        $sessionCondition = api_get_session_condition($sessionId);
1988
        $table = Database::get_course_table(TABLE_LP_VIEW);
1989
        $sql = "SELECT progress FROM $table
1990
                WHERE
1991
                    c_id = $courseId AND
1992
                    lp_id = $lpId AND
1993
                    user_id = $userId $sessionCondition ";
1994
        $res = Database::query($sql);
1995
1996
        $progress = 0;
1997
        if (Database::num_rows($res) > 0) {
1998
            $row = Database::fetch_array($res);
1999
            $progress = (int) $row['progress'];
2000
        }
2001
2002
        return $progress;
2003
    }
2004
2005
    /**
2006
     * @param array $lpList
2007
     * @param int   $userId
2008
     * @param int   $courseId
2009
     * @param int   $sessionId
2010
     *
2011
     * @return array
2012
     */
2013
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2014
    {
2015
        $lpList = array_map('intval', $lpList);
2016
        if (empty($lpList)) {
2017
            return [];
2018
        }
2019
2020
        $lpList = implode("','", $lpList);
2021
2022
        $userId = (int) $userId;
2023
        $courseId = (int) $courseId;
2024
        $sessionId = (int) $sessionId;
2025
2026
        $sessionCondition = api_get_session_condition($sessionId);
2027
        $table = Database::get_course_table(TABLE_LP_VIEW);
2028
        $sql = "SELECT lp_id, progress FROM $table
2029
                WHERE
2030
                    c_id = $courseId AND
2031
                    lp_id IN ('".$lpList."') AND
2032
                    user_id = $userId $sessionCondition ";
2033
        $res = Database::query($sql);
2034
2035
        if (Database::num_rows($res) > 0) {
2036
            $list = [];
2037
            while ($row = Database::fetch_array($res)) {
2038
                $list[$row['lp_id']] = $row['progress'];
2039
            }
2040
2041
            return $list;
2042
        }
2043
2044
        return [];
2045
    }
2046
2047
    /**
2048
     * Displays a progress bar
2049
     * completed so far.
2050
     *
2051
     * @param int    $percentage Progress value to display
2052
     * @param string $text_add   Text to display near the progress value
2053
     *
2054
     * @return string HTML string containing the progress bar
2055
     */
2056
    public static function get_progress_bar($percentage = -1, $text_add = '')
2057
    {
2058
        $text = $percentage.$text_add;
2059
2060
        return '<div class="progress">
2061
            <div id="progress_bar_value"
2062
                class="progress-bar progress-bar-success" role="progressbar"
2063
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2064
            '.$text.'
2065
            </div>
2066
        </div>';
2067
    }
2068
2069
    /**
2070
     * @param string $mode can be '%' or 'abs'
2071
     *                     otherwise this value will be used $this->progress_bar_mode
2072
     *
2073
     * @return string
2074
     */
2075
    public function getProgressBar($mode = null)
2076
    {
2077
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2078
2079
        return self::get_progress_bar($percentage, $text_add);
2080
    }
2081
2082
    /**
2083
     * Gets the progress bar info to display inside the progress bar.
2084
     * Also used by scorm_api.php.
2085
     *
2086
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2087
     *                     we display a number of completed elements per total elements
2088
     * @param int    $add  Additional steps to fake as completed
2089
     *
2090
     * @return array Percentage or number and symbol (% or /xx)
2091
     */
2092
    public function get_progress_bar_text($mode = '', $add = 0)
2093
    {
2094
        if (empty($mode)) {
2095
            $mode = $this->progress_bar_mode;
2096
        }
2097
        $text = '';
2098
        $percentage = 0;
2099
        // If the option to use the score as progress is set for this learning
2100
        // path, then the rules are completely different: we assume only one
2101
        // item exists and the progress of the LP depends on the score
2102
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2103
        if (true === $scoreAsProgressSetting) {
2104
            $scoreAsProgress = $this->getUseScoreAsProgress();
2105
            if ($scoreAsProgress) {
2106
                // Get single item's score
2107
                $itemId = $this->get_current_item_id();
2108
                $item = $this->getItem($itemId);
2109
                $score = $item->get_score();
2110
                $maxScore = $item->get_max();
2111
                if ($mode = '%') {
2112
                    if (!empty($maxScore)) {
2113
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2114
                    }
2115
                    $percentage = number_format($percentage, 0);
2116
                    $text = '%';
2117
                } else {
2118
                    $percentage = $score;
2119
                    $text = '/'.$maxScore;
2120
                }
2121
2122
                return [$percentage, $text];
2123
            }
2124
        }
2125
        // otherwise just continue the normal processing of progress
2126
        $total_items = $this->getTotalItemsCountWithoutDirs();
2127
        $completeItems = $this->get_complete_items_count();
2128
        if (0 != $add) {
2129
            $completeItems += $add;
2130
        }
2131
        if ($completeItems > $total_items) {
2132
            $completeItems = $total_items;
2133
        }
2134
        if ('%' === $mode) {
2135
            if ($total_items > 0) {
2136
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2137
            }
2138
            $percentage = number_format($percentage, 0);
2139
            $text = '%';
2140
        } elseif ('abs' === $mode) {
2141
            $percentage = $completeItems;
2142
            $text = '/'.$total_items;
2143
        }
2144
2145
        return [
2146
            $percentage,
2147
            $text,
2148
        ];
2149
    }
2150
2151
    /**
2152
     * Gets the progress bar mode.
2153
     *
2154
     * @return string The progress bar mode attribute
2155
     */
2156
    public function get_progress_bar_mode()
2157
    {
2158
        if (!empty($this->progress_bar_mode)) {
2159
            return $this->progress_bar_mode;
2160
        }
2161
2162
        return '%';
2163
    }
2164
2165
    /**
2166
     * Gets the learnpath theme (remote or local).
2167
     *
2168
     * @return string Learnpath theme
2169
     */
2170
    public function get_theme()
2171
    {
2172
        if (!empty($this->theme)) {
2173
            return $this->theme;
2174
        }
2175
2176
        return '';
2177
    }
2178
2179
    /**
2180
     * Gets the learnpath session id.
2181
     *
2182
     * @return int
2183
     */
2184
    public function get_lp_session_id()
2185
    {
2186
        if (!empty($this->lp_session_id)) {
2187
            return (int) $this->lp_session_id;
2188
        }
2189
2190
        return 0;
2191
    }
2192
2193
    /**
2194
     * Generate a new prerequisites string for a given item. If this item was a sco and
2195
     * its prerequisites were strings (instead of IDs), then transform those strings into
2196
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2197
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2198
     * same rule as the scormExport() method.
2199
     *
2200
     * @param int $item_id Item ID
2201
     *
2202
     * @return string Prerequisites string ready for the export as SCORM
2203
     */
2204
    public function get_scorm_prereq_string($item_id)
2205
    {
2206
        if ($this->debug > 0) {
2207
            error_log('In learnpath::get_scorm_prereq_string()');
2208
        }
2209
        if (!is_object($this->items[$item_id])) {
2210
            return false;
2211
        }
2212
        /** @var learnpathItem $oItem */
2213
        $oItem = $this->items[$item_id];
2214
        $prereq = $oItem->get_prereq_string();
2215
2216
        if (empty($prereq)) {
2217
            return '';
2218
        }
2219
        if (preg_match('/^\d+$/', $prereq) &&
2220
            isset($this->items[$prereq]) &&
2221
            is_object($this->items[$prereq])
2222
        ) {
2223
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2224
            // then simply return it (with the ITEM_ prefix).
2225
            //return 'ITEM_' . $prereq;
2226
            return $this->items[$prereq]->ref;
2227
        } else {
2228
            if (isset($this->refs_list[$prereq])) {
2229
                // It's a simple string item from which the ID can be found in the refs list,
2230
                // so we can transform it directly to an ID for export.
2231
                return $this->items[$this->refs_list[$prereq]]->ref;
2232
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2233
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2234
            } else {
2235
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2236
                // and replace them, one by one, by the internal IDs (chamilo db)
2237
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2238
                // by a space as well.
2239
                $find = [
2240
                    '&',
2241
                    '|',
2242
                    '~',
2243
                    '=',
2244
                    '<>',
2245
                    '{',
2246
                    '}',
2247
                    '*',
2248
                    '(',
2249
                    ')',
2250
                ];
2251
                $replace = [
2252
                    ' ',
2253
                    ' ',
2254
                    ' ',
2255
                    ' ',
2256
                    ' ',
2257
                    ' ',
2258
                    ' ',
2259
                    ' ',
2260
                    ' ',
2261
                    ' ',
2262
                ];
2263
                $prereq_mod = str_replace($find, $replace, $prereq);
2264
                $ids = explode(' ', $prereq_mod);
2265
                foreach ($ids as $id) {
2266
                    $id = trim($id);
2267
                    if (isset($this->refs_list[$id])) {
2268
                        $prereq = preg_replace(
2269
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2270
                            'ITEM_'.$this->refs_list[$id],
2271
                            $prereq
2272
                        );
2273
                    }
2274
                }
2275
2276
                return $prereq;
2277
            }
2278
        }
2279
    }
2280
2281
    /**
2282
     * Returns the XML DOM document's node.
2283
     *
2284
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2285
     * @param string   $id       The identifier to look for
2286
     *
2287
     * @return mixed The reference to the element found with that identifier. False if not found
2288
     */
2289
    public function get_scorm_xml_node(&$children, $id)
2290
    {
2291
        for ($i = 0; $i < $children->length; $i++) {
2292
            $item_temp = $children->item($i);
2293
            if ('item' === $item_temp->nodeName) {
2294
                if ($item_temp->getAttribute('identifier') == $id) {
2295
                    return $item_temp;
2296
                }
2297
            }
2298
            $subchildren = $item_temp->childNodes;
2299
            if ($subchildren && $subchildren->length > 0) {
2300
                $val = $this->get_scorm_xml_node($subchildren, $id);
2301
                if (is_object($val)) {
2302
                    return $val;
2303
                }
2304
            }
2305
        }
2306
2307
        return false;
2308
    }
2309
2310
    /**
2311
     * Gets the status list for all LP's items.
2312
     *
2313
     * @return array Array of [index] => [item ID => current status]
2314
     */
2315
    public function get_items_status_list()
2316
    {
2317
        $list = [];
2318
        foreach ($this->ordered_items as $item_id) {
2319
            $list[] = [
2320
                $item_id => $this->items[$item_id]->get_status(),
2321
            ];
2322
        }
2323
2324
        return $list;
2325
    }
2326
2327
    /**
2328
     * Return the number of interactions for the given learnpath Item View ID.
2329
     * This method can be used as static.
2330
     *
2331
     * @param int $lp_iv_id  Item View ID
2332
     * @param int $course_id course id
2333
     *
2334
     * @return int
2335
     */
2336
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2337
    {
2338
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2339
        $lp_iv_id = (int) $lp_iv_id;
2340
        $course_id = (int) $course_id;
2341
2342
        $sql = "SELECT count(*) FROM $table
2343
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2344
        $res = Database::query($sql);
2345
        $num = 0;
2346
        if (Database::num_rows($res)) {
2347
            $row = Database::fetch_array($res);
2348
            $num = $row[0];
2349
        }
2350
2351
        return $num;
2352
    }
2353
2354
    /**
2355
     * Return the interactions as an array for the given lp_iv_id.
2356
     * This method can be used as static.
2357
     *
2358
     * @param int $lp_iv_id Learnpath Item View ID
2359
     *
2360
     * @return array
2361
     *
2362
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2363
     */
2364
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2365
    {
2366
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2367
        $list = [];
2368
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2369
        $lp_iv_id = (int) $lp_iv_id;
2370
2371
        if (empty($lp_iv_id) || empty($course_id)) {
2372
            return [];
2373
        }
2374
2375
        $sql = "SELECT * FROM $table
2376
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2377
                ORDER BY order_id ASC";
2378
        $res = Database::query($sql);
2379
        $num = Database::num_rows($res);
2380
        if ($num > 0) {
2381
            $list[] = [
2382
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2383
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2384
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2385
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2386
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2387
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2388
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2389
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2390
                'student_response_formatted' => '',
2391
            ];
2392
            while ($row = Database::fetch_array($res)) {
2393
                $studentResponseFormatted = urldecode($row['student_response']);
2394
                $content_student_response = explode('__|', $studentResponseFormatted);
2395
                if (count($content_student_response) > 0) {
2396
                    if (count($content_student_response) >= 3) {
2397
                        // Pop the element off the end of array.
2398
                        array_pop($content_student_response);
2399
                    }
2400
                    $studentResponseFormatted = implode(',', $content_student_response);
2401
                }
2402
2403
                $list[] = [
2404
                    'order_id' => $row['order_id'] + 1,
2405
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2406
                    'type' => $row['interaction_type'],
2407
                    'time' => $row['completion_time'],
2408
                    'correct_responses' => '', // Hide correct responses from students.
2409
                    'student_response' => $row['student_response'],
2410
                    'result' => $row['result'],
2411
                    'latency' => $row['latency'],
2412
                    'student_response_formatted' => $studentResponseFormatted,
2413
                ];
2414
            }
2415
        }
2416
2417
        return $list;
2418
    }
2419
2420
    /**
2421
     * Return the number of objectives for the given learnpath Item View ID.
2422
     * This method can be used as static.
2423
     *
2424
     * @param int $lp_iv_id  Item View ID
2425
     * @param int $course_id Course ID
2426
     *
2427
     * @return int Number of objectives
2428
     */
2429
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2430
    {
2431
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2432
        $course_id = (int) $course_id;
2433
        $lp_iv_id = (int) $lp_iv_id;
2434
        $sql = "SELECT count(*) FROM $table
2435
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2436
        //@todo seems that this always returns 0
2437
        $res = Database::query($sql);
2438
        $num = 0;
2439
        if (Database::num_rows($res)) {
2440
            $row = Database::fetch_array($res);
2441
            $num = $row[0];
2442
        }
2443
2444
        return $num;
2445
    }
2446
2447
    /**
2448
     * Return the objectives as an array for the given lp_iv_id.
2449
     * This method can be used as static.
2450
     *
2451
     * @param int $lpItemViewId Learnpath Item View ID
2452
     * @param int $course_id
2453
     *
2454
     * @return array
2455
     *
2456
     * @todo    Translate labels
2457
     */
2458
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2459
    {
2460
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2461
        $lpItemViewId = (int) $lpItemViewId;
2462
2463
        if (empty($course_id) || empty($lpItemViewId)) {
2464
            return [];
2465
        }
2466
2467
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2468
        $sql = "SELECT * FROM $table
2469
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2470
                ORDER BY order_id ASC";
2471
        $res = Database::query($sql);
2472
        $num = Database::num_rows($res);
2473
        $list = [];
2474
        if ($num > 0) {
2475
            $list[] = [
2476
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2477
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2478
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2479
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2480
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2481
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2482
            ];
2483
            while ($row = Database::fetch_array($res)) {
2484
                $list[] = [
2485
                    'order_id' => $row['order_id'] + 1,
2486
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2487
                    'score_raw' => $row['score_raw'],
2488
                    'score_max' => $row['score_max'],
2489
                    'score_min' => $row['score_min'],
2490
                    'status' => $row['status'],
2491
                ];
2492
            }
2493
        }
2494
2495
        return $list;
2496
    }
2497
2498
    /**
2499
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2500
     * used by get_html_toc() to be ready to display.
2501
     *
2502
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2503
     */
2504
    public function get_toc()
2505
    {
2506
        $toc = [];
2507
        foreach ($this->ordered_items as $item_id) {
2508
            // TODO: Change this link generation and use new function instead.
2509
            $toc[] = [
2510
                'id' => $item_id,
2511
                'title' => $this->items[$item_id]->get_title(),
2512
                'status' => $this->items[$item_id]->get_status(),
2513
                'status_class' => self::getStatusCSSClassName($this->items[$item_id]->get_status()),
2514
                'level' => $this->items[$item_id]->get_level(),
2515
                'type' => $this->items[$item_id]->get_type(),
2516
                'description' => $this->items[$item_id]->get_description(),
2517
                'path' => $this->items[$item_id]->get_path(),
2518
                'parent' => $this->items[$item_id]->get_parent(),
2519
            ];
2520
        }
2521
2522
        return $toc;
2523
    }
2524
2525
    /**
2526
     * Returns the CSS class name associated with a given item status.
2527
     *
2528
     * @param $status string an item status
2529
     *
2530
     * @return string CSS class name
2531
     */
2532
    public static function getStatusCSSClassName($status)
2533
    {
2534
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2535
            return self::STATUS_CSS_CLASS_NAME[$status];
2536
        }
2537
2538
        return '';
2539
    }
2540
2541
    /**
2542
     * Generate and return the table of contents for this learnpath. The JS
2543
     * table returned is used inside of scorm_api.php.
2544
     *
2545
     * @param string $varname
2546
     *
2547
     * @return string A JS array variable construction
2548
     */
2549
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2550
    {
2551
        $toc = $varname.' = new Array();';
2552
        foreach ($this->ordered_items as $item_id) {
2553
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2554
        }
2555
2556
        return $toc;
2557
    }
2558
2559
    /**
2560
     * Gets the learning path type.
2561
     *
2562
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2563
     *
2564
     * @return mixed Type ID or name, depending on the parameter
2565
     */
2566
    public function get_type($get_name = false)
2567
    {
2568
        $res = false;
2569
        if (!empty($this->type) && (!$get_name)) {
2570
            $res = $this->type;
2571
        }
2572
2573
        return $res;
2574
    }
2575
2576
    /**
2577
     * Gets the learning path type as static method.
2578
     *
2579
     * @param int $lp_id
2580
     *
2581
     * @return mixed Type ID or name, depending on the parameter
2582
     */
2583
    public static function get_type_static($lp_id = 0)
2584
    {
2585
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2586
        $lp_id = (int) $lp_id;
2587
        $sql = "SELECT lp_type FROM $tbl_lp
2588
                WHERE iid = $lp_id";
2589
        $res = Database::query($sql);
2590
        if (false === $res) {
2591
            return null;
2592
        }
2593
        if (Database::num_rows($res) <= 0) {
2594
            return null;
2595
        }
2596
        $row = Database::fetch_array($res);
2597
2598
        return $row['lp_type'];
2599
    }
2600
2601
    /**
2602
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2603
     * This method can be used as abstract and is recursive.
2604
     *
2605
     * @param CLp $lp
2606
     * @param int $parent    Parent ID of the items to look for
2607
     *
2608
     * @return array Ordered list of item IDs (empty array on error)
2609
     */
2610
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2611
    {
2612
        $parent = (int) $parent;
2613
2614
        $criteria = Criteria::create()
2615
            ->orderBy(
2616
                [
2617
                    'displayOrder' => Criteria::ASC,
2618
                ]
2619
            );
2620
        $items = $lp->getItems()->matching($criteria);
2621
        $items = $items->filter(
2622
            function (CLpItem $element) use ($parent) {
2623
                if (empty($parent)) {
2624
                    $parent = null;
2625
                    return $element->getParent() === $parent;
2626
                } else {
2627
                    if (null !== $element->getParent()) {
2628
                        return $element->getParent()->getIid() === $parent;
2629
                    }
2630
                    return false;
2631
                }
2632
            }
2633
        );
2634
2635
        /*
2636
         $lp = (int) $lp;
2637
        $parent = (int) $parent;
2638
2639
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2640
        $sql = "SELECT iid FROM $tbl_lp_item
2641
                WHERE lp_id = $lp AND parent_item_id = $parent
2642
                ORDER BY display_order";
2643
2644
        $res = Database::query($sql);
2645
        while ($row = Database::fetch_array($res)) {
2646
            $sublist = self::get_flat_ordered_items_list(
2647
                $lp,
2648
                $row['iid'],
2649
                $course_id
2650
            );
2651
            $list[] = $row['iid'];
2652
            foreach ($sublist as $item) {
2653
                $list[] = $item;
2654
            }
2655
        }
2656
        */
2657
2658
        $list = [];
2659
        foreach ($items as $item) {
2660
            $itemId = $item->getIid();
2661
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2662
            $list[] = $itemId;
2663
            foreach ($sublist as $subItem) {
2664
                $list[] = $subItem;
2665
            }
2666
        }
2667
2668
        return $list;
2669
    }
2670
2671
    public static function getChapterTypes(): array
2672
    {
2673
        return [
2674
            'dir',
2675
        ];
2676
    }
2677
2678
    /**
2679
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
2680
     *
2681
     * @param array $toc_list
2682
     *
2683
     * @return array HTML TOC ready to display
2684
     */
2685
    public function getListArrayToc($toc_list = [])
2686
    {
2687
        $lpItemRepo = Container::getLpItemRepository();
2688
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
2689
        $options = [
2690
            'decorate' => false,
2691
        ];
2692
        $list = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
2693
2694
        return $list;
2695
2696
2697
        if (empty($toc_list)) {
0 ignored issues
show
Unused Code introduced by
IfNode is not reachable.

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

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

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

    return false;
}

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

Loading history...
2698
            $toc_list = $this->get_toc();
2699
        }
2700
        // Temporary variables.
2701
        $currentItemId = $this->get_current_item_id();
2702
        $list = [];
2703
        $arrayList = [];
2704
2705
        foreach ($toc_list as $item) {
2706
            $list['id'] = $item['id'];
2707
            $list['status'] = $item['status'];
2708
            $cssStatus = null;
2709
2710
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
2711
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
2712
            }
2713
2714
            $classStyle = ' ';
2715
            $dirTypes = self::getChapterTypes();
2716
2717
            if (in_array($item['type'], $dirTypes)) {
2718
                $classStyle = 'scorm_item_section ';
2719
            }
2720
            if ($item['id'] == $this->current) {
2721
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
2722
            } elseif (!in_array($item['type'], $dirTypes)) {
2723
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
2724
            }
2725
            $title = $item['title'];
2726
            if (empty($title)) {
2727
                $title = self::rl_get_resource_name(
2728
                    api_get_course_id(),
2729
                    $this->get_id(),
2730
                    $item['id']
2731
                );
2732
            }
2733
            $title = Security::remove_XSS($item['title']);
2734
2735
            if (empty($item['description'])) {
2736
                $list['description'] = $title;
2737
            } else {
2738
                $list['description'] = $item['description'];
2739
            }
2740
2741
            $list['class'] = $classStyle.' '.$cssStatus;
2742
            $list['level'] = $item['level'];
2743
            $list['type'] = $item['type'];
2744
2745
            if (in_array($item['type'], $dirTypes)) {
2746
                $list['css_level'] = 'level_'.$item['level'];
2747
            } else {
2748
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
2749
            }
2750
2751
            if (in_array($item['type'], $dirTypes)) {
2752
                $list['title'] = stripslashes($title);
2753
            } else {
2754
                $list['title'] = stripslashes($title);
2755
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
2756
                $list['current_id'] = $currentItemId;
2757
            }
2758
            $arrayList[] = $list;
2759
        }
2760
2761
        return $arrayList;
2762
    }
2763
2764
    /**
2765
     * Returns an HTML-formatted string ready to display with teacher buttons
2766
     * in LP view menu.
2767
     *
2768
     * @return string HTML TOC ready to display
2769
     */
2770
    public function get_teacher_toc_buttons()
2771
    {
2772
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
2773
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
2774
        $html = '';
2775
        if ($isAllow && false == $hideIcons) {
2776
            if ($this->get_lp_session_id() == api_get_session_id()) {
2777
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
2778
                $html .= '<div class="btn-group">';
2779
                $html .= "<a
2780
                    class='btn btn-sm btn-default'
2781
                    href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false'
2782
                    target='_parent'>".
2783
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
2784
                $html .= "<a
2785
                    class='btn btn-sm btn-default'
2786
                    href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false'
2787
                    target='_parent'>".
2788
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
2789
                $html .= '<a
2790
                    class="btn btn-sm btn-default"
2791
                    href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
2792
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
2793
                $html .= '</div>';
2794
                $html .= '</div>';
2795
            }
2796
        }
2797
2798
        return $html;
2799
    }
2800
2801
    /**
2802
     * Gets the learnpath name/title.
2803
     *
2804
     * @return string Learnpath name/title
2805
     */
2806
    public function get_name()
2807
    {
2808
        if (!empty($this->name)) {
2809
            return $this->name;
2810
        }
2811
2812
        return 'N/A';
2813
    }
2814
2815
    /**
2816
     * @return string
2817
     */
2818
    public function getNameNoTags()
2819
    {
2820
        return strip_tags($this->get_name());
2821
    }
2822
2823
    /**
2824
     * Gets a link to the resource from the present location, depending on item ID.
2825
     *
2826
     * @param string $type         Type of link expected
2827
     * @param int    $item_id      Learnpath item ID
2828
     * @param bool   $provided_toc
2829
     *
2830
     * @return string $provided_toc Link to the lp_item resource
2831
     */
2832
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
2833
    {
2834
        $course_id = $this->get_course_int_id();
2835
        $item_id = (int) $item_id;
2836
2837
        if (empty($item_id)) {
2838
            $item_id = $this->get_current_item_id();
2839
2840
            if (empty($item_id)) {
2841
                //still empty, this means there was no item_id given and we are not in an object context or
2842
                //the object property is empty, return empty link
2843
                $this->first();
2844
2845
                return '';
2846
            }
2847
        }
2848
2849
        $file = '';
2850
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2851
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2852
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2853
2854
        $sql = "SELECT
2855
                    l.lp_type as ltype,
2856
                    l.path as lpath,
2857
                    li.item_type as litype,
2858
                    li.path as lipath,
2859
                    li.parameters as liparams
2860
        		FROM $lp_table l
2861
                INNER JOIN $lp_item_table li
2862
                ON (li.lp_id = l.iid)
2863
        		WHERE
2864
        		    li.iid = $item_id
2865
        		";
2866
        $res = Database::query($sql);
2867
        if (Database::num_rows($res) > 0) {
2868
            $row = Database::fetch_array($res);
2869
            $lp_type = $row['ltype'];
2870
            $lp_path = $row['lpath'];
2871
            $lp_item_type = $row['litype'];
2872
            $lp_item_path = $row['lipath'];
2873
            $lp_item_params = $row['liparams'];
2874
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
2875
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
2876
            }
2877
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
2878
            if ('http' === $type) {
2879
                //web path
2880
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
2881
            } else {
2882
                //$course_path = $sys_course_path; //system path
2883
            }
2884
2885
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
2886
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
2887
            if (in_array(
2888
                $lp_item_type,
2889
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
2890
            )
2891
            ) {
2892
                $lp_type = CLp::LP_TYPE;
2893
            }
2894
2895
            // Now go through the specific cases to get the end of the path
2896
            // @todo Use constants instead of int values.
2897
            switch ($lp_type) {
2898
                case CLp::LP_TYPE:
2899
                    $file = self::rl_get_resource_link_for_learnpath(
2900
                        $course_id,
2901
                        $this->get_id(),
2902
                        $item_id,
2903
                        $this->get_view_id()
2904
                    );
2905
                    switch ($lp_item_type) {
2906
                        case 'document':
2907
                            // Shows a button to download the file instead of just downloading the file directly.
2908
                            $documentPathInfo = pathinfo($file);
2909
                            if (isset($documentPathInfo['extension'])) {
2910
                                $parsed = parse_url($documentPathInfo['extension']);
2911
                                if (isset($parsed['path'])) {
2912
                                    $extension = $parsed['path'];
2913
                                    $extensionsToDownload = [
2914
                                        'zip',
2915
                                        'ppt',
2916
                                        'pptx',
2917
                                        'ods',
2918
                                        'xlsx',
2919
                                        'xls',
2920
                                        'csv',
2921
                                        'doc',
2922
                                        'docx',
2923
                                        'dot',
2924
                                    ];
2925
2926
                                    if (in_array($extension, $extensionsToDownload)) {
2927
                                        $file = api_get_path(WEB_CODE_PATH).
2928
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
2929
                                    }
2930
                                }
2931
                            }
2932
                            break;
2933
                        case 'dir':
2934
                            $file = 'lp_content.php?type=dir';
2935
                            break;
2936
                        case 'link':
2937
                            if (Link::is_youtube_link($file)) {
2938
                                $src = Link::get_youtube_video_id($file);
2939
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
2940
                            } elseif (Link::isVimeoLink($file)) {
2941
                                $src = Link::getVimeoLinkId($file);
2942
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
2943
                            } else {
2944
                                // If the current site is HTTPS and the link is
2945
                                // HTTP, browsers will refuse opening the link
2946
                                $urlId = api_get_current_access_url_id();
2947
                                $url = api_get_access_url($urlId, false);
2948
                                $protocol = substr($url['url'], 0, 5);
2949
                                if ('https' === $protocol) {
2950
                                    $linkProtocol = substr($file, 0, 5);
2951
                                    if ('http:' === $linkProtocol) {
2952
                                        //this is the special intervention case
2953
                                        $file = api_get_path(WEB_CODE_PATH).
2954
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
2955
                                    }
2956
                                }
2957
                            }
2958
                            break;
2959
                        case 'quiz':
2960
                            // Check how much attempts of a exercise exits in lp
2961
                            $lp_item_id = $this->get_current_item_id();
2962
                            $lp_view_id = $this->get_view_id();
2963
2964
                            $prevent_reinit = null;
2965
                            if (isset($this->items[$this->current])) {
2966
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
2967
                            }
2968
2969
                            if (empty($provided_toc)) {
2970
                                $list = $this->get_toc();
2971
                            } else {
2972
                                $list = $provided_toc;
2973
                            }
2974
2975
                            $type_quiz = false;
2976
                            foreach ($list as $toc) {
2977
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
2978
                                    $type_quiz = true;
2979
                                }
2980
                            }
2981
2982
                            if ($type_quiz) {
2983
                                $lp_item_id = (int) $lp_item_id;
2984
                                $lp_view_id = (int) $lp_view_id;
2985
                                $sql = "SELECT count(*) FROM $lp_item_view_table
2986
                                        WHERE
2987
                                            c_id = $course_id AND
2988
                                            lp_item_id='".$lp_item_id."' AND
2989
                                            lp_view_id ='".$lp_view_id."' AND
2990
                                            status='completed'";
2991
                                $result = Database::query($sql);
2992
                                $row_count = Database:: fetch_row($result);
2993
                                $count_item_view = (int) $row_count[0];
2994
                                $not_multiple_attempt = 0;
2995
                                if (1 === $prevent_reinit && $count_item_view > 0) {
2996
                                    $not_multiple_attempt = 1;
2997
                                }
2998
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
2999
                            }
3000
                            break;
3001
                    }
3002
3003
                    $tmp_array = explode('/', $file);
3004
                    $document_name = $tmp_array[count($tmp_array) - 1];
3005
                    if (strpos($document_name, '_DELETED_')) {
3006
                        $file = 'blank.php?error=document_deleted';
3007
                    }
3008
                    break;
3009
                case CLp::SCORM_TYPE:
3010
                    if ('dir' !== $lp_item_type) {
3011
                        // Quite complex here:
3012
                        // We want to make sure 'http://' (and similar) links can
3013
                        // be loaded as is (withouth the Chamilo path in front) but
3014
                        // some contents use this form: resource.htm?resource=http://blablabla
3015
                        // which means we have to find a protocol at the path's start, otherwise
3016
                        // it should not be considered as an external URL.
3017
                        // if ($this->prerequisites_match($item_id)) {
3018
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3019
                            if ($this->debug > 2) {
3020
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3021
                            }
3022
                            // Distant url, return as is.
3023
                            $file = $lp_item_path;
3024
                        } else {
3025
                            if ($this->debug > 2) {
3026
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
3027
                            }
3028
                            // Prevent getting untranslatable urls.
3029
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3030
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3031
3032
                            /*$asset = $this->getEntity()->getAsset();
3033
                            $folder = Container::getAssetRepository()->getFolder($asset);
3034
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
3035
                            $file = null;
3036
                            if ($hasFile) {
3037
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
3038
                            }*/
3039
                            $file = $this->scormUrl.$lp_item_path;
3040
3041
                            // Prepare the path.
3042
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3043
                            // TODO: Fix this for urls with protocol header.
3044
                            $file = str_replace('//', '/', $file);
3045
                            $file = str_replace(':/', '://', $file);
3046
                            if ('/' === substr($lp_path, -1)) {
3047
                                $lp_path = substr($lp_path, 0, -1);
3048
                            }*/
3049
                            /*if (!$hasFile) {
3050
                                // if file not found.
3051
                                $decoded = html_entity_decode($lp_item_path);
3052
                                [$decoded] = explode('?', $decoded);
3053
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3054
                                    $file = self::rl_get_resource_link_for_learnpath(
3055
                                        $course_id,
3056
                                        $this->get_id(),
3057
                                        $item_id,
3058
                                        $this->get_view_id()
3059
                                    );
3060
                                    if (empty($file)) {
3061
                                        $file = 'blank.php?error=document_not_found';
3062
                                    } else {
3063
                                        $tmp_array = explode('/', $file);
3064
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3065
                                        if (strpos($document_name, '_DELETED_')) {
3066
                                            $file = 'blank.php?error=document_deleted';
3067
                                        } else {
3068
                                            $file = 'blank.php?error=document_not_found';
3069
                                        }
3070
                                    }
3071
                                } else {
3072
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3073
                                }
3074
                            }*/
3075
                        }
3076
3077
                        // We want to use parameters if they were defined in the imsmanifest
3078
                        if (false === strpos($file, 'blank.php')) {
3079
                            $lp_item_params = ltrim($lp_item_params, '?');
3080
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3081
                        }
3082
                    } else {
3083
                        $file = 'lp_content.php?type=dir';
3084
                    }
3085
                    break;
3086
                case CLp::AICC_TYPE:
3087
                    // Formatting AICC HACP append URL.
3088
                    $aicc_append = '?aicc_sid='.
3089
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3090
                    if (!empty($lp_item_params)) {
3091
                        $aicc_append .= $lp_item_params.'&';
3092
                    }
3093
                    if ('dir' !== $lp_item_type) {
3094
                        // Quite complex here:
3095
                        // We want to make sure 'http://' (and similar) links can
3096
                        // be loaded as is (withouth the Chamilo path in front) but
3097
                        // some contents use this form: resource.htm?resource=http://blablabla
3098
                        // which means we have to find a protocol at the path's start, otherwise
3099
                        // it should not be considered as an external URL.
3100
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3101
                            if ($this->debug > 2) {
3102
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3103
                            }
3104
                            // Distant url, return as is.
3105
                            $file = $lp_item_path;
3106
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3107
                            /*
3108
                            if (stristr($file,'<servername>') !== false) {
3109
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3110
                            }
3111
                            */
3112
                            if (false !== stripos($file, '<servername>')) {
3113
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3114
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3115
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3116
                            }
3117
3118
                            $file .= $aicc_append;
3119
                        } else {
3120
                            if ($this->debug > 2) {
3121
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3122
                            }
3123
                            // Prevent getting untranslatable urls.
3124
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3125
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3126
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3127
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3128
                            // TODO: Fix this for urls with protocol header.
3129
                            $file = str_replace('//', '/', $file);
3130
                            $file = str_replace(':/', '://', $file);
3131
                            $file .= $aicc_append;
3132
                        }
3133
                    } else {
3134
                        $file = 'lp_content.php?type=dir';
3135
                    }
3136
                    break;
3137
                case 4:
3138
                default:
3139
                    break;
3140
            }
3141
            // Replace &amp; by & because &amp; will break URL with params
3142
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3143
        }
3144
        if ($this->debug > 2) {
3145
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3146
        }
3147
3148
        return $file;
3149
    }
3150
3151
    /**
3152
     * Gets the latest usable view or generate a new one.
3153
     *
3154
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3155
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3156
     *
3157
     * @return int DB lp_view id
3158
     */
3159
    public function get_view($attempt_num = 0, $userId = null)
3160
    {
3161
        $search = '';
3162
        $attempt_num = (int) $attempt_num;
3163
        // Use $attempt_num to enable multi-views management (disabled so far).
3164
        if (!empty($attempt_num)) {
3165
            $search = 'AND view_count = '.$attempt_num;
3166
        }
3167
3168
        $course_id = api_get_course_int_id();
3169
        $sessionId = api_get_session_id();
3170
3171
        // Check user ID.
3172
        if (empty($userId)) {
3173
            if (empty($this->get_user_id())) {
3174
                $this->error = 'User ID is empty in learnpath::get_view()';
3175
3176
                return null;
3177
            } else {
3178
                $userId = $this->get_user_id();
3179
            }
3180
        }
3181
        $sessionCondition = api_get_session_condition($sessionId);
3182
3183
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3184
        $table = Database::get_course_table(TABLE_LP_VIEW);
3185
        $sql = "SELECT iid FROM $table
3186
        		WHERE
3187
        		    c_id = $course_id AND
3188
        		    lp_id = ".$this->get_id()." AND
3189
        		    user_id = ".$userId."
3190
        		    $sessionCondition
3191
        		    $search
3192
                ORDER BY view_count DESC";
3193
        $res = Database::query($sql);
3194
        if (Database::num_rows($res) > 0) {
3195
            $row = Database::fetch_array($res);
3196
            $this->lp_view_id = $row['iid'];
3197
        } elseif (!api_is_invitee()) {
3198
            $params = [
3199
                'c_id' => $course_id,
3200
                'lp_id' => $this->get_id(),
3201
                'user_id' => $this->get_user_id(),
3202
                'view_count' => 1,
3203
                'last_item' => 0,
3204
            ];
3205
            if (!empty($sessionId)) {
3206
                $params['session_id']  = $sessionId;
3207
            }
3208
            $this->lp_view_id = Database::insert($table, $params);
3209
        }
3210
3211
        return $this->lp_view_id;
3212
    }
3213
3214
    /**
3215
     * Gets the current view id.
3216
     *
3217
     * @return int View ID (from lp_view)
3218
     */
3219
    public function get_view_id()
3220
    {
3221
        if (!empty($this->lp_view_id)) {
3222
            return (int) $this->lp_view_id;
3223
        }
3224
3225
        return 0;
3226
    }
3227
3228
    /**
3229
     * Gets the update queue.
3230
     *
3231
     * @return array Array containing IDs of items to be updated by JavaScript
3232
     */
3233
    public function get_update_queue()
3234
    {
3235
        return $this->update_queue;
3236
    }
3237
3238
    /**
3239
     * Gets the user ID.
3240
     *
3241
     * @return int User ID
3242
     */
3243
    public function get_user_id()
3244
    {
3245
        if (!empty($this->user_id)) {
3246
            return (int) $this->user_id;
3247
        }
3248
3249
        return false;
3250
    }
3251
3252
    /**
3253
     * Checks if any of the items has an audio element attached.
3254
     *
3255
     * @return bool True or false
3256
     */
3257
    public function has_audio()
3258
    {
3259
        $has = false;
3260
        foreach ($this->items as $i => $item) {
3261
            if (!empty($this->items[$i]->audio)) {
3262
                $has = true;
3263
                break;
3264
            }
3265
        }
3266
3267
        return $has;
3268
    }
3269
3270
    /**
3271
     * Moves an item up and down at its level.
3272
     *
3273
     * @param int    $id        Item to move up and down
3274
     * @param string $direction Direction 'up' or 'down'
3275
     *
3276
     * @return bool|int
3277
     */
3278
    public function move_item($id, $direction)
3279
    {
3280
        $course_id = api_get_course_int_id();
3281
        if (empty($id) || empty($direction)) {
3282
            return false;
3283
        }
3284
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3285
        $sql_sel = "SELECT *
3286
                    FROM $tbl_lp_item
3287
                    WHERE
3288
                        iid = $id
3289
                    ";
3290
        $res_sel = Database::query($sql_sel);
3291
        // Check if elem exists.
3292
        if (Database::num_rows($res_sel) < 1) {
3293
            return false;
3294
        }
3295
        // Gather data.
3296
        $row = Database::fetch_array($res_sel);
3297
        $previous = $row['previous_item_id'];
3298
        $next = $row['next_item_id'];
3299
        $display = $row['display_order'];
3300
        $parent = $row['parent_item_id'];
3301
        $lp = $row['lp_id'];
3302
        // Update the item (switch with previous/next one).
3303
        switch ($direction) {
3304
            case 'up':
3305
                if ($display > 1) {
3306
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3307
                                 WHERE iid = $previous";
3308
                    $res_sel2 = Database::query($sql_sel2);
3309
                    if (Database::num_rows($res_sel2) < 1) {
3310
                        $previous_previous = 0;
3311
                    }
3312
                    // Gather data.
3313
                    $row2 = Database::fetch_array($res_sel2);
3314
                    $previous_previous = $row2['previous_item_id'];
3315
                    // Update previous_previous item (switch "next" with current).
3316
                    if (0 != $previous_previous) {
3317
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3318
                                        next_item_id = $id
3319
                                    WHERE iid = $previous_previous";
3320
                        Database::query($sql_upd2);
3321
                    }
3322
                    // Update previous item (switch with current).
3323
                    if (0 != $previous) {
3324
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3325
                                    next_item_id = $next,
3326
                                    previous_item_id = $id,
3327
                                    display_order = display_order +1
3328
                                    WHERE iid = $previous";
3329
                        Database::query($sql_upd2);
3330
                    }
3331
3332
                    // Update current item (switch with previous).
3333
                    if (0 != $id) {
3334
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3335
                                        next_item_id = $previous,
3336
                                        previous_item_id = $previous_previous,
3337
                                        display_order = display_order-1
3338
                                    WHERE c_id = ".$course_id." AND id = $id";
3339
                        Database::query($sql_upd2);
3340
                    }
3341
                    // Update next item (new previous item).
3342
                    if (!empty($next)) {
3343
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3344
                                     WHERE iid = $next";
3345
                        Database::query($sql_upd2);
3346
                    }
3347
                    $display = $display - 1;
3348
                }
3349
                break;
3350
            case 'down':
3351
                if (0 != $next) {
3352
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3353
                                 WHERE iid = $next";
3354
                    $res_sel2 = Database::query($sql_sel2);
3355
                    if (Database::num_rows($res_sel2) < 1) {
3356
                        $next_next = 0;
3357
                    }
3358
                    // Gather data.
3359
                    $row2 = Database::fetch_array($res_sel2);
3360
                    $next_next = $row2['next_item_id'];
3361
                    // Update previous item (switch with current).
3362
                    if (0 != $previous) {
3363
                        $sql_upd2 = "UPDATE $tbl_lp_item
3364
                                     SET next_item_id = $next
3365
                                     WHERE iid = $previous";
3366
                        Database::query($sql_upd2);
3367
                    }
3368
                    // Update current item (switch with previous).
3369
                    if (0 != $id) {
3370
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3371
                                     previous_item_id = $next,
3372
                                     next_item_id = $next_next,
3373
                                     display_order = display_order + 1
3374
                                     WHERE iid = $id";
3375
                        Database::query($sql_upd2);
3376
                    }
3377
3378
                    // Update next item (new previous item).
3379
                    if (0 != $next) {
3380
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3381
                                     previous_item_id = $previous,
3382
                                     next_item_id = $id,
3383
                                     display_order = display_order-1
3384
                                     WHERE iid = $next";
3385
                        Database::query($sql_upd2);
3386
                    }
3387
3388
                    // Update next_next item (switch "previous" with current).
3389
                    if (0 != $next_next) {
3390
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3391
                                     previous_item_id = $id
3392
                                     WHERE iid = $next_next";
3393
                        Database::query($sql_upd2);
3394
                    }
3395
                    $display = $display + 1;
3396
                }
3397
                break;
3398
            default:
3399
                return false;
3400
        }
3401
3402
        return $display;
3403
    }
3404
3405
    /**
3406
     * Move a LP up (display_order).
3407
     *
3408
     * @param int $lp_id      Learnpath ID
3409
     * @param int $categoryId Category ID
3410
     *
3411
     * @return bool
3412
     */
3413
    public static function move_up($lp_id, $categoryId = 0)
3414
    {
3415
        $courseId = api_get_course_int_id();
3416
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3417
3418
        $categoryCondition = '';
3419
        if (!empty($categoryId)) {
3420
            $categoryId = (int) $categoryId;
3421
            $categoryCondition = " AND category_id = $categoryId";
3422
        }
3423
        $sql = "SELECT * FROM $lp_table
3424
                WHERE c_id = $courseId
3425
                $categoryCondition
3426
                ORDER BY display_order";
3427
        $res = Database::query($sql);
3428
        if (false === $res) {
3429
            return false;
3430
        }
3431
3432
        $lps = [];
3433
        $lp_order = [];
3434
        $num = Database::num_rows($res);
3435
        // First check the order is correct, globally (might be wrong because
3436
        // of versions < 1.8.4)
3437
        if ($num > 0) {
3438
            $i = 1;
3439
            while ($row = Database::fetch_array($res)) {
3440
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3441
                    $sql = "UPDATE $lp_table SET display_order = $i
3442
                            WHERE iid = ".$row['iid'];
3443
                    Database::query($sql);
3444
                }
3445
                $row['display_order'] = $i;
3446
                $lps[$row['iid']] = $row;
3447
                $lp_order[$i] = $row['iid'];
3448
                $i++;
3449
            }
3450
        }
3451
        if ($num > 1) { // If there's only one element, no need to sort.
3452
            $order = $lps[$lp_id]['display_order'];
3453
            if ($order > 1) { // If it's the first element, no need to move up.
3454
                $sql = "UPDATE $lp_table SET display_order = $order
3455
                        WHERE iid = ".$lp_order[$order - 1];
3456
                Database::query($sql);
3457
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3458
                        WHERE iid = $lp_id";
3459
                Database::query($sql);
3460
            }
3461
        }
3462
3463
        return true;
3464
    }
3465
3466
    /**
3467
     * Move a learnpath down (display_order).
3468
     *
3469
     * @param int $lp_id      Learnpath ID
3470
     * @param int $categoryId Category ID
3471
     *
3472
     * @return bool
3473
     */
3474
    public static function move_down($lp_id, $categoryId = 0)
3475
    {
3476
        $courseId = api_get_course_int_id();
3477
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3478
3479
        $categoryCondition = '';
3480
        if (!empty($categoryId)) {
3481
            $categoryId = (int) $categoryId;
3482
            $categoryCondition = " AND category_id = $categoryId";
3483
        }
3484
3485
        $sql = "SELECT * FROM $lp_table
3486
                WHERE c_id = $courseId
3487
                $categoryCondition
3488
                ORDER BY display_order";
3489
        $res = Database::query($sql);
3490
        if (false === $res) {
3491
            return false;
3492
        }
3493
        $lps = [];
3494
        $lp_order = [];
3495
        $num = Database::num_rows($res);
3496
        $max = 0;
3497
        // First check the order is correct, globally (might be wrong because
3498
        // of versions < 1.8.4).
3499
        if ($num > 0) {
3500
            $i = 1;
3501
            while ($row = Database::fetch_array($res)) {
3502
                $max = $i;
3503
                if ($row['display_order'] != $i) {
3504
                    // If we find a gap in the order, we need to fix it.
3505
                    $sql = "UPDATE $lp_table SET display_order = $i
3506
                              WHERE iid = ".$row['iid'];
3507
                    Database::query($sql);
3508
                }
3509
                $row['display_order'] = $i;
3510
                $lps[$row['iid']] = $row;
3511
                $lp_order[$i] = $row['iid'];
3512
                $i++;
3513
            }
3514
        }
3515
        if ($num > 1) { // If there's only one element, no need to sort.
3516
            $order = $lps[$lp_id]['display_order'];
3517
            if ($order < $max) { // If it's the first element, no need to move up.
3518
                $sql = "UPDATE $lp_table SET display_order = $order
3519
                        WHERE iid = ".$lp_order[$order + 1];
3520
                Database::query($sql);
3521
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
3522
                        WHERE iid = $lp_id";
3523
                Database::query($sql);
3524
            }
3525
        }
3526
3527
        return true;
3528
    }
3529
3530
    /**
3531
     * Updates learnpath attributes to point to the next element
3532
     * The last part is similar to set_current_item but processing the other way around.
3533
     */
3534
    public function next()
3535
    {
3536
        if ($this->debug > 0) {
3537
            error_log('In learnpath::next()', 0);
3538
        }
3539
        $this->last = $this->get_current_item_id();
3540
        $this->items[$this->last]->save(
3541
            false,
3542
            $this->prerequisites_match($this->last)
3543
        );
3544
        $this->autocomplete_parents($this->last);
3545
        $new_index = $this->get_next_index();
3546
        if ($this->debug > 2) {
3547
            error_log('New index: '.$new_index, 0);
3548
        }
3549
        $this->index = $new_index;
3550
        if ($this->debug > 2) {
3551
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
3552
        }
3553
        $this->current = $this->ordered_items[$new_index];
3554
        if ($this->debug > 2) {
3555
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
3556
        }
3557
    }
3558
3559
    /**
3560
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
3561
     * class, this might be redefined to allow several behaviours depending on the document type.
3562
     *
3563
     * @param int $id Resource ID
3564
     */
3565
    public function open($id)
3566
    {
3567
        // TODO:
3568
        // set the current resource attribute to this resource
3569
        // switch on element type (redefine in child class?)
3570
        // set status for this item to "opened"
3571
        // start timer
3572
        // initialise score
3573
        $this->index = 0; //or = the last item seen (see $this->last)
3574
    }
3575
3576
    /**
3577
     * Check that all prerequisites are fulfilled. Returns true and an
3578
     * empty string on success, returns false
3579
     * and the prerequisite string on error.
3580
     * This function is based on the rules for aicc_script language as
3581
     * described in the SCORM 1.2 CAM documentation page 108.
3582
     *
3583
     * @param int $itemId Optional item ID. If none given, uses the current open item.
3584
     *
3585
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
3586
     *              string otherwise
3587
     */
3588
    public function prerequisites_match($itemId = null)
3589
    {
3590
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
3591
        if ($allow) {
3592
            if (api_is_allowed_to_edit() ||
3593
                api_is_platform_admin(true) ||
3594
                api_is_drh() ||
3595
                api_is_coach(api_get_session_id(), api_get_course_int_id())
3596
            ) {
3597
                return true;
3598
            }
3599
        }
3600
3601
        $debug = $this->debug;
3602
        if ($debug > 0) {
3603
            error_log('In learnpath::prerequisites_match()');
3604
        }
3605
3606
        if (empty($itemId)) {
3607
            $itemId = $this->current;
3608
        }
3609
3610
        $currentItem = $this->getItem($itemId);
3611
3612
        if ($currentItem) {
3613
            if (2 == $this->type) {
3614
                // Getting prereq from scorm
3615
                $prereq_string = $this->get_scorm_prereq_string($itemId);
3616
            } else {
3617
                $prereq_string = $currentItem->get_prereq_string();
3618
            }
3619
3620
            if (empty($prereq_string)) {
3621
                if ($debug > 0) {
3622
                    error_log('Found prereq_string is empty return true');
3623
                }
3624
3625
                return true;
3626
            }
3627
3628
            // Clean spaces.
3629
            $prereq_string = str_replace(' ', '', $prereq_string);
3630
            if ($debug > 0) {
3631
                error_log('Found prereq_string: '.$prereq_string, 0);
3632
            }
3633
3634
            // Now send to the parse_prereq() function that will check this component's prerequisites.
3635
            $result = $currentItem->parse_prereq(
3636
                $prereq_string,
3637
                $this->items,
3638
                $this->refs_list,
3639
                $this->get_user_id()
3640
            );
3641
3642
            if (false === $result) {
3643
                $this->set_error_msg($currentItem->prereq_alert);
3644
            }
3645
        } else {
3646
            $result = true;
3647
            if ($debug > 1) {
3648
                error_log('$this->items['.$itemId.'] was not an object', 0);
3649
            }
3650
        }
3651
3652
        if ($debug > 1) {
3653
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
3654
        }
3655
3656
        return $result;
3657
    }
3658
3659
    /**
3660
     * Updates learnpath attributes to point to the previous element
3661
     * The last part is similar to set_current_item but processing the other way around.
3662
     */
3663
    public function previous()
3664
    {
3665
        $this->last = $this->get_current_item_id();
3666
        $this->items[$this->last]->save(
3667
            false,
3668
            $this->prerequisites_match($this->last)
3669
        );
3670
        $this->autocomplete_parents($this->last);
3671
        $new_index = $this->get_previous_index();
3672
        $this->index = $new_index;
3673
        $this->current = $this->ordered_items[$new_index];
3674
    }
3675
3676
    /**
3677
     * Publishes a learnpath. This basically means show or hide the learnpath
3678
     * to normal users.
3679
     * Can be used as abstract.
3680
     *
3681
     * @param int $id         Learnpath ID
3682
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
3683
     *
3684
     * @return bool
3685
     */
3686
    public static function toggleVisibility($id, $visibility = 1)
3687
    {
3688
        $repo = Container::getLpRepository();
3689
        $lp = $repo->find($id);
3690
3691
        if (!$lp) {
3692
            return false;
3693
        }
3694
3695
        $visibility = (int) $visibility;
3696
3697
        if (1 === $visibility) {
3698
            $repo->setVisibilityPublished($lp);
3699
        } else {
3700
            $repo->setVisibilityDraft($lp);
3701
        }
3702
3703
        return true;
3704
3705
        /*$action = 'visible';
3706
        if (1 != $set_visibility) {
3707
            $action = 'invisible';
3708
            self::toggle_publish($lp_id, 'i');
3709
        }
3710
3711
        return api_item_property_update(
3712
            api_get_course_info(),
3713
            TOOL_LEARNPATH,
3714
            $lp_id,
3715
            $action,
3716
            api_get_user_id()
3717
        );*/
3718
    }
3719
3720
    /**
3721
     * Publishes a learnpath category.
3722
     * This basically means show or hide the learnpath category to normal users.
3723
     *
3724
     * @param int $id
3725
     * @param int $visibility
3726
     *
3727
     * @return bool
3728
     */
3729
    public static function toggleCategoryVisibility($id, $visibility = 1)
3730
    {
3731
        $repo = Container::getLpCategoryRepository();
3732
        $resource = $repo->find($id);
3733
3734
        if (!$resource) {
3735
            return false;
3736
        }
3737
3738
        $visibility = (int) $visibility;
3739
3740
        if (1 === $visibility) {
3741
            $repo->setVisibilityPublished($resource);
3742
        } else {
3743
            $repo->setVisibilityDraft($resource);
3744
            self::toggleCategoryPublish($id, 0);
3745
        }
3746
3747
        return false;
3748
        /*
3749
        $action = 'visible';
3750
        if (1 != $visibility) {
3751
            self::toggleCategoryPublish($id, 0);
3752
            $action = 'invisible';
3753
        }
3754
3755
        return api_item_property_update(
3756
            api_get_course_info(),
3757
            TOOL_LEARNPATH_CATEGORY,
3758
            $id,
3759
            $action,
3760
            api_get_user_id()
3761
        );*/
3762
    }
3763
3764
    /**
3765
     * Publishes a learnpath. This basically means show or hide the learnpath
3766
     * on the course homepage.
3767
     *
3768
     * @param int    $id            Learnpath id
3769
     * @param string $setVisibility New visibility (v/i - visible/invisible)
3770
     *
3771
     * @return bool
3772
     */
3773
    public static function togglePublish($id, $setVisibility = 'v')
3774
    {
3775
        $addShortcut = false;
3776
        if ('v' === $setVisibility) {
3777
            $addShortcut = true;
3778
        }
3779
        $repo = Container::getLpRepository();
3780
        /** @var CLp $lp */
3781
        $lp = $repo->find($id);
3782
        if (null === $lp) {
3783
            return false;
3784
        }
3785
        $repoShortcut = Container::getShortcutRepository();
3786
        $courseEntity = api_get_course_entity();
3787
3788
        if ($addShortcut) {
3789
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
3790
        } else {
3791
            $repoShortcut->removeShortCut($lp);
3792
        }
3793
3794
        return true;
3795
3796
        /*
3797
        $course_id = api_get_course_int_id();
3798
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3799
        $lp_id = (int) $lp_id;
3800
        $sql = "SELECT * FROM $tbl_lp
3801
                WHERE iid = $lp_id";
3802
        $result = Database::query($sql);
3803
3804
        if (Database::num_rows($result)) {
3805
            $row = Database::fetch_array($result);
3806
            $name = Database::escape_string($row['name']);
3807
            if ($set_visibility == 'i') {
3808
                $v = 0;
3809
            }
3810
            if ($set_visibility == 'v') {
3811
                $v = 1;
3812
            }
3813
3814
            $session_id = api_get_session_id();
3815
            $session_condition = api_get_session_condition($session_id);
3816
3817
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
3818
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
3819
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
3820
3821
            $sql = "SELECT * FROM $tbl_tool
3822
                    WHERE
3823
                        c_id = $course_id AND
3824
                        (link = '$link' OR link = '$oldLink') AND
3825
                        image = 'scormbuilder.gif' AND
3826
                        (
3827
                            link LIKE '$link%' OR
3828
                            link LIKE '$oldLink%'
3829
                        )
3830
                        $session_condition
3831
                    ";
3832
3833
            $result = Database::query($sql);
3834
            $num = Database::num_rows($result);
3835
            if ($set_visibility == 'i' && $num > 0) {
3836
                $sql = "DELETE FROM $tbl_tool
3837
                        WHERE
3838
                            c_id = $course_id AND
3839
                            (link = '$link' OR link = '$oldLink') AND
3840
                            image='scormbuilder.gif'
3841
                            $session_condition";
3842
                Database::query($sql);
3843
            } elseif ($set_visibility == 'v' && $num == 0) {
3844
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
3845
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
3846
                Database::query($sql);
3847
                $insertId = Database::insert_id();
3848
                if ($insertId) {
3849
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
3850
                    Database::query($sql);
3851
                }
3852
            } elseif ($set_visibility == 'v' && $num > 0) {
3853
                $sql = "UPDATE $tbl_tool SET
3854
                            c_id = $course_id,
3855
                            name = '$name',
3856
                            link = '$link',
3857
                            image = 'scormbuilder.gif',
3858
                            visibility = '$v',
3859
                            admin = '0',
3860
                            address = 'pastillegris.gif',
3861
                            added_tool = 0,
3862
                            session_id = $session_id
3863
                        WHERE
3864
                            c_id = ".$course_id." AND
3865
                            (link = '$link' OR link = '$oldLink') AND
3866
                            image='scormbuilder.gif'
3867
                            $session_condition
3868
                        ";
3869
                Database::query($sql);
3870
            } else {
3871
                // Parameter and database incompatible, do nothing, exit.
3872
                return false;
3873
            }
3874
        } else {
3875
            return false;
3876
        }*/
3877
    }
3878
3879
    /**
3880
     * Show or hide the learnpath category on the course homepage.
3881
     *
3882
     * @param int $id
3883
     * @param int $setVisibility
3884
     *
3885
     * @return bool
3886
     */
3887
    public static function toggleCategoryPublish($id, $setVisibility = 1)
3888
    {
3889
        $setVisibility = (int) $setVisibility;
3890
        $addShortcut = false;
3891
        if (1 === $setVisibility) {
3892
            $addShortcut = true;
3893
        }
3894
3895
        $repo = Container::getLpCategoryRepository();
3896
        /** @var CLpCategory $lp */
3897
        $category = $repo->find($id);
3898
3899
        if (null === $category) {
3900
            return false;
3901
        }
3902
3903
        $repoShortcut = Container::getShortcutRepository();
3904
        if ($addShortcut) {
3905
            $courseEntity = api_get_course_entity(api_get_course_int_id());
3906
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
3907
        } else {
3908
            $repoShortcut->removeShortCut($category);
3909
        }
3910
3911
        return true;
3912
    }
3913
3914
    /**
3915
     * Check if the learnpath category is visible for a user.
3916
     *
3917
     * @return bool
3918
     */
3919
    public static function categoryIsVisibleForStudent(
3920
        CLpCategory $category,
3921
        User $user,
3922
        Course $course,
3923
        Session $session = null
3924
    ) {
3925
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3926
3927
        if ($isAllowedToEdit) {
3928
            return true;
3929
        }
3930
3931
        $repo = Container::getLpCategoryRepository();
3932
        $categoryVisibility = $category->isVisible($course, $session);
3933
3934
        /*$categoryVisibility = api_get_item_visibility(
3935
            $courseInfo,
3936
            TOOL_LEARNPATH_CATEGORY,
3937
            $category->getId(),
3938
            $sessionId
3939
        );*/
3940
3941
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
3942
            return false;
3943
        }
3944
3945
        $subscriptionSettings = self::getSubscriptionSettings();
3946
3947
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
3948
            return true;
3949
        }
3950
3951
        $noUserSubscribed = false;
3952
        $noGroupSubscribed = true;
3953
        $users = $category->getUsers();
3954
        if (empty($users) || !$users->count()) {
3955
            $noUserSubscribed = true;
3956
        } elseif ($category->hasUserAdded($user)) {
3957
            return true;
3958
        }
3959
3960
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
3961
        $em = Database::getManager();
3962
3963
        /** @var ItemPropertyRepository $itemRepo */
3964
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
3965
3966
        /** @var CourseRepository $courseRepo */
3967
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
3968
        $session = null;
3969
        if (!empty($sessionId)) {
3970
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
3971
        }
3972
3973
        $course = $courseRepo->find($courseId);
3974
3975
        if (0 != $courseId) {
3976
            // Subscribed groups to a LP
3977
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
3978
                    TOOL_LEARNPATH_CATEGORY,
3979
                    $category->getId(),
3980
                    $course,
3981
                    $session
3982
                );
3983
        }
3984
3985
        if (!empty($subscribedGroupsInLp)) {
3986
            $noGroupSubscribed = false;
3987
            if (!empty($groups)) {
3988
                $groups = array_column($groups, 'iid');
3989
                /** @var CItemProperty $item */
3990
                foreach ($subscribedGroupsInLp as $item) {
3991
                    if ($item->getGroup() &&
3992
                        in_array($item->getGroup()->getId(), $groups)
3993
                    ) {
3994
                        return true;
3995
                    }
3996
                }
3997
            }
3998
        }
3999
        $response = $noGroupSubscribed && $noUserSubscribed;
4000
4001
        return $response;
4002
    }
4003
4004
    /**
4005
     * Check if a learnpath category is published as course tool.
4006
     *
4007
     * @param int $courseId
4008
     *
4009
     * @return bool
4010
     */
4011
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4012
    {
4013
        return false;
4014
        $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...
4015
        $em = Database::getManager();
4016
4017
        $tools = $em
4018
            ->createQuery("
4019
                SELECT t FROM ChamiloCourseBundle:CTool t
4020
                WHERE t.course = :course AND
4021
                    t.name = :name AND
4022
                    t.image LIKE 'lp_category.%' AND
4023
                    t.link LIKE :link
4024
            ")
4025
            ->setParameters([
4026
                'course' => $courseId,
4027
                'name' => strip_tags($category->getName()),
4028
                'link' => "$link%",
4029
            ])
4030
            ->getResult();
4031
4032
        /** @var CTool $tool */
4033
        $tool = current($tools);
4034
4035
        return $tool ? $tool->getVisibility() : false;
4036
    }
4037
4038
    /**
4039
     * Restart the whole learnpath. Return the URL of the first element.
4040
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4041
     * To use a similar method  statically, use the create_new_attempt() method.
4042
     *
4043
     * @return bool
4044
     */
4045
    public function restart()
4046
    {
4047
        if ($this->debug > 0) {
4048
            error_log('In learnpath::restart()', 0);
4049
        }
4050
        // TODO
4051
        // Call autosave method to save the current progress.
4052
        //$this->index = 0;
4053
        if (api_is_invitee()) {
4054
            return false;
4055
        }
4056
        $session_id = api_get_session_id();
4057
        $course_id = api_get_course_int_id();
4058
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4059
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4060
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4061
        if ($this->debug > 2) {
4062
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4063
        }
4064
        Database::query($sql);
4065
        $view_id = Database::insert_id();
4066
4067
        if ($view_id) {
4068
            $this->lp_view_id = $view_id;
4069
            $this->attempt = $this->attempt + 1;
4070
        } else {
4071
            $this->error = 'Could not insert into item_view table...';
4072
4073
            return false;
4074
        }
4075
        $this->autocomplete_parents($this->current);
4076
        foreach ($this->items as $index => $dummy) {
4077
            $this->items[$index]->restart();
4078
            $this->items[$index]->set_lp_view($this->lp_view_id);
4079
        }
4080
        $this->first();
4081
4082
        return true;
4083
    }
4084
4085
    /**
4086
     * Saves the current item.
4087
     *
4088
     * @return bool
4089
     */
4090
    public function save_current()
4091
    {
4092
        $debug = $this->debug;
4093
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4094
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4095
        if ($debug) {
4096
            error_log('save_current() saving item '.$this->current, 0);
4097
            error_log(''.print_r($this->items, true), 0);
4098
        }
4099
        if (isset($this->items[$this->current]) &&
4100
            is_object($this->items[$this->current])
4101
        ) {
4102
            if ($debug) {
4103
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4104
            }
4105
4106
            $res = $this->items[$this->current]->save(
4107
                false,
4108
                $this->prerequisites_match($this->current)
4109
            );
4110
            $this->autocomplete_parents($this->current);
4111
            $status = $this->items[$this->current]->get_status();
4112
            $this->update_queue[$this->current] = $status;
4113
4114
            if ($debug) {
4115
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4116
            }
4117
4118
            return $res;
4119
        }
4120
4121
        return false;
4122
    }
4123
4124
    /**
4125
     * Saves the given item.
4126
     *
4127
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4128
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4129
     *
4130
     * @return bool
4131
     */
4132
    public function save_item($item_id = null, $from_outside = true)
4133
    {
4134
        $debug = $this->debug;
4135
        if ($debug) {
4136
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4137
        }
4138
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4139
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4140
        if (empty($item_id)) {
4141
            $item_id = (int) $_REQUEST['id'];
4142
        }
4143
4144
        if (empty($item_id)) {
4145
            $item_id = $this->get_current_item_id();
4146
        }
4147
        if (isset($this->items[$item_id]) &&
4148
            is_object($this->items[$item_id])
4149
        ) {
4150
            if ($debug) {
4151
                error_log('Object exists');
4152
            }
4153
4154
            // Saving the item.
4155
            $res = $this->items[$item_id]->save(
4156
                $from_outside,
4157
                $this->prerequisites_match($item_id)
4158
            );
4159
4160
            if ($debug) {
4161
                error_log('update_queue before:');
4162
                error_log(print_r($this->update_queue, 1));
4163
            }
4164
            $this->autocomplete_parents($item_id);
4165
4166
            $status = $this->items[$item_id]->get_status();
4167
            $this->update_queue[$item_id] = $status;
4168
4169
            if ($debug) {
4170
                error_log('get_status(): '.$status);
4171
                error_log('update_queue after:');
4172
                error_log(print_r($this->update_queue, 1));
4173
            }
4174
4175
            return $res;
4176
        }
4177
4178
        return false;
4179
    }
4180
4181
    /**
4182
     * Saves the last item seen's ID only in case.
4183
     */
4184
    public function save_last()
4185
    {
4186
        $course_id = api_get_course_int_id();
4187
        $debug = $this->debug;
4188
        if ($debug) {
4189
            error_log('In learnpath::save_last()', 0);
4190
        }
4191
        $session_condition = api_get_session_condition(
4192
            api_get_session_id(),
4193
            true,
4194
            false
4195
        );
4196
        $table = Database::get_course_table(TABLE_LP_VIEW);
4197
4198
        $userId = $this->get_user_id();
4199
        if (empty($userId)) {
4200
            $userId = api_get_user_id();
4201
            if ($debug) {
4202
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4203
            }
4204
        }
4205
        if (isset($this->current) && !api_is_invitee()) {
4206
            if ($debug) {
4207
                error_log('Saving current item ('.$this->current.') for later review', 0);
4208
            }
4209
            $sql = "UPDATE $table SET
4210
                        last_item = ".$this->get_current_item_id()."
4211
                    WHERE
4212
                        c_id = $course_id AND
4213
                        lp_id = ".$this->get_id()." AND
4214
                        user_id = ".$userId." ".$session_condition;
4215
4216
            if ($debug) {
4217
                error_log('Saving last item seen : '.$sql, 0);
4218
            }
4219
            Database::query($sql);
4220
        }
4221
4222
        if (!api_is_invitee()) {
4223
            // Save progress.
4224
            [$progress] = $this->get_progress_bar_text('%');
4225
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4226
            $scoreAsProgress = $this->getUseScoreAsProgress();
4227
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4228
                if ($debug) {
4229
                    error_log("Return false: Dont save score: $score");
4230
                    error_log("progress: $progress");
4231
                }
4232
4233
                return false;
4234
            }
4235
4236
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4237
                $storedProgress = self::getProgress(
4238
                    $this->get_id(),
4239
                    $userId,
4240
                    $course_id,
4241
                    $this->get_lp_session_id()
4242
                );
4243
4244
                // Check if the stored progress is higher than the new value
4245
                if ($storedProgress >= $progress) {
4246
                    if ($debug) {
4247
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4248
                    }
4249
4250
                    return false;
4251
                }
4252
            }
4253
            if ($progress >= 0 && $progress <= 100) {
4254
                $progress = (int) $progress;
4255
                $sql = "UPDATE $table SET
4256
                            progress = $progress
4257
                        WHERE
4258
                            c_id = $course_id AND
4259
                            lp_id = ".$this->get_id()." AND
4260
                            user_id = ".$userId." ".$session_condition;
4261
                // Ignore errors as some tables might not have the progress field just yet.
4262
                Database::query($sql);
4263
                $this->progress_db = $progress;
4264
            }
4265
        }
4266
    }
4267
4268
    /**
4269
     * Sets the current item ID (checks if valid and authorized first).
4270
     *
4271
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4272
     */
4273
    public function set_current_item($item_id = null)
4274
    {
4275
        $debug = $this->debug;
4276
        if ($debug) {
4277
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4278
        }
4279
        if (empty($item_id)) {
4280
            if ($debug) {
4281
                error_log('No new current item given, ignore...', 0);
4282
            }
4283
            // Do nothing.
4284
        } else {
4285
            if ($debug) {
4286
                error_log('New current item given is '.$item_id.'...', 0);
4287
            }
4288
            if (is_numeric($item_id)) {
4289
                $item_id = (int) $item_id;
4290
                // TODO: Check in database here.
4291
                $this->last = $this->current;
4292
                $this->current = $item_id;
4293
                // TODO: Update $this->index as well.
4294
                foreach ($this->ordered_items as $index => $item) {
4295
                    if ($item == $this->current) {
4296
                        $this->index = $index;
4297
                        break;
4298
                    }
4299
                }
4300
                if ($debug) {
4301
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4302
                }
4303
            } else {
4304
                if ($debug) {
4305
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4306
                }
4307
            }
4308
        }
4309
    }
4310
4311
    /**
4312
     * Sets the JS lib setting in the database directly.
4313
     * This is the JavaScript library file this lp needs to load on startup.
4314
     *
4315
     * @param string $lib Proximity setting
4316
     *
4317
     * @return bool True on update success. False otherwise.
4318
     */
4319
    public function set_jslib($lib = '')
4320
    {
4321
        $lp = $this->get_id();
4322
4323
        if (0 != $lp) {
4324
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4325
            $lib = Database::escape_string($lib);
4326
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4327
                    WHERE iid = $lp";
4328
            $res = Database::query($sql);
4329
4330
            return $res;
4331
        }
4332
4333
        return false;
4334
    }
4335
4336
    /**
4337
     * Set index specified prefix terms for all items in this path.
4338
     *
4339
     * @param string $terms_string Comma-separated list of terms
4340
     * @param string $prefix       Xapian term prefix
4341
     *
4342
     * @return bool False on error, true otherwise
4343
     */
4344
    public function set_terms_by_prefix($terms_string, $prefix)
4345
    {
4346
        $course_id = api_get_course_int_id();
4347
        if ('true' !== api_get_setting('search_enabled')) {
4348
            return false;
4349
        }
4350
4351
        if (!extension_loaded('xapian')) {
4352
            return false;
4353
        }
4354
4355
        $terms_string = trim($terms_string);
4356
        $terms = explode(',', $terms_string);
4357
        array_walk($terms, 'trim_value');
4358
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4359
4360
        // Don't do anything if no change, verify only at DB, not the search engine.
4361
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
4362
            return false;
4363
        }
4364
4365
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4366
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4367
4368
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4369
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4370
        $lp_id = (int) $_POST['lp_id'];
4371
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4372
        $result = Database::query($sql);
4373
        $di = new ChamiloIndexer();
4374
4375
        while ($lp_item = Database::fetch_array($result)) {
4376
            // Get search_did.
4377
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4378
            $sql = 'SELECT * FROM %s
4379
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4380
                    LIMIT 1';
4381
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4382
4383
            //echo $sql; echo '<br>';
4384
            $res = Database::query($sql);
4385
            if (Database::num_rows($res) > 0) {
4386
                $se_ref = Database::fetch_array($res);
4387
                // Compare terms.
4388
                $doc = $di->get_document($se_ref['search_did']);
4389
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
4390
                $xterms = [];
4391
                foreach ($xapian_terms as $xapian_term) {
4392
                    $xterms[] = substr($xapian_term['name'], 1);
4393
                }
4394
4395
                $dterms = $terms;
4396
                $missing_terms = array_diff($dterms, $xterms);
4397
                $deprecated_terms = array_diff($xterms, $dterms);
4398
4399
                // Save it to search engine.
4400
                foreach ($missing_terms as $term) {
4401
                    $doc->add_term($prefix.$term, 1);
4402
                }
4403
                foreach ($deprecated_terms as $term) {
4404
                    $doc->remove_term($prefix.$term);
4405
                }
4406
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
4407
                $di->getDb()->flush();
4408
            }
4409
        }
4410
4411
        return true;
4412
    }
4413
4414
    /**
4415
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
4416
     *
4417
     * @param int $id DB ID of the item
4418
     */
4419
    public function set_previous_item($id)
4420
    {
4421
        if ($this->debug > 0) {
4422
            error_log('In learnpath::set_previous_item()', 0);
4423
        }
4424
        $this->last = $id;
4425
    }
4426
4427
    /**
4428
     * Sets use_max_score.
4429
     *
4430
     * @param int $use_max_score Optional string giving the new location of this learnpath
4431
     *
4432
     * @return bool True on success / False on error
4433
     */
4434
    public function set_use_max_score($use_max_score = 1)
4435
    {
4436
        $use_max_score = (int) $use_max_score;
4437
        $this->use_max_score = $use_max_score;
4438
        $table = Database::get_course_table(TABLE_LP_MAIN);
4439
        $lp_id = $this->get_id();
4440
        $sql = "UPDATE $table SET
4441
                    use_max_score = '".$this->use_max_score."'
4442
                WHERE iid = $lp_id";
4443
        Database::query($sql);
4444
4445
        return true;
4446
    }
4447
4448
    /**
4449
     * Sets and saves the expired_on date.
4450
     *
4451
     * @return bool Returns true if author's name is not empty
4452
     */
4453
    public function set_modified_on()
4454
    {
4455
        $this->modified_on = api_get_utc_datetime();
4456
        $table = Database::get_course_table(TABLE_LP_MAIN);
4457
        $lp_id = $this->get_id();
4458
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
4459
                WHERE iid = $lp_id";
4460
        Database::query($sql);
4461
4462
        return true;
4463
    }
4464
4465
    /**
4466
     * Sets the object's error message.
4467
     *
4468
     * @param string $error Error message. If empty, reinits the error string
4469
     */
4470
    public function set_error_msg($error = '')
4471
    {
4472
        if ($this->debug > 0) {
4473
            error_log('In learnpath::set_error_msg()', 0);
4474
        }
4475
        if (empty($error)) {
4476
            $this->error = '';
4477
        } else {
4478
            $this->error .= $error;
4479
        }
4480
    }
4481
4482
    /**
4483
     * Launches the current item if not 'sco'
4484
     * (starts timer and make sure there is a record ready in the DB).
4485
     *
4486
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
4487
     *
4488
     * @return bool
4489
     */
4490
    public function start_current_item($allow_new_attempt = false)
4491
    {
4492
        $debug = $this->debug;
4493
        if ($debug) {
4494
            error_log('In learnpath::start_current_item()');
4495
            error_log('current: '.$this->current);
4496
        }
4497
        if (0 != $this->current && isset($this->items[$this->current]) &&
4498
            is_object($this->items[$this->current])
4499
        ) {
4500
            $type = $this->get_type();
4501
            $item_type = $this->items[$this->current]->get_type();
4502
            if ($debug) {
4503
                error_log('item type: '.$item_type);
4504
                error_log('lp type: '.$type);
4505
            }
4506
            if ((2 == $type && 'sco' !== $item_type) ||
4507
                (3 == $type && 'au' !== $item_type) ||
4508
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
4509
            ) {
4510
                $this->items[$this->current]->open($allow_new_attempt);
4511
                $this->autocomplete_parents($this->current);
4512
                $prereq_check = $this->prerequisites_match($this->current);
4513
                if ($debug) {
4514
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
4515
                }
4516
                $this->items[$this->current]->save(false, $prereq_check);
4517
            }
4518
            // If sco, then it is supposed to have been updated by some other call.
4519
            if ('sco' === $item_type) {
4520
                $this->items[$this->current]->restart();
4521
            }
4522
        }
4523
        if ($debug) {
4524
            error_log('lp_view_session_id');
4525
            error_log($this->lp_view_session_id);
4526
            error_log('api session id');
4527
            error_log(api_get_session_id());
4528
            error_log('End of learnpath::start_current_item()');
4529
        }
4530
4531
        return true;
4532
    }
4533
4534
    /**
4535
     * Stops the processing and counters for the old item (as held in $this->last).
4536
     *
4537
     * @return bool True/False
4538
     */
4539
    public function stop_previous_item()
4540
    {
4541
        $debug = $this->debug;
4542
        if ($debug) {
4543
            error_log('In learnpath::stop_previous_item()', 0);
4544
        }
4545
4546
        if (0 != $this->last && $this->last != $this->current &&
4547
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
4548
        ) {
4549
            if ($debug) {
4550
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
4551
            }
4552
            switch ($this->get_type()) {
4553
                case '3':
4554
                    if ('au' != $this->items[$this->last]->get_type()) {
4555
                        if ($debug) {
4556
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
4557
                        }
4558
                        $this->items[$this->last]->close();
4559
                    } else {
4560
                        if ($debug) {
4561
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
4562
                        }
4563
                    }
4564
                    break;
4565
                case '2':
4566
                    if ('sco' != $this->items[$this->last]->get_type()) {
4567
                        if ($debug) {
4568
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
4569
                        }
4570
                        $this->items[$this->last]->close();
4571
                    } else {
4572
                        if ($debug) {
4573
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
4574
                        }
4575
                    }
4576
                    break;
4577
                case '1':
4578
                default:
4579
                    if ($debug) {
4580
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
4581
                    }
4582
                    $this->items[$this->last]->close();
4583
                    break;
4584
            }
4585
        } else {
4586
            if ($debug) {
4587
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
4588
            }
4589
4590
            return false;
4591
        }
4592
4593
        return true;
4594
    }
4595
4596
    /**
4597
     * Updates the default view mode from fullscreen to embedded and inversely.
4598
     *
4599
     * @return string The current default view mode ('fullscreen' or 'embedded')
4600
     */
4601
    public function update_default_view_mode()
4602
    {
4603
        $table = Database::get_course_table(TABLE_LP_MAIN);
4604
        $sql = "SELECT * FROM $table
4605
                WHERE iid = ".$this->get_id();
4606
        $res = Database::query($sql);
4607
        if (Database::num_rows($res) > 0) {
4608
            $row = Database::fetch_array($res);
4609
            $default_view_mode = $row['default_view_mod'];
4610
            $view_mode = $default_view_mode;
4611
            switch ($default_view_mode) {
4612
                case 'fullscreen': // default with popup
4613
                    $view_mode = 'embedded';
4614
                    break;
4615
                case 'embedded': // default view with left menu
4616
                    $view_mode = 'embedframe';
4617
                    break;
4618
                case 'embedframe': //folded menu
4619
                    $view_mode = 'impress';
4620
                    break;
4621
                case 'impress':
4622
                    $view_mode = 'fullscreen';
4623
                    break;
4624
            }
4625
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
4626
                    WHERE iid = ".$this->get_id();
4627
            Database::query($sql);
4628
            $this->mode = $view_mode;
4629
4630
            return $view_mode;
4631
        }
4632
4633
        return -1;
4634
    }
4635
4636
    /**
4637
     * Updates the default behaviour about auto-commiting SCORM updates.
4638
     *
4639
     * @return bool True if auto-commit has been set to 'on', false otherwise
4640
     */
4641
    public function update_default_scorm_commit()
4642
    {
4643
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4644
        $sql = "SELECT * FROM $lp_table
4645
                WHERE iid = ".$this->get_id();
4646
        $res = Database::query($sql);
4647
        if (Database::num_rows($res) > 0) {
4648
            $row = Database::fetch_array($res);
4649
            $force = $row['force_commit'];
4650
            if (1 == $force) {
4651
                $force = 0;
4652
                $force_return = false;
4653
            } elseif (0 == $force) {
4654
                $force = 1;
4655
                $force_return = true;
4656
            }
4657
            $sql = "UPDATE $lp_table SET force_commit = $force
4658
                    WHERE iid = ".$this->get_id();
4659
            Database::query($sql);
4660
            $this->force_commit = $force_return;
4661
4662
            return $force_return;
4663
        }
4664
4665
        return -1;
4666
    }
4667
4668
    /**
4669
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
4670
     *
4671
     * @return bool True on success, false on failure
4672
     */
4673
    public function update_display_order()
4674
    {
4675
        return;
4676
        $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...
4677
        $table = Database::get_course_table(TABLE_LP_MAIN);
4678
        $sql = "SELECT * FROM $table
4679
                WHERE c_id = $course_id
4680
                ORDER BY display_order";
4681
        $res = Database::query($sql);
4682
        if (false === $res) {
4683
            return false;
4684
        }
4685
4686
        $num = Database::num_rows($res);
4687
        // First check the order is correct, globally (might be wrong because
4688
        // of versions < 1.8.4).
4689
        if ($num > 0) {
4690
            $i = 1;
4691
            while ($row = Database::fetch_array($res)) {
4692
                if ($row['display_order'] != $i) {
4693
                    // If we find a gap in the order, we need to fix it.
4694
                    $sql = "UPDATE $table SET display_order = $i
4695
                            WHERE iid = ".$row['iid'];
4696
                    Database::query($sql);
4697
                }
4698
                $i++;
4699
            }
4700
        }
4701
4702
        return true;
4703
    }
4704
4705
    /**
4706
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
4707
     *
4708
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
4709
     */
4710
    public function update_reinit()
4711
    {
4712
        $force = $this->entity->getPreventReinit();
4713
        if (1 == $force) {
4714
            $force = 0;
4715
        } elseif (0 == $force) {
4716
            $force = 1;
4717
        }
4718
4719
        $table = Database::get_course_table(TABLE_LP_MAIN);
4720
        $sql = "UPDATE $table SET prevent_reinit = $force
4721
                WHERE iid = ".$this->get_id();
4722
        Database::query($sql);
4723
        $this->prevent_reinit = $force;
4724
4725
        return $force;
4726
    }
4727
4728
    /**
4729
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
4730
     *
4731
     * @return string 'single', 'multi' or 'seriousgame'
4732
     *
4733
     * @author ndiechburg <[email protected]>
4734
     */
4735
    public function get_attempt_mode()
4736
    {
4737
        //Set default value for seriousgame_mode
4738
        if (!isset($this->seriousgame_mode)) {
4739
            $this->seriousgame_mode = 0;
4740
        }
4741
        // Set default value for prevent_reinit
4742
        if (!isset($this->prevent_reinit)) {
4743
            $this->prevent_reinit = 1;
4744
        }
4745
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4746
            return 'seriousgame';
4747
        }
4748
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
4749
            return 'single';
4750
        }
4751
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
4752
            return 'multiple';
4753
        }
4754
4755
        return 'single';
4756
    }
4757
4758
    /**
4759
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
4760
     *
4761
     * @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...
4762
     *
4763
     * @return bool
4764
     *
4765
     * @author ndiechburg <[email protected]>
4766
     */
4767
    public function set_attempt_mode($mode)
4768
    {
4769
        switch ($mode) {
4770
            case 'seriousgame':
4771
                $sg_mode = 1;
4772
                $prevent_reinit = 1;
4773
                break;
4774
            case 'single':
4775
                $sg_mode = 0;
4776
                $prevent_reinit = 1;
4777
                break;
4778
            case 'multiple':
4779
                $sg_mode = 0;
4780
                $prevent_reinit = 0;
4781
                break;
4782
            default:
4783
                $sg_mode = 0;
4784
                $prevent_reinit = 0;
4785
                break;
4786
        }
4787
        $this->prevent_reinit = $prevent_reinit;
4788
        $this->seriousgame_mode = $sg_mode;
4789
        $table = Database::get_course_table(TABLE_LP_MAIN);
4790
        $sql = "UPDATE $table SET
4791
                prevent_reinit = $prevent_reinit ,
4792
                seriousgame_mode = $sg_mode
4793
                WHERE iid = ".$this->get_id();
4794
        $res = Database::query($sql);
4795
        if ($res) {
0 ignored issues
show
introduced by
$res is of type Doctrine\DBAL\Driver\Statement, thus it always evaluated to true.
Loading history...
4796
            return true;
4797
        } else {
4798
            return false;
4799
        }
4800
    }
4801
4802
    /**
4803
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
4804
     *
4805
     * @author ndiechburg <[email protected]>
4806
     */
4807
    public function switch_attempt_mode()
4808
    {
4809
        $mode = $this->get_attempt_mode();
4810
        switch ($mode) {
4811
            case 'single':
4812
                $next_mode = 'multiple';
4813
                break;
4814
            case 'multiple':
4815
                $next_mode = 'seriousgame';
4816
                break;
4817
            case 'seriousgame':
4818
            default:
4819
                $next_mode = 'single';
4820
                break;
4821
        }
4822
        $this->set_attempt_mode($next_mode);
4823
    }
4824
4825
    /**
4826
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
4827
     * but possibility to do again a completed item.
4828
     *
4829
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
4830
     *
4831
     * @author ndiechburg <[email protected]>
4832
     */
4833
    public function set_seriousgame_mode()
4834
    {
4835
        $table = Database::get_course_table(TABLE_LP_MAIN);
4836
        $force = $this->entity->getSeriousgameMode();
4837
        if (1 == $force) {
4838
            $force = 0;
4839
        } elseif (0 == $force) {
4840
            $force = 1;
4841
        }
4842
        $sql = "UPDATE $table SET seriousgame_mode = $force
4843
                WHERE iid = ".$this->get_id();
4844
        Database::query($sql);
4845
        $this->seriousgame_mode = $force;
4846
4847
        return $force;
4848
    }
4849
4850
    /**
4851
     * Updates the "scorm_debug" value that shows or hide the debug window.
4852
     *
4853
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
4854
     */
4855
    public function update_scorm_debug()
4856
    {
4857
        $table = Database::get_course_table(TABLE_LP_MAIN);
4858
        $force = $this->entity->getDebug();
4859
        if (1 == $force) {
4860
            $force = 0;
4861
        } elseif (0 == $force) {
4862
            $force = 1;
4863
        }
4864
        $sql = "UPDATE $table SET debug = $force
4865
                WHERE iid = ".$this->get_id();
4866
        Database::query($sql);
4867
        $this->scorm_debug = $force;
4868
4869
        return $force;
4870
    }
4871
4872
    /**
4873
     * Function that makes a call to the function sort_tree_array and create_tree_array.
4874
     *
4875
     * @author Kevin Van Den Haute
4876
     *
4877
     * @param  array
4878
     */
4879
    public function tree_array($array)
4880
    {
4881
        $array = $this->sort_tree_array($array);
4882
        $this->create_tree_array($array);
4883
    }
4884
4885
    /**
4886
     * Creates an array with the elements of the learning path tree in it.
4887
     *
4888
     * @author Kevin Van Den Haute
4889
     *
4890
     * @param array $array
4891
     * @param int   $parent
4892
     * @param int   $depth
4893
     * @param array $tmp
4894
     */
4895
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
4896
    {
4897
        if (is_array($array)) {
4898
            for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
4899
                if ($array[$i]['parent_item_id'] == $parent) {
4900
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
4901
                        $tmp[] = $array[$i]['parent_item_id'];
4902
                        $depth++;
4903
                    }
4904
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
4905
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
4906
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
4907
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
4908
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
4909
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
4910
                    $this->arrMenu[] = [
4911
                        'id' => $array[$i]['id'],
4912
                        'ref' => $ref,
4913
                        'item_type' => $array[$i]['item_type'],
4914
                        'title' => $array[$i]['title'],
4915
                        'title_raw' => $array[$i]['title_raw'],
4916
                        'path' => $path,
4917
                        'description' => $array[$i]['description'],
4918
                        'parent_item_id' => $array[$i]['parent_item_id'],
4919
                        'previous_item_id' => $array[$i]['previous_item_id'],
4920
                        'next_item_id' => $array[$i]['next_item_id'],
4921
                        'min_score' => $array[$i]['min_score'],
4922
                        'max_score' => $array[$i]['max_score'],
4923
                        'mastery_score' => $array[$i]['mastery_score'],
4924
                        'display_order' => $array[$i]['display_order'],
4925
                        'prerequisite' => $preq,
4926
                        'depth' => $depth,
4927
                        'audio' => $audio,
4928
                        'prerequisite_min_score' => $prerequisiteMinScore,
4929
                        'prerequisite_max_score' => $prerequisiteMaxScore,
4930
                    ];
4931
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
4932
                }
4933
            }
4934
        }
4935
    }
4936
4937
    /**
4938
     * Sorts a multi dimensional array by parent id and display order.
4939
     *
4940
     * @author Kevin Van Den Haute
4941
     *
4942
     * @param array $array (array with al the learning path items in it)
4943
     *
4944
     * @return array
4945
     */
4946
    public function sort_tree_array($array)
4947
    {
4948
        foreach ($array as $key => $row) {
4949
            $parent[$key] = $row['parent_item_id'];
4950
            $position[$key] = $row['display_order'];
4951
        }
4952
4953
        if (count($array) > 0) {
4954
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
4955
        }
4956
4957
        return $array;
4958
    }
4959
4960
    /**
4961
     * Function that creates a html list of learning path items so that we can add audio files to them.
4962
     *
4963
     * @author Kevin Van Den Haute
4964
     *
4965
     * @return string
4966
     */
4967
    public function overview()
4968
    {
4969
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
4970
        $return = '';
0 ignored issues
show
Unused Code introduced by
$return = '' 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...
4971
        $update_audio = $_GET['updateaudio'] ?? null;
4972
4973
        // we need to start a form when we want to update all the mp3 files
4974
        if ('true' == $update_audio) {
4975
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS($_GET['updateaudio']).'&action='.Security::remove_XSS($_GET['action']).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
4976
        }
4977
        $return .= '<div id="message"></div>';
4978
        if (0 == count($this->items)) {
4979
            $return .= Display::return_message(get_lang('You should add some items to your learning path, otherwise you won\'t be able to attach audio files to them'), 'normal');
4980
        } else {
4981
            $return_audio = '<table class="table table-hover table-striped data_table">';
4982
            $return_audio .= '<tr>';
4983
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
4984
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
4985
            $return_audio .= '</tr>';
4986
4987
            if ('true' != $update_audio) {
4988
                $return .= '<div class="col-md-12">';
4989
                $return .= self::return_new_tree($update_audio);
4990
                $return .= '</div>';
4991
                $return .= Display::div(
4992
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
4993
                    ['style' => 'float:left; margin-top:15px;width:100%']
4994
                );
4995
            } else {
4996
                $return_audio .= self::return_new_tree($update_audio);
4997
                $return .= $return_audio.'</table>';
4998
            }
4999
5000
            // We need to close the form when we are updating the mp3 files.
5001
            if ('true' == $update_audio) {
5002
                $return .= '<div class="footer-audio">';
5003
                $return .= Display::button(
5004
                    'save_audio',
5005
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5006
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5007
                );
5008
                $return .= '</div>';
5009
            }
5010
        }
5011
5012
        // We need to close the form when we are updating the mp3 files.
5013
        if ('true' === $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5014
            $return .= '</form>';
5015
        }
5016
5017
        return $return;
5018
    }
5019
5020
    /**
5021
     * @param string $update_audio
5022
     *
5023
     * @return array
5024
     */
5025
    public function processBuildMenuElements($update_audio = 'false')
5026
    {
5027
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5028
        $arrLP = $this->getItemsForForm();
5029
5030
        $this->tree_array($arrLP);
5031
        $arrLP = $this->arrMenu ?? [];
5032
        $default_data = null;
5033
        $default_content = null;
5034
        $elements = [];
5035
        $return_audio = null;
5036
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5037
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5038
        $countItems = count($arrLP);
5039
5040
        $upIcon = Display::return_icon(
5041
            'up.png',
5042
            get_lang('Up'),
5043
            [],
5044
            ICON_SIZE_TINY
5045
        );
5046
5047
        $disableUpIcon = Display::return_icon(
5048
            'up_na.png',
5049
            get_lang('Up'),
5050
            [],
5051
            ICON_SIZE_TINY
5052
        );
5053
5054
        $downIcon = Display::return_icon(
5055
            'down.png',
5056
            get_lang('Down'),
5057
            [],
5058
            ICON_SIZE_TINY
5059
        );
5060
5061
        $disableDownIcon = Display::return_icon(
5062
            'down_na.png',
5063
            get_lang('Down'),
5064
            [],
5065
            ICON_SIZE_TINY
5066
        );
5067
5068
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5069
5070
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5071
        $plugin = null;
5072
        if ($pluginCalendar) {
5073
            $plugin = LearningCalendarPlugin::create();
5074
        }
5075
5076
        for ($i = 0; $i < $countItems; $i++) {
5077
            $parent_id = $arrLP[$i]['parent_item_id'];
5078
            $title = $arrLP[$i]['title'];
5079
            $title_cut = $arrLP[$i]['title_raw'];
5080
            if (false === $show) {
5081
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5082
            }
5083
            // Link for the documents
5084
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5085
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5086
                $title_cut = Display::url(
5087
                    $title_cut,
5088
                    $url,
5089
                    [
5090
                        'class' => 'ajax moved',
5091
                        'data-title' => $title,
5092
                        'title' => $title,
5093
                    ]
5094
                );
5095
            }
5096
5097
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5098
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5099
                Session::write('pathItem', $arrLP[$i]['path']);
5100
            }
5101
5102
            $oddClass = 'row_even';
5103
            if (0 == ($i % 2)) {
5104
                $oddClass = 'row_odd';
5105
            }
5106
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5107
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5108
5109
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5110
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5111
            } else {
5112
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5113
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5114
                } else {
5115
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5116
                        $icon = Display::return_icon('certificate.png');
5117
                    } else {
5118
                        $icon = Display::return_icon('folder_document.png');
5119
                    }
5120
                }
5121
            }
5122
5123
            // The audio column.
5124
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5125
            $audio = '';
5126
            if (!$update_audio || 'true' != $update_audio) {
5127
                if (empty($arrLP[$i]['audio'])) {
5128
                    $audio .= '';
5129
                }
5130
            } else {
5131
                $types = self::getChapterTypes();
5132
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5133
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5134
                    if (!empty($arrLP[$i]['audio'])) {
5135
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5136
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5137
                    }
5138
                }
5139
            }
5140
5141
            $return_audio .= Display::span($icon.' '.$title).
5142
                Display::tag(
5143
                    'td',
5144
                    $audio,
5145
                    ['style' => '']
5146
                );
5147
            $return_audio .= '</td>';
5148
            $move_icon = '';
5149
            $move_item_icon = '';
5150
            $edit_icon = '';
5151
            $delete_icon = '';
5152
            $audio_icon = '';
5153
            $prerequisities_icon = '';
5154
            $forumIcon = '';
5155
            $previewIcon = '';
5156
            $pluginCalendarIcon = '';
5157
            $orderIcons = '';
5158
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5159
5160
            if ($is_allowed_to_edit) {
5161
                if (!$update_audio || 'true' != $update_audio) {
5162
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5163
                        $move_icon .= '<a class="moved" href="#">';
5164
                        $move_icon .= Display::return_icon(
5165
                            'move_everywhere.png',
5166
                            get_lang('Move'),
5167
                            [],
5168
                            ICON_SIZE_TINY
5169
                        );
5170
                        $move_icon .= '</a>';
5171
                    }
5172
                }
5173
5174
                // No edit for this item types
5175
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5176
                    if ('dir' != $arrLP[$i]['item_type']) {
5177
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
5178
                        $edit_icon .= Display::return_icon(
5179
                            'edit.png',
5180
                            get_lang('Edit section description/name'),
5181
                            [],
5182
                            ICON_SIZE_TINY
5183
                        );
5184
                        $edit_icon .= '</a>';
5185
5186
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5187
                            $forumThread = null;
5188
                            if (isset($this->items[$arrLP[$i]['id']])) {
5189
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5190
                                    $this->course_int_id,
5191
                                    $this->lp_session_id
5192
                                );
5193
                            }
5194
                            if ($forumThread) {
5195
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5196
                                        'action' => 'dissociate_forum',
5197
                                        'id' => $arrLP[$i]['id'],
5198
                                        'lp_id' => $this->lp_id,
5199
                                    ]);
5200
                                $forumIcon = Display::url(
5201
                                    Display::return_icon(
5202
                                        'forum.png',
5203
                                        get_lang('Dissociate the forum of this learning path item'),
5204
                                        [],
5205
                                        ICON_SIZE_TINY
5206
                                    ),
5207
                                    $forumIconUrl,
5208
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5209
                                );
5210
                            } else {
5211
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5212
                                        'action' => 'create_forum',
5213
                                        'id' => $arrLP[$i]['id'],
5214
                                        'lp_id' => $this->lp_id,
5215
                                    ]);
5216
                                $forumIcon = Display::url(
5217
                                    Display::return_icon(
5218
                                        'forum.png',
5219
                                        get_lang('Associate a forum to this learning path item'),
5220
                                        [],
5221
                                        ICON_SIZE_TINY
5222
                                    ),
5223
                                    $forumIconUrl,
5224
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5225
                                );
5226
                            }
5227
                        }
5228
                    } else {
5229
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
5230
                        $edit_icon .= Display::return_icon(
5231
                            'edit.png',
5232
                            get_lang('Edit section description/name'),
5233
                            [],
5234
                            ICON_SIZE_TINY
5235
                        );
5236
                        $edit_icon .= '</a>';
5237
                    }
5238
                } else {
5239
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5240
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5241
                        $edit_icon .= Display::return_icon(
5242
                            'edit.png',
5243
                            get_lang('Edit'),
5244
                            [],
5245
                            ICON_SIZE_TINY
5246
                        );
5247
                        $edit_icon .= '</a>';
5248
                    }
5249
                }
5250
5251
                if ($pluginCalendar) {
5252
                    $pluginLink = $pluginUrl.
5253
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5254
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5255
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5256
                    if ($itemInfo && 1 == $itemInfo['value']) {
5257
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5258
                    }
5259
                    $pluginCalendarIcon = Display::url(
5260
                        $iconCalendar,
5261
                        $pluginLink,
5262
                        ['class' => 'btn btn-default']
5263
                    );
5264
                }
5265
5266
                if ('final_item' != $arrLP[$i]['item_type']) {
5267
                    $orderIcons = Display::url(
5268
                        $upIcon,
5269
                        'javascript:void(0)',
5270
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5271
                    );
5272
                    $orderIcons .= Display::url(
5273
                        $downIcon,
5274
                        'javascript:void(0)',
5275
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5276
                    );
5277
                }
5278
                $delete_icon .= ' <a
5279
5280
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5281
                    onclick="return confirmation(\''.addslashes($title).'\');"
5282
                    class="btn btn-default">';
5283
                $delete_icon .= Display::return_icon(
5284
                    'delete.png',
5285
                    get_lang('Delete section'),
5286
                    [],
5287
                    ICON_SIZE_TINY
5288
                );
5289
                $delete_icon .= '</a>';
5290
5291
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5292
                $previewImage = Display::return_icon(
5293
                    'preview_view.png',
5294
                    get_lang('Preview'),
5295
                    [],
5296
                    ICON_SIZE_TINY
5297
                );
5298
5299
                switch ($arrLP[$i]['item_type']) {
5300
                    case TOOL_DOCUMENT:
5301
                    case TOOL_LP_FINAL_ITEM:
5302
                    case TOOL_READOUT_TEXT:
5303
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5304
                        $previewIcon = Display::url(
5305
                            $previewImage,
5306
                            $urlPreviewLink,
5307
                            [
5308
                                'target' => '_blank',
5309
                                'class' => 'btn btn-default',
5310
                                'data-title' => $arrLP[$i]['title'],
5311
                                'title' => $arrLP[$i]['title'],
5312
                            ]
5313
                        );
5314
                        break;
5315
                    case TOOL_THREAD:
5316
                    case TOOL_FORUM:
5317
                    case TOOL_QUIZ:
5318
                    case TOOL_STUDENTPUBLICATION:
5319
                    case TOOL_LINK:
5320
                        $class = 'btn btn-default';
5321
                        $target = '_blank';
5322
                        $link = self::rl_get_resource_link_for_learnpath(
5323
                            $this->course_int_id,
5324
                            $this->lp_id,
5325
                            $arrLP[$i]['id'],
5326
                            0
5327
                        );
5328
                        $previewIcon = Display::url(
5329
                            $previewImage,
5330
                            $link,
5331
                            [
5332
                                'class' => $class,
5333
                                'data-title' => $arrLP[$i]['title'],
5334
                                'title' => $arrLP[$i]['title'],
5335
                                'target' => $target,
5336
                            ]
5337
                        );
5338
                        break;
5339
                    default:
5340
                        $previewIcon = Display::url(
5341
                            $previewImage,
5342
                            $url.'&action=view_item',
5343
                            ['class' => 'btn btn-default', 'target' => '_blank']
5344
                        );
5345
                        break;
5346
                }
5347
5348
                if ('dir' != $arrLP[$i]['item_type']) {
5349
                    $prerequisities_icon = Display::url(
5350
                        Display::return_icon(
5351
                            'accept.png',
5352
                            get_lang('Prerequisites'),
5353
                            [],
5354
                            ICON_SIZE_TINY
5355
                        ),
5356
                        $url.'&action=edit_item_prereq',
5357
                        ['class' => 'btn btn-default']
5358
                    );
5359
                    if ('final_item' != $arrLP[$i]['item_type']) {
5360
                        /*$move_item_icon = Display::url(
5361
                            Display::return_icon(
5362
                                'move.png',
5363
                                get_lang('Move'),
5364
                                [],
5365
                                ICON_SIZE_TINY
5366
                            ),
5367
                            $url.'&action=move_item',
5368
                            ['class' => 'btn btn-default']
5369
                        );*/
5370
                    }
5371
                    $audio_icon = Display::url(
5372
                        Display::return_icon(
5373
                            'audio.png',
5374
                            get_lang('Upload'),
5375
                            [],
5376
                            ICON_SIZE_TINY
5377
                        ),
5378
                        $url.'&action=add_audio',
5379
                        ['class' => 'btn btn-default']
5380
                    );
5381
                }
5382
            }
5383
            if ('true' != $update_audio) {
5384
                $row = $move_icon.' '.$icon.
5385
                    Display::span($title_cut).
5386
                    Display::tag(
5387
                        'div',
5388
                        "<div class=\"btn-group btn-group-xs\">
5389
                                    $previewIcon
5390
                                    $audio
5391
                                    $edit_icon
5392
                                    $pluginCalendarIcon
5393
                                    $forumIcon
5394
                                    $prerequisities_icon
5395
                                    $move_item_icon
5396
                                    $audio_icon
5397
                                    $orderIcons
5398
                                    $delete_icon
5399
                                </div>",
5400
                        ['class' => 'btn-toolbar button_actions']
5401
                    );
5402
            } else {
5403
                $row =
5404
                    Display::span($title.$icon).
5405
                    Display::span($audio, ['class' => 'button_actions']);
5406
            }
5407
5408
            $default_data[$arrLP[$i]['id']] = $row;
5409
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
5410
5411
            if (empty($parent_id)) {
5412
                $elements[$arrLP[$i]['id']]['data'] = $row;
5413
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5414
            } else {
5415
                $parent_arrays = [];
5416
                if ($arrLP[$i]['depth'] > 1) {
5417
                    // Getting list of parents
5418
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
5419
                        foreach ($arrLP as $item) {
5420
                            if ($item['id'] == $parent_id) {
5421
                                if (0 == $item['parent_item_id']) {
5422
                                    $parent_id = $item['id'];
5423
                                    break;
5424
                                } else {
5425
                                    $parent_id = $item['parent_item_id'];
5426
                                    if (empty($parent_arrays)) {
5427
                                        $parent_arrays[] = intval($item['id']);
5428
                                    }
5429
                                    $parent_arrays[] = $parent_id;
5430
                                    break;
5431
                                }
5432
                            }
5433
                        }
5434
                    }
5435
                }
5436
5437
                if (!empty($parent_arrays)) {
5438
                    $parent_arrays = array_reverse($parent_arrays);
5439
                    $val = '$elements';
5440
                    $x = 0;
5441
                    foreach ($parent_arrays as $item) {
5442
                        if ($x != count($parent_arrays) - 1) {
5443
                            $val .= '["'.$item.'"]["children"]';
5444
                        } else {
5445
                            $val .= '["'.$item.'"]["children"]';
5446
                        }
5447
                        $x++;
5448
                    }
5449
                    $val .= "";
5450
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
5451
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
5452
                } else {
5453
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
5454
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5455
                }
5456
            }
5457
        }
5458
5459
        return [
5460
            'elements' => $elements,
5461
            'default_data' => $default_data,
5462
            'default_content' => $default_content,
5463
            'return_audio' => $return_audio,
5464
        ];
5465
    }
5466
5467
    /**
5468
     * @param string $updateAudio true/false strings
5469
     *
5470
     * @return string
5471
     */
5472
    public function returnLpItemList($updateAudio)
5473
    {
5474
        $result = $this->processBuildMenuElements($updateAudio);
5475
5476
        $html = self::print_recursive(
5477
            $result['elements'],
5478
            $result['default_data'],
5479
            $result['default_content']
5480
        );
5481
5482
        if (!empty($html)) {
5483
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
5484
        }
5485
5486
        return $html;
5487
    }
5488
5489
    public function showBuildSideBar($updateAudio = false, $dropElementHere = false, $type = null)
5490
    {
5491
        $sureToDelete = trim(get_lang('AreYouSureToDeleteJS'));
5492
5493
        $ajax_url = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?lp_id='.$this->get_id().'&'.api_get_cidreq();
5494
5495
        $content = "
5496
        <script>
5497
            function confirmation(name) {
5498
                if (confirm('$sureToDelete' + name)) {
5499
                    return true;
5500
                } else {
5501
                    return false;
5502
                }
5503
            }
5504
5505
5506
            function refreshTree() {
5507
                var params = '&a=get_lp_item_tree';
5508
                $.get(
5509
                    '".$ajax_url."',
5510
                    params,
5511
                    function(result) {
5512
                        $('#lp_item_list').html(result);
5513
                    }
5514
                );
5515
            }
5516
5517
5518
5519
            $(function () {
5520
                //$('.scrollbar-inner').scrollbar();
5521
                $('#subtab').on('click', 'a:first', function() {
5522
                    window.location.reload();
5523
                });
5524
                $('#subtab ').on('click', 'a:first', function () {
5525
                    window.location.reload();
5526
                });
5527
                expandColumnToggle('#hide_bar_template', {
5528
                    selector: '#lp_sidebar'
5529
                }, {
5530
                    selector: '#doc_form'
5531
                });
5532
5533
                $('.lp-btn-associate-forum').on('click', function (e) {
5534
                    var associate = confirm('".get_lang('ConfirmAssociateForumToLPItem')."');
5535
                    if (!associate) {
5536
                        e.preventDefault();
5537
                    }
5538
                });
5539
5540
                $('.lp-btn-dissociate-forum').on('click', function (e) {
5541
                    var dissociate = confirm('".get_lang('ConfirmDissociateForumToLPItem')."');
5542
                    if (!dissociate) {
5543
                        e.preventDefault();
5544
                    }
5545
                });
5546
5547
                // hide the current template list for new documment until it tab clicked
5548
                $('#frmModel').hide();
5549
            });
5550
5551
            // document template for new document tab handler
5552
            $(document).on('shown.bs.tab', 'a[data-toggle=\"tab\"]', function (e) {
5553
                var id = e.target.id;
5554
                if (id == 'subtab2') {
5555
                    $('#frmModel').show();
5556
                } else {
5557
                    $('#frmModel').hide();
5558
                }
5559
            });
5560
5561
          function deleteItem(event) {
5562
          console.log($(event));
5563
            var id = $(event).attr('data-id');
5564
            var title = $(event).attr('data-title');
5565
            var params = '&a=delete_item&id=' + id;
5566
            if (confirmation(title)) {
5567
                $.get(
5568
                    '".$ajax_url."',
5569
                    params,
5570
                    function(result) {
5571
                        refreshTree();
5572
                    }
5573
                );
5574
            }
5575
        }
5576
        </script>";
5577
5578
        $content .= '<div id="lp_sidebar" class="col-md-4">';
5579
        $content .= $this->return_new_tree($updateAudio, $dropElementHere);
5580
5581
        $documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
5582
5583
        $repo = Container::getDocumentRepository();
5584
        $document = $repo->find($documentId);
5585
        if ($document) {
5586
            // Show the template list
5587
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
5588
        }
5589
5590
        // Show the template list.
5591
        if (('document' === $type || 'step' === $type) && !isset($_GET['file'])) {
5592
            // Show the template list.
5593
            $content .= '<div id="frmModel" class="scrollbar-inner lp-add-item">';
5594
            $content .= '</div>';
5595
        }
5596
        $content .= '</div>';
5597
5598
        echo $content;
5599
    }
5600
5601
    /**
5602
     * @param bool  $updateAudio
5603
     * @param bool   $dropElement
5604
     *
5605
     * @return string
5606
     */
5607
    public function return_new_tree($updateAudio = false, $dropElement = false)
5608
    {
5609
        /*$result = $this->processBuildMenuElements($update_audio);
5610
        $list = '<ul id="lp_item_list">';
5611
        $tree = $this->print_recursive(
5612
            $result['elements'],
5613
            $result['default_data'],
5614
            $result['default_content']
5615
        );
5616
5617
        if (!empty($tree)) {
5618
            $list .= $tree;
5619
        } else {
5620
            if ($drop_element_here) {
5621
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
5622
            }
5623
        }
5624
        $list .= '</ul>';*/
5625
5626
        $list = $this->getBuildTree(false, $dropElement);
5627
5628
        $return = Display::panelCollapse(
5629
            $this->name,
5630
            $list,
5631
            'scorm-list',
5632
            null,
5633
            'scorm-list-accordion',
5634
            'scorm-list-collapse'
5635
        );
5636
5637
        if ($updateAudio) {
5638
            //$return = $result['return_audio'];
5639
        }
5640
5641
        return $return;
5642
    }
5643
5644
    public function getBuildTree($noWrapper = false, $dropElement = false)
5645
    {
5646
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5647
5648
        $upIcon = Display::return_icon(
5649
            'up.png',
5650
            get_lang('Up'),
5651
            [],
5652
            ICON_SIZE_TINY
5653
        );
5654
5655
        $disableUpIcon = Display::return_icon(
5656
            'up_na.png',
5657
            get_lang('Up'),
5658
            [],
5659
            ICON_SIZE_TINY
5660
        );
5661
5662
        $downIcon = Display::return_icon(
5663
            'down.png',
5664
            get_lang('Down'),
5665
            [],
5666
            ICON_SIZE_TINY
5667
        );
5668
5669
        $previewImage = Display::return_icon(
5670
            'preview_view.png',
5671
            get_lang('Preview'),
5672
            [],
5673
            ICON_SIZE_TINY
5674
        );
5675
        $lpItemRepo = Container::getLpItemRepository();
5676
        $itemRoot = $lpItemRepo->getItemRoot($this->get_id());
5677
5678
        $options = [
5679
            'decorate' => true,
5680
            'rootOpen' => function($tree) use ($noWrapper) {
5681
                if ($tree[0]['lvl'] === 1) {
5682
                    if ($noWrapper) {
5683
                        return '';
5684
                    }
5685
                    return '<ul id="lp_item_list" class="list-group nested-sortable">';
5686
                }
5687
5688
                return '<ul class="list-group nested-sortable">';
5689
            },
5690
            'rootClose' => function($tree) use ($noWrapper, $dropElement)  {
5691
                if ($tree[0]['lvl'] === 1) {
5692
                    if ($dropElement) {
5693
                        //return Display::return_message(get_lang('Drag and drop an element here'));
5694
                        //return $this->getDropElementHtml();
5695
                    }
5696
                    if ($noWrapper) {
5697
                        return '';
5698
                    }
5699
                }
5700
5701
                return '</ul>';
5702
            },
5703
            'childOpen' => function($child) {
5704
                $id = $child['iid'];
5705
                return '<li id="'.$id.'" data-id="'.$id.'"  class="list-group-item nested-'.$child['lvl'].'">';
5706
            },
5707
            'childClose' => '',
5708
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
5709
                $fullTitle = $node['title'];
5710
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
5711
                $itemId = $node['iid'];
5712
                $type = $node['itemType'];
5713
                $lpId = $this->get_id();
5714
5715
                $moveIcon = '';
5716
                if (TOOL_LP_FINAL_ITEM !== $type) {
5717
                    $moveIcon .= '<a class="moved" href="#">';
5718
                    $moveIcon .= Display::return_icon(
5719
                        'move_everywhere.png',
5720
                        get_lang('Move'),
5721
                        [],
5722
                        ICON_SIZE_TINY
5723
                    );
5724
                    $moveIcon .= '</a>';
5725
                }
5726
5727
                $iconName = str_replace(' ', '', $type);
5728
                $icon = Display::return_icon(
5729
                    'lp_'.$iconName.'.png',
5730
                    '',
5731
                    [],
5732
                    ICON_SIZE_TINY
5733
                );
5734
5735
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
5736
                $previewIcon = Display::url(
5737
                    $previewImage,
5738
                    $urlPreviewLink,
5739
                    [
5740
                        'target' => '_blank',
5741
                        'class' => 'btn btn-default',
5742
                        'data-title' => $title,
5743
                        'title' => $title,
5744
                    ]
5745
                );
5746
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
5747
5748
                $preRequisitiesIcon = Display::url(
5749
                    Display::return_icon(
5750
                        'accept.png',
5751
                        get_lang('Prerequisites'),
5752
                        [],
5753
                        ICON_SIZE_TINY
5754
                    ),
5755
                    $url.'&action=edit_item_prereq',
5756
                    ['class' => 'btn btn-default']
5757
                );
5758
5759
                $editIcon = '';
5760
                $editIcon .= '<a
5761
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
5762
                    class="btn btn-default"
5763
                    >';
5764
                $editIcon .= Display::return_icon(
5765
                    'edit.png',
5766
                    get_lang('Edit section description/name'),
5767
                    [],
5768
                    ICON_SIZE_TINY
5769
                );
5770
                $editIcon .= '</a>';
5771
                $orderIcons = '';
5772
                /*if ('final_item' !== $type) {
5773
                    $orderIcons = Display::url(
5774
                        $upIcon,
5775
                        'javascript:void(0)',
5776
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
5777
                    );
5778
                    $orderIcons .= Display::url(
5779
                        $downIcon,
5780
                        'javascript:void(0)',
5781
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
5782
                    );
5783
                }*/
5784
5785
                $deleteIcon = ' <a
5786
                    data-id = '.$itemId.'
5787
                    data-title = \''.addslashes($title).'\'
5788
                    href="javascript:void(0);"
5789
                    onclick="return deleteItem(this);"
5790
                    class="btn btn-default">';
5791
                $deleteIcon .= Display::return_icon(
5792
                    'delete.png',
5793
                    get_lang('Delete section'),
5794
                    [],
5795
                    ICON_SIZE_TINY
5796
                );
5797
                $deleteIcon .= '</a>';
5798
                $extra = '';
5799
5800
                if ('dir' === $type && empty($node['__children'])) {
5801
                    $level = $node['lvl'] + 1;
5802
                    $extra = '<ul class="list-group nested-sortable">
5803
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
5804
                              </ul>';
5805
                }
5806
                //return $title;
5807
                $buttons = Display::tag(
5808
                    'div',
5809
                    "<div class=\"btn-group btn-group-sm\">
5810
                                $editIcon
5811
                                $preRequisitiesIcon
5812
                                $orderIcons
5813
                                $deleteIcon
5814
                               </div>",
5815
                    ['class' => 'btn-toolbar button_actions']
5816
                );
5817
                //$buttons = '';
5818
                //$extra = '';
5819
                //Display::span($title, ['title' => $fullTitle])
5820
                //return $title.                $extra;
5821
                return
5822
                   // '<div class="item_data">'.
5823
                    $moveIcon.' '.$icon.' '.
5824
                    $title.
5825
                    $extra.
5826
                    $buttons
5827
                    //.'</div>'
5828
                    ;
5829
            },
5830
        ];
5831
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
5832
5833
        if (empty($tree) && $dropElement) {
5834
            return $this->getDropElementHtml($noWrapper);
5835
        }
5836
5837
        return $tree;
5838
    }
5839
5840
    public function getDropElementHtml($noWrapper = false)
5841
    {
5842
        $li = '<li class="list-group-item">'.
5843
            Display::return_message(get_lang('Drag and drop an element here')).
5844
            '</li>';
5845
        if ($noWrapper) {
5846
            return $li;
5847
        }
5848
5849
        return
5850
            '<ul id="lp_item_list" class="list-group nested-sortable">
5851
            '.$li.'
5852
            </ul>';
5853
    }
5854
5855
    /**
5856
     * @param array $elements
5857
     * @param array $default_data
5858
     * @param array $default_content
5859
     *
5860
     * @return string
5861
     */
5862
    public function print_recursive($elements, $default_data, $default_content)
5863
    {
5864
        $return = '';
5865
        foreach ($elements as $key => $item) {
5866
            if (isset($item['load_data']) || empty($item['data'])) {
5867
                $item['data'] = $default_data[$item['load_data']];
5868
                $item['type'] = $default_content[$item['load_data']]['item_type'];
5869
            }
5870
            $sub_list = '';
5871
            if (isset($item['type']) && 'dir' === $item['type']) {
5872
                // empty value
5873
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
5874
            }
5875
            if (empty($item['children'])) {
5876
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
5877
                $active = null;
5878
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
5879
                    $active = 'active';
5880
                }
5881
                $return .= Display::tag(
5882
                    'li',
5883
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
5884
                    ['id' => $key, 'class' => 'record li_container']
5885
                );
5886
            } else {
5887
                // Sections
5888
                $data = '';
5889
                if (isset($item['children'])) {
5890
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
5891
                }
5892
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
5893
                $return .= Display::tag(
5894
                    'li',
5895
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
5896
                    ['id' => $key, 'class' => 'record li_container']
5897
                );
5898
            }
5899
        }
5900
5901
        return $return;
5902
    }
5903
5904
    /**
5905
     * This function builds the action menu.
5906
     *
5907
     * @param bool   $returnString           Optional
5908
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
5909
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
5910
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
5911
     * @param string $action
5912
     * @param array  $extraField
5913
     *
5914
     * @return string
5915
     */
5916
    public function build_action_menu(
5917
        $returnString = false,
5918
        $showRequirementButtons = true,
5919
        $isConfigPage = false,
5920
        $allowExpand = true,
5921
        $action = '',
5922
        $extraField = []
5923
    ) {
5924
        $actionsRight = '';
5925
        $lpId = $this->lp_id;
5926
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
5927
            $back = Display::url(
5928
                Display::return_icon(
5929
                    'back.png',
5930
                    get_lang('Back to learning paths'),
5931
                    '',
5932
                    ICON_SIZE_MEDIUM
5933
                ),
5934
                'lp_controller.php?'.api_get_cidreq()
5935
            );
5936
        } else {
5937
            $back = Display::url(
5938
                Display::return_icon(
5939
                    'back.png',
5940
                    get_lang('Back'),
5941
                    '',
5942
                    ICON_SIZE_MEDIUM
5943
                ),
5944
                $extraField['backTo']
5945
            );
5946
        }
5947
5948
        /*if ($backToBuild) {
5949
            $back = Display::url(
5950
                Display::return_icon(
5951
                    'back.png',
5952
                    get_lang('GoBack'),
5953
                    '',
5954
                    ICON_SIZE_MEDIUM
5955
                ),
5956
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
5957
            );
5958
        }*/
5959
5960
        $actionsLeft = $back;
5961
5962
        $actionsLeft .= Display::url(
5963
            Display::return_icon(
5964
                'preview_view.png',
5965
                get_lang('Preview'),
5966
                '',
5967
                ICON_SIZE_MEDIUM
5968
            ),
5969
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5970
                'action' => 'view',
5971
                'lp_id' => $lpId,
5972
                'isStudentView' => 'true',
5973
            ])
5974
        );
5975
5976
        $actionsLeft .= Display::url(
5977
            Display::return_icon(
5978
                'upload_audio.png',
5979
                get_lang('Add audio'),
5980
                '',
5981
                ICON_SIZE_MEDIUM
5982
            ),
5983
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5984
                'action' => 'admin_view',
5985
                'lp_id' => $lpId,
5986
                'updateaudio' => 'true',
5987
            ])
5988
        );
5989
5990
        $subscriptionSettings = self::getSubscriptionSettings();
5991
5992
        $request = api_request_uri();
5993
        if (false === strpos($request, 'edit')) {
5994
            $actionsLeft .= Display::url(
5995
                Display::return_icon(
5996
                    'settings.png',
5997
                    get_lang('Course settings'),
5998
                    '',
5999
                    ICON_SIZE_MEDIUM
6000
                ),
6001
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6002
                    'action' => 'edit',
6003
                    'lp_id' => $lpId,
6004
                ])
6005
            );
6006
        }
6007
6008
        if ((false === strpos($request, 'build') &&
6009
            false === strpos($request, 'add_item')) ||
6010
            in_array($action, ['add_audio'])
6011
        ) {
6012
            $actionsLeft .= Display::url(
6013
                Display::return_icon(
6014
                    'edit.png',
6015
                    get_lang('Edit'),
6016
                    '',
6017
                    ICON_SIZE_MEDIUM
6018
                ),
6019
                'lp_controller.php?'.http_build_query([
6020
                    'action' => 'build',
6021
                    'lp_id' => $lpId,
6022
                ]).'&'.api_get_cidreq()
6023
            );
6024
        }
6025
6026
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6027
            if (1 == $this->subscribeUsers &&
6028
                $subscriptionSettings['allow_add_users_to_lp']) {
6029
                $actionsLeft .= Display::url(
6030
                    Display::return_icon(
6031
                        'user.png',
6032
                        get_lang('Subscribe users to learning path'),
6033
                        '',
6034
                        ICON_SIZE_MEDIUM
6035
                    ),
6036
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6037
                );
6038
            }
6039
        }
6040
6041
        if ($allowExpand) {
6042
            /*$actionsLeft .= Display::url(
6043
                Display::return_icon(
6044
                    'expand.png',
6045
                    get_lang('Expand'),
6046
                    ['id' => 'expand'],
6047
                    ICON_SIZE_MEDIUM
6048
                ).
6049
                Display::return_icon(
6050
                    'contract.png',
6051
                    get_lang('Collapse'),
6052
                    ['id' => 'contract', 'class' => 'hide'],
6053
                    ICON_SIZE_MEDIUM
6054
                ),
6055
                '#',
6056
                ['role' => 'button', 'id' => 'hide_bar_template']
6057
            );*/
6058
        }
6059
6060
        if ($showRequirementButtons) {
6061
            $buttons = [
6062
                [
6063
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6064
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6065
                        'action' => 'set_previous_step_as_prerequisite',
6066
                        'lp_id' => $lpId,
6067
                    ]),
6068
                ],
6069
                [
6070
                    'title' => get_lang('Clear all prerequisites'),
6071
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6072
                        'action' => 'clear_prerequisites',
6073
                        'lp_id' => $lpId,
6074
                    ]),
6075
                ],
6076
            ];
6077
            $actionsRight = Display::groupButtonWithDropDown(
6078
                get_lang('Prerequisites options'),
6079
                $buttons,
6080
                true
6081
            );
6082
        }
6083
6084
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6085
            $actionsLeft .= Display::url(
6086
                Display::return_icon(
6087
                    'add-groups.png',
6088
                    get_lang('Author'),
6089
                    '',
6090
                    ICON_SIZE_MEDIUM
6091
                ),
6092
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6093
                    'action' => 'author_view',
6094
                    'lp_id' => $lpId,
6095
                ])
6096
            );
6097
        }
6098
6099
        $toolbar = Display::toolbarAction(
6100
            'actions-lp-controller',
6101
            [$actionsLeft, $actionsRight]
6102
        );
6103
6104
        if ($returnString) {
6105
            return $toolbar;
6106
        }
6107
6108
        echo $toolbar;
6109
    }
6110
6111
    /**
6112
     * Creates the default learning path folder.
6113
     *
6114
     * @param array $course
6115
     * @param int   $creatorId
6116
     *
6117
     * @return CDocument
6118
     */
6119
    public static function generate_learning_path_folder($course, $creatorId = 0)
6120
    {
6121
        // Creating learning_path folder
6122
        $dir = 'learning_path';
6123
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6124
6125
        return create_unexisting_directory(
6126
            $course,
6127
            $creatorId,
6128
            0,
6129
            null,
6130
            0,
6131
            '',
6132
            $dir,
6133
            get_lang('Learning paths'),
6134
            0
6135
        );
6136
    }
6137
6138
    /**
6139
     * @param array  $course
6140
     * @param string $lp_name
6141
     * @param int    $creatorId
6142
     *
6143
     * @return CDocument
6144
     */
6145
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6146
    {
6147
        $filepath = '';
6148
        $dir = '/learning_path/';
6149
6150
        if (empty($lp_name)) {
6151
            $lp_name = $this->name;
6152
        }
6153
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6154
        $parent = self::generate_learning_path_folder($course, $creatorId);
6155
6156
        // Limits title size
6157
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6158
        $dir = $dir.$title;
6159
6160
        // Creating LP folder
6161
        $documentId = null;
6162
        $folder = null;
6163
        if ($parent) {
6164
            $folder = create_unexisting_directory(
6165
                $course,
6166
                $creatorId,
6167
                0,
6168
                0,
6169
                0,
6170
                $filepath,
6171
                $dir,
6172
                $lp_name,
6173
                '',
6174
                false,
6175
                false,
6176
                $parent
6177
            );
6178
        }
6179
6180
        return $folder;
6181
    }
6182
6183
    /**
6184
     * Create a new document //still needs some finetuning.
6185
     *
6186
     * @param array  $courseInfo
6187
     * @param string $content
6188
     * @param string $title
6189
     * @param string $extension
6190
     * @param int    $parentId
6191
     * @param int    $creatorId  creator id
6192
     *
6193
     * @return int
6194
     */
6195
    public function create_document(
6196
        $courseInfo,
6197
        $content = '',
6198
        $title = '',
6199
        $extension = 'html',
6200
        $parentId = 0,
6201
        $creatorId = 0
6202
    ) {
6203
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6204
        $sessionId = api_get_session_id();
6205
6206
        // Generates folder
6207
        $this->generate_lp_folder($courseInfo);
6208
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6209
        // is already escaped twice when it gets here.
6210
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6211
        if (!empty($title)) {
6212
            $title = api_replace_dangerous_char(stripslashes($title));
6213
        } else {
6214
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6215
        }
6216
6217
        $title = disable_dangerous_file($title);
6218
        $filename = $title;
6219
        $tmp_filename = $filename;
6220
        /*$i = 0;
6221
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6222
            $tmp_filename = $filename.'_'.++$i;
6223
        }*/
6224
        $filename = $tmp_filename.'.'.$extension;
6225
6226
        if ('html' === $extension) {
6227
            $content = stripslashes($content);
6228
            $content = str_replace(
6229
                api_get_path(WEB_COURSE_PATH),
6230
                api_get_path(REL_PATH).'courses/',
6231
                $content
6232
            );
6233
6234
            // Change the path of mp3 to absolute.
6235
            // The first regexp deals with :// urls.
6236
            /*$content = preg_replace(
6237
                "|(flashvars=\"file=)([^:/]+)/|",
6238
                "$1".api_get_path(
6239
                    REL_COURSE_PATH
6240
                ).$courseInfo['path'].'/document/',
6241
                $content
6242
            );*/
6243
            // The second regexp deals with audio/ urls.
6244
            /*$content = preg_replace(
6245
                "|(flashvars=\"file=)([^/]+)/|",
6246
                "$1".api_get_path(
6247
                    REL_COURSE_PATH
6248
                ).$courseInfo['path'].'/document/$2/',
6249
                $content
6250
            );*/
6251
            // For flv player: To prevent edition problem with firefox,
6252
            // we have to use a strange tip (don't blame me please).
6253
            $content = str_replace(
6254
                '</body>',
6255
                '<style type="text/css">body{}</style></body>',
6256
                $content
6257
            );
6258
        }
6259
6260
        $document = DocumentManager::addDocument(
6261
            $courseInfo,
6262
            null,
6263
            'file',
6264
            '',
6265
            $tmp_filename,
6266
            '',
6267
            0, //readonly
6268
            true,
6269
            null,
6270
            $sessionId,
6271
            $creatorId,
6272
            false,
6273
            $content,
6274
            $parentId
6275
        );
6276
6277
        $document_id = $document->getIid();
6278
        if ($document_id) {
6279
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6280
            $new_title = $originalTitle;
6281
6282
            if ($new_comment || $new_title) {
6283
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6284
                $ct = '';
6285
                if ($new_comment) {
6286
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6287
                }
6288
                if ($new_title) {
6289
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6290
                }
6291
6292
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6293
                        WHERE iid = $document_id ";
6294
                Database::query($sql);
6295
            }
6296
        }
6297
6298
        return $document_id;
6299
    }
6300
6301
    /**
6302
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6303
     */
6304
    public function edit_document()
6305
    {
6306
        $repo = Container::getDocumentRepository();
6307
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6308
            $id = (int) $_REQUEST['document_id'];
6309
            /** @var CDocument $document */
6310
            $document = $repo->find($id);
6311
            if ($document->getResourceNode()->hasEditableTextContent()) {
6312
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6313
                /*   $nodeRepo = Container::getDocumentRepository()->getResourceNodeRepository();
6314
                   $nodeRepo->update($document->getResourceNode());
6315
                   var_dump($document->getResourceNode()->getContent());
6316
                   exit;*/
6317
            }
6318
            $document->setTitle($_REQUEST['title']);
6319
            $repo->update($document);
6320
        }
6321
    }
6322
6323
    /**
6324
     * Displays the selected item, with a panel for manipulating the item.
6325
     *
6326
     * @param CLpItem $lpItem
6327
     * @param string  $msg
6328
     * @param bool    $show_actions
6329
     *
6330
     * @return string
6331
     */
6332
    public function display_item($lpItem, $msg = null, $show_actions = true)
6333
    {
6334
        $course_id = api_get_course_int_id();
6335
        $return = '';
6336
6337
        if (null === $lpItem) {
6338
            return '';
6339
        }
6340
        $item_id = $lpItem->getIid();
6341
        $itemType = $lpItem->getItemType();
6342
        $lpId = $lpItem->getLp()->getIid();
6343
        $path = $lpItem->getPath();
6344
6345
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6346
6347
        // Prevents wrong parent selection for document, see Bug#1251.
6348
        if ('dir' !== $itemType) {
6349
            Session::write('parent_item_id', $lpItem->getParentItemId());
6350
        }
6351
6352
        if ($show_actions) {
6353
            $return .= $this->displayItemMenu($lpItem);
6354
        }
6355
        $return .= '<div style="padding:10px;">';
6356
6357
        if ('' != $msg) {
6358
            $return .= $msg;
6359
        }
6360
6361
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6362
6363
        switch ($itemType) {
6364
            case TOOL_THREAD:
6365
                $link = $this->rl_get_resource_link_for_learnpath(
6366
                    $course_id,
6367
                    $lpId,
6368
                    $item_id,
6369
                    0
6370
                );
6371
                $return .= Display::url(
6372
                    get_lang('Go to thread'),
6373
                    $link,
6374
                    ['class' => 'btn btn-primary']
6375
                );
6376
                break;
6377
            case TOOL_FORUM:
6378
                $return .= Display::url(
6379
                    get_lang('Go to the forum'),
6380
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6381
                    ['class' => 'btn btn-primary']
6382
                );
6383
                break;
6384
            case TOOL_QUIZ:
6385
                if (!empty($path)) {
6386
                    $exercise = new Exercise();
6387
                    $exercise->read($path);
6388
                    $return .= $exercise->description.'<br />';
6389
                    $return .= Display::url(
6390
                        get_lang('Go to exercise'),
6391
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6392
                        ['class' => 'btn btn-primary']
6393
                    );
6394
                }
6395
                break;
6396
            case TOOL_LP_FINAL_ITEM:
6397
                $return .= $this->getSavedFinalItem();
6398
                break;
6399
            case TOOL_DOCUMENT:
6400
            case TOOL_READOUT_TEXT:
6401
                $repo = Container::getDocumentRepository();
6402
                /** @var CDocument $document */
6403
                $document = $repo->find($lpItem->getPath());
6404
                $return .= $this->display_document($document, true, true);
6405
                break;
6406
            case TOOL_HOTPOTATOES:
6407
                $return .= $this->display_document($document, false, true);
6408
                break;
6409
        }
6410
        $return .= '</div>';
6411
6412
        return $return;
6413
    }
6414
6415
    /**
6416
     * Shows the needed forms for editing a specific item.
6417
     *
6418
     * @param CLpItem $lpItem
6419
     *
6420
     * @throws Exception
6421
     *
6422
     *
6423
     * @return string
6424
     */
6425
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6426
    {
6427
        $return = '';
6428
6429
        if (empty($lpItem)) {
6430
            return '';
6431
        }
6432
        $item_id = $lpItem->getIid();
6433
        $itemType = $lpItem->getItemType();
6434
        $path = $lpItem->getPath();
6435
6436
        switch ($itemType) {
6437
            case 'dir':
6438
            case 'asset':
6439
            case 'sco':
6440
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6441
                    $return .= $this->displayItemMenu($lpItem);
6442
                    $return .= $this->display_item_form(
6443
                        $lpItem,
6444
                        'edit'
6445
                    );
6446
                } else {
6447
                    $return .= $this->display_item_form(
6448
                        $lpItem,
6449
                        'edit_item'
6450
                    );
6451
                }
6452
                break;
6453
            case TOOL_LP_FINAL_ITEM:
6454
            case TOOL_DOCUMENT:
6455
            case TOOL_READOUT_TEXT:
6456
                $return .= $this->displayItemMenu($lpItem);
6457
                $return .= $this->displayDocumentForm('edit', $lpItem);
6458
                break;
6459
            case TOOL_LINK:
6460
                $link = null;
6461
                if (!empty($path)) {
6462
                    $repo = Container::getLinkRepository();
6463
                    $link = $repo->find($path);
6464
                }
6465
                $return .= $this->displayItemMenu($lpItem);
6466
                $return .= $this->display_link_form('edit', $lpItem, $link);
6467
6468
                break;
6469
            case TOOL_QUIZ:
6470
                if (!empty($path)) {
6471
                    $repo = Container::getQuizRepository();
6472
                    $resource = $repo->find($path);
6473
                }
6474
                $return .= $this->displayItemMenu($lpItem);
6475
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6476
                break;
6477
            /*case TOOL_HOTPOTATOES:
6478
                $return .= $this->displayItemMenu($lpItem);
6479
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6480
                break;*/
6481
            case TOOL_STUDENTPUBLICATION:
6482
                if (!empty($path)) {
6483
                    $repo = Container::getStudentPublicationRepository();
6484
                    $resource = $repo->find($path);
6485
                }
6486
                $return .= $this->displayItemMenu($lpItem);
6487
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6488
                break;
6489
            case TOOL_FORUM:
6490
                if (!empty($path)) {
6491
                    $repo = Container::getForumRepository();
6492
                    $resource = $repo->find($path);
6493
                }
6494
                $return .= $this->displayItemMenu($lpItem);
6495
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6496
                break;
6497
            case TOOL_THREAD:
6498
                if (!empty($path)) {
6499
                    $repo = Container::getForumPostRepository();
6500
                    $resource = $repo->find($path);
6501
                }
6502
                $return .= $this->displayItemMenu($lpItem);
6503
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6504
                break;
6505
        }
6506
6507
        return $return;
6508
    }
6509
6510
    /**
6511
     * Function that displays a list with al the resources that
6512
     * could be added to the learning path.
6513
     *
6514
     * @throws Exception
6515
     *
6516
     *
6517
     * @return bool
6518
     */
6519
    public function displayResources()
6520
    {
6521
        // Get all the docs.
6522
        $documents = $this->get_documents(true);
6523
6524
        // Get all the exercises.
6525
        $exercises = $this->get_exercises();
6526
6527
        // Get all the links.
6528
        $links = $this->get_links();
6529
6530
        // Get all the student publications.
6531
        $works = $this->get_student_publications();
6532
6533
        // Get all the forums.
6534
        $forums = $this->get_forums();
6535
6536
        // Get the final item form (see BT#11048) .
6537
        $finish = $this->getFinalItemForm();
6538
        $size = ICON_SIZE_MEDIUM; //ICON_SIZE_BIG
6539
        $headers = [
6540
            Display::return_icon('folder_document.png', get_lang('Documents'), [], $size),
6541
            Display::return_icon('quiz.png', get_lang('Tests'), [], $size),
6542
            Display::return_icon('links.png', get_lang('Links'), [], $size),
6543
            Display::return_icon('works.png', get_lang('Assignments'), [], $size),
6544
            Display::return_icon('forum.png', get_lang('Forums'), [], $size),
6545
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], $size),
6546
            Display::return_icon('certificate.png', get_lang('Certificate'), [], $size),
6547
        ];
6548
6549
        /*echo Display::return_message(
6550
            get_lang('Click on the [Learner view] button to see your learning path'),
6551
            'normal'
6552
        );*/
6553
        $section = $this->displayNewSectionForm();
6554
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6555
6556
        echo Display::tabs(
6557
            $headers,
6558
            [
6559
                $documents,
6560
                $exercises,
6561
                $links,
6562
                $works,
6563
                $forums,
6564
                $section,
6565
                $finish,
6566
            ],
6567
            'resource_tab',
6568
            [],
6569
            [],
6570
            $selected
6571
        );
6572
6573
        return true;
6574
    }
6575
6576
    /**
6577
     * Returns the extension of a document.
6578
     *
6579
     * @param string $filename
6580
     *
6581
     * @return string Extension (part after the last dot)
6582
     */
6583
    public function get_extension($filename)
6584
    {
6585
        $explode = explode('.', $filename);
6586
6587
        return $explode[count($explode) - 1];
6588
    }
6589
6590
    /**
6591
     * @return string
6592
     */
6593
    public function getCurrentBuildingModeURL()
6594
    {
6595
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
6596
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
6597
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
6598
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
6599
6600
        $currentUrl = api_get_self().'?'.api_get_cidreq().
6601
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
6602
6603
        return $currentUrl;
6604
    }
6605
6606
    /**
6607
     * Displays a document by id.
6608
     *
6609
     * @param CDocument $document
6610
     * @param bool      $show_title
6611
     * @param bool      $iframe
6612
     * @param bool      $edit_link
6613
     *
6614
     * @return string
6615
     */
6616
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
6617
    {
6618
        $return = '';
6619
        if (!$document) {
6620
            return '';
6621
        }
6622
6623
        $repo = Container::getDocumentRepository();
6624
6625
        // TODO: Add a path filter.
6626
        if ($iframe) {
6627
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
6628
            $url = $repo->getResourceFileUrl($document);
6629
6630
            $return .= '<iframe
6631
                id="learnpath_preview_frame"
6632
                frameborder="0"
6633
                height="400"
6634
                width="100%"
6635
                scrolling="auto"
6636
                src="'.$url.'"></iframe>';
6637
        } else {
6638
            $return = $repo->getResourceFileContent($document);
6639
        }
6640
6641
        return $return;
6642
    }
6643
6644
    /**
6645
     * Return HTML form to add/edit a link item.
6646
     *
6647
     * @param string  $action (add/edit)
6648
     * @param CLpItem $lpItem
6649
     * @param CLink   $link
6650
     *
6651
     * @throws Exception
6652
     *
6653
     *
6654
     * @return string HTML form
6655
     */
6656
    public function display_link_form($action, $lpItem, $link)
6657
    {
6658
        $item_url = '';
6659
        if ($link) {
6660
            $item_url = stripslashes($link->getUrl());
6661
        }
6662
        $form = new FormValidator(
6663
            'edit_link',
6664
            'POST',
6665
            $this->getCurrentBuildingModeURL()
6666
        );
6667
6668
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6669
6670
        $urlAttributes = ['class' => 'learnpath_item_form'];
6671
        $urlAttributes['disabled'] = 'disabled';
6672
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
6673
        $form->setDefault('url', $item_url);
6674
6675
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6676
6677
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6678
    }
6679
6680
    /**
6681
     * Return HTML form to add/edit a quiz.
6682
     *
6683
     * @param string  $action   Action (add/edit)
6684
     * @param CLpItem $lpItem   Item ID if already exists
6685
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
6686
     *
6687
     * @throws Exception
6688
     *
6689
     * @return string HTML form
6690
     */
6691
    public function display_quiz_form($action, $lpItem, $exercise)
6692
    {
6693
        $form = new FormValidator(
6694
            'quiz_form',
6695
            'POST',
6696
            $this->getCurrentBuildingModeURL()
6697
        );
6698
6699
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6700
6701
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6702
6703
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6704
    }
6705
6706
    /**
6707
     * Return the form to display the forum edit/add option.
6708
     *
6709
     * @param CLpItem $lpItem
6710
     *
6711
     * @throws Exception
6712
     *
6713
     * @return string HTML form
6714
     */
6715
    public function display_forum_form($action, $lpItem, $resource)
6716
    {
6717
        $form = new FormValidator(
6718
            'forum_form',
6719
            'POST',
6720
            $this->getCurrentBuildingModeURL()
6721
        );
6722
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6723
6724
        if ('add' === $action) {
6725
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
6726
        } else {
6727
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
6728
        }
6729
6730
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6731
    }
6732
6733
    /**
6734
     * Return HTML form to add/edit forum threads.
6735
     *
6736
     * @param string  $action
6737
     * @param CLpItem $lpItem
6738
     * @param string  $resource
6739
     *
6740
     * @throws Exception
6741
     *
6742
     * @return string HTML form
6743
     */
6744
    public function display_thread_form($action, $lpItem, $resource)
6745
    {
6746
        $form = new FormValidator(
6747
            'thread_form',
6748
            'POST',
6749
            $this->getCurrentBuildingModeURL()
6750
        );
6751
6752
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6753
6754
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6755
6756
        return $form->returnForm();
6757
    }
6758
6759
    /**
6760
     * Return the HTML form to display an item (generally a dir item).
6761
     *
6762
     * @param CLpItem $lpItem
6763
     * @param string  $action
6764
     *
6765
     * @throws Exception
6766
     *
6767
     *
6768
     * @return string HTML form
6769
     */
6770
    public function display_item_form(
6771
        $lpItem,
6772
        $action = 'add_item'
6773
    ) {
6774
        $item_type = $lpItem->getItemType();
6775
6776
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6777
6778
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6779
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6780
6781
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6782
6783
        return $form->returnForm();
6784
    }
6785
6786
    /**
6787
     * Return HTML form to add/edit a student publication (work).
6788
     *
6789
     * @param string              $action
6790
     * @param CStudentPublication $resource
6791
     *
6792
     * @throws Exception
6793
     *
6794
     * @return string HTML form
6795
     */
6796
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
6797
    {
6798
        $form = new FormValidator('frm_student_publication', 'post', '#');
6799
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6800
6801
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6802
6803
        $return = '<div class="sectioncomment">';
6804
        $return .= $form->returnForm();
6805
        $return .= '</div>';
6806
6807
        return $return;
6808
    }
6809
6810
    public function displayNewSectionForm()
6811
    {
6812
        $action = 'add_item';
6813
        $item_type = 'dir';
6814
6815
        $lpItem = new CLpItem();
6816
        $lpItem->setTitle('dir');
6817
        $lpItem->setItemType('dir');
6818
6819
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6820
6821
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6822
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
6823
6824
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6825
        $form->addElement('hidden', 'type', 'dir');
6826
6827
        return $form->returnForm();
6828
    }
6829
6830
    /**
6831
     * Returns the form to update or create a document.
6832
     *
6833
     * @param string  $action (add/edit)
6834
     * @param CLpItem $lpItem
6835
     *
6836
     *
6837
     * @throws Exception
6838
     *
6839
     * @return string HTML form
6840
     */
6841
    public function displayDocumentForm($action = 'add', $lpItem = null)
6842
    {
6843
        $courseInfo = api_get_course_info();
6844
6845
        $form = new FormValidator(
6846
            'form',
6847
            'POST',
6848
            $this->getCurrentBuildingModeURL(),
6849
            '',
6850
            ['enctype' => 'multipart/form-data']
6851
        );
6852
6853
        $data = $this->generate_lp_folder($courseInfo);
6854
6855
        if (null !== $lpItem) {
6856
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6857
        }
6858
6859
        switch ($action) {
6860
            case 'add':
6861
                $folders = DocumentManager::get_all_document_folders(
6862
                    $courseInfo,
6863
                    0,
6864
                    true
6865
                );
6866
                DocumentManager::build_directory_selector(
6867
                    $folders,
6868
                    '',
6869
                    [],
6870
                    true,
6871
                    $form,
6872
                    'directory_parent_id'
6873
                );
6874
6875
                if ($data) {
6876
                    $defaults['directory_parent_id'] = $data->getIid();
6877
                }
6878
6879
                break;
6880
        }
6881
6882
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6883
6884
        return $form->returnForm();
6885
    }
6886
6887
    /**
6888
     * @param array  $courseInfo
6889
     * @param string $content
6890
     * @param string $title
6891
     * @param int    $parentId
6892
     *
6893
     * @throws \Doctrine\ORM\ORMException
6894
     * @throws \Doctrine\ORM\OptimisticLockException
6895
     * @throws \Doctrine\ORM\TransactionRequiredException
6896
     *
6897
     * @return int
6898
     */
6899
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
6900
    {
6901
        $creatorId = api_get_user_id();
6902
        $sessionId = api_get_session_id();
6903
6904
        // Generates folder
6905
        $result = $this->generate_lp_folder($courseInfo);
6906
        $dir = $result['dir'];
6907
6908
        if (empty($parentId) || '/' == $parentId) {
6909
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6910
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6911
6912
            if ('/' === $parentId) {
6913
                $dir = '/';
6914
            }
6915
6916
            // Please, do not modify this dirname formatting.
6917
            if (strstr($dir, '..')) {
6918
                $dir = '/';
6919
            }
6920
6921
            if (!empty($dir[0]) && '.' == $dir[0]) {
6922
                $dir = substr($dir, 1);
6923
            }
6924
            if (!empty($dir[0]) && '/' != $dir[0]) {
6925
                $dir = '/'.$dir;
6926
            }
6927
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6928
                $dir .= '/';
6929
            }
6930
        } else {
6931
            $parentInfo = DocumentManager::get_document_data_by_id(
6932
                $parentId,
6933
                $courseInfo['code']
6934
            );
6935
            if (!empty($parentInfo)) {
6936
                $dir = $parentInfo['path'].'/';
6937
            }
6938
        }
6939
6940
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6941
6942
        if (!is_dir($filepath)) {
6943
            $dir = '/';
6944
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6945
        }
6946
6947
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6948
6949
        if (!empty($title)) {
6950
            $title = api_replace_dangerous_char(stripslashes($title));
6951
        } else {
6952
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6953
        }
6954
6955
        $title = disable_dangerous_file($title);
6956
        $filename = $title;
6957
        $content = !empty($content) ? $content : $_POST['content_lp'];
6958
        $tmpFileName = $filename;
6959
6960
        $i = 0;
6961
        while (file_exists($filepath.$tmpFileName.'.html')) {
6962
            $tmpFileName = $filename.'_'.++$i;
6963
        }
6964
6965
        $filename = $tmpFileName.'.html';
6966
        $content = stripslashes($content);
6967
6968
        if (file_exists($filepath.$filename)) {
6969
            return 0;
6970
        }
6971
6972
        $putContent = file_put_contents($filepath.$filename, $content);
6973
6974
        if (false === $putContent) {
6975
            return 0;
6976
        }
6977
6978
        $fileSize = filesize($filepath.$filename);
6979
        $saveFilePath = $dir.$filename;
6980
6981
        $document = DocumentManager::addDocument(
6982
            $courseInfo,
6983
            $saveFilePath,
6984
            'file',
6985
            $fileSize,
6986
            $tmpFileName,
6987
            '',
6988
            0, //readonly
6989
            true,
6990
            null,
6991
            $sessionId,
6992
            $creatorId
6993
        );
6994
6995
        $documentId = $document->getId();
6996
6997
        if (!$document) {
6998
            return 0;
6999
        }
7000
7001
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7002
        $newTitle = $originalTitle;
7003
7004
        if ($newComment || $newTitle) {
7005
            $em = Database::getManager();
7006
7007
            if ($newComment) {
7008
                $document->setComment($newComment);
7009
            }
7010
7011
            if ($newTitle) {
7012
                $document->setTitle($newTitle);
7013
            }
7014
7015
            $em->persist($document);
7016
            $em->flush();
7017
        }
7018
7019
        return $documentId;
7020
    }
7021
7022
    /**
7023
     * Displays the menu for manipulating a step.
7024
     *
7025
     * @return string
7026
     */
7027
    public function displayItemMenu(CLpItem $lpItem)
7028
    {
7029
        $item_id = $lpItem->getIid();
7030
        $audio = $lpItem->getAudio();
7031
        $itemType = $lpItem->getItemType();
7032
        $path = $lpItem->getPath();
7033
7034
        $return = '<div class="actions">';
7035
        $audio_player = null;
7036
        // We display an audio player if needed.
7037
        if (!empty($audio)) {
7038
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7039
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7040
                .'<audio src="'.$webAudioPath.'" controls>'
7041
                .'</div><br>';*/
7042
        }
7043
7044
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7045
7046
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7047
            $return .= Display::url(
7048
                Display::return_icon(
7049
                    'edit.png',
7050
                    get_lang('Edit'),
7051
                    [],
7052
                    ICON_SIZE_SMALL
7053
                ),
7054
                $url.'&action=edit_item&path_item='.$path
7055
            );
7056
7057
            /*$return .= Display::url(
7058
                Display::return_icon(
7059
                    'move.png',
7060
                    get_lang('Move'),
7061
                    [],
7062
                    ICON_SIZE_SMALL
7063
                ),
7064
                $url.'&action=move_item'
7065
            );*/
7066
        }
7067
7068
        // Commented for now as prerequisites cannot be added to chapters.
7069
        if ('dir' !== $itemType) {
7070
            $return .= Display::url(
7071
                Display::return_icon(
7072
                    'accept.png',
7073
                    get_lang('Prerequisites'),
7074
                    [],
7075
                    ICON_SIZE_SMALL
7076
                ),
7077
                $url.'&action=edit_item_prereq'
7078
            );
7079
        }
7080
        $return .= Display::url(
7081
            Display::return_icon(
7082
                'delete.png',
7083
                get_lang('Delete'),
7084
                [],
7085
                ICON_SIZE_SMALL
7086
            ),
7087
            $url.'&action=delete_item'
7088
        );
7089
7090
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7091
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7092
            if (empty($documentData)) {
7093
                // Try with iid
7094
                $table = Database::get_course_table(TABLE_DOCUMENT);
7095
                $sql = "SELECT path FROM $table
7096
                        WHERE
7097
                              c_id = ".api_get_course_int_id()." AND
7098
                              iid = ".$path." AND
7099
                              path NOT LIKE '%_DELETED_%'";
7100
                $result = Database::query($sql);
7101
                $documentData = Database::fetch_array($result);
7102
                if ($documentData) {
7103
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7104
                }
7105
            }
7106
            if (isset($documentData['absolute_path_from_document'])) {
7107
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7108
            }
7109
        }*/
7110
7111
        $return .= '</div>';
7112
7113
        if (!empty($audio_player)) {
7114
            $return .= $audio_player;
7115
        }
7116
7117
        return $return;
7118
    }
7119
7120
    /**
7121
     * Creates the javascript needed for filling up the checkboxes without page reload.
7122
     *
7123
     * @return string
7124
     */
7125
    public function get_js_dropdown_array()
7126
    {
7127
        $course_id = api_get_course_int_id();
7128
        $return = 'var child_name = new Array();'."\n";
7129
        $return .= 'var child_value = new Array();'."\n\n";
7130
        $return .= 'child_name[0] = new Array();'."\n";
7131
        $return .= 'child_value[0] = new Array();'."\n\n";
7132
7133
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7134
        $sql = "SELECT * FROM ".$tbl_lp_item."
7135
                WHERE
7136
                    lp_id = ".$this->lp_id." AND
7137
                    parent_item_id = 0
7138
                ORDER BY display_order ASC";
7139
        Database::query($sql);
7140
        $i = 0;
7141
7142
        $list = $this->getItemsForForm(true);
7143
7144
        foreach ($list as $row_zero) {
7145
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7146
                if (TOOL_QUIZ == $row_zero['item_type']) {
7147
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7148
                }
7149
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7150
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7151
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7152
            }
7153
        }
7154
7155
        $return .= "\n";
7156
        $sql = "SELECT * FROM $tbl_lp_item
7157
                WHERE lp_id = ".$this->lp_id;
7158
        $res = Database::query($sql);
7159
        while ($row = Database::fetch_array($res)) {
7160
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7161
                           WHERE
7162
                                parent_item_id = ".$row['iid']."
7163
                           ORDER BY display_order ASC";
7164
            $res_parent = Database::query($sql_parent);
7165
            $i = 0;
7166
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7167
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7168
7169
            while ($row_parent = Database::fetch_array($res_parent)) {
7170
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7171
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7172
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7173
            }
7174
            $return .= "\n";
7175
        }
7176
7177
        $return .= "
7178
            function load_cbo(id) {
7179
                if (!id) {
7180
                    return false;
7181
                }
7182
7183
                var cbo = document.getElementById('previous');
7184
                for(var i = cbo.length - 1; i > 0; i--) {
7185
                    cbo.options[i] = null;
7186
                }
7187
7188
                var k=0;
7189
                for(var i = 1; i <= child_name[id].length; i++){
7190
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7191
                    option.style.paddingLeft = '40px';
7192
                    cbo.options[i] = option;
7193
                    k = i;
7194
                }
7195
7196
                cbo.options[k].selected = true;
7197
                //$('#previous').selectpicker('refresh');
7198
            }";
7199
7200
        return $return;
7201
    }
7202
7203
    /**
7204
     * Display the form to allow moving an item.
7205
     *
7206
     * @param CLpItem $lpItem
7207
     *
7208
     * @throws Exception
7209
     *
7210
     *
7211
     * @return string HTML form
7212
     */
7213
    public function display_move_item($lpItem)
7214
    {
7215
        $return = '';
7216
        $path = $lpItem->getPath();
7217
7218
        if ($lpItem) {
7219
            $itemType = $lpItem->getItemType();
7220
            switch ($itemType) {
7221
                case 'dir':
7222
                case 'asset':
7223
                    $return .= $this->displayItemMenu($lpItem);
7224
                    $return .= $this->display_item_form(
7225
                        $lpItem,
7226
                        get_lang('Move the current section'),
7227
                        'move',
7228
                        $row
7229
                    );
7230
                    break;
7231
                case TOOL_DOCUMENT:
7232
                    $return .= $this->displayItemMenu($lpItem);
7233
                    $return .= $this->displayDocumentForm('move', $lpItem);
7234
                    break;
7235
                case TOOL_LINK:
7236
                    $link = null;
7237
                    if (!empty($path)) {
7238
                        $repo = Container::getLinkRepository();
7239
                        $link = $repo->find($path);
7240
                    }
7241
                    $return .= $this->displayItemMenu($lpItem);
7242
                    $return .= $this->display_link_form('move', $lpItem, $link);
7243
                    break;
7244
                case TOOL_HOTPOTATOES:
7245
                    $return .= $this->displayItemMenu($lpItem);
7246
                    $return .= $this->display_link_form('move', $lpItem, $row);
7247
                    break;
7248
                case TOOL_QUIZ:
7249
                    $return .= $this->displayItemMenu($lpItem);
7250
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7251
                    break;
7252
                case TOOL_STUDENTPUBLICATION:
7253
                    $return .= $this->displayItemMenu($lpItem);
7254
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7255
                    break;
7256
                case TOOL_FORUM:
7257
                    $return .= $this->displayItemMenu($lpItem);
7258
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7259
                    break;
7260
                case TOOL_THREAD:
7261
                    $return .= $this->displayItemMenu($lpItem);
7262
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7263
                    break;
7264
            }
7265
        }
7266
7267
        return $return;
7268
    }
7269
7270
    /**
7271
     * Return HTML form to allow prerequisites selection.
7272
     *
7273
     * @todo use FormValidator
7274
     *
7275
     * @return string HTML form
7276
     */
7277
    public function display_item_prerequisites_form(CLpItem $lpItem)
7278
    {
7279
        $course_id = api_get_course_int_id();
7280
        $prerequisiteId = $lpItem->getPrerequisite();
7281
        $itemId = $lpItem->getIid();
7282
7283
        $return = '<legend>';
7284
        $return .= get_lang('Add/edit prerequisites');
7285
        $return .= '</legend>';
7286
        $return .= '<form method="POST">';
7287
        $return .= '<div class="table-responsive">';
7288
        $return .= '<table class="table table-hover">';
7289
        $return .= '<thead>';
7290
        $return .= '<tr>';
7291
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7292
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7293
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7294
        $return .= '</tr>';
7295
        $return .= '</thead>';
7296
7297
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7298
        $return .= '<tbody>';
7299
        $return .= '<tr>';
7300
        $return .= '<td colspan="3">';
7301
        $return .= '<div class="radio learnpath"><label for="idnone">';
7302
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7303
        $return .= get_lang('none').'</label>';
7304
        $return .= '</div>';
7305
        $return .= '</tr>';
7306
        // @todo use entitites
7307
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7308
        $sql = "SELECT * FROM $tbl_lp_item
7309
                WHERE lp_id = ".$this->lp_id;
7310
        $result = Database::query($sql);
7311
7312
        $selectedMinScore = [];
7313
        $selectedMaxScore = [];
7314
        $masteryScore = [];
7315
        while ($row = Database::fetch_array($result)) {
7316
            if ($row['iid'] == $itemId) {
7317
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7318
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7319
            }
7320
            $masteryScore[$row['iid']] = $row['mastery_score'];
7321
        }
7322
7323
        $arrLP = $this->getItemsForForm();
7324
        $this->tree_array($arrLP);
7325
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7326
        unset($this->arrMenu);
7327
7328
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7329
            $item = $arrLP[$i];
7330
7331
            if ($item['id'] == $itemId) {
7332
                break;
7333
            }
7334
7335
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7336
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7337
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7338
7339
            $return .= '<tr>';
7340
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7341
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7342
            $return .= '<label for="id'.$item['id'].'">';
7343
7344
            $checked = '';
7345
            if (null !== $prerequisiteId) {
7346
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7347
            }
7348
7349
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7350
7351
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7352
7353
            $icon_name = str_replace(' ', '', $item['item_type']);
7354
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7355
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7356
            } else {
7357
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7358
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7359
                } else {
7360
                    $return .= Display::return_icon('folder_document.png');
7361
                }
7362
            }
7363
7364
            $return .= $item['title'].'</label>';
7365
            $return .= '</div>';
7366
            $return .= '</td>';
7367
7368
            if (TOOL_QUIZ == $item['item_type']) {
7369
                // lets update max_score Tests information depending of the Tests Advanced properties
7370
                $lpItemObj = new LpItem($course_id, $item['id']);
7371
                $exercise = new Exercise($course_id);
7372
                $exercise->read($lpItemObj->path);
7373
                $lpItemObj->max_score = $exercise->get_max_score();
7374
                $lpItemObj->update();
7375
                $item['max_score'] = $lpItemObj->max_score;
7376
7377
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7378
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7379
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7380
                }
7381
7382
                $return .= '<td>';
7383
                $return .= '<input
7384
                    class="form-control"
7385
                    size="4" maxlength="3"
7386
                    name="min_'.$item['id'].'"
7387
                    type="number"
7388
                    min="0"
7389
                    step="any"
7390
                    max="'.$item['max_score'].'"
7391
                    value="'.$selectedMinScoreValue.'"
7392
                />';
7393
                $return .= '</td>';
7394
                $return .= '<td>';
7395
                $return .= '<input
7396
                    class="form-control"
7397
                    size="4"
7398
                    maxlength="3"
7399
                    name="max_'.$item['id'].'"
7400
                    type="number"
7401
                    min="0"
7402
                    step="any"
7403
                    max="'.$item['max_score'].'"
7404
                    value="'.$selectedMaxScoreValue.'"
7405
                />';
7406
                $return .= '</td>';
7407
            }
7408
7409
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7410
                $return .= '<td>';
7411
                $return .= '<input
7412
                    size="4"
7413
                    maxlength="3"
7414
                    name="min_'.$item['id'].'"
7415
                    type="number"
7416
                    min="0"
7417
                    step="any"
7418
                    max="'.$item['max_score'].'"
7419
                    value="'.$selectedMinScoreValue.'"
7420
                />';
7421
                $return .= '</td>';
7422
                $return .= '<td>';
7423
                $return .= '<input
7424
                    size="4"
7425
                    maxlength="3"
7426
                    name="max_'.$item['id'].'"
7427
                    type="number"
7428
                    min="0"
7429
                    step="any"
7430
                    max="'.$item['max_score'].'"
7431
                    value="'.$selectedMaxScoreValue.'"
7432
                />';
7433
                $return .= '</td>';
7434
            }
7435
            $return .= '</tr>';
7436
        }
7437
        $return .= '<tr>';
7438
        $return .= '</tr>';
7439
        $return .= '</tbody>';
7440
        $return .= '</table>';
7441
        $return .= '</div>';
7442
        $return .= '<div class="form-group">';
7443
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7444
            get_lang('Save prerequisites settings').'</button>';
7445
        $return .= '</form>';
7446
7447
        return $return;
7448
    }
7449
7450
    /**
7451
     * Return HTML list to allow prerequisites selection for lp.
7452
     *
7453
     * @return string HTML form
7454
     */
7455
    public function display_lp_prerequisites_list()
7456
    {
7457
        $lp_id = $this->lp_id;
7458
        $prerequisiteId = $this->entity->getPrerequisite();
7459
7460
        $repo = Container::getLpRepository();
7461
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
7462
        /** @var CLp[] $lps */
7463
        $lps = $qb->getQuery()->getResult();
7464
7465
        //$session_id = api_get_session_id();
7466
        /*$session_condition = api_get_session_condition($session_id, true, true);
7467
        $sql = "SELECT * FROM $tbl_lp
7468
                WHERE c_id = $course_id $session_condition
7469
                ORDER BY display_order ";
7470
        $rs = Database::query($sql);*/
7471
        $return = '';
7472
        $return .= '<select name="prerequisites" class="form-control">';
7473
        $return .= '<option value="0">'.get_lang('none').'</option>';
7474
7475
        foreach ($lps as $lp) {
7476
            $myLpId = $lp->getIid();
7477
            if ($myLpId == $lp_id) {
7478
                continue;
7479
            }
7480
            $return .= '<option
7481
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
7482
                $lp->getName().
7483
                '</option>';
7484
        }
7485
7486
        $return .= '</select>';
7487
7488
        return $return;
7489
    }
7490
7491
    /**
7492
     * Creates a list with all the documents in it.
7493
     *
7494
     * @param bool $showInvisibleFiles
7495
     *
7496
     * @throws Exception
7497
     *
7498
     *
7499
     * @return string
7500
     */
7501
    public function get_documents($showInvisibleFiles = false)
7502
    {
7503
        $course_info = api_get_course_info();
7504
        $sessionId = api_get_session_id();
7505
        $documentTree = DocumentManager::get_document_preview(
7506
            $course_info,
7507
            $this->lp_id,
7508
            null,
7509
            $sessionId,
7510
            true,
7511
            null,
7512
            null,
7513
            $showInvisibleFiles,
7514
            true
7515
        );
7516
7517
        $form = new FormValidator(
7518
            'form_upload',
7519
            'POST',
7520
            $this->getCurrentBuildingModeURL(),
7521
            '',
7522
            ['enctype' => 'multipart/form-data']
7523
        );
7524
7525
        $folders = DocumentManager::get_all_document_folders(
7526
            api_get_course_info(),
7527
            0,
7528
            true
7529
        );
7530
7531
        $folder = $this->generate_lp_folder(api_get_course_info());
7532
7533
        DocumentManager::build_directory_selector(
7534
            $folders,
7535
            $folder->getIid(),
7536
            [],
7537
            true,
7538
            $form,
7539
            'directory_parent_id'
7540
        );
7541
7542
        $group = [
7543
            $form->createElement(
7544
                'radio',
7545
                'if_exists',
7546
                get_lang('If file exists:'),
7547
                get_lang('Do nothing'),
7548
                'nothing'
7549
            ),
7550
            $form->createElement(
7551
                'radio',
7552
                'if_exists',
7553
                null,
7554
                get_lang('Overwrite the existing file'),
7555
                'overwrite'
7556
            ),
7557
            $form->createElement(
7558
                'radio',
7559
                'if_exists',
7560
                null,
7561
                get_lang('Rename the uploaded file if it exists'),
7562
                'rename'
7563
            ),
7564
        ];
7565
        $form->addGroup($group, null, get_lang('If file exists:'));
7566
7567
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7568
        $defaultFileExistsOption = 'rename';
7569
        if (!empty($fileExistsOption)) {
7570
            $defaultFileExistsOption = $fileExistsOption;
7571
        }
7572
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7573
7574
        // Check box options
7575
        $form->addElement(
7576
            'checkbox',
7577
            'unzip',
7578
            get_lang('Options'),
7579
            get_lang('Uncompress zip')
7580
        );
7581
7582
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7583
        $form->addMultipleUpload($url);
7584
7585
        $lpItem = new CLpItem();
7586
        $lpItem->setTitle('');
7587
        $lpItem->setItemType(TOOL_DOCUMENT);
7588
        $new = $this->displayDocumentForm('add', $lpItem);
7589
7590
        /*$lpItem = new CLpItem();
7591
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7592
        $frmReadOutText = $this->displayDocumentForm('add');*/
7593
7594
        $headers = [
7595
            get_lang('Files'),
7596
            get_lang('Create a new document'),
7597
            //get_lang('Create read-out text'),
7598
            get_lang('Upload'),
7599
        ];
7600
7601
        return Display::tabs(
7602
            $headers,
7603
            [$documentTree, $new, $form->returnForm()],
7604
            'subtab'
7605
        );
7606
    }
7607
7608
    /**
7609
     * Creates a list with all the exercises (quiz) in it.
7610
     *
7611
     * @return string
7612
     */
7613
    public function get_exercises()
7614
    {
7615
        $course_id = api_get_course_int_id();
7616
        $session_id = api_get_session_id();
7617
        $userInfo = api_get_user_info();
7618
7619
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7620
        $condition_session = api_get_session_condition($session_id, true, true);
7621
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7622
7623
        //$activeCondition = ' active <> -1 ';
7624
        $active = 2;
7625
        if ($setting) {
7626
            $active = 1;
7627
            //$activeCondition = ' active = 1 ';
7628
        }
7629
7630
        $categoryCondition = '';
7631
7632
        $keyword = $_REQUEST['keyword'] ?? null;
7633
        $categoryId = $_REQUEST['category_id'] ?? null;
7634
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
7635
            $categoryCondition = " AND exercise_category_id = $categoryId ";
7636
        }
7637
7638
        $keywordCondition = '';
7639
7640
        if (!empty($keyword)) {
7641
            $keyword = Database::escape_string($keyword);
7642
            $keywordCondition = " AND title LIKE '%$keyword%' ";
7643
        }
7644
        */
7645
        $course = api_get_course_entity($course_id);
7646
        $session = api_get_session_entity($session_id);
7647
7648
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
7649
        /** @var CQuiz[] $exercises */
7650
        $exercises = $qb->getQuery()->getResult();
7651
7652
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
7653
                     WHERE
7654
                            c_id = $course_id AND
7655
                            $activeCondition
7656
                            $condition_session
7657
                            $categoryCondition
7658
                            $keywordCondition
7659
                     ORDER BY title ASC";
7660
        $res_quiz = Database::query($sql_quiz);*/
7661
7662
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
7663
7664
        // Create a search-box
7665
        $form = new FormValidator('search_simple', 'get', $currentUrl);
7666
        $form->addHidden('action', 'add_item');
7667
        $form->addHidden('type', 'step');
7668
        $form->addHidden('lp_id', $this->lp_id);
7669
        $form->addHidden('lp_build_selected', '2');
7670
7671
        $form->addCourseHiddenParams();
7672
        $form->addText(
7673
            'keyword',
7674
            get_lang('Search'),
7675
            false,
7676
            [
7677
                'aria-label' => get_lang('Search'),
7678
            ]
7679
        );
7680
7681
        if (api_get_configuration_value('allow_exercise_categories')) {
7682
            $manager = new ExerciseCategoryManager();
7683
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
7684
            if (!empty($options)) {
7685
                $form->addSelect(
7686
                    'category_id',
7687
                    get_lang('Category'),
7688
                    $options,
7689
                    ['placeholder' => get_lang('Please select an option')]
7690
                );
7691
            }
7692
        }
7693
7694
        $form->addButtonSearch(get_lang('Search'));
7695
        $return = $form->returnForm();
7696
7697
        $return .= '<ul class=" list-group lp_resource">';
7698
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7699
        $return .= Display::return_icon('new_exercice.png');
7700
        $return .= '<a
7701
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
7702
            get_lang('New test').'</a>';
7703
        $return .= '</li>';
7704
7705
        $previewIcon = Display::return_icon(
7706
            'preview_view.png',
7707
            get_lang('Preview')
7708
        );
7709
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
7710
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7711
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
7712
        foreach ($exercises as $exercise) {
7713
            $exerciseId = $exercise->getIid();
7714
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
7715
            $visibility = $exercise->isVisible($course, $session);
7716
7717
            $link = Display::url(
7718
                $previewIcon,
7719
                $exerciseUrl.'&exerciseId='.$exerciseId,
7720
                ['target' => '_blank']
7721
            );
7722
            $return .= '<li class="list-group-item lp_resource_element" title="'.$title.'">';
7723
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
7724
            $return .= $quizIcon;
7725
            $sessionStar = '';
7726
            /*$sessionStar = api_get_session_image(
7727
                $row_quiz['session_id'],
7728
                $userInfo['status']
7729
            );*/
7730
            $return .= Display::url(
7731
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
7732
                api_get_self().'?'.
7733
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
7734
                [
7735
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
7736
                    'data_type' => 'quiz',
7737
                    'data-id' => $exerciseId,
7738
                ]
7739
            );
7740
            $return .= '</li>';
7741
        }
7742
7743
        $return .= '</ul>';
7744
7745
        return $return;
7746
    }
7747
7748
    /**
7749
     * Creates a list with all the links in it.
7750
     *
7751
     * @return string
7752
     */
7753
    public function get_links()
7754
    {
7755
        $sessionId = api_get_session_id();
7756
        $repo = Container::getLinkRepository();
7757
7758
        $course = api_get_course_entity();
7759
        $session = api_get_session_entity($sessionId);
7760
        $qb = $repo->getResourcesByCourse($course, $session);
7761
        /** @var CLink[] $links */
7762
        $links = $qb->getQuery()->getResult();
7763
7764
        $selfUrl = api_get_self();
7765
        $courseIdReq = api_get_cidreq();
7766
        $userInfo = api_get_user_info();
7767
7768
        $moveEverywhereIcon = Display::return_icon(
7769
            'move_everywhere.png',
7770
            get_lang('Move'),
7771
            [],
7772
            ICON_SIZE_TINY
7773
        );
7774
7775
        /*$condition_session = api_get_session_condition(
7776
            $session_id,
7777
            true,
7778
            true,
7779
            'link.session_id'
7780
        );
7781
7782
        $sql = "SELECT
7783
                    link.id as link_id,
7784
                    link.title as link_title,
7785
                    link.session_id as link_session_id,
7786
                    link.category_id as category_id,
7787
                    link_category.category_title as category_title
7788
                FROM $tbl_link as link
7789
                LEFT JOIN $linkCategoryTable as link_category
7790
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
7791
                WHERE link.c_id = $course_id $condition_session
7792
                ORDER BY link_category.category_title ASC, link.title ASC";
7793
        $result = Database::query($sql);*/
7794
        $categorizedLinks = [];
7795
        $categories = [];
7796
7797
        foreach ($links as $link) {
7798
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
7799
            if (empty($categoryId)) {
7800
                $categories[0] = get_lang('Uncategorized');
7801
            } else {
7802
                $category = $link->getCategory();
7803
                $categories[$categoryId] = $category->getCategoryTitle();
7804
            }
7805
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
7806
        }
7807
7808
        $linksHtmlCode =
7809
            '<script>
7810
            function toggle_tool(tool, id) {
7811
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
7812
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
7813
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7814
                } else {
7815
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
7816
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7817
                }
7818
            }
7819
        </script>
7820
7821
        <ul class="list-group lp_resource">
7822
            <li class="list-group-item lp_resource_element disable_drag ">
7823
                '.Display::return_icon('linksnew.gif').'
7824
                <a
7825
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
7826
                title="'.get_lang('Add a link').'">'.
7827
                get_lang('Add a link').'
7828
                </a>
7829
            </li>';
7830
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
7831
        foreach ($categorizedLinks as $categoryId => $links) {
7832
            $linkNodes = null;
7833
            /** @var CLink $link */
7834
            foreach ($links as $key => $link) {
7835
                $title = $link->getTitle();
7836
                $linkUrl = Display::url(
7837
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7838
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
7839
                    ['target' => '_blank']
7840
                );
7841
7842
                if ($link->isVisible($course, $session)) {
7843
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
7844
                    $sessionStar = '';
7845
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
7846
                    $link = Display::url(
7847
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
7848
                        $url,
7849
                        [
7850
                            'class' => 'moved link_with_id',
7851
                            'data-id' => $key,
7852
                            'data_type' => TOOL_LINK,
7853
                            'title' => $title,
7854
                        ]
7855
                    );
7856
                    $linkNodes .=
7857
                        '<li class="list-group-item lp_resource_element">
7858
                         <a class="moved" href="#">'.
7859
                            $moveEverywhereIcon.
7860
                        '</a>
7861
                        '.$linkIcon.$link.'
7862
                        </li>';
7863
                }
7864
            }
7865
            $linksHtmlCode .=
7866
                '<li class="list-group-item disable_drag">
7867
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
7868
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
7869
                        align="absbottom" />
7870
                    </a>
7871
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
7872
                </li>
7873
            '.
7874
                $linkNodes.
7875
            '';
7876
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
7877
        }
7878
        $linksHtmlCode .= '</ul>';
7879
7880
        return $linksHtmlCode;
7881
    }
7882
7883
    /**
7884
     * Creates a list with all the student publications in it.
7885
     *
7886
     * @return string
7887
     */
7888
    public function get_student_publications()
7889
    {
7890
        $return = '<ul class="list-group lp_resource">';
7891
        $return .= '<li class="list-group-item lp_resource_element">';
7892
        /*
7893
        $return .= Display::return_icon('works_new.gif');
7894
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
7895
            get_lang('Add the Assignments tool to the course').'</a>';
7896
        $return .= '</li>';*/
7897
7898
        $works = getWorkListTeacher(0, 100, null, null, null);
7899
        if (!empty($works)) {
7900
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
7901
            foreach ($works as $work) {
7902
                $link = Display::url(
7903
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7904
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
7905
                    ['target' => '_blank']
7906
                );
7907
7908
                $return .= '<li class="list-group-item lp_resource_element">';
7909
                $return .= '<a class="moved" href="#">';
7910
                $return .= Display::return_icon(
7911
                    'move_everywhere.png',
7912
                    get_lang('Move'),
7913
                    [],
7914
                    ICON_SIZE_TINY
7915
                );
7916
                $return .= '</a> ';
7917
7918
                $return .= $icon;
7919
                $return .= Display::url(
7920
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
7921
                    api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
7922
                    [
7923
                        'class' => 'moved link_with_id',
7924
                        'data-id' => $work['iid'],
7925
                        'data_type' => TOOL_STUDENTPUBLICATION,
7926
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
7927
                    ]
7928
                );
7929
7930
                $return .= '</li>';
7931
            }
7932
        }
7933
7934
        $return .= '</ul>';
7935
7936
        return $return;
7937
    }
7938
7939
    /**
7940
     * Creates a list with all the forums in it.
7941
     *
7942
     * @return string
7943
     */
7944
    public function get_forums()
7945
    {
7946
        $forumCategories = get_forum_categories();
7947
        $forumsInNoCategory = get_forums_in_category(0);
7948
        if (!empty($forumsInNoCategory)) {
7949
            $forumCategories = array_merge(
7950
                $forumCategories,
7951
                [
7952
                    [
7953
                        'cat_id' => 0,
7954
                        'session_id' => 0,
7955
                        'visibility' => 1,
7956
                        'cat_comment' => null,
7957
                    ],
7958
                ]
7959
            );
7960
        }
7961
7962
        $a_forums = [];
7963
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7964
        $sessionEntity = api_get_session_entity(api_get_session_id());
7965
7966
        foreach ($forumCategories as $forumCategory) {
7967
            // The forums in this category.
7968
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
7969
            if (!empty($forumsInCategory)) {
7970
                foreach ($forumsInCategory as $forum) {
7971
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
7972
                        $a_forums[] = $forum;
7973
                    }
7974
                }
7975
            }
7976
        }
7977
7978
        $return = '<ul class="list-group lp_resource">';
7979
7980
        // First add link
7981
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7982
        $return .= Display::return_icon('new_forum.png');
7983
        $return .= Display::url(
7984
            get_lang('Create a new forum'),
7985
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
7986
                'action' => 'add',
7987
                'content' => 'forum',
7988
                'lp_id' => $this->lp_id,
7989
            ]),
7990
            ['title' => get_lang('Create a new forum')]
7991
        );
7992
        $return .= '</li>';
7993
7994
        $return .= '<script>
7995
            function toggle_forum(forum_id) {
7996
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
7997
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
7998
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7999
                } else {
8000
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8001
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8002
                }
8003
            }
8004
        </script>';
8005
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8006
        foreach ($a_forums as $forum) {
8007
            $forumId = $forum->getIid();
8008
            $title = Security::remove_XSS($forum->getForumTitle());
8009
            $link = Display::url(
8010
                Display::return_icon('preview_view.png', get_lang('Preview')),
8011
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8012
                ['target' => '_blank']
8013
            );
8014
8015
            $return .= '<li class="list-group-item lp_resource_element">';
8016
            $return .= '<a class="moved" href="#">';
8017
            $return .= $moveIcon;
8018
            $return .= ' </a>';
8019
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8020
8021
            $moveLink = Display::url(
8022
                $title.' '.$link,
8023
                api_get_self().'?'.
8024
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
8025
                [
8026
                    'class' => 'moved link_with_id',
8027
                    'data-id' => $forumId,
8028
                    'data_type' => TOOL_FORUM,
8029
                    'title' => $title,
8030
                    'style' => 'vertical-align:middle',
8031
                ]
8032
            );
8033
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8034
                            <img
8035
                                src="'.Display::returnIconPath('add.png').'"
8036
                                id="forum_'.$forumId.'_opener" align="absbottom"
8037
                             />
8038
                        </a>
8039
                        '.$moveLink;
8040
            $return .= '</li>';
8041
8042
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8043
            $threads = get_threads($forumId);
8044
            if (is_array($threads)) {
8045
                foreach ($threads as $thread) {
8046
                    $threadId = $thread->getIid();
8047
                    $link = Display::url(
8048
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8049
                        api_get_path(WEB_CODE_PATH).
8050
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8051
                        ['target' => '_blank']
8052
                    );
8053
8054
                    $return .= '<li class="list-group-item lp_resource_element">';
8055
                    $return .= '&nbsp;<a class="moved" href="#">';
8056
                    $return .= $moveIcon;
8057
                    $return .= ' </a>';
8058
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8059
                    $return .= '<a
8060
                        class="moved link_with_id"
8061
                        data-id="'.$thread->getIid().'"
8062
                        data_type="'.TOOL_THREAD.'"
8063
                        title="'.$thread->getThreadTitle().'"
8064
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
8065
                        >'.
8066
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8067
                    $return .= '</li>';
8068
                }
8069
            }
8070
            $return .= '</div>';
8071
        }
8072
        $return .= '</ul>';
8073
8074
        return $return;
8075
    }
8076
8077
    /**
8078
     * // TODO: The output encoding should be equal to the system encoding.
8079
     *
8080
     * Exports the learning path as a SCORM package. This is the main function that
8081
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8082
     * whole thing and returns the zip.
8083
     *
8084
     * This method needs to be called in PHP5, as it will fail with non-adequate
8085
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8086
     * you need to call it on a learnpath object.
8087
     *
8088
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8089
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8090
     * path has been modified, it should use the generic method here below.
8091
     *
8092
     * @return string Returns the zip package string, or null if error
8093
     */
8094
    public function scormExport()
8095
    {
8096
        api_set_more_memory_and_time_limits();
8097
8098
        $_course = api_get_course_info();
8099
        $course_id = $_course['real_id'];
8100
        // Create the zip handler (this will remain available throughout the method).
8101
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8102
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8103
        $temp_dir_short = uniqid('scorm_export', true);
8104
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8105
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8106
        $zip_folder = new PclZip($temp_zip_file);
8107
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8108
        $root_path = $main_path = api_get_path(SYS_PATH);
8109
        $files_cleanup = [];
8110
8111
        // Place to temporarily stash the zip file.
8112
        // create the temp dir if it doesn't exist
8113
        // or do a cleanup before creating the zip file.
8114
        if (!is_dir($temp_zip_dir)) {
8115
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8116
        } else {
8117
            // Cleanup: Check the temp dir for old files and delete them.
8118
            $handle = opendir($temp_zip_dir);
8119
            while (false !== ($file = readdir($handle))) {
8120
                if ('.' != $file && '..' != $file) {
8121
                    unlink("$temp_zip_dir/$file");
8122
                }
8123
            }
8124
            closedir($handle);
8125
        }
8126
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8127
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8128
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8129
        ) {
8130
            // Remove the possible . at the end of the path.
8131
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8132
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8133
            mkdir(
8134
                $dest_path_to_scorm_folder,
8135
                api_get_permissions_for_new_directories(),
8136
                true
8137
            );
8138
            copyr(
8139
                $current_course_path.'/scorm/'.$this->path,
8140
                $dest_path_to_scorm_folder,
8141
                ['imsmanifest'],
8142
                $zip_files
8143
            );
8144
        }
8145
8146
        // Build a dummy imsmanifest structure.
8147
        // Do not add to the zip yet (we still need it).
8148
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8149
        // Aggregation Model official document, section "2.3 Content Packaging".
8150
        // We are going to build a UTF-8 encoded manifest.
8151
        // Later we will recode it to the desired (and supported) encoding.
8152
        $xmldoc = new DOMDocument('1.0');
8153
        $root = $xmldoc->createElement('manifest');
8154
        $root->setAttribute('identifier', 'SingleCourseManifest');
8155
        $root->setAttribute('version', '1.1');
8156
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8157
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8158
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8159
        $root->setAttribute(
8160
            'xsi:schemaLocation',
8161
            'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'
8162
        );
8163
        // Build mandatory sub-root container elements.
8164
        $metadata = $xmldoc->createElement('metadata');
8165
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8166
        $metadata->appendChild($md_schema);
8167
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8168
        $metadata->appendChild($md_schemaversion);
8169
        $root->appendChild($metadata);
8170
8171
        $organizations = $xmldoc->createElement('organizations');
8172
        $resources = $xmldoc->createElement('resources');
8173
8174
        // Build the only organization we will use in building our learnpaths.
8175
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8176
        $organization = $xmldoc->createElement('organization');
8177
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8178
        // To set the title of the SCORM entity (=organization), we take the name given
8179
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8180
        // learning path charset) as it is the encoding that defines how it is stored
8181
        // in the database. Then we convert it to HTML entities again as the "&" character
8182
        // alone is not authorized in XML (must be &amp;).
8183
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8184
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8185
        $organization->appendChild($org_title);
8186
        $folder_name = 'document';
8187
8188
        // Removes the learning_path/scorm_folder path when exporting see #4841
8189
        $path_to_remove = '';
8190
        $path_to_replace = '';
8191
        $result = $this->generate_lp_folder($_course);
8192
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8193
            $path_to_remove = 'document'.$result['dir'];
8194
            $path_to_replace = $folder_name.'/';
8195
        }
8196
8197
        // Fixes chamilo scorm exports
8198
        if ('chamilo_scorm_export' === $this->ref) {
8199
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8200
        }
8201
8202
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8203
        $link_updates = [];
8204
        $links_to_create = [];
8205
        foreach ($this->ordered_items as $index => $itemId) {
8206
            /** @var learnpathItem $item */
8207
            $item = $this->items[$itemId];
8208
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8209
                // Get included documents from this item.
8210
                if ('sco' === $item->type) {
8211
                    $inc_docs = $item->get_resources_from_source(
8212
                        null,
8213
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8214
                    );
8215
                } else {
8216
                    $inc_docs = $item->get_resources_from_source();
8217
                }
8218
8219
                // Give a child element <item> to the <organization> element.
8220
                $my_item_id = $item->get_id();
8221
                $my_item = $xmldoc->createElement('item');
8222
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8223
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8224
                $my_item->setAttribute('isvisible', 'true');
8225
                // Give a child element <title> to the <item> element.
8226
                $my_title = $xmldoc->createElement(
8227
                    'title',
8228
                    htmlspecialchars(
8229
                        api_utf8_encode($item->get_title()),
8230
                        ENT_QUOTES,
8231
                        'UTF-8'
8232
                    )
8233
                );
8234
                $my_item->appendChild($my_title);
8235
                // Give a child element <adlcp:prerequisites> to the <item> element.
8236
                $my_prereqs = $xmldoc->createElement(
8237
                    'adlcp:prerequisites',
8238
                    $this->get_scorm_prereq_string($my_item_id)
8239
                );
8240
                $my_prereqs->setAttribute('type', 'aicc_script');
8241
                $my_item->appendChild($my_prereqs);
8242
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8243
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8244
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8245
                //$xmldoc->createElement('adlcp:timelimitaction','');
8246
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8247
                //$xmldoc->createElement('adlcp:datafromlms','');
8248
                // Give a child element <adlcp:masteryscore> to the <item> element.
8249
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8250
                $my_item->appendChild($my_masteryscore);
8251
8252
                // Attach this item to the organization element or hits parent if there is one.
8253
                if (!empty($item->parent) && 0 != $item->parent) {
8254
                    $children = $organization->childNodes;
8255
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8256
                    if (is_object($possible_parent)) {
8257
                        $possible_parent->appendChild($my_item);
8258
                    } else {
8259
                        if ($this->debug > 0) {
8260
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8261
                        }
8262
                    }
8263
                } else {
8264
                    if ($this->debug > 0) {
8265
                        error_log('No parent');
8266
                    }
8267
                    $organization->appendChild($my_item);
8268
                }
8269
8270
                // Get the path of the file(s) from the course directory root.
8271
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8272
                $my_xml_file_path = $my_file_path;
8273
                if (!empty($path_to_remove)) {
8274
                    // From docs
8275
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8276
8277
                    // From quiz
8278
                    if ('chamilo_scorm_export' === $this->ref) {
8279
                        $path_to_remove = 'scorm/'.$this->path.'/';
8280
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8281
                    }
8282
                }
8283
8284
                $my_sub_dir = dirname($my_file_path);
8285
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8286
                $my_xml_sub_dir = $my_sub_dir;
8287
                // Give a <resource> child to the <resources> element
8288
                $my_resource = $xmldoc->createElement('resource');
8289
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8290
                $my_resource->setAttribute('type', 'webcontent');
8291
                $my_resource->setAttribute('href', $my_xml_file_path);
8292
                // adlcp:scormtype can be either 'sco' or 'asset'.
8293
                if ('sco' === $item->type) {
8294
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8295
                } else {
8296
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8297
                }
8298
                // xml:base is the base directory to find the files declared in this resource.
8299
                $my_resource->setAttribute('xml:base', '');
8300
                // Give a <file> child to the <resource> element.
8301
                $my_file = $xmldoc->createElement('file');
8302
                $my_file->setAttribute('href', $my_xml_file_path);
8303
                $my_resource->appendChild($my_file);
8304
8305
                // Dependency to other files - not yet supported.
8306
                $i = 1;
8307
                if ($inc_docs) {
8308
                    foreach ($inc_docs as $doc_info) {
8309
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8310
                            continue;
8311
                        }
8312
                        $my_dep = $xmldoc->createElement('resource');
8313
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8314
                        $my_dep->setAttribute('identifier', $res_id);
8315
                        $my_dep->setAttribute('type', 'webcontent');
8316
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8317
                        $my_dep_file = $xmldoc->createElement('file');
8318
                        // Check type of URL.
8319
                        if ('remote' == $doc_info[1]) {
8320
                            // Remote file. Save url as is.
8321
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8322
                            $my_dep->setAttribute('xml:base', '');
8323
                        } elseif ('local' === $doc_info[1]) {
8324
                            switch ($doc_info[2]) {
8325
                                case 'url':
8326
                                    // Local URL - save path as url for now, don't zip file.
8327
                                    $abs_path = api_get_path(SYS_PATH).
8328
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8329
                                    $current_dir = dirname($abs_path);
8330
                                    $current_dir = str_replace('\\', '/', $current_dir);
8331
                                    $file_path = realpath($abs_path);
8332
                                    $file_path = str_replace('\\', '/', $file_path);
8333
                                    $my_dep_file->setAttribute('href', $file_path);
8334
                                    $my_dep->setAttribute('xml:base', '');
8335
                                    if (false !== strstr($file_path, $main_path)) {
8336
                                        // The calculated real path is really inside Chamilo's root path.
8337
                                        // Reduce file path to what's under the DocumentRoot.
8338
                                        $replace = $file_path;
8339
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8340
                                        $destinationFile = $file_path;
8341
8342
                                        if (false !== strstr($file_path, 'upload/users')) {
8343
                                            $pos = strpos($file_path, 'my_files/');
8344
                                            if (false !== $pos) {
8345
                                                $onlyDirectory = str_replace(
8346
                                                    'upload/users/',
8347
                                                    '',
8348
                                                    substr($file_path, $pos, strlen($file_path))
8349
                                                );
8350
                                            }
8351
                                            $replace = $onlyDirectory;
8352
                                            $destinationFile = $replace;
8353
                                        }
8354
                                        $zip_files_abs[] = $file_path;
8355
                                        $link_updates[$my_file_path][] = [
8356
                                            'orig' => $doc_info[0],
8357
                                            'dest' => $destinationFile,
8358
                                            'replace' => $replace,
8359
                                        ];
8360
                                        $my_dep_file->setAttribute('href', $file_path);
8361
                                        $my_dep->setAttribute('xml:base', '');
8362
                                    } elseif (empty($file_path)) {
8363
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8364
                                        $file_path = str_replace('//', '/', $file_path);
8365
                                        if (file_exists($file_path)) {
8366
                                            // We get the relative path.
8367
                                            $file_path = substr($file_path, strlen($current_dir));
8368
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8369
                                            $link_updates[$my_file_path][] = [
8370
                                                'orig' => $doc_info[0],
8371
                                                'dest' => $file_path,
8372
                                            ];
8373
                                            $my_dep_file->setAttribute('href', $file_path);
8374
                                            $my_dep->setAttribute('xml:base', '');
8375
                                        }
8376
                                    }
8377
                                    break;
8378
                                case 'abs':
8379
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8380
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8381
                                    $my_dep->setAttribute('xml:base', '');
8382
8383
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8384
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8385
                                    $abs_img_path_without_subdir = $doc_info[0];
8386
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8387
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8388
                                    if (0 === $pos) {
8389
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8390
                                    }
8391
8392
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8393
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8394
8395
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8396
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8397
                                    // Check if the current document is in that path.
8398
                                    if (false !== strstr($file_path, $cur_path)) {
8399
                                        $destinationFile = substr($file_path, strlen($cur_path));
8400
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8401
8402
                                        $fileToTest = $cur_path.$my_file_path;
8403
                                        if (!empty($path_to_remove)) {
8404
                                            $fileToTest = str_replace(
8405
                                                $path_to_remove.'/',
8406
                                                $path_to_replace,
8407
                                                $cur_path.$my_file_path
8408
                                            );
8409
                                        }
8410
8411
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8412
8413
                                        // Put the current document in the zip (this array is the array
8414
                                        // that will manage documents already in the course folder - relative).
8415
                                        $zip_files[] = $filePathNoCoursePart;
8416
                                        // Update the links to the current document in the
8417
                                        // containing document (make them relative).
8418
                                        $link_updates[$my_file_path][] = [
8419
                                            'orig' => $doc_info[0],
8420
                                            'dest' => $destinationFile,
8421
                                            'replace' => $relative_path,
8422
                                        ];
8423
8424
                                        $my_dep_file->setAttribute('href', $file_path);
8425
                                        $my_dep->setAttribute('xml:base', '');
8426
                                    } elseif (false !== strstr($file_path, $main_path)) {
8427
                                        // The calculated real path is really inside Chamilo's root path.
8428
                                        // Reduce file path to what's under the DocumentRoot.
8429
                                        $file_path = substr($file_path, strlen($root_path));
8430
                                        $zip_files_abs[] = $file_path;
8431
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8432
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8433
                                        $my_dep->setAttribute('xml:base', '');
8434
                                    } elseif (empty($file_path)) {
8435
                                        // Probably this is an image inside "/main" directory
8436
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8437
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8438
8439
                                        if (file_exists($file_path)) {
8440
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8441
                                                // We get the relative path.
8442
                                                $pos = strpos($file_path, 'main/default_course_document/');
8443
                                                if (false !== $pos) {
8444
                                                    $onlyDirectory = str_replace(
8445
                                                        'main/default_course_document/',
8446
                                                        '',
8447
                                                        substr($file_path, $pos, strlen($file_path))
8448
                                                    );
8449
                                                }
8450
8451
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8452
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8453
                                                $zip_files_abs[] = $fileAbs;
8454
                                                $link_updates[$my_file_path][] = [
8455
                                                    'orig' => $doc_info[0],
8456
                                                    'dest' => $destinationFile,
8457
                                                ];
8458
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8459
                                                $my_dep->setAttribute('xml:base', '');
8460
                                            }
8461
                                        }
8462
                                    }
8463
                                    break;
8464
                                case 'rel':
8465
                                    // Path relative to the current document.
8466
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8467
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8468
                                        // Relative path going up.
8469
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8470
                                        $current_dir = str_replace('\\', '/', $current_dir);
8471
                                        $file_path = realpath($current_dir.$doc_info[0]);
8472
                                        $file_path = str_replace('\\', '/', $file_path);
8473
                                        if (false !== strstr($file_path, $main_path)) {
8474
                                            // The calculated real path is really inside Chamilo's root path.
8475
                                            // Reduce file path to what's under the DocumentRoot.
8476
                                            $file_path = substr($file_path, strlen($root_path));
8477
                                            $zip_files_abs[] = $file_path;
8478
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8479
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8480
                                            $my_dep->setAttribute('xml:base', '');
8481
                                        }
8482
                                    } else {
8483
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8484
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8485
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8486
                                    }
8487
                                    break;
8488
                                default:
8489
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8490
                                    $my_dep->setAttribute('xml:base', '');
8491
                                    break;
8492
                            }
8493
                        }
8494
                        $my_dep->appendChild($my_dep_file);
8495
                        $resources->appendChild($my_dep);
8496
                        $dependency = $xmldoc->createElement('dependency');
8497
                        $dependency->setAttribute('identifierref', $res_id);
8498
                        $my_resource->appendChild($dependency);
8499
                        $i++;
8500
                    }
8501
                }
8502
                $resources->appendChild($my_resource);
8503
                $zip_files[] = $my_file_path;
8504
            } else {
8505
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8506
                switch ($item->type) {
8507
                    case TOOL_LINK:
8508
                        $my_item = $xmldoc->createElement('item');
8509
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8510
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8511
                        $my_item->setAttribute('isvisible', 'true');
8512
                        // Give a child element <title> to the <item> element.
8513
                        $my_title = $xmldoc->createElement(
8514
                            'title',
8515
                            htmlspecialchars(
8516
                                api_utf8_encode($item->get_title()),
8517
                                ENT_QUOTES,
8518
                                'UTF-8'
8519
                            )
8520
                        );
8521
                        $my_item->appendChild($my_title);
8522
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8523
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8524
                        $my_prereqs->setAttribute('type', 'aicc_script');
8525
                        $my_item->appendChild($my_prereqs);
8526
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8527
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8528
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8529
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8530
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8531
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8532
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8533
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8534
                        $my_item->appendChild($my_masteryscore);
8535
8536
                        // Attach this item to the organization element or its parent if there is one.
8537
                        if (!empty($item->parent) && 0 != $item->parent) {
8538
                            $children = $organization->childNodes;
8539
                            for ($i = 0; $i < $children->length; $i++) {
8540
                                $item_temp = $children->item($i);
8541
                                if ('item' == $item_temp->nodeName) {
8542
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8543
                                        $item_temp->appendChild($my_item);
8544
                                    }
8545
                                }
8546
                            }
8547
                        } else {
8548
                            $organization->appendChild($my_item);
8549
                        }
8550
8551
                        $my_file_path = 'link_'.$item->get_id().'.html';
8552
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8553
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8554
                        $rs = Database::query($sql);
8555
                        if ($link = Database::fetch_array($rs)) {
8556
                            $url = $link['url'];
8557
                            $title = stripslashes($link['title']);
8558
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8559
                            $my_xml_file_path = $my_file_path;
8560
                            $my_sub_dir = dirname($my_file_path);
8561
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8562
                            $my_xml_sub_dir = $my_sub_dir;
8563
                            // Give a <resource> child to the <resources> element.
8564
                            $my_resource = $xmldoc->createElement('resource');
8565
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8566
                            $my_resource->setAttribute('type', 'webcontent');
8567
                            $my_resource->setAttribute('href', $my_xml_file_path);
8568
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8569
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8570
                            // xml:base is the base directory to find the files declared in this resource.
8571
                            $my_resource->setAttribute('xml:base', '');
8572
                            // give a <file> child to the <resource> element.
8573
                            $my_file = $xmldoc->createElement('file');
8574
                            $my_file->setAttribute('href', $my_xml_file_path);
8575
                            $my_resource->appendChild($my_file);
8576
                            $resources->appendChild($my_resource);
8577
                        }
8578
                        break;
8579
                    case TOOL_QUIZ:
8580
                        $exe_id = $item->path;
8581
                        // Should be using ref when everything will be cleaned up in this regard.
8582
                        $exe = new Exercise();
8583
                        $exe->read($exe_id);
8584
                        $my_item = $xmldoc->createElement('item');
8585
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8586
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8587
                        $my_item->setAttribute('isvisible', 'true');
8588
                        // Give a child element <title> to the <item> element.
8589
                        $my_title = $xmldoc->createElement(
8590
                            'title',
8591
                            htmlspecialchars(
8592
                                api_utf8_encode($item->get_title()),
8593
                                ENT_QUOTES,
8594
                                'UTF-8'
8595
                            )
8596
                        );
8597
                        $my_item->appendChild($my_title);
8598
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8599
                        $my_item->appendChild($my_max_score);
8600
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8601
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8602
                        $my_prereqs->setAttribute('type', 'aicc_script');
8603
                        $my_item->appendChild($my_prereqs);
8604
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8605
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8606
                        $my_item->appendChild($my_masteryscore);
8607
8608
                        // Attach this item to the organization element or hits parent if there is one.
8609
                        if (!empty($item->parent) && 0 != $item->parent) {
8610
                            $children = $organization->childNodes;
8611
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8612
                            if ($possible_parent) {
8613
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
8614
                                    $possible_parent->appendChild($my_item);
8615
                                }
8616
                            }
8617
                        } else {
8618
                            $organization->appendChild($my_item);
8619
                        }
8620
8621
                        // Get the path of the file(s) from the course directory root
8622
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8623
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
8624
                        // Write the contents of the exported exercise into a (big) html file
8625
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
8626
                        $scormExercise = new ScormExercise($exe, true);
8627
                        $contents = $scormExercise->export();
8628
8629
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
8630
                        $res = file_put_contents($tmp_file_path, $contents);
8631
                        if (false === $res) {
8632
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
8633
                        }
8634
                        $files_cleanup[] = $tmp_file_path;
8635
                        $my_xml_file_path = $my_file_path;
8636
                        $my_sub_dir = dirname($my_file_path);
8637
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8638
                        $my_xml_sub_dir = $my_sub_dir;
8639
                        // Give a <resource> child to the <resources> element.
8640
                        $my_resource = $xmldoc->createElement('resource');
8641
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8642
                        $my_resource->setAttribute('type', 'webcontent');
8643
                        $my_resource->setAttribute('href', $my_xml_file_path);
8644
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8645
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
8646
                        // xml:base is the base directory to find the files declared in this resource.
8647
                        $my_resource->setAttribute('xml:base', '');
8648
                        // Give a <file> child to the <resource> element.
8649
                        $my_file = $xmldoc->createElement('file');
8650
                        $my_file->setAttribute('href', $my_xml_file_path);
8651
                        $my_resource->appendChild($my_file);
8652
8653
                        // Get included docs.
8654
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
8655
8656
                        // Dependency to other files - not yet supported.
8657
                        $i = 1;
8658
                        foreach ($inc_docs as $doc_info) {
8659
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
8660
                                continue;
8661
                            }
8662
                            $my_dep = $xmldoc->createElement('resource');
8663
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8664
                            $my_dep->setAttribute('identifier', $res_id);
8665
                            $my_dep->setAttribute('type', 'webcontent');
8666
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
8667
                            $my_dep_file = $xmldoc->createElement('file');
8668
                            // Check type of URL.
8669
                            if ('remote' == $doc_info[1]) {
8670
                                // Remote file. Save url as is.
8671
                                $my_dep_file->setAttribute('href', $doc_info[0]);
8672
                                $my_dep->setAttribute('xml:base', '');
8673
                            } elseif ('local' == $doc_info[1]) {
8674
                                switch ($doc_info[2]) {
8675
                                    case 'url': // Local URL - save path as url for now, don't zip file.
8676
                                        // Save file but as local file (retrieve from URL).
8677
                                        $abs_path = api_get_path(SYS_PATH).
8678
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8679
                                        $current_dir = dirname($abs_path);
8680
                                        $current_dir = str_replace('\\', '/', $current_dir);
8681
                                        $file_path = realpath($abs_path);
8682
                                        $file_path = str_replace('\\', '/', $file_path);
8683
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8684
                                        $my_dep->setAttribute('xml:base', '');
8685
                                        if (false !== strstr($file_path, $main_path)) {
8686
                                            // The calculated real path is really inside the chamilo root path.
8687
                                            // Reduce file path to what's under the DocumentRoot.
8688
                                            $file_path = substr($file_path, strlen($root_path));
8689
                                            $zip_files_abs[] = $file_path;
8690
                                            $link_updates[$my_file_path][] = [
8691
                                                'orig' => $doc_info[0],
8692
                                                'dest' => 'document/'.$file_path,
8693
                                            ];
8694
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8695
                                            $my_dep->setAttribute('xml:base', '');
8696
                                        } elseif (empty($file_path)) {
8697
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8698
                                            $file_path = str_replace('//', '/', $file_path);
8699
                                            if (file_exists($file_path)) {
8700
                                                $file_path = substr($file_path, strlen($current_dir));
8701
                                                // We get the relative path.
8702
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8703
                                                $link_updates[$my_file_path][] = [
8704
                                                    'orig' => $doc_info[0],
8705
                                                    'dest' => 'document/'.$file_path,
8706
                                                ];
8707
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8708
                                                $my_dep->setAttribute('xml:base', '');
8709
                                            }
8710
                                        }
8711
                                        break;
8712
                                    case 'abs':
8713
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8714
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8715
                                        $current_dir = str_replace('\\', '/', $current_dir);
8716
                                        $file_path = realpath($doc_info[0]);
8717
                                        $file_path = str_replace('\\', '/', $file_path);
8718
                                        $my_dep_file->setAttribute('href', $file_path);
8719
                                        $my_dep->setAttribute('xml:base', '');
8720
8721
                                        if (false !== strstr($file_path, $main_path)) {
8722
                                            // The calculated real path is really inside the chamilo root path.
8723
                                            // Reduce file path to what's under the DocumentRoot.
8724
                                            $file_path = substr($file_path, strlen($root_path));
8725
                                            $zip_files_abs[] = $file_path;
8726
                                            $link_updates[$my_file_path][] = [
8727
                                                'orig' => $doc_info[0],
8728
                                                'dest' => $file_path,
8729
                                            ];
8730
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8731
                                            $my_dep->setAttribute('xml:base', '');
8732
                                        } elseif (empty($file_path)) {
8733
                                            $docSysPartPath = str_replace(
8734
                                                api_get_path(REL_COURSE_PATH),
8735
                                                '',
8736
                                                $doc_info[0]
8737
                                            );
8738
8739
                                            $docSysPartPathNoCourseCode = str_replace(
8740
                                                $_course['directory'].'/',
8741
                                                '',
8742
                                                $docSysPartPath
8743
                                            );
8744
8745
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
8746
                                            if (file_exists($docSysPath)) {
8747
                                                $file_path = $docSysPartPathNoCourseCode;
8748
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8749
                                                $link_updates[$my_file_path][] = [
8750
                                                    'orig' => $doc_info[0],
8751
                                                    'dest' => $file_path,
8752
                                                ];
8753
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8754
                                                $my_dep->setAttribute('xml:base', '');
8755
                                            }
8756
                                        }
8757
                                        break;
8758
                                    case 'rel':
8759
                                        // Path relative to the current document. Save xml:base as current document's
8760
                                        // directory and save file in zip as subdir.file_path
8761
                                        if ('..' === substr($doc_info[0], 0, 2)) {
8762
                                            // Relative path going up.
8763
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8764
                                            $current_dir = str_replace('\\', '/', $current_dir);
8765
                                            $file_path = realpath($current_dir.$doc_info[0]);
8766
                                            $file_path = str_replace('\\', '/', $file_path);
8767
                                            if (false !== strstr($file_path, $main_path)) {
8768
                                                // The calculated real path is really inside Chamilo's root path.
8769
                                                // Reduce file path to what's under the DocumentRoot.
8770
8771
                                                $file_path = substr($file_path, strlen($root_path));
8772
                                                $file_path_dest = $file_path;
8773
8774
                                                // File path is courses/CHAMILO/document/....
8775
                                                $info_file_path = explode('/', $file_path);
8776
                                                if ('courses' == $info_file_path[0]) {
8777
                                                    // Add character "/" in file path.
8778
                                                    $file_path_dest = 'document/'.$file_path;
8779
                                                }
8780
                                                $zip_files_abs[] = $file_path;
8781
8782
                                                $link_updates[$my_file_path][] = [
8783
                                                    'orig' => $doc_info[0],
8784
                                                    'dest' => $file_path_dest,
8785
                                                ];
8786
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8787
                                                $my_dep->setAttribute('xml:base', '');
8788
                                            }
8789
                                        } else {
8790
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8791
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
8792
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8793
                                        }
8794
                                        break;
8795
                                    default:
8796
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
8797
                                        $my_dep->setAttribute('xml:base', '');
8798
                                        break;
8799
                                }
8800
                            }
8801
                            $my_dep->appendChild($my_dep_file);
8802
                            $resources->appendChild($my_dep);
8803
                            $dependency = $xmldoc->createElement('dependency');
8804
                            $dependency->setAttribute('identifierref', $res_id);
8805
                            $my_resource->appendChild($dependency);
8806
                            $i++;
8807
                        }
8808
                        $resources->appendChild($my_resource);
8809
                        $zip_files[] = $my_file_path;
8810
                        break;
8811
                    default:
8812
                        // Get the path of the file(s) from the course directory root
8813
                        $my_file_path = 'non_exportable.html';
8814
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
8815
                        $my_xml_file_path = $my_file_path;
8816
                        $my_sub_dir = dirname($my_file_path);
8817
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8818
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
8819
                        $my_xml_sub_dir = $my_sub_dir;
8820
                        // Give a <resource> child to the <resources> element.
8821
                        $my_resource = $xmldoc->createElement('resource');
8822
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8823
                        $my_resource->setAttribute('type', 'webcontent');
8824
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
8825
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8826
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
8827
                        // xml:base is the base directory to find the files declared in this resource.
8828
                        $my_resource->setAttribute('xml:base', '');
8829
                        // Give a <file> child to the <resource> element.
8830
                        $my_file = $xmldoc->createElement('file');
8831
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
8832
                        $my_resource->appendChild($my_file);
8833
                        $resources->appendChild($my_resource);
8834
                        break;
8835
                }
8836
            }
8837
        }
8838
        $organizations->appendChild($organization);
8839
        $root->appendChild($organizations);
8840
        $root->appendChild($resources);
8841
        $xmldoc->appendChild($root);
8842
8843
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
8844
8845
        // then add the file to the zip, then destroy the file (this is done automatically).
8846
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
8847
        foreach ($zip_files as $file_path) {
8848
            if (empty($file_path)) {
8849
                continue;
8850
            }
8851
8852
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
8853
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
8854
8855
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
8856
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
8857
            }
8858
8859
            $this->create_path($dest_file);
8860
            @copy($filePath, $dest_file);
8861
8862
            // Check if the file needs a link update.
8863
            if (in_array($file_path, array_keys($link_updates))) {
8864
                $string = file_get_contents($dest_file);
8865
                unlink($dest_file);
8866
                foreach ($link_updates[$file_path] as $old_new) {
8867
                    // This is an ugly hack that allows .flv files to be found by the flv player that
8868
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
8869
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
8870
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
8871
                    if ('flv' === substr($old_new['dest'], -3) &&
8872
                        'main/' === substr($old_new['dest'], 0, 5)
8873
                    ) {
8874
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
8875
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
8876
                        'video/' === substr($old_new['dest'], 0, 6)
8877
                    ) {
8878
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
8879
                    }
8880
8881
                    // Fix to avoid problems with default_course_document
8882
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
8883
                        $newDestination = $old_new['dest'];
8884
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
8885
                            $newDestination = $old_new['replace'];
8886
                        }
8887
                    } else {
8888
                        $newDestination = str_replace('document/', '', $old_new['dest']);
8889
                    }
8890
                    $string = str_replace($old_new['orig'], $newDestination, $string);
8891
8892
                    // Add files inside the HTMLs
8893
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
8894
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
8895
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
8896
                        copy(
8897
                            $sys_course_path.$new_path,
8898
                            $destinationFile
8899
                        );
8900
                    }
8901
                }
8902
                file_put_contents($dest_file, $string);
8903
            }
8904
8905
            if (file_exists($filePath) && $copyAll) {
8906
                $extension = $this->get_extension($filePath);
8907
                if (in_array($extension, ['html', 'html'])) {
8908
                    $containerOrigin = dirname($filePath);
8909
                    $containerDestination = dirname($dest_file);
8910
8911
                    $finder = new Finder();
8912
                    $finder->files()->in($containerOrigin)
8913
                        ->notName('*_DELETED_*')
8914
                        ->exclude('share_folder')
8915
                        ->exclude('chat_files')
8916
                        ->exclude('certificates')
8917
                    ;
8918
8919
                    if (is_dir($containerOrigin) &&
8920
                        is_dir($containerDestination)
8921
                    ) {
8922
                        $fs = new Filesystem();
8923
                        $fs->mirror(
8924
                            $containerOrigin,
8925
                            $containerDestination,
8926
                            $finder
8927
                        );
8928
                    }
8929
                }
8930
            }
8931
        }
8932
8933
        foreach ($zip_files_abs as $file_path) {
8934
            if (empty($file_path)) {
8935
                continue;
8936
            }
8937
8938
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
8939
                continue;
8940
            }
8941
8942
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
8943
            if (false !== strstr($file_path, 'upload/users')) {
8944
                $pos = strpos($file_path, 'my_files/');
8945
                if (false !== $pos) {
8946
                    $onlyDirectory = str_replace(
8947
                        'upload/users/',
8948
                        '',
8949
                        substr($file_path, $pos, strlen($file_path))
8950
                    );
8951
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
8952
                }
8953
            }
8954
8955
            if (false !== strstr($file_path, 'default_course_document/')) {
8956
                $replace = str_replace('/main', '', $file_path);
8957
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
8958
            }
8959
8960
            if (empty($dest_file)) {
8961
                continue;
8962
            }
8963
8964
            $this->create_path($dest_file);
8965
            copy($main_path.$file_path, $dest_file);
8966
            // Check if the file needs a link update.
8967
            if (in_array($file_path, array_keys($link_updates))) {
8968
                $string = file_get_contents($dest_file);
8969
                unlink($dest_file);
8970
                foreach ($link_updates[$file_path] as $old_new) {
8971
                    // This is an ugly hack that allows .flv files to be found by the flv player that
8972
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
8973
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
8974
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
8975
                    if ('flv' == substr($old_new['dest'], -3) &&
8976
                        'main/' == substr($old_new['dest'], 0, 5)
8977
                    ) {
8978
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
8979
                    }
8980
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
8981
                }
8982
                file_put_contents($dest_file, $string);
8983
            }
8984
        }
8985
8986
        if (is_array($links_to_create)) {
8987
            foreach ($links_to_create as $file => $link) {
8988
                $content = '<!DOCTYPE html><head>
8989
                            <meta charset="'.api_get_language_isocode().'" />
8990
                            <title>'.$link['title'].'</title>
8991
                            </head>
8992
                            <body dir="'.api_get_text_direction().'">
8993
                            <div style="text-align:center">
8994
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
8995
                            </body>
8996
                            </html>';
8997
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
8998
            }
8999
        }
9000
9001
        // Add non exportable message explanation.
9002
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9003
        $file_content = '<!DOCTYPE html><head>
9004
                        <meta charset="'.api_get_language_isocode().'" />
9005
                        <title>'.$lang_not_exportable.'</title>
9006
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9007
                        </head>
9008
                        <body dir="'.api_get_text_direction().'">';
9009
        $file_content .=
9010
            <<<EOD
9011
                    <style>
9012
            .error-message {
9013
                font-family: arial, verdana, helvetica, sans-serif;
9014
                border-width: 1px;
9015
                border-style: solid;
9016
                left: 50%;
9017
                margin: 10px auto;
9018
                min-height: 30px;
9019
                padding: 5px;
9020
                right: 50%;
9021
                width: 500px;
9022
                background-color: #FFD1D1;
9023
                border-color: #FF0000;
9024
                color: #000;
9025
            }
9026
        </style>
9027
    <body>
9028
        <div class="error-message">
9029
            $lang_not_exportable
9030
        </div>
9031
    </body>
9032
</html>
9033
EOD;
9034
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9035
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9036
        }
9037
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9038
9039
        // Add the extra files that go along with a SCORM package.
9040
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9041
9042
        $fs = new Filesystem();
9043
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9044
9045
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9046
        $manifest = @$xmldoc->saveXML();
9047
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9048
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9049
        $zip_folder->add(
9050
            $archivePath.'/'.$temp_dir_short,
9051
            PCLZIP_OPT_REMOVE_PATH,
9052
            $archivePath.'/'.$temp_dir_short.'/'
9053
        );
9054
9055
        // Clean possible temporary files.
9056
        foreach ($files_cleanup as $file) {
9057
            $res = unlink($file);
9058
            if (false === $res) {
9059
                error_log(
9060
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9061
                    0
9062
                );
9063
            }
9064
        }
9065
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9066
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9067
    }
9068
9069
    /**
9070
     * @param int $lp_id
9071
     *
9072
     * @return bool
9073
     */
9074
    public function scorm_export_to_pdf($lp_id)
9075
    {
9076
        $lp_id = (int) $lp_id;
9077
        $files_to_export = [];
9078
9079
        $sessionId = api_get_session_id();
9080
        $course_data = api_get_course_info($this->cc);
9081
9082
        $lp = Container::getLpRepository()->find($lp_id);
9083
        if (!empty($course_data)) {
9084
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9085
            $list = self::get_flat_ordered_items_list($lp);
9086
            if (!empty($list)) {
9087
                foreach ($list as $item_id) {
9088
                    $item = $this->items[$item_id];
9089
                    switch ($item->type) {
9090
                        case 'document':
9091
                            // Getting documents from a LP with chamilo documents
9092
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9093
                            // Try loading document from the base course.
9094
                            if (empty($file_data) && !empty($sessionId)) {
9095
                                $file_data = DocumentManager::get_document_data_by_id(
9096
                                    $item->path,
9097
                                    $this->cc,
9098
                                    false,
9099
                                    0
9100
                                );
9101
                            }
9102
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9103
                            if (file_exists($file_path)) {
9104
                                $files_to_export[] = [
9105
                                    'title' => $item->get_title(),
9106
                                    'path' => $file_path,
9107
                                ];
9108
                            }
9109
                            break;
9110
                        case 'asset': //commes from a scorm package generated by chamilo
9111
                        case 'sco':
9112
                            $file_path = $scorm_path.'/'.$item->path;
9113
                            if (file_exists($file_path)) {
9114
                                $files_to_export[] = [
9115
                                    'title' => $item->get_title(),
9116
                                    'path' => $file_path,
9117
                                ];
9118
                            }
9119
                            break;
9120
                        case 'dir':
9121
                            $files_to_export[] = [
9122
                                'title' => $item->get_title(),
9123
                                'path' => null,
9124
                            ];
9125
                            break;
9126
                    }
9127
                }
9128
            }
9129
9130
            $pdf = new PDF();
9131
            $result = $pdf->html_to_pdf(
9132
                $files_to_export,
9133
                $this->name,
9134
                $this->cc,
9135
                true,
9136
                true,
9137
                true,
9138
                $this->get_name()
9139
            );
9140
9141
            return $result;
9142
        }
9143
9144
        return false;
9145
    }
9146
9147
    /**
9148
     * Temp function to be moved in main_api or the best place around for this.
9149
     * Creates a file path if it doesn't exist.
9150
     *
9151
     * @param string $path
9152
     */
9153
    public function create_path($path)
9154
    {
9155
        $path_bits = explode('/', dirname($path));
9156
9157
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9158
        $path_built = IS_WINDOWS_OS ? '' : '/';
9159
        foreach ($path_bits as $bit) {
9160
            if (!empty($bit)) {
9161
                $new_path = $path_built.$bit;
9162
                if (is_dir($new_path)) {
9163
                    $path_built = $new_path.'/';
9164
                } else {
9165
                    mkdir($new_path, api_get_permissions_for_new_directories());
9166
                    $path_built = $new_path.'/';
9167
                }
9168
            }
9169
        }
9170
    }
9171
9172
    /**
9173
     * @param int    $lp_id
9174
     * @param string $status
9175
     */
9176
    public function set_autolaunch($lp_id, $status)
9177
    {
9178
        $course_id = api_get_course_int_id();
9179
        $lp_id = (int) $lp_id;
9180
        $status = (int) $status;
9181
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9182
9183
        // Setting everything to autolaunch = 0
9184
        $attributes['autolaunch'] = 0;
9185
        $where = [
9186
            'session_id = ? AND c_id = ? ' => [
9187
                api_get_session_id(),
9188
                $course_id,
9189
            ],
9190
        ];
9191
        Database::update($lp_table, $attributes, $where);
9192
        if (1 == $status) {
9193
            //Setting my lp_id to autolaunch = 1
9194
            $attributes['autolaunch'] = 1;
9195
            $where = [
9196
                'iid = ? AND session_id = ? AND c_id = ?' => [
9197
                    $lp_id,
9198
                    api_get_session_id(),
9199
                    $course_id,
9200
                ],
9201
            ];
9202
            Database::update($lp_table, $attributes, $where);
9203
        }
9204
    }
9205
9206
    /**
9207
     * Gets previous_item_id for the next element of the lp_item table.
9208
     *
9209
     * @author Isaac flores paz
9210
     *
9211
     * @return int Previous item ID
9212
     */
9213
    public function select_previous_item_id()
9214
    {
9215
        $course_id = api_get_course_int_id();
9216
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9217
9218
        // Get the max order of the items
9219
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9220
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9221
        $rs_max_order = Database::query($sql);
9222
        $row_max_order = Database::fetch_object($rs_max_order);
9223
        $max_order = $row_max_order->display_order;
9224
        // Get the previous item ID
9225
        $sql = "SELECT iid as previous FROM $table_lp_item
9226
                WHERE
9227
                    c_id = $course_id AND
9228
                    lp_id = ".$this->lp_id." AND
9229
                    display_order = '$max_order' ";
9230
        $rs_max = Database::query($sql);
9231
        $row_max = Database::fetch_object($rs_max);
9232
9233
        // Return the previous item ID
9234
        return $row_max->previous;
9235
    }
9236
9237
    /**
9238
     * Copies an LP.
9239
     */
9240
    public function copy()
9241
    {
9242
        // Course builder
9243
        $cb = new CourseBuilder();
9244
9245
        //Setting tools that will be copied
9246
        $cb->set_tools_to_build(['learnpaths']);
9247
9248
        //Setting elements that will be copied
9249
        $cb->set_tools_specific_id_list(
9250
            ['learnpaths' => [$this->lp_id]]
9251
        );
9252
9253
        $course = $cb->build();
9254
9255
        //Course restorer
9256
        $course_restorer = new CourseRestorer($course);
9257
        $course_restorer->set_add_text_in_items(true);
9258
        $course_restorer->set_tool_copy_settings(
9259
            ['learnpaths' => ['reset_dates' => true]]
9260
        );
9261
        $course_restorer->restore(
9262
            api_get_course_id(),
9263
            api_get_session_id(),
9264
            false,
9265
            false
9266
        );
9267
    }
9268
9269
    /**
9270
     * Verify document size.
9271
     *
9272
     * @param string $s
9273
     *
9274
     * @return bool
9275
     */
9276
    public static function verify_document_size($s)
9277
    {
9278
        $post_max = ini_get('post_max_size');
9279
        if ('M' == substr($post_max, -1, 1)) {
9280
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9281
        } elseif ('G' == substr($post_max, -1, 1)) {
9282
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9283
        }
9284
        $upl_max = ini_get('upload_max_filesize');
9285
        if ('M' == substr($upl_max, -1, 1)) {
9286
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9287
        } elseif ('G' == substr($upl_max, -1, 1)) {
9288
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9289
        }
9290
9291
        $repo = Container::getDocumentRepository();
9292
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9293
9294
        $course_max_space = DocumentManager::get_course_quota();
9295
        $total_size = filesize($s) + $documents_total_space;
9296
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9297
            return true;
9298
        }
9299
9300
        return false;
9301
    }
9302
9303
    /**
9304
     * Clear LP prerequisites.
9305
     */
9306
    public function clearPrerequisites()
9307
    {
9308
        $course_id = $this->get_course_int_id();
9309
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9310
        $lp_id = $this->get_id();
9311
        // Cleaning prerequisites
9312
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9313
                WHERE lp_id = $lp_id";
9314
        Database::query($sql);
9315
9316
        // Cleaning mastery score for exercises
9317
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9318
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
9319
        Database::query($sql);
9320
    }
9321
9322
    public function set_previous_step_as_prerequisite_for_all_items()
9323
    {
9324
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9325
        $course_id = $this->get_course_int_id();
9326
        $lp_id = $this->get_id();
9327
9328
        if (!empty($this->items)) {
9329
            $previous_item_id = null;
9330
            $previous_item_max = 0;
9331
            $previous_item_type = null;
9332
            $last_item_not_dir = null;
9333
            $last_item_not_dir_type = null;
9334
            $last_item_not_dir_max = null;
9335
9336
            foreach ($this->ordered_items as $itemId) {
9337
                $item = $this->getItem($itemId);
9338
                // if there was a previous item... (otherwise jump to set it)
9339
                if (!empty($previous_item_id)) {
9340
                    $current_item_id = $item->get_id(); //save current id
9341
                    if ('dir' != $item->get_type()) {
9342
                        // Current item is not a folder, so it qualifies to get a prerequisites
9343
                        if ('quiz' == $last_item_not_dir_type) {
9344
                            // if previous is quiz, mark its max score as default score to be achieved
9345
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9346
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9347
                            Database::query($sql);
9348
                        }
9349
                        // now simply update the prerequisite to set it to the last non-chapter item
9350
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9351
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
9352
                        Database::query($sql);
9353
                        // record item as 'non-chapter' reference
9354
                        $last_item_not_dir = $item->get_id();
9355
                        $last_item_not_dir_type = $item->get_type();
9356
                        $last_item_not_dir_max = $item->get_max();
9357
                    }
9358
                } else {
9359
                    if ('dir' != $item->get_type()) {
9360
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9361
                        $last_item_not_dir = $item->get_id();
9362
                        $last_item_not_dir_type = $item->get_type();
9363
                        $last_item_not_dir_max = $item->get_max();
9364
                    }
9365
                }
9366
                // Saving the item as "previous item" for the next loop
9367
                $previous_item_id = $item->get_id();
9368
                $previous_item_max = $item->get_max();
9369
                $previous_item_type = $item->get_type();
9370
            }
9371
        }
9372
    }
9373
9374
    /**
9375
     * @param array $params
9376
     *
9377
     * @return int
9378
     */
9379
    public static function createCategory($params)
9380
    {
9381
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9382
9383
        $item = new CLpCategory();
9384
        $item
9385
            ->setName($params['name'])
9386
            ->setParent($courseEntity)
9387
            ->addCourseLink($courseEntity, api_get_session_entity())
9388
        ;
9389
9390
        $repo = Container::getLpCategoryRepository();
9391
        $repo->create($item);
9392
9393
        return $item->getIid();
9394
    }
9395
9396
    /**
9397
     * @param array $params
9398
     */
9399
    public static function updateCategory($params)
9400
    {
9401
        $em = Database::getManager();
9402
        /** @var CLpCategory $item */
9403
        $item = $em->find(CLpCategory::class, $params['id']);
9404
        if ($item) {
9405
            $item->setName($params['name']);
9406
            $em->persist($item);
9407
            $em->flush();
9408
        }
9409
    }
9410
9411
    /**
9412
     * @param int $id
9413
     */
9414
    public static function moveUpCategory($id)
9415
    {
9416
        $id = (int) $id;
9417
        $em = Database::getManager();
9418
        /** @var CLpCategory $item */
9419
        $item = $em->find(CLpCategory::class, $id);
9420
        if ($item) {
9421
            $position = $item->getPosition() - 1;
9422
            $item->setPosition($position);
9423
            $em->persist($item);
9424
            $em->flush();
9425
        }
9426
    }
9427
9428
    /**
9429
     * @param int $id
9430
     */
9431
    public static function moveDownCategory($id)
9432
    {
9433
        $id = (int) $id;
9434
        $em = Database::getManager();
9435
        /** @var CLpCategory $item */
9436
        $item = $em->find(CLpCategory::class, $id);
9437
        if ($item) {
9438
            $position = $item->getPosition() + 1;
9439
            $item->setPosition($position);
9440
            $em->persist($item);
9441
            $em->flush();
9442
        }
9443
    }
9444
9445
    /**
9446
     * @param int $courseId
9447
     *
9448
     * @return int|mixed
9449
     */
9450
    public static function getCountCategories($courseId)
9451
    {
9452
        if (empty($courseId)) {
9453
            return 0;
9454
        }
9455
        $repo = Container::getLpCategoryRepository();
9456
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9457
        $qb->addSelect('count(resource)');
9458
9459
        return $qb->getSingleScalarResult();
9460
    }
9461
9462
    /**
9463
     * @param int $courseId
9464
     *
9465
     * @return CLpCategory[]
9466
     */
9467
    public static function getCategories($courseId)
9468
    {
9469
        $em = Database::getManager();
9470
9471
        // Using doctrine extensions
9472
        $repo = Container::getLpCategoryRepository();
9473
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9474
9475
        return $qb->getQuery()->getResult();
9476
9477
        //return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9478
    }
9479
9480
    public static function getCategorySessionId($id)
9481
    {
9482
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9483
            return 0;
9484
        }
9485
9486
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9487
        $id = (int) $id;
9488
9489
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9490
        $result = Database::query($sql);
9491
        $result = Database::fetch_array($result, 'ASSOC');
9492
9493
        if ($result) {
9494
            return (int) $result['session_id'];
9495
        }
9496
9497
        return 0;
9498
    }
9499
9500
    /**
9501
     * @param int $id
9502
     */
9503
    public static function deleteCategory($id): bool
9504
    {
9505
        $repo = Container::getLpCategoryRepository();
9506
        /** @var CLpCategory $category */
9507
        $category = $repo->find($id);
9508
        if ($category) {
9509
            $em = Database::getManager();
9510
            $lps = $category->getLps();
9511
9512
            foreach ($lps as $lp) {
9513
                $lp->setCategory(null);
9514
                $em->persist($lp);
9515
            }
9516
9517
            // Removing category.
9518
            $em->remove($category);
9519
            $em->flush();
9520
9521
            return true;
9522
        }
9523
9524
        return false;
9525
    }
9526
9527
    /**
9528
     * @param int  $courseId
9529
     * @param bool $addSelectOption
9530
     *
9531
     * @return mixed
9532
     */
9533
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9534
    {
9535
        $repo = Container::getLpCategoryRepository();
9536
        $items = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9537
        $cats = [];
9538
        if ($addSelectOption) {
9539
            $cats = [get_lang('Select a category')];
9540
        }
9541
9542
        if (!empty($items)) {
9543
            foreach ($items as $cat) {
9544
                $cats[$cat->getIid()] = $cat->getName();
9545
            }
9546
        }
9547
9548
        return $cats;
9549
    }
9550
9551
    /**
9552
     * @param string $courseCode
9553
     * @param int    $lpId
9554
     * @param int    $user_id
9555
     *
9556
     * @return learnpath
9557
     */
9558
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9559
    {
9560
        $debug = 0;
9561
        $learnPath = null;
9562
        $lpObject = Session::read('lpobject');
9563
9564
        $repo = Container::getLpRepository();
9565
        $lp = $repo->find($lpId);
9566
        if (null !== $lpObject) {
9567
            /** @var learnpath $learnPath */
9568
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
9569
            $learnPath->entity = $lp;
9570
            if ($debug) {
9571
                error_log('getLpFromSession: unserialize');
9572
                error_log('------getLpFromSession------');
9573
                error_log('------unserialize------');
9574
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9575
                error_log("api_get_sessionid: ".api_get_session_id());
9576
            }
9577
        }
9578
9579
        if (!is_object($learnPath)) {
9580
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
9581
            if ($debug) {
9582
                error_log('------getLpFromSession------');
9583
                error_log('getLpFromSession: create new learnpath');
9584
                error_log("create new LP with $courseCode - $lpId - $user_id");
9585
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9586
                error_log("api_get_sessionid: ".api_get_session_id());
9587
            }
9588
        }
9589
9590
        return $learnPath;
9591
    }
9592
9593
    /**
9594
     * @param int $itemId
9595
     *
9596
     * @return learnpathItem|false
9597
     */
9598
    public function getItem($itemId)
9599
    {
9600
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
9601
            return $this->items[$itemId];
9602
        }
9603
9604
        return false;
9605
    }
9606
9607
    /**
9608
     * @return int
9609
     */
9610
    public function getCurrentAttempt()
9611
    {
9612
        $attempt = $this->getItem($this->get_current_item_id());
9613
        if ($attempt) {
9614
            return $attempt->get_attempt_id();
9615
        }
9616
9617
        return 0;
9618
    }
9619
9620
    /**
9621
     * @return int
9622
     */
9623
    public function getCategoryId()
9624
    {
9625
        return (int) $this->categoryId;
9626
    }
9627
9628
    /**
9629
     * Get whether this is a learning path with the possibility to subscribe
9630
     * users or not.
9631
     *
9632
     * @return int
9633
     */
9634
    public function getSubscribeUsers()
9635
    {
9636
        return $this->subscribeUsers;
9637
    }
9638
9639
    /**
9640
     * Calculate the count of stars for a user in this LP
9641
     * This calculation is based on the following rules:
9642
     * - the student gets one star when he gets to 50% of the learning path
9643
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
9644
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
9645
     * - the student gets the final star when the score for the *last* test is >= 80%.
9646
     *
9647
     * @param int $sessionId Optional. The session ID
9648
     *
9649
     * @return int The count of stars
9650
     */
9651
    public function getCalculateStars($sessionId = 0)
9652
    {
9653
        $stars = 0;
9654
        $progress = self::getProgress(
9655
            $this->lp_id,
9656
            $this->user_id,
9657
            $this->course_int_id,
9658
            $sessionId
9659
        );
9660
9661
        if ($progress >= 50) {
9662
            $stars++;
9663
        }
9664
9665
        // Calculate stars chapters evaluation
9666
        $exercisesItems = $this->getExercisesItems();
9667
9668
        if (!empty($exercisesItems)) {
9669
            $totalResult = 0;
9670
9671
            foreach ($exercisesItems as $exerciseItem) {
9672
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9673
                    $this->user_id,
9674
                    $exerciseItem->path,
9675
                    $this->course_int_id,
9676
                    $sessionId,
9677
                    $this->lp_id,
9678
                    $exerciseItem->db_id
9679
                );
9680
9681
                $exerciseResultInfo = end($exerciseResultInfo);
9682
9683
                if (!$exerciseResultInfo) {
9684
                    continue;
9685
                }
9686
9687
                if (!empty($exerciseResultInfo['max_score'])) {
9688
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
9689
                } else {
9690
                    $exerciseResult = 0;
9691
                }
9692
                $totalResult += $exerciseResult;
9693
            }
9694
9695
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
9696
9697
            if ($totalExerciseAverage >= 50) {
9698
                $stars++;
9699
            }
9700
9701
            if ($totalExerciseAverage >= 80) {
9702
                $stars++;
9703
            }
9704
        }
9705
9706
        // Calculate star for final evaluation
9707
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9708
9709
        if (!empty($finalEvaluationItem)) {
9710
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9711
                $this->user_id,
9712
                $finalEvaluationItem->path,
9713
                $this->course_int_id,
9714
                $sessionId,
9715
                $this->lp_id,
9716
                $finalEvaluationItem->db_id
9717
            );
9718
9719
            $evaluationResultInfo = end($evaluationResultInfo);
9720
9721
            if ($evaluationResultInfo) {
9722
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
9723
                if ($evaluationResult >= 80) {
9724
                    $stars++;
9725
                }
9726
            }
9727
        }
9728
9729
        return $stars;
9730
    }
9731
9732
    /**
9733
     * Get the items of exercise type.
9734
     *
9735
     * @return array The items. Otherwise return false
9736
     */
9737
    public function getExercisesItems()
9738
    {
9739
        $exercises = [];
9740
        foreach ($this->items as $item) {
9741
            if ('quiz' != $item->type) {
9742
                continue;
9743
            }
9744
            $exercises[] = $item;
9745
        }
9746
9747
        array_pop($exercises);
9748
9749
        return $exercises;
9750
    }
9751
9752
    /**
9753
     * Get the item of exercise type (evaluation type).
9754
     *
9755
     * @return array The final evaluation. Otherwise return false
9756
     */
9757
    public function getFinalEvaluationItem()
9758
    {
9759
        $exercises = [];
9760
        foreach ($this->items as $item) {
9761
            if (TOOL_QUIZ !== $item->type) {
9762
                continue;
9763
            }
9764
9765
            $exercises[] = $item;
9766
        }
9767
9768
        return array_pop($exercises);
9769
    }
9770
9771
    /**
9772
     * Calculate the total points achieved for the current user in this learning path.
9773
     *
9774
     * @param int $sessionId Optional. The session Id
9775
     *
9776
     * @return int
9777
     */
9778
    public function getCalculateScore($sessionId = 0)
9779
    {
9780
        // Calculate stars chapters evaluation
9781
        $exercisesItems = $this->getExercisesItems();
9782
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9783
        $totalExercisesResult = 0;
9784
        $totalEvaluationResult = 0;
9785
9786
        if (false !== $exercisesItems) {
9787
            foreach ($exercisesItems as $exerciseItem) {
9788
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9789
                    $this->user_id,
9790
                    $exerciseItem->path,
9791
                    $this->course_int_id,
9792
                    $sessionId,
9793
                    $this->lp_id,
9794
                    $exerciseItem->db_id
9795
                );
9796
9797
                $exerciseResultInfo = end($exerciseResultInfo);
9798
9799
                if (!$exerciseResultInfo) {
9800
                    continue;
9801
                }
9802
9803
                $totalExercisesResult += $exerciseResultInfo['score'];
9804
            }
9805
        }
9806
9807
        if (!empty($finalEvaluationItem)) {
9808
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9809
                $this->user_id,
9810
                $finalEvaluationItem->path,
9811
                $this->course_int_id,
9812
                $sessionId,
9813
                $this->lp_id,
9814
                $finalEvaluationItem->db_id
9815
            );
9816
9817
            $evaluationResultInfo = end($evaluationResultInfo);
9818
9819
            if ($evaluationResultInfo) {
9820
                $totalEvaluationResult += $evaluationResultInfo['score'];
9821
            }
9822
        }
9823
9824
        return $totalExercisesResult + $totalEvaluationResult;
9825
    }
9826
9827
    /**
9828
     * Check if URL is not allowed to be show in a iframe.
9829
     *
9830
     * @param string $src
9831
     *
9832
     * @return string
9833
     */
9834
    public function fixBlockedLinks($src)
9835
    {
9836
        $urlInfo = parse_url($src);
9837
9838
        $platformProtocol = 'https';
9839
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
9840
            $platformProtocol = 'http';
9841
        }
9842
9843
        $protocolFixApplied = false;
9844
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
9845
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
9846
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
9847
9848
        if ($platformProtocol != $scheme) {
9849
            Session::write('x_frame_source', $src);
9850
            $src = 'blank.php?error=x_frames_options';
9851
            $protocolFixApplied = true;
9852
        }
9853
9854
        if (false == $protocolFixApplied) {
9855
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
9856
                // Check X-Frame-Options
9857
                $ch = curl_init();
9858
                $options = [
9859
                    CURLOPT_URL => $src,
9860
                    CURLOPT_RETURNTRANSFER => true,
9861
                    CURLOPT_HEADER => true,
9862
                    CURLOPT_FOLLOWLOCATION => true,
9863
                    CURLOPT_ENCODING => "",
9864
                    CURLOPT_AUTOREFERER => true,
9865
                    CURLOPT_CONNECTTIMEOUT => 120,
9866
                    CURLOPT_TIMEOUT => 120,
9867
                    CURLOPT_MAXREDIRS => 10,
9868
                ];
9869
9870
                $proxySettings = api_get_configuration_value('proxy_settings');
9871
                if (!empty($proxySettings) &&
9872
                    isset($proxySettings['curl_setopt_array'])
9873
                ) {
9874
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
9875
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
9876
                }
9877
9878
                curl_setopt_array($ch, $options);
9879
                $response = curl_exec($ch);
9880
                $httpCode = curl_getinfo($ch);
9881
                $headers = substr($response, 0, $httpCode['header_size']);
9882
9883
                $error = false;
9884
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
9885
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
9886
                ) {
9887
                    $error = true;
9888
                }
9889
9890
                if ($error) {
9891
                    Session::write('x_frame_source', $src);
9892
                    $src = 'blank.php?error=x_frames_options';
9893
                }
9894
            }
9895
        }
9896
9897
        return $src;
9898
    }
9899
9900
    /**
9901
     * Check if this LP has a created forum in the basis course.
9902
     *
9903
     * @deprecated
9904
     *
9905
     * @return bool
9906
     */
9907
    public function lpHasForum()
9908
    {
9909
        $forumTable = Database::get_course_table(TABLE_FORUM);
9910
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
9911
9912
        $fakeFrom = "
9913
            $forumTable f
9914
            INNER JOIN $itemProperty ip
9915
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
9916
        ";
9917
9918
        $resultData = Database::select(
9919
            'COUNT(f.iid) AS qty',
9920
            $fakeFrom,
9921
            [
9922
                'where' => [
9923
                    'ip.visibility != ? AND ' => 2,
9924
                    'ip.tool = ? AND ' => TOOL_FORUM,
9925
                    'f.c_id = ? AND ' => intval($this->course_int_id),
9926
                    'f.lp_id = ?' => intval($this->lp_id),
9927
                ],
9928
            ],
9929
            'first'
9930
        );
9931
9932
        return $resultData['qty'] > 0;
9933
    }
9934
9935
    /**
9936
     * Get the forum for this learning path.
9937
     *
9938
     * @param int $sessionId
9939
     *
9940
     * @return array
9941
     */
9942
    public function getForum($sessionId = 0)
9943
    {
9944
        $repo = Container::getForumRepository();
9945
9946
        $course = api_get_course_entity();
9947
        $session = api_get_session_entity($sessionId);
9948
        $qb = $repo->getResourcesByCourse($course, $session);
9949
9950
        return $qb->getQuery()->getResult();
9951
    }
9952
9953
    /**
9954
     * Create a forum for this learning path.
9955
     *
9956
     * @return int The forum ID if was created. Otherwise return false
9957
     */
9958
    public function createForum(CForumCategory $forumCategory)
9959
    {
9960
        return store_forum(
9961
            [
9962
                'lp_id' => $this->lp_id,
9963
                'forum_title' => $this->name,
9964
                'forum_comment' => null,
9965
                'forum_category' => $forumCategory->getIid(),
9966
                'students_can_edit_group' => ['students_can_edit' => 0],
9967
                'allow_new_threads_group' => ['allow_new_threads' => 0],
9968
                'default_view_type_group' => ['default_view_type' => 'flat'],
9969
                'group_forum' => 0,
9970
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
9971
            ],
9972
            [],
9973
            true
9974
        );
9975
    }
9976
9977
    /**
9978
     * Get the LP Final Item form.
9979
     *
9980
     * @throws Exception
9981
     *
9982
     *
9983
     * @return string
9984
     */
9985
    public function getFinalItemForm()
9986
    {
9987
        $finalItem = $this->getFinalItem();
9988
        $title = '';
9989
9990
        if ($finalItem) {
9991
            $title = $finalItem->get_title();
9992
            $buttonText = get_lang('Save');
9993
            $content = $this->getSavedFinalItem();
9994
        } else {
9995
            $buttonText = get_lang('Add this document to the course');
9996
            $content = $this->getFinalItemTemplate();
9997
        }
9998
9999
        $editorConfig = [
10000
            'ToolbarSet' => 'LearningPathDocuments',
10001
            'Width' => '100%',
10002
            'Height' => '500',
10003
            'FullPage' => true,
10004
//            'CreateDocumentDir' => $relative_prefix,
10005
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10006
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10007
        ];
10008
10009
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10010
            'type' => 'document',
10011
            'lp_id' => $this->lp_id,
10012
        ]);
10013
10014
        $form = new FormValidator('final_item', 'POST', $url);
10015
        $form->addText('title', get_lang('Title'));
10016
        $form->addButtonSave($buttonText);
10017
        $form->addHtml(
10018
            Display::return_message(
10019
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10020
                'normal',
10021
                false
10022
            )
10023
        );
10024
10025
        $renderer = $form->defaultRenderer();
10026
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10027
10028
        $form->addHtmlEditor(
10029
            'content_lp_certificate',
10030
            null,
10031
            true,
10032
            false,
10033
            $editorConfig,
10034
            true
10035
        );
10036
        $form->addHidden('action', 'add_final_item');
10037
        $form->addHidden('path', Session::read('pathItem'));
10038
        $form->addHidden('previous', $this->get_last());
10039
        $form->setDefaults(
10040
            ['title' => $title, 'content_lp_certificate' => $content]
10041
        );
10042
10043
        if ($form->validate()) {
10044
            $values = $form->exportValues();
10045
            $lastItemId = $this->getLastInFirstLevel();
10046
10047
            if (!$finalItem) {
10048
                $documentId = $this->create_document(
10049
                    $this->course_info,
10050
                    $values['content_lp_certificate'],
10051
                    $values['title']
10052
                );
10053
                $this->add_item(
10054
                    0,
10055
                    $lastItemId,
10056
                    'final_item',
10057
                    $documentId,
10058
                    $values['title'],
10059
                    ''
10060
                );
10061
10062
                Display::addFlash(
10063
                    Display::return_message(get_lang('Added'))
10064
                );
10065
            } else {
10066
                $this->edit_document($this->course_info);
10067
            }
10068
        }
10069
10070
        return $form->returnForm();
10071
    }
10072
10073
    /**
10074
     * Check if the current lp item is first, both, last or none from lp list.
10075
     *
10076
     * @param int $currentItemId
10077
     *
10078
     * @return string
10079
     */
10080
    public function isFirstOrLastItem($currentItemId)
10081
    {
10082
        $lpItemId = [];
10083
        $typeListNotToVerify = self::getChapterTypes();
10084
10085
        // Using get_toc() function instead $this->items because returns the correct order of the items
10086
        foreach ($this->get_toc() as $item) {
10087
            if (!in_array($item['type'], $typeListNotToVerify)) {
10088
                $lpItemId[] = $item['id'];
10089
            }
10090
        }
10091
10092
        $lastLpItemIndex = count($lpItemId) - 1;
10093
        $position = array_search($currentItemId, $lpItemId);
10094
10095
        switch ($position) {
10096
            case 0:
10097
                if (!$lastLpItemIndex) {
10098
                    $answer = 'both';
10099
                    break;
10100
                }
10101
10102
                $answer = 'first';
10103
                break;
10104
            case $lastLpItemIndex:
10105
                $answer = 'last';
10106
                break;
10107
            default:
10108
                $answer = 'none';
10109
        }
10110
10111
        return $answer;
10112
    }
10113
10114
    /**
10115
     * Get whether this is a learning path with the accumulated SCORM time or not.
10116
     *
10117
     * @return int
10118
     */
10119
    public function getAccumulateScormTime()
10120
    {
10121
        return $this->accumulateScormTime;
10122
    }
10123
10124
    /**
10125
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10126
     * the new learning path tool.
10127
     *
10128
     * The function is a big switch on tool type.
10129
     * In each case, we query the corresponding table for information and build the link
10130
     * with that information.
10131
     *
10132
     * @author Yannick Warnier <[email protected]> - rebranding based on
10133
     * previous work (display_addedresource_link_in_learnpath())
10134
     *
10135
     * @param int $course_id      Course code
10136
     * @param int $learningPathId The learning path ID (in lp table)
10137
     * @param int $id_in_path     the unique index in the items table
10138
     * @param int $lpViewId
10139
     *
10140
     * @return string
10141
     */
10142
    public static function rl_get_resource_link_for_learnpath(
10143
        $course_id,
10144
        $learningPathId,
10145
        $id_in_path,
10146
        $lpViewId
10147
    ) {
10148
        $session_id = api_get_session_id();
10149
10150
        $learningPathId = (int) $learningPathId;
10151
        $id_in_path = (int) $id_in_path;
10152
        $lpViewId = (int) $lpViewId;
10153
10154
        $em = Database::getManager();
10155
        $lpItemRepo = $em->getRepository(CLpItem::class);
10156
10157
        /** @var CLpItem $rowItem */
10158
        $rowItem = $lpItemRepo->findOneBy([
10159
            'lp' => $learningPathId,
10160
            'iid' => $id_in_path,
10161
        ]);
10162
10163
        if (!$rowItem) {
10164
            // Try one more time with "id"
10165
            /** @var CLpItem $rowItem */
10166
            $rowItem = $lpItemRepo->findOneBy([
10167
                'lp' => $learningPathId,
10168
                'id' => $id_in_path,
10169
            ]);
10170
10171
            if (!$rowItem) {
10172
                return -1;
10173
            }
10174
        }
10175
10176
        $type = $rowItem->getItemType();
10177
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10178
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10179
        $link = '';
10180
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
10181
10182
        switch ($type) {
10183
            case 'dir':
10184
                return $main_dir_path.'lp/blank.php';
10185
            case TOOL_CALENDAR_EVENT:
10186
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10187
            case TOOL_ANNOUNCEMENT:
10188
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10189
            case TOOL_LINK:
10190
                $linkInfo = Link::getLinkInfo($id);
10191
                if (isset($linkInfo['url'])) {
10192
                    return $linkInfo['url'];
10193
                }
10194
10195
                return '';
10196
            case TOOL_QUIZ:
10197
                if (empty($id)) {
10198
                    return '';
10199
                }
10200
10201
                // Get the lp_item_view with the highest view_count.
10202
                $learnpathItemViewResult = $em
10203
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10204
                    ->findBy(
10205
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getIid(), 'lpViewId' => $lpViewId],
10206
                        ['viewCount' => 'DESC'],
10207
                        1
10208
                    );
10209
                /** @var CLpItemView $learnpathItemViewData */
10210
                $learnpathItemViewData = current($learnpathItemViewResult);
10211
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
10212
10213
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10214
                    .http_build_query([
10215
                        'lp_init' => 1,
10216
                        'learnpath_item_view_id' => $learnpathItemViewId,
10217
                        'learnpath_id' => $learningPathId,
10218
                        'learnpath_item_id' => $id_in_path,
10219
                        'exerciseId' => $id,
10220
                    ]);
10221
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10222
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10223
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10224
                $myrow = Database::fetch_array($result);
10225
                $path = $myrow['path'];
10226
10227
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10228
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10229
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10230
            case TOOL_FORUM:
10231
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10232
            case TOOL_THREAD:
10233
                // forum post
10234
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10235
                if (empty($id)) {
10236
                    return '';
10237
                }
10238
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
10239
                $result = Database::query($sql);
10240
                $myrow = Database::fetch_array($result);
10241
10242
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10243
                    .$extraParams;
10244
            case TOOL_POST:
10245
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10246
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10247
                $myrow = Database::fetch_array($result);
10248
10249
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10250
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10251
            case TOOL_READOUT_TEXT:
10252
                return api_get_path(WEB_CODE_PATH).
10253
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10254
            case TOOL_DOCUMENT:
10255
                $repo = Container::getDocumentRepository();
10256
                $document = $repo->find($rowItem->getPath());
10257
                $params = [
10258
                    'cid' => $course_id,
10259
                    'sid' => $session_id,
10260
                ];
10261
                $file = $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
10262
10263
                return $file;
10264
10265
                $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...
10266
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10267
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10268
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10269
10270
                $openmethod = 2;
10271
                $officedoc = false;
10272
                Session::write('openmethod', $openmethod);
10273
                Session::write('officedoc', $officedoc);
10274
10275
                if ($showDirectUrl) {
10276
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10277
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10278
                        if (Link::isPdfLink($file)) {
10279
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10280
10281
                            return $pdfUrl;
10282
                        }
10283
                    }
10284
10285
                    return $file;
10286
                }
10287
10288
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10289
            case TOOL_LP_FINAL_ITEM:
10290
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10291
                    .$extraParams;
10292
            case 'assignments':
10293
                return $main_dir_path.'work/work.php?'.$extraParams;
10294
            case TOOL_DROPBOX:
10295
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10296
            case 'introduction_text': //DEPRECATED
10297
                return '';
10298
            case TOOL_COURSE_DESCRIPTION:
10299
                return $main_dir_path.'course_description?'.$extraParams;
10300
            case TOOL_GROUP:
10301
                return $main_dir_path.'group/group.php?'.$extraParams;
10302
            case TOOL_USER:
10303
                return $main_dir_path.'user/user.php?'.$extraParams;
10304
            case TOOL_STUDENTPUBLICATION:
10305
                if (!empty($rowItem->getPath())) {
10306
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10307
                }
10308
10309
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10310
        }
10311
10312
        return $link;
10313
    }
10314
10315
    /**
10316
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10317
     *
10318
     * @author Yannick Warnier <[email protected]>
10319
     *
10320
     * @param string $course_code    Course code
10321
     * @param int    $learningPathId
10322
     * @param int    $id_in_path     The resource ID
10323
     *
10324
     * @return string
10325
     */
10326
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10327
    {
10328
        $_course = api_get_course_info($course_code);
10329
        if (empty($_course)) {
10330
            return '';
10331
        }
10332
        $course_id = $_course['real_id'];
10333
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10334
        $learningPathId = (int) $learningPathId;
10335
        $id_in_path = (int) $id_in_path;
10336
10337
        $sql = "SELECT item_type, title, ref
10338
                FROM $tbl_lp_item
10339
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10340
        $res_item = Database::query($sql);
10341
10342
        if (Database::num_rows($res_item) < 1) {
10343
            return '';
10344
        }
10345
        $row_item = Database::fetch_array($res_item);
10346
        $type = strtolower($row_item['item_type']);
10347
        $id = $row_item['ref'];
10348
        $output = '';
10349
10350
        switch ($type) {
10351
            case TOOL_CALENDAR_EVENT:
10352
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10353
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10354
                $myrow = Database::fetch_array($result);
10355
                $output = $myrow['title'];
10356
                break;
10357
            case TOOL_ANNOUNCEMENT:
10358
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10359
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10360
                $myrow = Database::fetch_array($result);
10361
                $output = $myrow['title'];
10362
                break;
10363
            case TOOL_LINK:
10364
                // Doesn't take $target into account.
10365
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10366
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10367
                $myrow = Database::fetch_array($result);
10368
                $output = $myrow['title'];
10369
                break;
10370
            case TOOL_QUIZ:
10371
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10372
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10373
                $myrow = Database::fetch_array($result);
10374
                $output = $myrow['title'];
10375
                break;
10376
            case TOOL_FORUM:
10377
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10378
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10379
                $myrow = Database::fetch_array($result);
10380
                $output = $myrow['forum_name'];
10381
                break;
10382
            case TOOL_THREAD:
10383
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10384
                // Grabbing the title of the post.
10385
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10386
                $result_title = Database::query($sql_title);
10387
                $myrow_title = Database::fetch_array($result_title);
10388
                $output = $myrow_title['post_title'];
10389
                break;
10390
            case TOOL_POST:
10391
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10392
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10393
                $result = Database::query($sql);
10394
                $post = Database::fetch_array($result);
10395
                $output = $post['post_title'];
10396
                break;
10397
            case 'dir':
10398
            case TOOL_DOCUMENT:
10399
                $title = $row_item['title'];
10400
                $output = '-';
10401
                if (!empty($title)) {
10402
                    $output = $title;
10403
                }
10404
                break;
10405
            case 'hotpotatoes':
10406
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10407
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10408
                $myrow = Database::fetch_array($result);
10409
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10410
                $last = count($pathname) - 1; // Making a correct name for the link.
10411
                $filename = $pathname[$last]; // Making a correct name for the link.
10412
                $myrow['path'] = rawurlencode($myrow['path']);
10413
                $output = $filename;
10414
                break;
10415
        }
10416
10417
        return stripslashes($output);
10418
    }
10419
10420
    /**
10421
     * Get the parent names for the current item.
10422
     *
10423
     * @param int $newItemId Optional. The item ID
10424
     *
10425
     * @return array
10426
     */
10427
    public function getCurrentItemParentNames($newItemId = 0)
10428
    {
10429
        $newItemId = $newItemId ?: $this->get_current_item_id();
10430
        $return = [];
10431
        $item = $this->getItem($newItemId);
10432
        $parent = $this->getItem($item->get_parent());
10433
10434
        while ($parent) {
10435
            $return[] = $parent->get_title();
10436
            $parent = $this->getItem($parent->get_parent());
10437
        }
10438
10439
        return array_reverse($return);
10440
    }
10441
10442
    /**
10443
     * Reads and process "lp_subscription_settings" setting.
10444
     *
10445
     * @return array
10446
     */
10447
    public static function getSubscriptionSettings()
10448
    {
10449
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10450
        if (empty($subscriptionSettings)) {
10451
            // By default allow both settings
10452
            $subscriptionSettings = [
10453
                'allow_add_users_to_lp' => true,
10454
                'allow_add_users_to_lp_category' => true,
10455
            ];
10456
        } else {
10457
            $subscriptionSettings = $subscriptionSettings['options'];
10458
        }
10459
10460
        return $subscriptionSettings;
10461
    }
10462
10463
    /**
10464
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10465
     */
10466
    public function exportToCourseBuildFormat()
10467
    {
10468
        if (!api_is_allowed_to_edit()) {
10469
            return false;
10470
        }
10471
10472
        $courseBuilder = new CourseBuilder();
10473
        $itemList = [];
10474
        /** @var learnpathItem $item */
10475
        foreach ($this->items as $item) {
10476
            $itemList[$item->get_type()][] = $item->get_path();
10477
        }
10478
10479
        if (empty($itemList)) {
10480
            return false;
10481
        }
10482
10483
        if (isset($itemList['document'])) {
10484
            // Get parents
10485
            foreach ($itemList['document'] as $documentId) {
10486
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10487
                if (!empty($documentInfo['parents'])) {
10488
                    foreach ($documentInfo['parents'] as $parentInfo) {
10489
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10490
                            continue;
10491
                        }
10492
                        $itemList['document'][] = $parentInfo['iid'];
10493
                    }
10494
                }
10495
            }
10496
10497
            $courseInfo = api_get_course_info();
10498
            foreach ($itemList['document'] as $documentId) {
10499
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10500
                $items = DocumentManager::get_resources_from_source_html(
10501
                    $documentInfo['absolute_path'],
10502
                    true,
10503
                    TOOL_DOCUMENT
10504
                );
10505
10506
                if (!empty($items)) {
10507
                    foreach ($items as $item) {
10508
                        // Get information about source url
10509
                        $url = $item[0]; // url
10510
                        $scope = $item[1]; // scope (local, remote)
10511
                        $type = $item[2]; // type (rel, abs, url)
10512
10513
                        $origParseUrl = parse_url($url);
10514
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10515
10516
                        if ('local' === $scope) {
10517
                            if ('abs' === $type || 'rel' === $type) {
10518
                                $documentFile = strstr($realOrigPath, 'document');
10519
                                if (false !== strpos($realOrigPath, $documentFile)) {
10520
                                    $documentFile = str_replace('document', '', $documentFile);
10521
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10522
                                    // Document found! Add it to the list
10523
                                    if ($itemDocumentId) {
10524
                                        $itemList['document'][] = $itemDocumentId;
10525
                                    }
10526
                                }
10527
                            }
10528
                        }
10529
                    }
10530
                }
10531
            }
10532
10533
            $courseBuilder->build_documents(
10534
                api_get_session_id(),
10535
                $this->get_course_int_id(),
10536
                true,
10537
                $itemList['document']
10538
            );
10539
        }
10540
10541
        if (isset($itemList['quiz'])) {
10542
            $courseBuilder->build_quizzes(
10543
                api_get_session_id(),
10544
                $this->get_course_int_id(),
10545
                true,
10546
                $itemList['quiz']
10547
            );
10548
        }
10549
10550
        /*if (!empty($itemList['thread'])) {
10551
            $postList = [];
10552
            foreach ($itemList['thread'] as $postId) {
10553
                $post = get_post_information($postId);
10554
                if ($post) {
10555
                    if (!isset($itemList['forum'])) {
10556
                        $itemList['forum'] = [];
10557
                    }
10558
                    $itemList['forum'][] = $post['forum_id'];
10559
                    $postList[] = $postId;
10560
                }
10561
            }
10562
10563
            if (!empty($postList)) {
10564
                $courseBuilder->build_forum_posts(
10565
                    $this->get_course_int_id(),
10566
                    null,
10567
                    null,
10568
                    $postList
10569
                );
10570
            }
10571
        }*/
10572
10573
        if (!empty($itemList['thread'])) {
10574
            $threadList = [];
10575
            $em = Database::getManager();
10576
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
10577
            foreach ($itemList['thread'] as $threadId) {
10578
                /** @var CForumThread $thread */
10579
                $thread = $repo->find($threadId);
10580
                if ($thread) {
10581
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
10582
                    $threadList[] = $thread->getIid();
10583
                }
10584
            }
10585
10586
            if (!empty($threadList)) {
10587
                $courseBuilder->build_forum_topics(
10588
                    api_get_session_id(),
10589
                    $this->get_course_int_id(),
10590
                    null,
10591
                    $threadList
10592
                );
10593
            }
10594
        }
10595
10596
        $forumCategoryList = [];
10597
        if (isset($itemList['forum'])) {
10598
            foreach ($itemList['forum'] as $forumId) {
10599
                $forumInfo = get_forums($forumId);
10600
                $forumCategoryList[] = $forumInfo['forum_category'];
10601
            }
10602
        }
10603
10604
        if (!empty($forumCategoryList)) {
10605
            $courseBuilder->build_forum_category(
10606
                api_get_session_id(),
10607
                $this->get_course_int_id(),
10608
                true,
10609
                $forumCategoryList
10610
            );
10611
        }
10612
10613
        if (!empty($itemList['forum'])) {
10614
            $courseBuilder->build_forums(
10615
                api_get_session_id(),
10616
                $this->get_course_int_id(),
10617
                true,
10618
                $itemList['forum']
10619
            );
10620
        }
10621
10622
        if (isset($itemList['link'])) {
10623
            $courseBuilder->build_links(
10624
                api_get_session_id(),
10625
                $this->get_course_int_id(),
10626
                true,
10627
                $itemList['link']
10628
            );
10629
        }
10630
10631
        if (!empty($itemList['student_publication'])) {
10632
            $courseBuilder->build_works(
10633
                api_get_session_id(),
10634
                $this->get_course_int_id(),
10635
                true,
10636
                $itemList['student_publication']
10637
            );
10638
        }
10639
10640
        $courseBuilder->build_learnpaths(
10641
            api_get_session_id(),
10642
            $this->get_course_int_id(),
10643
            true,
10644
            [$this->get_id()],
10645
            false
10646
        );
10647
10648
        $courseBuilder->restoreDocumentsFromList();
10649
10650
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
10651
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
10652
        $result = DocumentManager::file_send_for_download(
10653
            $zipPath,
10654
            true,
10655
            $this->get_name().'.zip'
10656
        );
10657
10658
        if ($result) {
10659
            api_not_allowed();
10660
        }
10661
10662
        return true;
10663
    }
10664
10665
    /**
10666
     * Get whether this is a learning path with the accumulated work time or not.
10667
     *
10668
     * @return int
10669
     */
10670
    public function getAccumulateWorkTime()
10671
    {
10672
        return (int) $this->accumulateWorkTime;
10673
    }
10674
10675
    /**
10676
     * Get whether this is a learning path with the accumulated work time or not.
10677
     *
10678
     * @return int
10679
     */
10680
    public function getAccumulateWorkTimeTotalCourse()
10681
    {
10682
        $table = Database::get_course_table(TABLE_LP_MAIN);
10683
        $sql = "SELECT SUM(accumulate_work_time) AS total
10684
                FROM $table
10685
                WHERE c_id = ".$this->course_int_id;
10686
        $result = Database::query($sql);
10687
        $row = Database::fetch_array($result);
10688
10689
        return (int) $row['total'];
10690
    }
10691
10692
    /**
10693
     * @param int $lpId
10694
     * @param int $courseId
10695
     *
10696
     * @return mixed
10697
     */
10698
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
10699
    {
10700
        $lpId = (int) $lpId;
10701
        $table = Database::get_course_table(TABLE_LP_MAIN);
10702
        $sql = "SELECT accumulate_work_time
10703
                FROM $table
10704
                WHERE iid = $lpId";
10705
        $result = Database::query($sql);
10706
        $row = Database::fetch_array($result);
10707
10708
        return $row['accumulate_work_time'];
10709
    }
10710
10711
    /**
10712
     * @param int $courseId
10713
     *
10714
     * @return int
10715
     */
10716
    public static function getAccumulateWorkTimeTotal($courseId)
10717
    {
10718
        $table = Database::get_course_table(TABLE_LP_MAIN);
10719
        $courseId = (int) $courseId;
10720
        $sql = "SELECT SUM(accumulate_work_time) AS total
10721
                FROM $table
10722
                WHERE c_id = $courseId";
10723
        $result = Database::query($sql);
10724
        $row = Database::fetch_array($result);
10725
10726
        return (int) $row['total'];
10727
    }
10728
10729
    /**
10730
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
10731
     * and put the images in.
10732
     *
10733
     * @return array
10734
     */
10735
    public static function getIconSelect()
10736
    {
10737
        $theme = api_get_visual_theme();
10738
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
10739
        $icons = ['' => get_lang('Please select an option')];
10740
10741
        if (is_dir($path)) {
10742
            $finder = new Finder();
10743
            $finder->files()->in($path);
10744
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
10745
            /** @var SplFileInfo $file */
10746
            foreach ($finder as $file) {
10747
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
10748
                    $icons[$file->getFilename()] = $file->getFilename();
10749
                }
10750
            }
10751
        }
10752
10753
        return $icons;
10754
    }
10755
10756
    /**
10757
     * @param int $lpId
10758
     *
10759
     * @return string
10760
     */
10761
    public static function getSelectedIcon($lpId)
10762
    {
10763
        $extraFieldValue = new ExtraFieldValue('lp');
10764
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
10765
        $icon = '';
10766
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
10767
            $icon = $lpIcon['value'];
10768
        }
10769
10770
        return $icon;
10771
    }
10772
10773
    /**
10774
     * @param int $lpId
10775
     *
10776
     * @return string
10777
     */
10778
    public static function getSelectedIconHtml($lpId)
10779
    {
10780
        $icon = self::getSelectedIcon($lpId);
10781
10782
        if (empty($icon)) {
10783
            return '';
10784
        }
10785
10786
        $theme = api_get_visual_theme();
10787
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
10788
10789
        return Display::img($path);
10790
    }
10791
10792
    /**
10793
     * @param string $value
10794
     *
10795
     * @return string
10796
     */
10797
    public function cleanItemTitle($value)
10798
    {
10799
        $value = Security::remove_XSS(strip_tags($value));
10800
10801
        return $value;
10802
    }
10803
10804
    public function setItemTitle(FormValidator $form)
10805
    {
10806
        if (api_get_configuration_value('save_titles_as_html')) {
10807
            $form->addHtmlEditor(
10808
                'title',
10809
                get_lang('Title'),
10810
                true,
10811
                false,
10812
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
10813
            );
10814
        } else {
10815
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
10816
            $form->applyFilter('title', 'trim');
10817
            $form->applyFilter('title', 'html_filter');
10818
        }
10819
    }
10820
10821
    /**
10822
     * @return array
10823
     */
10824
    public function getItemsForForm($addParentCondition = false)
10825
    {
10826
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10827
10828
        $sql = "SELECT * FROM $tbl_lp_item
10829
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
10830
10831
        if ($addParentCondition) {
10832
            $sql .= ' AND parent_item_id IS NULL ';
10833
        }
10834
        $sql .= ' ORDER BY display_order ASC';
10835
10836
        $result = Database::query($sql);
10837
        $arrLP = [];
10838
        while ($row = Database::fetch_array($result)) {
10839
            $arrLP[] = [
10840
                'iid' => $row['iid'],
10841
                'id' => $row['iid'],
10842
                'item_type' => $row['item_type'],
10843
                'title' => $this->cleanItemTitle($row['title']),
10844
                'title_raw' => $row['title'],
10845
                'path' => $row['path'],
10846
                'description' => Security::remove_XSS($row['description']),
10847
                'parent_item_id' => $row['parent_item_id'],
10848
                'previous_item_id' => $row['previous_item_id'],
10849
                'next_item_id' => $row['next_item_id'],
10850
                'display_order' => $row['display_order'],
10851
                'max_score' => $row['max_score'],
10852
                'min_score' => $row['min_score'],
10853
                'mastery_score' => $row['mastery_score'],
10854
                'prerequisite' => $row['prerequisite'],
10855
                'max_time_allowed' => $row['max_time_allowed'],
10856
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10857
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10858
            ];
10859
        }
10860
10861
        return $arrLP;
10862
    }
10863
10864
    /**
10865
     * Gets whether this SCORM learning path has been marked to use the score
10866
     * as progress. Takes into account whether the learnpath matches (SCORM
10867
     * content + less than 2 items).
10868
     *
10869
     * @return bool True if the score should be used as progress, false otherwise
10870
     */
10871
    public function getUseScoreAsProgress()
10872
    {
10873
        // If not a SCORM, we don't care about the setting
10874
        if (2 != $this->get_type()) {
10875
            return false;
10876
        }
10877
        // If more than one step in the SCORM, we don't care about the setting
10878
        if ($this->get_total_items_count() > 1) {
10879
            return false;
10880
        }
10881
        $extraFieldValue = new ExtraFieldValue('lp');
10882
        $doUseScore = false;
10883
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
10884
            $this->get_id(),
10885
            'use_score_as_progress'
10886
        );
10887
        if (!empty($useScore) && isset($useScore['value'])) {
10888
            $doUseScore = $useScore['value'];
10889
        }
10890
10891
        return $doUseScore;
10892
    }
10893
10894
    /**
10895
     * Get the user identifier (user_id or username
10896
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
10897
     *
10898
     * @return string User ID or username, depending on configuration setting
10899
     */
10900
    public static function getUserIdentifierForExternalServices()
10901
    {
10902
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
10903
            return api_get_user_info(api_get_user_id())['username'];
10904
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
10905
            $extraFieldValue = new ExtraFieldValue('user');
10906
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
10907
                api_get_user_id(),
10908
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
10909
            );
10910
10911
            return $extrafield['value'];
10912
        } else {
10913
            return api_get_user_id();
10914
        }
10915
    }
10916
10917
    /**
10918
     * Save the new order for learning path items.
10919
     *
10920
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
10921
     *
10922
     * @param array $orderList A associative array with item ID as key and parent ID as value.
10923
     */
10924
    public function sortItemByOrderList(array $orderList = [])
10925
    {
10926
        if (empty($orderList)) {
10927
            return true;
10928
        }
10929
        $lpItemRepo = Container::getLpItemRepository();
10930
        $rootParent = $lpItemRepo->getItemRoot($this->get_id());
10931
10932
        /*$previous = 2;
10933
        $next = 0;
10934
        $rootParent->setPreviousItemId(1);
10935
        $last = $this->updateList($orderList, $rootParent, $previous, $next);
10936
        $rootParent->setNextItemId($last + 1);
10937
*/
10938
        $em = Database::getManager();
10939
        //echo '<pre>';
10940
        //var_dump($orderList);
10941
//        $em->persist($rootParent);
10942
10943
        /*$node = new \Tree\Node\Node($rootParent->getIid());
10944
        $parentList[$rootParent->getIid()] = $node;
10945
10946
        foreach ($orderList as $item) {
10947
            $itemId = $item->id ?? 0;
10948
            if (empty($itemId)) {
10949
                continue;
10950
            }
10951
            $parentId = $item->parent_id ?? 0;
10952
10953
            if (empty($parentId)) {
10954
                $parent = $rootParent;
10955
                $parentId = $rootParent->getIid();
10956
            } else {
10957
                //$parent = $lpItemRepo->find($parentId);
10958
            }
10959
            //$parentList[$itemId] = ;
10960
10961
            $child = new \Tree\Node\Node($itemId);
10962
            $parentList[$itemId] = $child;
10963
            $parentList[$parentId]->addChild($child);
10964
        }
10965
10966
        $builder = new Tree\Builder\NodeBuilder;
10967
        echo '<pre>';
10968
        foreach ($node->getChildren() as $child) {
10969
            var_dump($child->getValue().'-'.$child->getDepth().'-'.$child->getSize());
10970
10971
            var_dump("---");
10972
            foreach ($child->getNeighbors() as $neighbor) {
10973
10974
                var_dump($neighbor->getValue(), $child->getSize(), $neighbor->getDepth());
10975
            }
10976
        }
10977
exit;*/
10978
10979
        $counter = 2;
10980
        /*$rootParent->setPreviousItemId(1);
10981
        $rootParent->setDisplayOrder(0);
10982
        $rootParent->setLaunchData(1);*/
10983
        //echo '<pre>';
10984
        $rootParent->setDisplayOrder(1);
10985
        foreach ($orderList as $item) {
10986
            $itemId  = $item->id ?? 0;
10987
            if (empty($itemId)) {
10988
                continue;
10989
            }
10990
            $parentId = $item->parent_id ?? 0;
10991
            $parent = $rootParent;
10992
            if (!empty($parentId)) {
10993
                $parentExists = $lpItemRepo->find($parentId);
10994
                if (null !== $parentExists) {
10995
                    $parent = $parentExists;
10996
                }
10997
            }
10998
10999
            if (isset($parentOrder[$parent->getIid()])) {
11000
                $parentOrder[$parent->getIid()]++;
11001
            } else {
11002
                $parentOrder[$parent->getIid()] = 0;
11003
            }
11004
            /** @var CLpItem $item */
11005
            $item = $lpItemRepo->find($itemId);
11006
11007
            $previousId = 2;
11008
            if (isset($previous[$counter - 1])) {
11009
                $previousId = $previous[$counter - 1];
11010
            }
11011
            $previous[$counter] = $counter + 2;
11012
11013
            $item->setParent($parent);
11014
            //$item->setPreviousItemId($counter);
11015
            //$item->setNextItemId($counter + 1);
11016
            //$item->setDisplayOrder($parentOrder[$parent->getIid()]);
11017
            var_dump($item->getIid().'-'.$counter);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($item->getIid() . '-' . $counter) looks like debug code. Are you sure you do not want to remove it?
Loading history...
11018
            //$item->setPreviousItemId(0);
11019
            //$item->setNextItemId(0);
11020
            $item->setDisplayOrder($counter);
11021
            $em->persist($item);
11022
            $counter++;
11023
        }
11024
        //$rootParent->setNextItemId($counter+1);
11025
        $em->persist($rootParent);
11026
        $em->flush();
11027
        var_dump($lpItemRepo->verify());
11028
        $lpItemRepo->reorder($rootParent, 'displayOrder');
11029
11030
        var_dump($lpItemRepo->verify());
11031
        return true;
11032
        /*
11033
        $lpItemRepo->recoverNode($rootParent);
11034
        var_dump($lpItemRepo->verify());
11035
11036
        $em->flush();
11037
        exit;*/
11038
    }
11039
11040
    private static function updateList($list, $parent, &$previous, &$next)
11041
    {
11042
        //echo '<pre>';        var_dump($list);exit;
11043
        $lpItemRepo = Container::getLpItemRepository();
11044
        $em = Database::getManager();
11045
        $counter = 0;
11046
        foreach ($list as $item) {
11047
            if (empty($item) || !isset($item->id)) {
11048
                continue;
11049
            }
11050
            $id = $item->id;
11051
            $children = $item->children;
11052
11053
            /** @var CLpItem $lpItem */
11054
            $lpItem = $lpItemRepo->find($id);
11055
            $lpItem
11056
                ->setParent($parent)
11057
                ->setDisplayOrder($counter)
11058
                ->setPreviousItemId($previous);
11059
            ;
11060
            $previous++;
11061
            $counter++;
11062
            $childExists = false;
11063
            foreach ($children as $child) {
11064
                if (isset($child->id)) {
11065
                    $childExists = true;
11066
                    break;
11067
                }
11068
            }
11069
11070
            if ($childExists) {
11071
                $next = self::updateList($children, $lpItem, $previous, $next);
11072
                $lpItem->setNextItemId($next + 1);
11073
                $previous++;
11074
            } else {
11075
                $next = $previous;
11076
                $previous++;
11077
                $lpItem->setNextItemId($next);
11078
            }
11079
            $em->persist($lpItem);
11080
        }
11081
11082
        return $next;
11083
    }
11084
11085
    /**
11086
     * Get the depth level of LP item.
11087
     *
11088
     * @param array $items
11089
     * @param int   $currentItemId
11090
     *
11091
     * @return int
11092
     */
11093
    private static function get_level_for_item($items, $currentItemId)
11094
    {
11095
        $parentItemId = 0;
11096
        if (isset($items[$currentItemId])) {
11097
            $parentItemId = $items[$currentItemId]->parent;
11098
        }
11099
11100
        if (0 == $parentItemId) {
11101
            return 0;
11102
        }
11103
11104
        return self::get_level_for_item($items, $parentItemId) + 1;
11105
    }
11106
11107
    /**
11108
     * Generate the link for a learnpath category as course tool.
11109
     *
11110
     * @param int $categoryId
11111
     *
11112
     * @return string
11113
     */
11114
    private static function getCategoryLinkForTool($categoryId)
11115
    {
11116
        $categoryId = (int) $categoryId;
11117
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
11118
            .http_build_query(
11119
                [
11120
                    'action' => 'view_category',
11121
                    'id' => $categoryId,
11122
                ]
11123
            );
11124
    }
11125
11126
    /**
11127
     * Return the scorm item type object with spaces replaced with _
11128
     * The return result is use to build a css classname like scorm_type_$return.
11129
     *
11130
     * @param $in_type
11131
     *
11132
     * @return mixed
11133
     */
11134
    private static function format_scorm_type_item($in_type)
11135
    {
11136
        return str_replace(' ', '_', $in_type);
11137
    }
11138
11139
    /**
11140
     * Check and obtain the lp final item if exist.
11141
     *
11142
     * @return learnpathItem
11143
     */
11144
    private function getFinalItem()
11145
    {
11146
        if (empty($this->items)) {
11147
            return null;
11148
        }
11149
11150
        foreach ($this->items as $item) {
11151
            if ('final_item' !== $item->type) {
11152
                continue;
11153
            }
11154
11155
            return $item;
11156
        }
11157
    }
11158
11159
    /**
11160
     * Get the LP Final Item Template.
11161
     *
11162
     * @return string
11163
     */
11164
    private function getFinalItemTemplate()
11165
    {
11166
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11167
    }
11168
11169
    /**
11170
     * Get the LP Final Item Url.
11171
     *
11172
     * @return string
11173
     */
11174
    private function getSavedFinalItem()
11175
    {
11176
        $finalItem = $this->getFinalItem();
11177
11178
        $repo = Container::getDocumentRepository();
11179
        /** @var CDocument $document */
11180
        $document = $repo->find($finalItem->path);
11181
11182
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11183
            return $repo->getResourceFileContent($document);
11184
        }
11185
11186
        return '';
11187
    }
11188
}
11189