Passed
Push — master ( c76aa7...6c061b )
by Julito
08:15 queued 11s
created

learnpath::display_student_publication_form()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 3
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\User;
7
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
8
use Chamilo\CoreBundle\Framework\Container;
9
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
12
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
13
use Chamilo\CourseBundle\Entity\CDocument;
14
use Chamilo\CourseBundle\Entity\CForumCategory;
15
use Chamilo\CourseBundle\Entity\CForumThread;
16
use Chamilo\CourseBundle\Entity\CLink;
17
use Chamilo\CourseBundle\Entity\CLp;
18
use Chamilo\CourseBundle\Entity\CLpCategory;
19
use Chamilo\CourseBundle\Entity\CLpItem;
20
use Chamilo\CourseBundle\Entity\CLpItemView;
21
use Chamilo\CourseBundle\Entity\CQuiz;
22
use Chamilo\CourseBundle\Entity\CStudentPublication;
23
use Chamilo\CourseBundle\Entity\CTool;
24
use ChamiloSession as Session;
25
use Doctrine\Common\Collections\Criteria;
26
use PhpZip\ZipFile;
27
use Symfony\Component\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
                    }*/
5695
                    if ($noWrapper) {
5696
                        return '';
5697
                    }
5698
                }
5699
5700
                return '</ul>';
5701
            },
5702
            'childOpen' => function($child) {
5703
                $id = $child['iid'];
5704
                return '<li id="'.$id.'" data-id="'.$id.'"  class="list-group-item nested-'.$child['lvl'].'">';
5705
            },
5706
            'childClose' => '',
5707
            'nodeDecorator' => function ($node) use ($mainUrl, $previewImage, $upIcon, $downIcon) {
5708
                $fullTitle = $node['title'];
5709
                $title = cut($fullTitle, self::MAX_LP_ITEM_TITLE_LENGTH);
5710
                $itemId = $node['iid'];
5711
                $type = $node['itemType'];
5712
                $lpId = $this->get_id();
5713
5714
                $moveIcon = '';
5715
                if (TOOL_LP_FINAL_ITEM !== $type) {
5716
                    $moveIcon .= '<a class="moved" href="#">';
5717
                    $moveIcon .= Display::return_icon(
5718
                        'move_everywhere.png',
5719
                        get_lang('Move'),
5720
                        [],
5721
                        ICON_SIZE_TINY
5722
                    );
5723
                    $moveIcon .= '</a>';
5724
                }
5725
5726
                $iconName = str_replace(' ', '', $type);
5727
                $icon = Display::return_icon(
5728
                    'lp_'.$iconName.'.png',
5729
                    '',
5730
                    [],
5731
                    ICON_SIZE_TINY
5732
                );
5733
5734
                $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$itemId.'&lp_id='.$lpId;
5735
                $previewIcon = Display::url(
5736
                    $previewImage,
5737
                    $urlPreviewLink,
5738
                    [
5739
                        'target' => '_blank',
5740
                        'class' => 'btn btn-default',
5741
                        'data-title' => $title,
5742
                        'title' => $title,
5743
                    ]
5744
                );
5745
                $url = $mainUrl.'&view=build&id='.$itemId.'&lp_id='.$lpId;
5746
5747
                $preRequisitiesIcon = Display::url(
5748
                    Display::return_icon(
5749
                        'accept.png',
5750
                        get_lang('Prerequisites'),
5751
                        [],
5752
                        ICON_SIZE_TINY
5753
                    ),
5754
                    $url.'&action=edit_item_prereq',
5755
                    ['class' => 'btn btn-default']
5756
                );
5757
5758
                $editIcon = '';
5759
                $editIcon .= '<a
5760
                    href="'.$mainUrl.'&action=edit_item&view=build&id='.$itemId.'&lp_id='.$lpId.'&path_item='.$node['path'].'"
5761
                    class="btn btn-default"
5762
                    >';
5763
                $editIcon .= Display::return_icon(
5764
                    'edit.png',
5765
                    get_lang('Edit section description/name'),
5766
                    [],
5767
                    ICON_SIZE_TINY
5768
                );
5769
                $editIcon .= '</a>';
5770
                $orderIcons = '';
5771
                /*if ('final_item' !== $type) {
5772
                    $orderIcons = Display::url(
5773
                        $upIcon,
5774
                        'javascript:void(0)',
5775
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $itemId]
5776
                    );
5777
                    $orderIcons .= Display::url(
5778
                        $downIcon,
5779
                        'javascript:void(0)',
5780
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $itemId]
5781
                    );
5782
                }*/
5783
5784
                $deleteIcon = ' <a
5785
                    data-id = '.$itemId.'
5786
                    data-title = \''.addslashes($title).'\'
5787
                    href="javascript:void(0);"
5788
                    onclick="return deleteItem(this);"
5789
                    class="btn btn-default">';
5790
                $deleteIcon .= Display::return_icon(
5791
                    'delete.png',
5792
                    get_lang('Delete section'),
5793
                    [],
5794
                    ICON_SIZE_TINY
5795
                );
5796
                $deleteIcon .= '</a>';
5797
                $extra = '';
5798
5799
                if ('dir' === $type && empty($node['__children'])) {
5800
                    $level = $node['lvl'] + 1;
5801
                    $extra = '<ul class="list-group nested-sortable">
5802
                                <li class="list-group-item list-group-item-empty nested-'.$level.'"></li>
5803
                              </ul>';
5804
                }
5805
                //return $title;
5806
                $buttons = Display::tag(
5807
                    'div',
5808
                    "<div class=\"btn-group btn-group-sm\">
5809
                                $editIcon
5810
                                $preRequisitiesIcon
5811
                                $orderIcons
5812
                                $deleteIcon
5813
                               </div>",
5814
                    ['class' => 'btn-toolbar button_actions']
5815
                );
5816
                //$buttons = '';
5817
                //$extra = '';
5818
                //Display::span($title, ['title' => $fullTitle])
5819
                //return $title.                $extra;
5820
                return
5821
                   // '<div class="item_data">'.
5822
                    $moveIcon.' '.$icon.' '.
5823
                    $title.
5824
                    $extra.
5825
                    $buttons
5826
                    //.'</div>'
5827
                    ;
5828
            },
5829
        ];
5830
        $tree = $lpItemRepo->childrenHierarchy($itemRoot, false, $options);
5831
5832
        if (empty($tree) && $dropElement) {
5833
            return
5834
                '<ul id="lp_item_list">'.
5835
                Display::return_message(get_lang('Drag and drop an element here')).
5836
                '</ul>';
5837
        }
5838
5839
        return $tree;
5840
    }
5841
5842
    /**
5843
     * @param array $elements
5844
     * @param array $default_data
5845
     * @param array $default_content
5846
     *
5847
     * @return string
5848
     */
5849
    public function print_recursive($elements, $default_data, $default_content)
5850
    {
5851
        $return = '';
5852
        foreach ($elements as $key => $item) {
5853
            if (isset($item['load_data']) || empty($item['data'])) {
5854
                $item['data'] = $default_data[$item['load_data']];
5855
                $item['type'] = $default_content[$item['load_data']]['item_type'];
5856
            }
5857
            $sub_list = '';
5858
            if (isset($item['type']) && 'dir' === $item['type']) {
5859
                // empty value
5860
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
5861
            }
5862
            if (empty($item['children'])) {
5863
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
5864
                $active = null;
5865
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
5866
                    $active = 'active';
5867
                }
5868
                $return .= Display::tag(
5869
                    'li',
5870
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
5871
                    ['id' => $key, 'class' => 'record li_container']
5872
                );
5873
            } else {
5874
                // Sections
5875
                $data = '';
5876
                if (isset($item['children'])) {
5877
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
5878
                }
5879
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
5880
                $return .= Display::tag(
5881
                    'li',
5882
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
5883
                    ['id' => $key, 'class' => 'record li_container']
5884
                );
5885
            }
5886
        }
5887
5888
        return $return;
5889
    }
5890
5891
    /**
5892
     * This function builds the action menu.
5893
     *
5894
     * @param bool   $returnString           Optional
5895
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
5896
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
5897
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
5898
     * @param string $action
5899
     * @param array  $extraField
5900
     *
5901
     * @return string
5902
     */
5903
    public function build_action_menu(
5904
        $returnString = false,
5905
        $showRequirementButtons = true,
5906
        $isConfigPage = false,
5907
        $allowExpand = true,
5908
        $action = '',
5909
        $extraField = []
5910
    ) {
5911
        $actionsRight = '';
5912
        $lpId = $this->lp_id;
5913
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
5914
            $back = Display::url(
5915
                Display::return_icon(
5916
                    'back.png',
5917
                    get_lang('Back to learning paths'),
5918
                    '',
5919
                    ICON_SIZE_MEDIUM
5920
                ),
5921
                'lp_controller.php?'.api_get_cidreq()
5922
            );
5923
        } else {
5924
            $back = Display::url(
5925
                Display::return_icon(
5926
                    'back.png',
5927
                    get_lang('Back'),
5928
                    '',
5929
                    ICON_SIZE_MEDIUM
5930
                ),
5931
                $extraField['backTo']
5932
            );
5933
        }
5934
5935
        /*if ($backToBuild) {
5936
            $back = Display::url(
5937
                Display::return_icon(
5938
                    'back.png',
5939
                    get_lang('GoBack'),
5940
                    '',
5941
                    ICON_SIZE_MEDIUM
5942
                ),
5943
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
5944
            );
5945
        }*/
5946
5947
        $actionsLeft = $back;
5948
5949
        $actionsLeft .= Display::url(
5950
            Display::return_icon(
5951
                'preview_view.png',
5952
                get_lang('Preview'),
5953
                '',
5954
                ICON_SIZE_MEDIUM
5955
            ),
5956
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5957
                'action' => 'view',
5958
                'lp_id' => $lpId,
5959
                'isStudentView' => 'true',
5960
            ])
5961
        );
5962
5963
        $actionsLeft .= Display::url(
5964
            Display::return_icon(
5965
                'upload_audio.png',
5966
                get_lang('Add audio'),
5967
                '',
5968
                ICON_SIZE_MEDIUM
5969
            ),
5970
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5971
                'action' => 'admin_view',
5972
                'lp_id' => $lpId,
5973
                'updateaudio' => 'true',
5974
            ])
5975
        );
5976
5977
        $subscriptionSettings = self::getSubscriptionSettings();
5978
5979
        $request = api_request_uri();
5980
        if (false === strpos($request, 'edit')) {
5981
            $actionsLeft .= Display::url(
5982
                Display::return_icon(
5983
                    'settings.png',
5984
                    get_lang('Course settings'),
5985
                    '',
5986
                    ICON_SIZE_MEDIUM
5987
                ),
5988
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
5989
                    'action' => 'edit',
5990
                    'lp_id' => $lpId,
5991
                ])
5992
            );
5993
        }
5994
5995
        if ((false === strpos($request, 'build') &&
5996
            false === strpos($request, 'add_item')) ||
5997
            in_array($action, ['add_audio'])
5998
        ) {
5999
            $actionsLeft .= Display::url(
6000
                Display::return_icon(
6001
                    'edit.png',
6002
                    get_lang('Edit'),
6003
                    '',
6004
                    ICON_SIZE_MEDIUM
6005
                ),
6006
                'lp_controller.php?'.http_build_query([
6007
                    'action' => 'build',
6008
                    'lp_id' => $lpId,
6009
                ]).'&'.api_get_cidreq()
6010
            );
6011
        }
6012
6013
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6014
            if (1 == $this->subscribeUsers &&
6015
                $subscriptionSettings['allow_add_users_to_lp']) {
6016
                $actionsLeft .= Display::url(
6017
                    Display::return_icon(
6018
                        'user.png',
6019
                        get_lang('Subscribe users to learning path'),
6020
                        '',
6021
                        ICON_SIZE_MEDIUM
6022
                    ),
6023
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6024
                );
6025
            }
6026
        }
6027
6028
        if ($allowExpand) {
6029
            /*$actionsLeft .= Display::url(
6030
                Display::return_icon(
6031
                    'expand.png',
6032
                    get_lang('Expand'),
6033
                    ['id' => 'expand'],
6034
                    ICON_SIZE_MEDIUM
6035
                ).
6036
                Display::return_icon(
6037
                    'contract.png',
6038
                    get_lang('Collapse'),
6039
                    ['id' => 'contract', 'class' => 'hide'],
6040
                    ICON_SIZE_MEDIUM
6041
                ),
6042
                '#',
6043
                ['role' => 'button', 'id' => 'hide_bar_template']
6044
            );*/
6045
        }
6046
6047
        if ($showRequirementButtons) {
6048
            $buttons = [
6049
                [
6050
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6051
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6052
                        'action' => 'set_previous_step_as_prerequisite',
6053
                        'lp_id' => $lpId,
6054
                    ]),
6055
                ],
6056
                [
6057
                    'title' => get_lang('Clear all prerequisites'),
6058
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6059
                        'action' => 'clear_prerequisites',
6060
                        'lp_id' => $lpId,
6061
                    ]),
6062
                ],
6063
            ];
6064
            $actionsRight = Display::groupButtonWithDropDown(
6065
                get_lang('Prerequisites options'),
6066
                $buttons,
6067
                true
6068
            );
6069
        }
6070
6071
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6072
            $actionsLeft .= Display::url(
6073
                Display::return_icon(
6074
                    'add-groups.png',
6075
                    get_lang('Author'),
6076
                    '',
6077
                    ICON_SIZE_MEDIUM
6078
                ),
6079
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6080
                    'action' => 'author_view',
6081
                    'lp_id' => $lpId,
6082
                ])
6083
            );
6084
        }
6085
6086
        $toolbar = Display::toolbarAction(
6087
            'actions-lp-controller',
6088
            [$actionsLeft, $actionsRight]
6089
        );
6090
6091
        if ($returnString) {
6092
            return $toolbar;
6093
        }
6094
6095
        echo $toolbar;
6096
    }
6097
6098
    /**
6099
     * Creates the default learning path folder.
6100
     *
6101
     * @param array $course
6102
     * @param int   $creatorId
6103
     *
6104
     * @return CDocument
6105
     */
6106
    public static function generate_learning_path_folder($course, $creatorId = 0)
6107
    {
6108
        // Creating learning_path folder
6109
        $dir = 'learning_path';
6110
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6111
6112
        return create_unexisting_directory(
6113
            $course,
6114
            $creatorId,
6115
            0,
6116
            null,
6117
            0,
6118
            '',
6119
            $dir,
6120
            get_lang('Learning paths'),
6121
            0
6122
        );
6123
    }
6124
6125
    /**
6126
     * @param array  $course
6127
     * @param string $lp_name
6128
     * @param int    $creatorId
6129
     *
6130
     * @return CDocument
6131
     */
6132
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6133
    {
6134
        $filepath = '';
6135
        $dir = '/learning_path/';
6136
6137
        if (empty($lp_name)) {
6138
            $lp_name = $this->name;
6139
        }
6140
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6141
        $parent = self::generate_learning_path_folder($course, $creatorId);
6142
6143
        // Limits title size
6144
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6145
        $dir = $dir.$title;
6146
6147
        // Creating LP folder
6148
        $documentId = null;
6149
        $folder = null;
6150
        if ($parent) {
6151
            $folder = create_unexisting_directory(
6152
                $course,
6153
                $creatorId,
6154
                0,
6155
                0,
6156
                0,
6157
                $filepath,
6158
                $dir,
6159
                $lp_name,
6160
                '',
6161
                false,
6162
                false,
6163
                $parent
6164
            );
6165
        }
6166
6167
        return $folder;
6168
    }
6169
6170
    /**
6171
     * Create a new document //still needs some finetuning.
6172
     *
6173
     * @param array  $courseInfo
6174
     * @param string $content
6175
     * @param string $title
6176
     * @param string $extension
6177
     * @param int    $parentId
6178
     * @param int    $creatorId  creator id
6179
     *
6180
     * @return int
6181
     */
6182
    public function create_document(
6183
        $courseInfo,
6184
        $content = '',
6185
        $title = '',
6186
        $extension = 'html',
6187
        $parentId = 0,
6188
        $creatorId = 0
6189
    ) {
6190
        if (!empty($courseInfo)) {
6191
            $course_id = $courseInfo['real_id'];
6192
        } else {
6193
            $course_id = api_get_course_int_id();
6194
        }
6195
6196
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6197
        $sessionId = api_get_session_id();
6198
6199
        // Generates folder
6200
        $this->generate_lp_folder($courseInfo);
6201
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6202
        // is already escaped twice when it gets here.
6203
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6204
        if (!empty($title)) {
6205
            $title = api_replace_dangerous_char(stripslashes($title));
6206
        } else {
6207
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6208
        }
6209
6210
        $title = disable_dangerous_file($title);
6211
        $filename = $title;
6212
        $content = !empty($content) ? $content : $_POST['content_lp'];
6213
        $tmp_filename = $filename;
6214
        /*$i = 0;
6215
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6216
            $tmp_filename = $filename.'_'.++$i;
6217
        }*/
6218
        $filename = $tmp_filename.'.'.$extension;
6219
6220
        if ('html' === $extension) {
6221
            $content = stripslashes($content);
6222
            $content = str_replace(
6223
                api_get_path(WEB_COURSE_PATH),
6224
                api_get_path(REL_PATH).'courses/',
6225
                $content
6226
            );
6227
6228
            // Change the path of mp3 to absolute.
6229
            // The first regexp deals with :// urls.
6230
            /*$content = preg_replace(
6231
                "|(flashvars=\"file=)([^:/]+)/|",
6232
                "$1".api_get_path(
6233
                    REL_COURSE_PATH
6234
                ).$courseInfo['path'].'/document/',
6235
                $content
6236
            );*/
6237
            // The second regexp deals with audio/ urls.
6238
            /*$content = preg_replace(
6239
                "|(flashvars=\"file=)([^/]+)/|",
6240
                "$1".api_get_path(
6241
                    REL_COURSE_PATH
6242
                ).$courseInfo['path'].'/document/$2/',
6243
                $content
6244
            );*/
6245
            // For flv player: To prevent edition problem with firefox,
6246
            // we have to use a strange tip (don't blame me please).
6247
            $content = str_replace(
6248
                '</body>',
6249
                '<style type="text/css">body{}</style></body>',
6250
                $content
6251
            );
6252
        }
6253
6254
        //$save_file_path = $dir.$filename;
6255
6256
        $document = DocumentManager::addDocument(
6257
            $courseInfo,
6258
            null,
6259
            'file',
6260
            '',
6261
            $tmp_filename,
6262
            '',
6263
            0, //readonly
6264
            true,
6265
            null,
6266
            $sessionId,
6267
            $creatorId,
6268
            false,
6269
            $content,
6270
            $parentId
6271
        );
6272
6273
        $document_id = $document->getIid();
6274
        if ($document_id) {
6275
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6276
            $new_title = $originalTitle;
6277
6278
            if ($new_comment || $new_title) {
6279
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6280
                $ct = '';
6281
                if ($new_comment) {
6282
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6283
                }
6284
                if ($new_title) {
6285
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6286
                }
6287
6288
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6289
                        WHERE iid = $document_id ";
6290
                Database::query($sql);
6291
            }
6292
        }
6293
6294
        return $document_id;
6295
    }
6296
6297
    /**
6298
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6299
     */
6300
    public function edit_document()
6301
    {
6302
        $repo = Container::getDocumentRepository();
6303
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6304
            $id = (int) $_REQUEST['document_id'];
6305
            /** @var CDocument $document */
6306
            $document = $repo->find($id);
6307
            if ($document->getResourceNode()->hasEditableTextContent()) {
6308
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6309
                /*   $nodeRepo = Container::getDocumentRepository()->getResourceNodeRepository();
6310
                   $nodeRepo->update($document->getResourceNode());
6311
                   var_dump($document->getResourceNode()->getContent());
6312
                   exit;*/
6313
            }
6314
            $document->setTitle($_REQUEST['title']);
6315
            $repo->update($document);
6316
        }
6317
    }
6318
6319
    /**
6320
     * Displays the selected item, with a panel for manipulating the item.
6321
     *
6322
     * @param CLpItem $lpItem
6323
     * @param string  $msg
6324
     * @param bool    $show_actions
6325
     *
6326
     * @return string
6327
     */
6328
    public function display_item($lpItem, $msg = null, $show_actions = true)
6329
    {
6330
        $course_id = api_get_course_int_id();
6331
        $return = '';
6332
6333
        if (null === $lpItem) {
6334
            return '';
6335
        }
6336
        $item_id = $lpItem->getIid();
6337
        $itemType = $lpItem->getItemType();
6338
        $lpId = $lpItem->getLp()->getIid();
6339
        $path = $lpItem->getPath();
6340
6341
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6342
6343
        // Prevents wrong parent selection for document, see Bug#1251.
6344
        if ('dir' !== $itemType) {
6345
            Session::write('parent_item_id', $lpItem->getParentItemId());
6346
        }
6347
6348
        if ($show_actions) {
6349
            $return .= $this->displayItemMenu($lpItem);
6350
        }
6351
        $return .= '<div style="padding:10px;">';
6352
6353
        if ('' != $msg) {
6354
            $return .= $msg;
6355
        }
6356
6357
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6358
6359
        switch ($itemType) {
6360
            case TOOL_THREAD:
6361
                $link = $this->rl_get_resource_link_for_learnpath(
6362
                    $course_id,
6363
                    $lpId,
6364
                    $item_id,
6365
                    0
6366
                );
6367
                $return .= Display::url(
6368
                    get_lang('Go to thread'),
6369
                    $link,
6370
                    ['class' => 'btn btn-primary']
6371
                );
6372
                break;
6373
            case TOOL_FORUM:
6374
                $return .= Display::url(
6375
                    get_lang('Go to the forum'),
6376
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6377
                    ['class' => 'btn btn-primary']
6378
                );
6379
                break;
6380
            case TOOL_QUIZ:
6381
                if (!empty($path)) {
6382
                    $exercise = new Exercise();
6383
                    $exercise->read($path);
6384
                    $return .= $exercise->description.'<br />';
6385
                    $return .= Display::url(
6386
                        get_lang('Go to exercise'),
6387
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6388
                        ['class' => 'btn btn-primary']
6389
                    );
6390
                }
6391
                break;
6392
            case TOOL_LP_FINAL_ITEM:
6393
                $return .= $this->getSavedFinalItem();
6394
                break;
6395
            case TOOL_DOCUMENT:
6396
            case TOOL_READOUT_TEXT:
6397
                $repo = Container::getDocumentRepository();
6398
                /** @var CDocument $document */
6399
                $document = $repo->find($lpItem->getPath());
6400
                $return .= $this->display_document($document, true, true);
6401
                break;
6402
            case TOOL_HOTPOTATOES:
6403
                $return .= $this->display_document($document, false, true);
6404
                break;
6405
        }
6406
        $return .= '</div>';
6407
6408
        return $return;
6409
    }
6410
6411
    /**
6412
     * Shows the needed forms for editing a specific item.
6413
     *
6414
     * @param CLpItem $lpItem
6415
     *
6416
     * @throws Exception
6417
     *
6418
     *
6419
     * @return string
6420
     */
6421
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6422
    {
6423
        $return = '';
6424
6425
        if (empty($lpItem)) {
6426
            return '';
6427
        }
6428
        $item_id = $lpItem->getIid();
6429
        $itemType = $lpItem->getItemType();
6430
        $path = $lpItem->getPath();
6431
6432
        switch ($itemType) {
6433
            case 'dir':
6434
            case 'asset':
6435
            case 'sco':
6436
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6437
                    $return .= $this->displayItemMenu($lpItem);
6438
                    $return .= $this->display_item_form(
6439
                        $lpItem,
6440
                        'edit'
6441
                    );
6442
                } else {
6443
                    $return .= $this->display_item_form(
6444
                        $lpItem,
6445
                        'edit_item'
6446
                    );
6447
                }
6448
                break;
6449
            case TOOL_LP_FINAL_ITEM:
6450
            case TOOL_DOCUMENT:
6451
            case TOOL_READOUT_TEXT:
6452
                $return .= $this->displayItemMenu($lpItem);
6453
                $return .= $this->displayDocumentForm('edit', $lpItem);
6454
                break;
6455
            case TOOL_LINK:
6456
                $link = null;
6457
                if (!empty($path)) {
6458
                    $repo = Container::getLinkRepository();
6459
                    $link = $repo->find($path);
6460
                }
6461
                $return .= $this->displayItemMenu($lpItem);
6462
                $return .= $this->display_link_form('edit', $lpItem, $link);
6463
6464
                break;
6465
            case TOOL_QUIZ:
6466
                if (!empty($path)) {
6467
                    $repo = Container::getQuizRepository();
6468
                    $resource = $repo->find($path);
6469
                }
6470
                $return .= $this->displayItemMenu($lpItem);
6471
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6472
                break;
6473
            /*case TOOL_HOTPOTATOES:
6474
                $return .= $this->displayItemMenu($lpItem);
6475
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6476
                break;*/
6477
            case TOOL_STUDENTPUBLICATION:
6478
                if (!empty($path)) {
6479
                    $repo = Container::getStudentPublicationRepository();
6480
                    $resource = $repo->find($path);
6481
                }
6482
                $return .= $this->displayItemMenu($lpItem);
6483
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6484
                break;
6485
            case TOOL_FORUM:
6486
                if (!empty($path)) {
6487
                    $repo = Container::getForumRepository();
6488
                    $resource = $repo->find($path);
6489
                }
6490
                $return .= $this->displayItemMenu($lpItem);
6491
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6492
                break;
6493
            case TOOL_THREAD:
6494
                if (!empty($path)) {
6495
                    $repo = Container::getForumPostRepository();
6496
                    $resource = $repo->find($path);
6497
                }
6498
                $return .= $this->displayItemMenu($lpItem);
6499
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6500
                break;
6501
        }
6502
6503
        return $return;
6504
    }
6505
6506
    /**
6507
     * Function that displays a list with al the resources that
6508
     * could be added to the learning path.
6509
     *
6510
     * @throws Exception
6511
     *
6512
     *
6513
     * @return bool
6514
     */
6515
    public function displayResources()
6516
    {
6517
        // Get all the docs.
6518
        $documents = $this->get_documents(true);
6519
6520
        // Get all the exercises.
6521
        $exercises = $this->get_exercises();
6522
6523
        // Get all the links.
6524
        $links = $this->get_links();
6525
6526
        // Get all the student publications.
6527
        $works = $this->get_student_publications();
6528
6529
        // Get all the forums.
6530
        $forums = $this->get_forums();
6531
6532
        // Get the final item form (see BT#11048) .
6533
        $finish = $this->getFinalItemForm();
6534
6535
        $headers = [
6536
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
6537
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
6538
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
6539
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
6540
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
6541
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
6542
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
6543
        ];
6544
6545
        echo Display::return_message(
6546
            get_lang('Click on the [Learner view] button to see your learning path'),
6547
            'normal'
6548
        );
6549
        $section = $this->displayNewSectionForm();
6550
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6551
6552
        echo Display::tabs(
6553
            $headers,
6554
            [
6555
                $documents,
6556
                $exercises,
6557
                $links,
6558
                $works,
6559
                $forums,
6560
                $section,
6561
                $finish,
6562
            ],
6563
            'resource_tab',
6564
            [],
6565
            [],
6566
            $selected
6567
        );
6568
6569
        return true;
6570
    }
6571
6572
    /**
6573
     * Returns the extension of a document.
6574
     *
6575
     * @param string $filename
6576
     *
6577
     * @return string Extension (part after the last dot)
6578
     */
6579
    public function get_extension($filename)
6580
    {
6581
        $explode = explode('.', $filename);
6582
6583
        return $explode[count($explode) - 1];
6584
    }
6585
6586
    /**
6587
     * @return string
6588
     */
6589
    public function getCurrentBuildingModeURL()
6590
    {
6591
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
6592
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
6593
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
6594
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
6595
6596
        $currentUrl = api_get_self().'?'.api_get_cidreq().
6597
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
6598
6599
        return $currentUrl;
6600
    }
6601
6602
    /**
6603
     * Displays a document by id.
6604
     *
6605
     * @param CDocument $document
6606
     * @param bool      $show_title
6607
     * @param bool      $iframe
6608
     * @param bool      $edit_link
6609
     *
6610
     * @return string
6611
     */
6612
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
6613
    {
6614
        $return = '';
6615
        if (!$document) {
6616
            return '';
6617
        }
6618
6619
        $repo = Container::getDocumentRepository();
6620
6621
        // TODO: Add a path filter.
6622
        if ($iframe) {
6623
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
6624
            $url = $repo->getResourceFileUrl($document);
6625
6626
            $return .= '<iframe
6627
                id="learnpath_preview_frame"
6628
                frameborder="0"
6629
                height="400"
6630
                width="100%"
6631
                scrolling="auto"
6632
                src="'.$url.'"></iframe>';
6633
        } else {
6634
            $return = $repo->getResourceFileContent($document);
6635
        }
6636
6637
        return $return;
6638
    }
6639
6640
    /**
6641
     * Return HTML form to add/edit a link item.
6642
     *
6643
     * @param string  $action (add/edit)
6644
     * @param CLpItem $lpItem
6645
     * @param CLink   $link
6646
     *
6647
     * @throws Exception
6648
     *
6649
     *
6650
     * @return string HTML form
6651
     */
6652
    public function display_link_form($action, $lpItem, $link)
6653
    {
6654
        $item_url = '';
6655
        if ($link) {
6656
            $item_url = stripslashes($link->getUrl());
6657
        }
6658
        $form = new FormValidator(
6659
            'edit_link',
6660
            'POST',
6661
            $this->getCurrentBuildingModeURL()
6662
        );
6663
6664
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6665
6666
        $urlAttributes = ['class' => 'learnpath_item_form'];
6667
        $urlAttributes['disabled'] = 'disabled';
6668
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
6669
        $form->setDefault('url', $item_url);
6670
6671
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6672
6673
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6674
    }
6675
6676
    /**
6677
     * Return HTML form to add/edit a quiz.
6678
     *
6679
     * @param string  $action   Action (add/edit)
6680
     * @param CLpItem $lpItem   Item ID if already exists
6681
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
6682
     *
6683
     * @throws Exception
6684
     *
6685
     * @return string HTML form
6686
     */
6687
    public function display_quiz_form($action, $lpItem, $exercise)
6688
    {
6689
        $form = new FormValidator(
6690
            'quiz_form',
6691
            'POST',
6692
            $this->getCurrentBuildingModeURL()
6693
        );
6694
6695
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6696
6697
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6698
6699
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6700
    }
6701
6702
    /**
6703
     * Return the form to display the forum edit/add option.
6704
     *
6705
     * @param CLpItem $lpItem
6706
     *
6707
     * @throws Exception
6708
     *
6709
     * @return string HTML form
6710
     */
6711
    public function display_forum_form($action, $lpItem, $resource)
6712
    {
6713
        $form = new FormValidator(
6714
            'forum_form',
6715
            'POST',
6716
            $this->getCurrentBuildingModeURL()
6717
        );
6718
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6719
6720
        if ('add' === $action) {
6721
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
6722
        } else {
6723
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
6724
        }
6725
6726
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6727
    }
6728
6729
    /**
6730
     * Return HTML form to add/edit forum threads.
6731
     *
6732
     * @param string  $action
6733
     * @param CLpItem $lpItem
6734
     * @param string  $resource
6735
     *
6736
     * @throws Exception
6737
     *
6738
     * @return string HTML form
6739
     */
6740
    public function display_thread_form($action, $lpItem, $resource)
6741
    {
6742
        $form = new FormValidator(
6743
            'thread_form',
6744
            'POST',
6745
            $this->getCurrentBuildingModeURL()
6746
        );
6747
6748
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6749
6750
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6751
6752
        return $form->returnForm();
6753
    }
6754
6755
    /**
6756
     * Return the HTML form to display an item (generally a dir item).
6757
     *
6758
     * @param CLpItem $lpItem
6759
     * @param string  $action
6760
     *
6761
     * @throws Exception
6762
     *
6763
     *
6764
     * @return string HTML form
6765
     */
6766
    public function display_item_form(
6767
        $lpItem,
6768
        $action = 'add_item'
6769
    ) {
6770
        $item_type = $lpItem->getItemType();
6771
6772
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6773
6774
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6775
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6776
6777
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6778
6779
        return $form->returnForm();
6780
    }
6781
6782
    /**
6783
     * Return HTML form to add/edit a student publication (work).
6784
     *
6785
     * @param string              $action
6786
     * @param CStudentPublication $resource
6787
     *
6788
     * @throws Exception
6789
     *
6790
     * @return string HTML form
6791
     */
6792
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
6793
    {
6794
        $form = new FormValidator('frm_student_publication', 'post', '#');
6795
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6796
6797
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6798
6799
        $return = '<div class="sectioncomment">';
6800
        $return .= $form->returnForm();
6801
        $return .= '</div>';
6802
6803
        return $return;
6804
    }
6805
6806
    public function displayNewSectionForm()
6807
    {
6808
        $action = 'add_item';
6809
        $item_type = 'dir';
6810
6811
        $lpItem = new CLpItem();
6812
        $lpItem->setTitle('dir');
6813
        $lpItem->setItemType('dir');
6814
6815
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6816
6817
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6818
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
6819
6820
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6821
        $form->addElement('hidden', 'type', 'dir');
6822
6823
        return $form->returnForm();
6824
    }
6825
6826
    /**
6827
     * Returns the form to update or create a document.
6828
     *
6829
     * @param string  $action (add/edit)
6830
     * @param CLpItem $lpItem
6831
     *
6832
     *
6833
     * @throws Exception
6834
     *
6835
     * @return string HTML form
6836
     */
6837
    public function displayDocumentForm($action = 'add', $lpItem = null)
6838
    {
6839
        $courseInfo = api_get_course_info();
6840
6841
        $form = new FormValidator(
6842
            'form',
6843
            'POST',
6844
            $this->getCurrentBuildingModeURL(),
6845
            '',
6846
            ['enctype' => 'multipart/form-data']
6847
        );
6848
6849
        $data = $this->generate_lp_folder($courseInfo);
6850
6851
        if (null !== $lpItem) {
6852
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6853
        }
6854
6855
        switch ($action) {
6856
            case 'add':
6857
                $folders = DocumentManager::get_all_document_folders(
6858
                    $courseInfo,
6859
                    0,
6860
                    true
6861
                );
6862
                DocumentManager::build_directory_selector(
6863
                    $folders,
6864
                    '',
6865
                    [],
6866
                    true,
6867
                    $form,
6868
                    'directory_parent_id'
6869
                );
6870
6871
                if ($data) {
6872
                    $defaults['directory_parent_id'] = $data->getIid();
6873
                }
6874
6875
                break;
6876
        }
6877
6878
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6879
6880
        return $form->returnForm();
6881
    }
6882
6883
    /**
6884
     * @param array  $courseInfo
6885
     * @param string $content
6886
     * @param string $title
6887
     * @param int    $parentId
6888
     *
6889
     * @throws \Doctrine\ORM\ORMException
6890
     * @throws \Doctrine\ORM\OptimisticLockException
6891
     * @throws \Doctrine\ORM\TransactionRequiredException
6892
     *
6893
     * @return int
6894
     */
6895
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
6896
    {
6897
        $creatorId = api_get_user_id();
6898
        $sessionId = api_get_session_id();
6899
6900
        // Generates folder
6901
        $result = $this->generate_lp_folder($courseInfo);
6902
        $dir = $result['dir'];
6903
6904
        if (empty($parentId) || '/' == $parentId) {
6905
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6906
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6907
6908
            if ('/' === $parentId) {
6909
                $dir = '/';
6910
            }
6911
6912
            // Please, do not modify this dirname formatting.
6913
            if (strstr($dir, '..')) {
6914
                $dir = '/';
6915
            }
6916
6917
            if (!empty($dir[0]) && '.' == $dir[0]) {
6918
                $dir = substr($dir, 1);
6919
            }
6920
            if (!empty($dir[0]) && '/' != $dir[0]) {
6921
                $dir = '/'.$dir;
6922
            }
6923
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6924
                $dir .= '/';
6925
            }
6926
        } else {
6927
            $parentInfo = DocumentManager::get_document_data_by_id(
6928
                $parentId,
6929
                $courseInfo['code']
6930
            );
6931
            if (!empty($parentInfo)) {
6932
                $dir = $parentInfo['path'].'/';
6933
            }
6934
        }
6935
6936
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6937
6938
        if (!is_dir($filepath)) {
6939
            $dir = '/';
6940
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6941
        }
6942
6943
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6944
6945
        if (!empty($title)) {
6946
            $title = api_replace_dangerous_char(stripslashes($title));
6947
        } else {
6948
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6949
        }
6950
6951
        $title = disable_dangerous_file($title);
6952
        $filename = $title;
6953
        $content = !empty($content) ? $content : $_POST['content_lp'];
6954
        $tmpFileName = $filename;
6955
6956
        $i = 0;
6957
        while (file_exists($filepath.$tmpFileName.'.html')) {
6958
            $tmpFileName = $filename.'_'.++$i;
6959
        }
6960
6961
        $filename = $tmpFileName.'.html';
6962
        $content = stripslashes($content);
6963
6964
        if (file_exists($filepath.$filename)) {
6965
            return 0;
6966
        }
6967
6968
        $putContent = file_put_contents($filepath.$filename, $content);
6969
6970
        if (false === $putContent) {
6971
            return 0;
6972
        }
6973
6974
        $fileSize = filesize($filepath.$filename);
6975
        $saveFilePath = $dir.$filename;
6976
6977
        $document = DocumentManager::addDocument(
6978
            $courseInfo,
6979
            $saveFilePath,
6980
            'file',
6981
            $fileSize,
6982
            $tmpFileName,
6983
            '',
6984
            0, //readonly
6985
            true,
6986
            null,
6987
            $sessionId,
6988
            $creatorId
6989
        );
6990
6991
        $documentId = $document->getId();
6992
6993
        if (!$document) {
6994
            return 0;
6995
        }
6996
6997
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6998
        $newTitle = $originalTitle;
6999
7000
        if ($newComment || $newTitle) {
7001
            $em = Database::getManager();
7002
7003
            if ($newComment) {
7004
                $document->setComment($newComment);
7005
            }
7006
7007
            if ($newTitle) {
7008
                $document->setTitle($newTitle);
7009
            }
7010
7011
            $em->persist($document);
7012
            $em->flush();
7013
        }
7014
7015
        return $documentId;
7016
    }
7017
7018
    /**
7019
     * Displays the menu for manipulating a step.
7020
     *
7021
     * @return string
7022
     */
7023
    public function displayItemMenu(CLpItem $lpItem)
7024
    {
7025
        $item_id = $lpItem->getIid();
7026
        $audio = $lpItem->getAudio();
7027
        $itemType = $lpItem->getItemType();
7028
        $path = $lpItem->getPath();
7029
7030
        $return = '<div class="actions">';
7031
        $audio_player = null;
7032
        // We display an audio player if needed.
7033
        if (!empty($audio)) {
7034
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7035
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7036
                .'<audio src="'.$webAudioPath.'" controls>'
7037
                .'</div><br>';*/
7038
        }
7039
7040
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7041
7042
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7043
            $return .= Display::url(
7044
                Display::return_icon(
7045
                    'edit.png',
7046
                    get_lang('Edit'),
7047
                    [],
7048
                    ICON_SIZE_SMALL
7049
                ),
7050
                $url.'&action=edit_item&path_item='.$path
7051
            );
7052
7053
            /*$return .= Display::url(
7054
                Display::return_icon(
7055
                    'move.png',
7056
                    get_lang('Move'),
7057
                    [],
7058
                    ICON_SIZE_SMALL
7059
                ),
7060
                $url.'&action=move_item'
7061
            );*/
7062
        }
7063
7064
        // Commented for now as prerequisites cannot be added to chapters.
7065
        if ('dir' !== $itemType) {
7066
            $return .= Display::url(
7067
                Display::return_icon(
7068
                    'accept.png',
7069
                    get_lang('Prerequisites'),
7070
                    [],
7071
                    ICON_SIZE_SMALL
7072
                ),
7073
                $url.'&action=edit_item_prereq'
7074
            );
7075
        }
7076
        $return .= Display::url(
7077
            Display::return_icon(
7078
                'delete.png',
7079
                get_lang('Delete'),
7080
                [],
7081
                ICON_SIZE_SMALL
7082
            ),
7083
            $url.'&action=delete_item'
7084
        );
7085
7086
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7087
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7088
            if (empty($documentData)) {
7089
                // Try with iid
7090
                $table = Database::get_course_table(TABLE_DOCUMENT);
7091
                $sql = "SELECT path FROM $table
7092
                        WHERE
7093
                              c_id = ".api_get_course_int_id()." AND
7094
                              iid = ".$path." AND
7095
                              path NOT LIKE '%_DELETED_%'";
7096
                $result = Database::query($sql);
7097
                $documentData = Database::fetch_array($result);
7098
                if ($documentData) {
7099
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7100
                }
7101
            }
7102
            if (isset($documentData['absolute_path_from_document'])) {
7103
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7104
            }
7105
        }*/
7106
7107
        $return .= '</div>';
7108
7109
        if (!empty($audio_player)) {
7110
            $return .= $audio_player;
7111
        }
7112
7113
        return $return;
7114
    }
7115
7116
    /**
7117
     * Creates the javascript needed for filling up the checkboxes without page reload.
7118
     *
7119
     * @return string
7120
     */
7121
    public function get_js_dropdown_array()
7122
    {
7123
        $course_id = api_get_course_int_id();
7124
        $return = 'var child_name = new Array();'."\n";
7125
        $return .= 'var child_value = new Array();'."\n\n";
7126
        $return .= 'child_name[0] = new Array();'."\n";
7127
        $return .= 'child_value[0] = new Array();'."\n\n";
7128
7129
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7130
        $sql = "SELECT * FROM ".$tbl_lp_item."
7131
                WHERE
7132
                    lp_id = ".$this->lp_id." AND
7133
                    parent_item_id = 0
7134
                ORDER BY display_order ASC";
7135
        Database::query($sql);
7136
        $i = 0;
7137
7138
        $list = $this->getItemsForForm(true);
7139
7140
        foreach ($list as $row_zero) {
7141
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7142
                if (TOOL_QUIZ == $row_zero['item_type']) {
7143
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7144
                }
7145
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7146
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7147
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7148
            }
7149
        }
7150
7151
        $return .= "\n";
7152
        $sql = "SELECT * FROM $tbl_lp_item
7153
                WHERE lp_id = ".$this->lp_id;
7154
        $res = Database::query($sql);
7155
        while ($row = Database::fetch_array($res)) {
7156
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7157
                           WHERE
7158
                                parent_item_id = ".$row['iid']."
7159
                           ORDER BY display_order ASC";
7160
            $res_parent = Database::query($sql_parent);
7161
            $i = 0;
7162
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7163
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7164
7165
            while ($row_parent = Database::fetch_array($res_parent)) {
7166
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7167
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7168
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7169
            }
7170
            $return .= "\n";
7171
        }
7172
7173
        $return .= "
7174
            function load_cbo(id) {
7175
                if (!id) {
7176
                    return false;
7177
                }
7178
7179
                var cbo = document.getElementById('previous');
7180
                for(var i = cbo.length - 1; i > 0; i--) {
7181
                    cbo.options[i] = null;
7182
                }
7183
7184
                var k=0;
7185
                for(var i = 1; i <= child_name[id].length; i++){
7186
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7187
                    option.style.paddingLeft = '40px';
7188
                    cbo.options[i] = option;
7189
                    k = i;
7190
                }
7191
7192
                cbo.options[k].selected = true;
7193
                //$('#previous').selectpicker('refresh');
7194
            }";
7195
7196
        return $return;
7197
    }
7198
7199
    /**
7200
     * Display the form to allow moving an item.
7201
     *
7202
     * @param CLpItem $lpItem
7203
     *
7204
     * @throws Exception
7205
     *
7206
     *
7207
     * @return string HTML form
7208
     */
7209
    public function display_move_item($lpItem)
7210
    {
7211
        $return = '';
7212
        $path = $lpItem->getPath();
7213
7214
        if ($lpItem) {
7215
            $itemType = $lpItem->getItemType();
7216
            switch ($itemType) {
7217
                case 'dir':
7218
                case 'asset':
7219
                    $return .= $this->displayItemMenu($lpItem);
7220
                    $return .= $this->display_item_form(
7221
                        $lpItem,
7222
                        get_lang('Move the current section'),
7223
                        'move',
7224
                        $row
7225
                    );
7226
                    break;
7227
                case TOOL_DOCUMENT:
7228
                    $return .= $this->displayItemMenu($lpItem);
7229
                    $return .= $this->displayDocumentForm('move', $lpItem);
7230
                    break;
7231
                case TOOL_LINK:
7232
                    $link = null;
7233
                    if (!empty($path)) {
7234
                        $repo = Container::getLinkRepository();
7235
                        $link = $repo->find($path);
7236
                    }
7237
                    $return .= $this->displayItemMenu($lpItem);
7238
                    $return .= $this->display_link_form('move', $lpItem, $link);
7239
                    break;
7240
                case TOOL_HOTPOTATOES:
7241
                    $return .= $this->displayItemMenu($lpItem);
7242
                    $return .= $this->display_link_form('move', $lpItem, $row);
7243
                    break;
7244
                case TOOL_QUIZ:
7245
                    $return .= $this->displayItemMenu($lpItem);
7246
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7247
                    break;
7248
                case TOOL_STUDENTPUBLICATION:
7249
                    $return .= $this->displayItemMenu($lpItem);
7250
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7251
                    break;
7252
                case TOOL_FORUM:
7253
                    $return .= $this->displayItemMenu($lpItem);
7254
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7255
                    break;
7256
                case TOOL_THREAD:
7257
                    $return .= $this->displayItemMenu($lpItem);
7258
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7259
                    break;
7260
            }
7261
        }
7262
7263
        return $return;
7264
    }
7265
7266
    /**
7267
     * Return HTML form to allow prerequisites selection.
7268
     *
7269
     * @todo use FormValidator
7270
     *
7271
     * @return string HTML form
7272
     */
7273
    public function display_item_prerequisites_form(CLpItem $lpItem)
7274
    {
7275
        $course_id = api_get_course_int_id();
7276
        $prerequisiteId = $lpItem->getPrerequisite();
7277
        $itemId = $lpItem->getIid();
7278
7279
        $return = '<legend>';
7280
        $return .= get_lang('Add/edit prerequisites');
7281
        $return .= '</legend>';
7282
        $return .= '<form method="POST">';
7283
        $return .= '<div class="table-responsive">';
7284
        $return .= '<table class="table table-hover">';
7285
        $return .= '<thead>';
7286
        $return .= '<tr>';
7287
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7288
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7289
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7290
        $return .= '</tr>';
7291
        $return .= '</thead>';
7292
7293
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7294
        $return .= '<tbody>';
7295
        $return .= '<tr>';
7296
        $return .= '<td colspan="3">';
7297
        $return .= '<div class="radio learnpath"><label for="idnone">';
7298
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7299
        $return .= get_lang('none').'</label>';
7300
        $return .= '</div>';
7301
        $return .= '</tr>';
7302
        // @todo use entitites
7303
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7304
        $sql = "SELECT * FROM $tbl_lp_item
7305
                WHERE lp_id = ".$this->lp_id;
7306
        $result = Database::query($sql);
7307
7308
        $selectedMinScore = [];
7309
        $selectedMaxScore = [];
7310
        $masteryScore = [];
7311
        while ($row = Database::fetch_array($result)) {
7312
            if ($row['iid'] == $itemId) {
7313
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7314
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7315
            }
7316
            $masteryScore[$row['iid']] = $row['mastery_score'];
7317
        }
7318
7319
        $arrLP = $this->getItemsForForm();
7320
        $this->tree_array($arrLP);
7321
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7322
        unset($this->arrMenu);
7323
7324
        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...
7325
            $item = $arrLP[$i];
7326
7327
            if ($item['id'] == $itemId) {
7328
                break;
7329
            }
7330
7331
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7332
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7333
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7334
7335
            $return .= '<tr>';
7336
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7337
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7338
            $return .= '<label for="id'.$item['id'].'">';
7339
7340
            $checked = '';
7341
            if (null !== $prerequisiteId) {
7342
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7343
            }
7344
7345
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7346
7347
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7348
7349
            $icon_name = str_replace(' ', '', $item['item_type']);
7350
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7351
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7352
            } else {
7353
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7354
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7355
                } else {
7356
                    $return .= Display::return_icon('folder_document.png');
7357
                }
7358
            }
7359
7360
            $return .= $item['title'].'</label>';
7361
            $return .= '</div>';
7362
            $return .= '</td>';
7363
7364
            if (TOOL_QUIZ == $item['item_type']) {
7365
                // lets update max_score Tests information depending of the Tests Advanced properties
7366
                $lpItemObj = new LpItem($course_id, $item['id']);
7367
                $exercise = new Exercise($course_id);
7368
                $exercise->read($lpItemObj->path);
7369
                $lpItemObj->max_score = $exercise->get_max_score();
7370
                $lpItemObj->update();
7371
                $item['max_score'] = $lpItemObj->max_score;
7372
7373
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7374
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7375
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7376
                }
7377
7378
                $return .= '<td>';
7379
                $return .= '<input
7380
                    class="form-control"
7381
                    size="4" maxlength="3"
7382
                    name="min_'.$item['id'].'"
7383
                    type="number"
7384
                    min="0"
7385
                    step="any"
7386
                    max="'.$item['max_score'].'"
7387
                    value="'.$selectedMinScoreValue.'"
7388
                />';
7389
                $return .= '</td>';
7390
                $return .= '<td>';
7391
                $return .= '<input
7392
                    class="form-control"
7393
                    size="4"
7394
                    maxlength="3"
7395
                    name="max_'.$item['id'].'"
7396
                    type="number"
7397
                    min="0"
7398
                    step="any"
7399
                    max="'.$item['max_score'].'"
7400
                    value="'.$selectedMaxScoreValue.'"
7401
                />';
7402
                $return .= '</td>';
7403
            }
7404
7405
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7406
                $return .= '<td>';
7407
                $return .= '<input
7408
                    size="4"
7409
                    maxlength="3"
7410
                    name="min_'.$item['id'].'"
7411
                    type="number"
7412
                    min="0"
7413
                    step="any"
7414
                    max="'.$item['max_score'].'"
7415
                    value="'.$selectedMinScoreValue.'"
7416
                />';
7417
                $return .= '</td>';
7418
                $return .= '<td>';
7419
                $return .= '<input
7420
                    size="4"
7421
                    maxlength="3"
7422
                    name="max_'.$item['id'].'"
7423
                    type="number"
7424
                    min="0"
7425
                    step="any"
7426
                    max="'.$item['max_score'].'"
7427
                    value="'.$selectedMaxScoreValue.'"
7428
                />';
7429
                $return .= '</td>';
7430
            }
7431
            $return .= '</tr>';
7432
        }
7433
        $return .= '<tr>';
7434
        $return .= '</tr>';
7435
        $return .= '</tbody>';
7436
        $return .= '</table>';
7437
        $return .= '</div>';
7438
        $return .= '<div class="form-group">';
7439
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7440
            get_lang('Save prerequisites settings').'</button>';
7441
        $return .= '</form>';
7442
7443
        return $return;
7444
    }
7445
7446
    /**
7447
     * Return HTML list to allow prerequisites selection for lp.
7448
     *
7449
     * @return string HTML form
7450
     */
7451
    public function display_lp_prerequisites_list()
7452
    {
7453
        $lp_id = $this->lp_id;
7454
        $prerequisiteId = $this->entity->getPrerequisite();
7455
7456
        $repo = Container::getLpRepository();
7457
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
7458
        /** @var CLp[] $lps */
7459
        $lps = $qb->getQuery()->getResult();
7460
7461
        //$session_id = api_get_session_id();
7462
        /*$session_condition = api_get_session_condition($session_id, true, true);
7463
        $sql = "SELECT * FROM $tbl_lp
7464
                WHERE c_id = $course_id $session_condition
7465
                ORDER BY display_order ";
7466
        $rs = Database::query($sql);*/
7467
        $return = '';
7468
        $return .= '<select name="prerequisites" class="form-control">';
7469
        $return .= '<option value="0">'.get_lang('none').'</option>';
7470
7471
        foreach ($lps as $lp) {
7472
            $myLpId = $lp->getIid();
7473
            if ($myLpId == $lp_id) {
7474
                continue;
7475
            }
7476
            $return .= '<option
7477
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
7478
                $lp->getName().
7479
                '</option>';
7480
        }
7481
7482
        $return .= '</select>';
7483
7484
        return $return;
7485
    }
7486
7487
    /**
7488
     * Creates a list with all the documents in it.
7489
     *
7490
     * @param bool $showInvisibleFiles
7491
     *
7492
     * @throws Exception
7493
     *
7494
     *
7495
     * @return string
7496
     */
7497
    public function get_documents($showInvisibleFiles = false)
7498
    {
7499
        $course_info = api_get_course_info();
7500
        $sessionId = api_get_session_id();
7501
        $documentTree = DocumentManager::get_document_preview(
7502
            $course_info,
7503
            $this->lp_id,
7504
            null,
7505
            $sessionId,
7506
            true,
7507
            null,
7508
            null,
7509
            $showInvisibleFiles,
7510
            true
7511
        );
7512
7513
        $form = new FormValidator(
7514
            'form_upload',
7515
            'POST',
7516
            $this->getCurrentBuildingModeURL(),
7517
            '',
7518
            ['enctype' => 'multipart/form-data']
7519
        );
7520
7521
        $folders = DocumentManager::get_all_document_folders(
7522
            api_get_course_info(),
7523
            0,
7524
            true
7525
        );
7526
7527
        $folder = $this->generate_lp_folder(api_get_course_info());
7528
7529
        DocumentManager::build_directory_selector(
7530
            $folders,
7531
            $folder->getIid(),
7532
            [],
7533
            true,
7534
            $form,
7535
            'directory_parent_id'
7536
        );
7537
7538
        $group = [
7539
            $form->createElement(
7540
                'radio',
7541
                'if_exists',
7542
                get_lang('If file exists:'),
7543
                get_lang('Do nothing'),
7544
                'nothing'
7545
            ),
7546
            $form->createElement(
7547
                'radio',
7548
                'if_exists',
7549
                null,
7550
                get_lang('Overwrite the existing file'),
7551
                'overwrite'
7552
            ),
7553
            $form->createElement(
7554
                'radio',
7555
                'if_exists',
7556
                null,
7557
                get_lang('Rename the uploaded file if it exists'),
7558
                'rename'
7559
            ),
7560
        ];
7561
        $form->addGroup($group, null, get_lang('If file exists:'));
7562
7563
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7564
        $defaultFileExistsOption = 'rename';
7565
        if (!empty($fileExistsOption)) {
7566
            $defaultFileExistsOption = $fileExistsOption;
7567
        }
7568
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7569
7570
        // Check box options
7571
        $form->addElement(
7572
            'checkbox',
7573
            'unzip',
7574
            get_lang('Options'),
7575
            get_lang('Uncompress zip')
7576
        );
7577
7578
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7579
        $form->addMultipleUpload($url);
7580
7581
        $lpItem = new CLpItem();
7582
        $lpItem->setTitle('');
7583
        $lpItem->setItemType(TOOL_DOCUMENT);
7584
        $new = $this->displayDocumentForm('add', $lpItem);
7585
7586
        /*$lpItem = new CLpItem();
7587
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7588
        $frmReadOutText = $this->displayDocumentForm('add');*/
7589
7590
        $headers = [
7591
            get_lang('Files'),
7592
            get_lang('Create a new document'),
7593
            //get_lang('Create read-out text'),
7594
            get_lang('Upload'),
7595
        ];
7596
7597
        return Display::tabs(
7598
            $headers,
7599
            [$documentTree, $new, $form->returnForm()],
7600
            'subtab'
7601
        );
7602
    }
7603
7604
    /**
7605
     * Creates a list with all the exercises (quiz) in it.
7606
     *
7607
     * @return string
7608
     */
7609
    public function get_exercises()
7610
    {
7611
        $course_id = api_get_course_int_id();
7612
        $session_id = api_get_session_id();
7613
        $userInfo = api_get_user_info();
7614
7615
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7616
        $condition_session = api_get_session_condition($session_id, true, true);
7617
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7618
7619
        //$activeCondition = ' active <> -1 ';
7620
        $active = 2;
7621
        if ($setting) {
7622
            $active = 1;
7623
            //$activeCondition = ' active = 1 ';
7624
        }
7625
7626
        $categoryCondition = '';
7627
7628
        $keyword = $_REQUEST['keyword'] ?? null;
7629
        $categoryId = $_REQUEST['category_id'] ?? null;
7630
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
7631
            $categoryCondition = " AND exercise_category_id = $categoryId ";
7632
        }
7633
7634
        $keywordCondition = '';
7635
7636
        if (!empty($keyword)) {
7637
            $keyword = Database::escape_string($keyword);
7638
            $keywordCondition = " AND title LIKE '%$keyword%' ";
7639
        }
7640
        */
7641
        $course = api_get_course_entity($course_id);
7642
        $session = api_get_session_entity($session_id);
7643
7644
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
7645
        /** @var CQuiz[] $exercises */
7646
        $exercises = $qb->getQuery()->getResult();
7647
7648
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
7649
                     WHERE
7650
                            c_id = $course_id AND
7651
                            $activeCondition
7652
                            $condition_session
7653
                            $categoryCondition
7654
                            $keywordCondition
7655
                     ORDER BY title ASC";
7656
        $res_quiz = Database::query($sql_quiz);*/
7657
7658
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
7659
7660
        // Create a search-box
7661
        $form = new FormValidator('search_simple', 'get', $currentUrl);
7662
        $form->addHidden('action', 'add_item');
7663
        $form->addHidden('type', 'step');
7664
        $form->addHidden('lp_id', $this->lp_id);
7665
        $form->addHidden('lp_build_selected', '2');
7666
7667
        $form->addCourseHiddenParams();
7668
        $form->addText(
7669
            'keyword',
7670
            get_lang('Search'),
7671
            false,
7672
            [
7673
                'aria-label' => get_lang('Search'),
7674
            ]
7675
        );
7676
7677
        if (api_get_configuration_value('allow_exercise_categories')) {
7678
            $manager = new ExerciseCategoryManager();
7679
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
7680
            if (!empty($options)) {
7681
                $form->addSelect(
7682
                    'category_id',
7683
                    get_lang('Category'),
7684
                    $options,
7685
                    ['placeholder' => get_lang('Please select an option')]
7686
                );
7687
            }
7688
        }
7689
7690
        $form->addButtonSearch(get_lang('Search'));
7691
        $return = $form->returnForm();
7692
7693
        $return .= '<ul class=" list-group lp_resource">';
7694
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7695
        $return .= Display::return_icon('new_exercice.png');
7696
        $return .= '<a
7697
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
7698
            get_lang('New test').'</a>';
7699
        $return .= '</li>';
7700
7701
        $previewIcon = Display::return_icon(
7702
            'preview_view.png',
7703
            get_lang('Preview')
7704
        );
7705
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
7706
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7707
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
7708
        foreach ($exercises as $exercise) {
7709
            $exerciseId = $exercise->getIid();
7710
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
7711
            $visibility = $exercise->isVisible($course, $session);
7712
7713
            $link = Display::url(
7714
                $previewIcon,
7715
                $exerciseUrl.'&exerciseId='.$exerciseId,
7716
                ['target' => '_blank']
7717
            );
7718
            $return .= '<li class="list-group-item lp_resource_element" title="'.$title.'">';
7719
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
7720
            $return .= $quizIcon;
7721
            $sessionStar = '';
7722
            /*$sessionStar = api_get_session_image(
7723
                $row_quiz['session_id'],
7724
                $userInfo['status']
7725
            );*/
7726
            $return .= Display::url(
7727
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
7728
                api_get_self().'?'.
7729
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
7730
                [
7731
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
7732
                    'data_type' => 'quiz',
7733
                    'data-id' => $exerciseId,
7734
                ]
7735
            );
7736
            $return .= '</li>';
7737
        }
7738
7739
        $return .= '</ul>';
7740
7741
        return $return;
7742
    }
7743
7744
    /**
7745
     * Creates a list with all the links in it.
7746
     *
7747
     * @return string
7748
     */
7749
    public function get_links()
7750
    {
7751
        $sessionId = api_get_session_id();
7752
        $repo = Container::getLinkRepository();
7753
7754
        $course = api_get_course_entity();
7755
        $session = api_get_session_entity($sessionId);
7756
        $qb = $repo->getResourcesByCourse($course, $session);
7757
        /** @var CLink[] $links */
7758
        $links = $qb->getQuery()->getResult();
7759
7760
        $selfUrl = api_get_self();
7761
        $courseIdReq = api_get_cidreq();
7762
        $userInfo = api_get_user_info();
7763
7764
        $moveEverywhereIcon = Display::return_icon(
7765
            'move_everywhere.png',
7766
            get_lang('Move'),
7767
            [],
7768
            ICON_SIZE_TINY
7769
        );
7770
7771
        /*$condition_session = api_get_session_condition(
7772
            $session_id,
7773
            true,
7774
            true,
7775
            'link.session_id'
7776
        );
7777
7778
        $sql = "SELECT
7779
                    link.id as link_id,
7780
                    link.title as link_title,
7781
                    link.session_id as link_session_id,
7782
                    link.category_id as category_id,
7783
                    link_category.category_title as category_title
7784
                FROM $tbl_link as link
7785
                LEFT JOIN $linkCategoryTable as link_category
7786
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
7787
                WHERE link.c_id = $course_id $condition_session
7788
                ORDER BY link_category.category_title ASC, link.title ASC";
7789
        $result = Database::query($sql);*/
7790
        $categorizedLinks = [];
7791
        $categories = [];
7792
7793
        foreach ($links as $link) {
7794
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
7795
            if (empty($categoryId)) {
7796
                $categories[0] = get_lang('Uncategorized');
7797
            } else {
7798
                $category = $link->getCategory();
7799
                $categories[$categoryId] = $category->getCategoryTitle();
7800
            }
7801
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
7802
        }
7803
7804
        $linksHtmlCode =
7805
            '<script>
7806
            function toggle_tool(tool, id) {
7807
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
7808
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
7809
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7810
                } else {
7811
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
7812
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7813
                }
7814
            }
7815
        </script>
7816
7817
        <ul class="list-group lp_resource">
7818
            <li class="list-group-item lp_resource_element disable_drag ">
7819
                '.Display::return_icon('linksnew.gif').'
7820
                <a
7821
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
7822
                title="'.get_lang('Add a link').'">'.
7823
                get_lang('Add a link').'
7824
                </a>
7825
            </li>';
7826
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
7827
        foreach ($categorizedLinks as $categoryId => $links) {
7828
            $linkNodes = null;
7829
            /** @var CLink $link */
7830
            foreach ($links as $key => $link) {
7831
                $title = $link->getTitle();
7832
                $linkUrl = Display::url(
7833
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7834
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
7835
                    ['target' => '_blank']
7836
                );
7837
7838
                if ($link->isVisible($course, $session)) {
7839
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
7840
                    $sessionStar = '';
7841
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
7842
                    $link = Display::url(
7843
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
7844
                        $url,
7845
                        [
7846
                            'class' => 'moved link_with_id',
7847
                            'data-id' => $key,
7848
                            'data_type' => TOOL_LINK,
7849
                            'title' => $title,
7850
                        ]
7851
                    );
7852
                    $linkNodes .=
7853
                        '<li class="list-group-item lp_resource_element">
7854
                         <a class="moved" href="#">'.
7855
                            $moveEverywhereIcon.
7856
                        '</a>
7857
                        '.$linkIcon.$link.'
7858
                        </li>';
7859
                }
7860
            }
7861
            $linksHtmlCode .=
7862
                '<li class="list-group-item disable_drag">
7863
                    <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" >
7864
                        <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
7865
                        align="absbottom" />
7866
                    </a>
7867
                    <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
7868
                </li>
7869
            '.
7870
                $linkNodes.
7871
            '';
7872
            //<div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.
7873
        }
7874
        $linksHtmlCode .= '</ul>';
7875
7876
        return $linksHtmlCode;
7877
    }
7878
7879
    /**
7880
     * Creates a list with all the student publications in it.
7881
     *
7882
     * @return string
7883
     */
7884
    public function get_student_publications()
7885
    {
7886
        $return = '<ul class="list-group lp_resource">';
7887
        $return .= '<li class="list-group-item lp_resource_element">';
7888
        /*
7889
        $return .= Display::return_icon('works_new.gif');
7890
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
7891
            get_lang('Add the Assignments tool to the course').'</a>';
7892
        $return .= '</li>';*/
7893
7894
        $works = getWorkListTeacher(0, 100, null, null, null);
7895
        if (!empty($works)) {
7896
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
7897
            foreach ($works as $work) {
7898
                $link = Display::url(
7899
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7900
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
7901
                    ['target' => '_blank']
7902
                );
7903
7904
                $return .= '<li class="list-group-item lp_resource_element">';
7905
                $return .= '<a class="moved" href="#">';
7906
                $return .= Display::return_icon(
7907
                    'move_everywhere.png',
7908
                    get_lang('Move'),
7909
                    [],
7910
                    ICON_SIZE_TINY
7911
                );
7912
                $return .= '</a> ';
7913
7914
                $return .= $icon;
7915
                $return .= Display::url(
7916
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
7917
                    api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
7918
                    [
7919
                        'class' => 'moved link_with_id',
7920
                        'data-id' => $work['iid'],
7921
                        'data_type' => TOOL_STUDENTPUBLICATION,
7922
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
7923
                    ]
7924
                );
7925
7926
                $return .= '</li>';
7927
            }
7928
        }
7929
7930
        $return .= '</ul>';
7931
7932
        return $return;
7933
    }
7934
7935
    /**
7936
     * Creates a list with all the forums in it.
7937
     *
7938
     * @return string
7939
     */
7940
    public function get_forums()
7941
    {
7942
        $forumCategories = get_forum_categories();
7943
        $forumsInNoCategory = get_forums_in_category(0);
7944
        if (!empty($forumsInNoCategory)) {
7945
            $forumCategories = array_merge(
7946
                $forumCategories,
7947
                [
7948
                    [
7949
                        'cat_id' => 0,
7950
                        'session_id' => 0,
7951
                        'visibility' => 1,
7952
                        'cat_comment' => null,
7953
                    ],
7954
                ]
7955
            );
7956
        }
7957
7958
        $a_forums = [];
7959
        $courseEntity = api_get_course_entity(api_get_course_int_id());
7960
        $sessionEntity = api_get_session_entity(api_get_session_id());
7961
7962
        foreach ($forumCategories as $forumCategory) {
7963
            // The forums in this category.
7964
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
7965
            if (!empty($forumsInCategory)) {
7966
                foreach ($forumsInCategory as $forum) {
7967
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
7968
                        $a_forums[] = $forum;
7969
                    }
7970
                }
7971
            }
7972
        }
7973
7974
        $return = '<ul class="list-group lp_resource">';
7975
7976
        // First add link
7977
        $return .= '<li class="list-group-item lp_resource_element disable_drag">';
7978
        $return .= Display::return_icon('new_forum.png');
7979
        $return .= Display::url(
7980
            get_lang('Create a new forum'),
7981
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
7982
                'action' => 'add',
7983
                'content' => 'forum',
7984
                'lp_id' => $this->lp_id,
7985
            ]),
7986
            ['title' => get_lang('Create a new forum')]
7987
        );
7988
        $return .= '</li>';
7989
7990
        $return .= '<script>
7991
            function toggle_forum(forum_id) {
7992
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
7993
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
7994
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7995
                } else {
7996
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
7997
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7998
                }
7999
            }
8000
        </script>';
8001
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8002
        foreach ($a_forums as $forum) {
8003
            $forumId = $forum->getIid();
8004
            $title = Security::remove_XSS($forum->getForumTitle());
8005
            $link = Display::url(
8006
                Display::return_icon('preview_view.png', get_lang('Preview')),
8007
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8008
                ['target' => '_blank']
8009
            );
8010
8011
            $return .= '<li class="list-group-item lp_resource_element">';
8012
            $return .= '<a class="moved" href="#">';
8013
            $return .= $moveIcon;
8014
            $return .= ' </a>';
8015
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8016
8017
            $moveLink = Display::url(
8018
                $title.' '.$link,
8019
                api_get_self().'?'.
8020
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
8021
                [
8022
                    'class' => 'moved link_with_id',
8023
                    'data-id' => $forumId,
8024
                    'data_type' => TOOL_FORUM,
8025
                    'title' => $title,
8026
                    'style' => 'vertical-align:middle',
8027
                ]
8028
            );
8029
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8030
                            <img
8031
                                src="'.Display::returnIconPath('add.png').'"
8032
                                id="forum_'.$forumId.'_opener" align="absbottom"
8033
                             />
8034
                        </a>
8035
                        '.$moveLink;
8036
            $return .= '</li>';
8037
8038
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8039
            $threads = get_threads($forumId);
8040
            if (is_array($threads)) {
8041
                foreach ($threads as $thread) {
8042
                    $threadId = $thread->getIid();
8043
                    $link = Display::url(
8044
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8045
                        api_get_path(WEB_CODE_PATH).
8046
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8047
                        ['target' => '_blank']
8048
                    );
8049
8050
                    $return .= '<li class="list-group-item lp_resource_element">';
8051
                    $return .= '&nbsp;<a class="moved" href="#">';
8052
                    $return .= $moveIcon;
8053
                    $return .= ' </a>';
8054
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8055
                    $return .= '<a
8056
                        class="moved link_with_id"
8057
                        data-id="'.$thread->getIid().'"
8058
                        data_type="'.TOOL_THREAD.'"
8059
                        title="'.$thread->getThreadTitle().'"
8060
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
8061
                        >'.
8062
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8063
                    $return .= '</li>';
8064
                }
8065
            }
8066
            $return .= '</div>';
8067
        }
8068
        $return .= '</ul>';
8069
8070
        return $return;
8071
    }
8072
8073
    /**
8074
     * // TODO: The output encoding should be equal to the system encoding.
8075
     *
8076
     * Exports the learning path as a SCORM package. This is the main function that
8077
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8078
     * whole thing and returns the zip.
8079
     *
8080
     * This method needs to be called in PHP5, as it will fail with non-adequate
8081
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8082
     * you need to call it on a learnpath object.
8083
     *
8084
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8085
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8086
     * path has been modified, it should use the generic method here below.
8087
     *
8088
     * @return string Returns the zip package string, or null if error
8089
     */
8090
    public function scormExport()
8091
    {
8092
        api_set_more_memory_and_time_limits();
8093
8094
        $_course = api_get_course_info();
8095
        $course_id = $_course['real_id'];
8096
        // Create the zip handler (this will remain available throughout the method).
8097
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8098
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8099
        $temp_dir_short = uniqid('scorm_export', true);
8100
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8101
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8102
        $zip_folder = new PclZip($temp_zip_file);
8103
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8104
        $root_path = $main_path = api_get_path(SYS_PATH);
8105
        $files_cleanup = [];
8106
8107
        // Place to temporarily stash the zip file.
8108
        // create the temp dir if it doesn't exist
8109
        // or do a cleanup before creating the zip file.
8110
        if (!is_dir($temp_zip_dir)) {
8111
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8112
        } else {
8113
            // Cleanup: Check the temp dir for old files and delete them.
8114
            $handle = opendir($temp_zip_dir);
8115
            while (false !== ($file = readdir($handle))) {
8116
                if ('.' != $file && '..' != $file) {
8117
                    unlink("$temp_zip_dir/$file");
8118
                }
8119
            }
8120
            closedir($handle);
8121
        }
8122
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8123
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8124
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8125
        ) {
8126
            // Remove the possible . at the end of the path.
8127
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8128
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8129
            mkdir(
8130
                $dest_path_to_scorm_folder,
8131
                api_get_permissions_for_new_directories(),
8132
                true
8133
            );
8134
            copyr(
8135
                $current_course_path.'/scorm/'.$this->path,
8136
                $dest_path_to_scorm_folder,
8137
                ['imsmanifest'],
8138
                $zip_files
8139
            );
8140
        }
8141
8142
        // Build a dummy imsmanifest structure.
8143
        // Do not add to the zip yet (we still need it).
8144
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8145
        // Aggregation Model official document, section "2.3 Content Packaging".
8146
        // We are going to build a UTF-8 encoded manifest.
8147
        // Later we will recode it to the desired (and supported) encoding.
8148
        $xmldoc = new DOMDocument('1.0');
8149
        $root = $xmldoc->createElement('manifest');
8150
        $root->setAttribute('identifier', 'SingleCourseManifest');
8151
        $root->setAttribute('version', '1.1');
8152
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8153
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8154
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8155
        $root->setAttribute(
8156
            'xsi:schemaLocation',
8157
            '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'
8158
        );
8159
        // Build mandatory sub-root container elements.
8160
        $metadata = $xmldoc->createElement('metadata');
8161
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8162
        $metadata->appendChild($md_schema);
8163
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8164
        $metadata->appendChild($md_schemaversion);
8165
        $root->appendChild($metadata);
8166
8167
        $organizations = $xmldoc->createElement('organizations');
8168
        $resources = $xmldoc->createElement('resources');
8169
8170
        // Build the only organization we will use in building our learnpaths.
8171
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8172
        $organization = $xmldoc->createElement('organization');
8173
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8174
        // To set the title of the SCORM entity (=organization), we take the name given
8175
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8176
        // learning path charset) as it is the encoding that defines how it is stored
8177
        // in the database. Then we convert it to HTML entities again as the "&" character
8178
        // alone is not authorized in XML (must be &amp;).
8179
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8180
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8181
        $organization->appendChild($org_title);
8182
        $folder_name = 'document';
8183
8184
        // Removes the learning_path/scorm_folder path when exporting see #4841
8185
        $path_to_remove = '';
8186
        $path_to_replace = '';
8187
        $result = $this->generate_lp_folder($_course);
8188
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8189
            $path_to_remove = 'document'.$result['dir'];
8190
            $path_to_replace = $folder_name.'/';
8191
        }
8192
8193
        // Fixes chamilo scorm exports
8194
        if ('chamilo_scorm_export' === $this->ref) {
8195
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8196
        }
8197
8198
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8199
        $link_updates = [];
8200
        $links_to_create = [];
8201
        foreach ($this->ordered_items as $index => $itemId) {
8202
            /** @var learnpathItem $item */
8203
            $item = $this->items[$itemId];
8204
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8205
                // Get included documents from this item.
8206
                if ('sco' === $item->type) {
8207
                    $inc_docs = $item->get_resources_from_source(
8208
                        null,
8209
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8210
                    );
8211
                } else {
8212
                    $inc_docs = $item->get_resources_from_source();
8213
                }
8214
8215
                // Give a child element <item> to the <organization> element.
8216
                $my_item_id = $item->get_id();
8217
                $my_item = $xmldoc->createElement('item');
8218
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8219
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8220
                $my_item->setAttribute('isvisible', 'true');
8221
                // Give a child element <title> to the <item> element.
8222
                $my_title = $xmldoc->createElement(
8223
                    'title',
8224
                    htmlspecialchars(
8225
                        api_utf8_encode($item->get_title()),
8226
                        ENT_QUOTES,
8227
                        'UTF-8'
8228
                    )
8229
                );
8230
                $my_item->appendChild($my_title);
8231
                // Give a child element <adlcp:prerequisites> to the <item> element.
8232
                $my_prereqs = $xmldoc->createElement(
8233
                    'adlcp:prerequisites',
8234
                    $this->get_scorm_prereq_string($my_item_id)
8235
                );
8236
                $my_prereqs->setAttribute('type', 'aicc_script');
8237
                $my_item->appendChild($my_prereqs);
8238
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8239
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8240
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8241
                //$xmldoc->createElement('adlcp:timelimitaction','');
8242
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8243
                //$xmldoc->createElement('adlcp:datafromlms','');
8244
                // Give a child element <adlcp:masteryscore> to the <item> element.
8245
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8246
                $my_item->appendChild($my_masteryscore);
8247
8248
                // Attach this item to the organization element or hits parent if there is one.
8249
                if (!empty($item->parent) && 0 != $item->parent) {
8250
                    $children = $organization->childNodes;
8251
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8252
                    if (is_object($possible_parent)) {
8253
                        $possible_parent->appendChild($my_item);
8254
                    } else {
8255
                        if ($this->debug > 0) {
8256
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8257
                        }
8258
                    }
8259
                } else {
8260
                    if ($this->debug > 0) {
8261
                        error_log('No parent');
8262
                    }
8263
                    $organization->appendChild($my_item);
8264
                }
8265
8266
                // Get the path of the file(s) from the course directory root.
8267
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8268
                $my_xml_file_path = $my_file_path;
8269
                if (!empty($path_to_remove)) {
8270
                    // From docs
8271
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8272
8273
                    // From quiz
8274
                    if ('chamilo_scorm_export' === $this->ref) {
8275
                        $path_to_remove = 'scorm/'.$this->path.'/';
8276
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8277
                    }
8278
                }
8279
8280
                $my_sub_dir = dirname($my_file_path);
8281
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8282
                $my_xml_sub_dir = $my_sub_dir;
8283
                // Give a <resource> child to the <resources> element
8284
                $my_resource = $xmldoc->createElement('resource');
8285
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8286
                $my_resource->setAttribute('type', 'webcontent');
8287
                $my_resource->setAttribute('href', $my_xml_file_path);
8288
                // adlcp:scormtype can be either 'sco' or 'asset'.
8289
                if ('sco' === $item->type) {
8290
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8291
                } else {
8292
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8293
                }
8294
                // xml:base is the base directory to find the files declared in this resource.
8295
                $my_resource->setAttribute('xml:base', '');
8296
                // Give a <file> child to the <resource> element.
8297
                $my_file = $xmldoc->createElement('file');
8298
                $my_file->setAttribute('href', $my_xml_file_path);
8299
                $my_resource->appendChild($my_file);
8300
8301
                // Dependency to other files - not yet supported.
8302
                $i = 1;
8303
                if ($inc_docs) {
8304
                    foreach ($inc_docs as $doc_info) {
8305
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8306
                            continue;
8307
                        }
8308
                        $my_dep = $xmldoc->createElement('resource');
8309
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8310
                        $my_dep->setAttribute('identifier', $res_id);
8311
                        $my_dep->setAttribute('type', 'webcontent');
8312
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8313
                        $my_dep_file = $xmldoc->createElement('file');
8314
                        // Check type of URL.
8315
                        if ('remote' == $doc_info[1]) {
8316
                            // Remote file. Save url as is.
8317
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8318
                            $my_dep->setAttribute('xml:base', '');
8319
                        } elseif ('local' === $doc_info[1]) {
8320
                            switch ($doc_info[2]) {
8321
                                case 'url':
8322
                                    // Local URL - save path as url for now, don't zip file.
8323
                                    $abs_path = api_get_path(SYS_PATH).
8324
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8325
                                    $current_dir = dirname($abs_path);
8326
                                    $current_dir = str_replace('\\', '/', $current_dir);
8327
                                    $file_path = realpath($abs_path);
8328
                                    $file_path = str_replace('\\', '/', $file_path);
8329
                                    $my_dep_file->setAttribute('href', $file_path);
8330
                                    $my_dep->setAttribute('xml:base', '');
8331
                                    if (false !== strstr($file_path, $main_path)) {
8332
                                        // The calculated real path is really inside Chamilo's root path.
8333
                                        // Reduce file path to what's under the DocumentRoot.
8334
                                        $replace = $file_path;
8335
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8336
                                        $destinationFile = $file_path;
8337
8338
                                        if (false !== strstr($file_path, 'upload/users')) {
8339
                                            $pos = strpos($file_path, 'my_files/');
8340
                                            if (false !== $pos) {
8341
                                                $onlyDirectory = str_replace(
8342
                                                    'upload/users/',
8343
                                                    '',
8344
                                                    substr($file_path, $pos, strlen($file_path))
8345
                                                );
8346
                                            }
8347
                                            $replace = $onlyDirectory;
8348
                                            $destinationFile = $replace;
8349
                                        }
8350
                                        $zip_files_abs[] = $file_path;
8351
                                        $link_updates[$my_file_path][] = [
8352
                                            'orig' => $doc_info[0],
8353
                                            'dest' => $destinationFile,
8354
                                            'replace' => $replace,
8355
                                        ];
8356
                                        $my_dep_file->setAttribute('href', $file_path);
8357
                                        $my_dep->setAttribute('xml:base', '');
8358
                                    } elseif (empty($file_path)) {
8359
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8360
                                        $file_path = str_replace('//', '/', $file_path);
8361
                                        if (file_exists($file_path)) {
8362
                                            // We get the relative path.
8363
                                            $file_path = substr($file_path, strlen($current_dir));
8364
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8365
                                            $link_updates[$my_file_path][] = [
8366
                                                'orig' => $doc_info[0],
8367
                                                'dest' => $file_path,
8368
                                            ];
8369
                                            $my_dep_file->setAttribute('href', $file_path);
8370
                                            $my_dep->setAttribute('xml:base', '');
8371
                                        }
8372
                                    }
8373
                                    break;
8374
                                case 'abs':
8375
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8376
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8377
                                    $my_dep->setAttribute('xml:base', '');
8378
8379
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8380
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8381
                                    $abs_img_path_without_subdir = $doc_info[0];
8382
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8383
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8384
                                    if (0 === $pos) {
8385
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8386
                                    }
8387
8388
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8389
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8390
8391
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8392
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8393
                                    // Check if the current document is in that path.
8394
                                    if (false !== strstr($file_path, $cur_path)) {
8395
                                        $destinationFile = substr($file_path, strlen($cur_path));
8396
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8397
8398
                                        $fileToTest = $cur_path.$my_file_path;
8399
                                        if (!empty($path_to_remove)) {
8400
                                            $fileToTest = str_replace(
8401
                                                $path_to_remove.'/',
8402
                                                $path_to_replace,
8403
                                                $cur_path.$my_file_path
8404
                                            );
8405
                                        }
8406
8407
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8408
8409
                                        // Put the current document in the zip (this array is the array
8410
                                        // that will manage documents already in the course folder - relative).
8411
                                        $zip_files[] = $filePathNoCoursePart;
8412
                                        // Update the links to the current document in the
8413
                                        // containing document (make them relative).
8414
                                        $link_updates[$my_file_path][] = [
8415
                                            'orig' => $doc_info[0],
8416
                                            'dest' => $destinationFile,
8417
                                            'replace' => $relative_path,
8418
                                        ];
8419
8420
                                        $my_dep_file->setAttribute('href', $file_path);
8421
                                        $my_dep->setAttribute('xml:base', '');
8422
                                    } elseif (false !== strstr($file_path, $main_path)) {
8423
                                        // The calculated real path is really inside Chamilo's root path.
8424
                                        // Reduce file path to what's under the DocumentRoot.
8425
                                        $file_path = substr($file_path, strlen($root_path));
8426
                                        $zip_files_abs[] = $file_path;
8427
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8428
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8429
                                        $my_dep->setAttribute('xml:base', '');
8430
                                    } elseif (empty($file_path)) {
8431
                                        // Probably this is an image inside "/main" directory
8432
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8433
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8434
8435
                                        if (file_exists($file_path)) {
8436
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8437
                                                // We get the relative path.
8438
                                                $pos = strpos($file_path, 'main/default_course_document/');
8439
                                                if (false !== $pos) {
8440
                                                    $onlyDirectory = str_replace(
8441
                                                        'main/default_course_document/',
8442
                                                        '',
8443
                                                        substr($file_path, $pos, strlen($file_path))
8444
                                                    );
8445
                                                }
8446
8447
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8448
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8449
                                                $zip_files_abs[] = $fileAbs;
8450
                                                $link_updates[$my_file_path][] = [
8451
                                                    'orig' => $doc_info[0],
8452
                                                    'dest' => $destinationFile,
8453
                                                ];
8454
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8455
                                                $my_dep->setAttribute('xml:base', '');
8456
                                            }
8457
                                        }
8458
                                    }
8459
                                    break;
8460
                                case 'rel':
8461
                                    // Path relative to the current document.
8462
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8463
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8464
                                        // Relative path going up.
8465
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8466
                                        $current_dir = str_replace('\\', '/', $current_dir);
8467
                                        $file_path = realpath($current_dir.$doc_info[0]);
8468
                                        $file_path = str_replace('\\', '/', $file_path);
8469
                                        if (false !== strstr($file_path, $main_path)) {
8470
                                            // The calculated real path is really inside Chamilo's root path.
8471
                                            // Reduce file path to what's under the DocumentRoot.
8472
                                            $file_path = substr($file_path, strlen($root_path));
8473
                                            $zip_files_abs[] = $file_path;
8474
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8475
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8476
                                            $my_dep->setAttribute('xml:base', '');
8477
                                        }
8478
                                    } else {
8479
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8480
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8481
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8482
                                    }
8483
                                    break;
8484
                                default:
8485
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8486
                                    $my_dep->setAttribute('xml:base', '');
8487
                                    break;
8488
                            }
8489
                        }
8490
                        $my_dep->appendChild($my_dep_file);
8491
                        $resources->appendChild($my_dep);
8492
                        $dependency = $xmldoc->createElement('dependency');
8493
                        $dependency->setAttribute('identifierref', $res_id);
8494
                        $my_resource->appendChild($dependency);
8495
                        $i++;
8496
                    }
8497
                }
8498
                $resources->appendChild($my_resource);
8499
                $zip_files[] = $my_file_path;
8500
            } else {
8501
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8502
                switch ($item->type) {
8503
                    case TOOL_LINK:
8504
                        $my_item = $xmldoc->createElement('item');
8505
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8506
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8507
                        $my_item->setAttribute('isvisible', 'true');
8508
                        // Give a child element <title> to the <item> element.
8509
                        $my_title = $xmldoc->createElement(
8510
                            'title',
8511
                            htmlspecialchars(
8512
                                api_utf8_encode($item->get_title()),
8513
                                ENT_QUOTES,
8514
                                'UTF-8'
8515
                            )
8516
                        );
8517
                        $my_item->appendChild($my_title);
8518
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8519
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8520
                        $my_prereqs->setAttribute('type', 'aicc_script');
8521
                        $my_item->appendChild($my_prereqs);
8522
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8523
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8524
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8525
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8526
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8527
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8528
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8529
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8530
                        $my_item->appendChild($my_masteryscore);
8531
8532
                        // Attach this item to the organization element or its parent if there is one.
8533
                        if (!empty($item->parent) && 0 != $item->parent) {
8534
                            $children = $organization->childNodes;
8535
                            for ($i = 0; $i < $children->length; $i++) {
8536
                                $item_temp = $children->item($i);
8537
                                if ('item' == $item_temp->nodeName) {
8538
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8539
                                        $item_temp->appendChild($my_item);
8540
                                    }
8541
                                }
8542
                            }
8543
                        } else {
8544
                            $organization->appendChild($my_item);
8545
                        }
8546
8547
                        $my_file_path = 'link_'.$item->get_id().'.html';
8548
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8549
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8550
                        $rs = Database::query($sql);
8551
                        if ($link = Database::fetch_array($rs)) {
8552
                            $url = $link['url'];
8553
                            $title = stripslashes($link['title']);
8554
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8555
                            $my_xml_file_path = $my_file_path;
8556
                            $my_sub_dir = dirname($my_file_path);
8557
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8558
                            $my_xml_sub_dir = $my_sub_dir;
8559
                            // Give a <resource> child to the <resources> element.
8560
                            $my_resource = $xmldoc->createElement('resource');
8561
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8562
                            $my_resource->setAttribute('type', 'webcontent');
8563
                            $my_resource->setAttribute('href', $my_xml_file_path);
8564
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8565
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8566
                            // xml:base is the base directory to find the files declared in this resource.
8567
                            $my_resource->setAttribute('xml:base', '');
8568
                            // give a <file> child to the <resource> element.
8569
                            $my_file = $xmldoc->createElement('file');
8570
                            $my_file->setAttribute('href', $my_xml_file_path);
8571
                            $my_resource->appendChild($my_file);
8572
                            $resources->appendChild($my_resource);
8573
                        }
8574
                        break;
8575
                    case TOOL_QUIZ:
8576
                        $exe_id = $item->path;
8577
                        // Should be using ref when everything will be cleaned up in this regard.
8578
                        $exe = new Exercise();
8579
                        $exe->read($exe_id);
8580
                        $my_item = $xmldoc->createElement('item');
8581
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8582
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8583
                        $my_item->setAttribute('isvisible', 'true');
8584
                        // Give a child element <title> to the <item> element.
8585
                        $my_title = $xmldoc->createElement(
8586
                            'title',
8587
                            htmlspecialchars(
8588
                                api_utf8_encode($item->get_title()),
8589
                                ENT_QUOTES,
8590
                                'UTF-8'
8591
                            )
8592
                        );
8593
                        $my_item->appendChild($my_title);
8594
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8595
                        $my_item->appendChild($my_max_score);
8596
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8597
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8598
                        $my_prereqs->setAttribute('type', 'aicc_script');
8599
                        $my_item->appendChild($my_prereqs);
8600
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8601
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8602
                        $my_item->appendChild($my_masteryscore);
8603
8604
                        // Attach this item to the organization element or hits parent if there is one.
8605
                        if (!empty($item->parent) && 0 != $item->parent) {
8606
                            $children = $organization->childNodes;
8607
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8608
                            if ($possible_parent) {
8609
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
8610
                                    $possible_parent->appendChild($my_item);
8611
                                }
8612
                            }
8613
                        } else {
8614
                            $organization->appendChild($my_item);
8615
                        }
8616
8617
                        // Get the path of the file(s) from the course directory root
8618
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8619
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
8620
                        // Write the contents of the exported exercise into a (big) html file
8621
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
8622
                        $scormExercise = new ScormExercise($exe, true);
8623
                        $contents = $scormExercise->export();
8624
8625
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
8626
                        $res = file_put_contents($tmp_file_path, $contents);
8627
                        if (false === $res) {
8628
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
8629
                        }
8630
                        $files_cleanup[] = $tmp_file_path;
8631
                        $my_xml_file_path = $my_file_path;
8632
                        $my_sub_dir = dirname($my_file_path);
8633
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8634
                        $my_xml_sub_dir = $my_sub_dir;
8635
                        // Give a <resource> child to the <resources> element.
8636
                        $my_resource = $xmldoc->createElement('resource');
8637
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8638
                        $my_resource->setAttribute('type', 'webcontent');
8639
                        $my_resource->setAttribute('href', $my_xml_file_path);
8640
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8641
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
8642
                        // xml:base is the base directory to find the files declared in this resource.
8643
                        $my_resource->setAttribute('xml:base', '');
8644
                        // Give a <file> child to the <resource> element.
8645
                        $my_file = $xmldoc->createElement('file');
8646
                        $my_file->setAttribute('href', $my_xml_file_path);
8647
                        $my_resource->appendChild($my_file);
8648
8649
                        // Get included docs.
8650
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
8651
8652
                        // Dependency to other files - not yet supported.
8653
                        $i = 1;
8654
                        foreach ($inc_docs as $doc_info) {
8655
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
8656
                                continue;
8657
                            }
8658
                            $my_dep = $xmldoc->createElement('resource');
8659
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8660
                            $my_dep->setAttribute('identifier', $res_id);
8661
                            $my_dep->setAttribute('type', 'webcontent');
8662
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
8663
                            $my_dep_file = $xmldoc->createElement('file');
8664
                            // Check type of URL.
8665
                            if ('remote' == $doc_info[1]) {
8666
                                // Remote file. Save url as is.
8667
                                $my_dep_file->setAttribute('href', $doc_info[0]);
8668
                                $my_dep->setAttribute('xml:base', '');
8669
                            } elseif ('local' == $doc_info[1]) {
8670
                                switch ($doc_info[2]) {
8671
                                    case 'url': // Local URL - save path as url for now, don't zip file.
8672
                                        // Save file but as local file (retrieve from URL).
8673
                                        $abs_path = api_get_path(SYS_PATH).
8674
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8675
                                        $current_dir = dirname($abs_path);
8676
                                        $current_dir = str_replace('\\', '/', $current_dir);
8677
                                        $file_path = realpath($abs_path);
8678
                                        $file_path = str_replace('\\', '/', $file_path);
8679
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8680
                                        $my_dep->setAttribute('xml:base', '');
8681
                                        if (false !== strstr($file_path, $main_path)) {
8682
                                            // The calculated real path is really inside the chamilo root path.
8683
                                            // Reduce file path to what's under the DocumentRoot.
8684
                                            $file_path = substr($file_path, strlen($root_path));
8685
                                            $zip_files_abs[] = $file_path;
8686
                                            $link_updates[$my_file_path][] = [
8687
                                                'orig' => $doc_info[0],
8688
                                                'dest' => 'document/'.$file_path,
8689
                                            ];
8690
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8691
                                            $my_dep->setAttribute('xml:base', '');
8692
                                        } elseif (empty($file_path)) {
8693
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8694
                                            $file_path = str_replace('//', '/', $file_path);
8695
                                            if (file_exists($file_path)) {
8696
                                                $file_path = substr($file_path, strlen($current_dir));
8697
                                                // We get the relative path.
8698
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8699
                                                $link_updates[$my_file_path][] = [
8700
                                                    'orig' => $doc_info[0],
8701
                                                    'dest' => 'document/'.$file_path,
8702
                                                ];
8703
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8704
                                                $my_dep->setAttribute('xml:base', '');
8705
                                            }
8706
                                        }
8707
                                        break;
8708
                                    case 'abs':
8709
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8710
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8711
                                        $current_dir = str_replace('\\', '/', $current_dir);
8712
                                        $file_path = realpath($doc_info[0]);
8713
                                        $file_path = str_replace('\\', '/', $file_path);
8714
                                        $my_dep_file->setAttribute('href', $file_path);
8715
                                        $my_dep->setAttribute('xml:base', '');
8716
8717
                                        if (false !== strstr($file_path, $main_path)) {
8718
                                            // The calculated real path is really inside the chamilo root path.
8719
                                            // Reduce file path to what's under the DocumentRoot.
8720
                                            $file_path = substr($file_path, strlen($root_path));
8721
                                            $zip_files_abs[] = $file_path;
8722
                                            $link_updates[$my_file_path][] = [
8723
                                                'orig' => $doc_info[0],
8724
                                                'dest' => $file_path,
8725
                                            ];
8726
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8727
                                            $my_dep->setAttribute('xml:base', '');
8728
                                        } elseif (empty($file_path)) {
8729
                                            $docSysPartPath = str_replace(
8730
                                                api_get_path(REL_COURSE_PATH),
8731
                                                '',
8732
                                                $doc_info[0]
8733
                                            );
8734
8735
                                            $docSysPartPathNoCourseCode = str_replace(
8736
                                                $_course['directory'].'/',
8737
                                                '',
8738
                                                $docSysPartPath
8739
                                            );
8740
8741
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
8742
                                            if (file_exists($docSysPath)) {
8743
                                                $file_path = $docSysPartPathNoCourseCode;
8744
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8745
                                                $link_updates[$my_file_path][] = [
8746
                                                    'orig' => $doc_info[0],
8747
                                                    'dest' => $file_path,
8748
                                                ];
8749
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8750
                                                $my_dep->setAttribute('xml:base', '');
8751
                                            }
8752
                                        }
8753
                                        break;
8754
                                    case 'rel':
8755
                                        // Path relative to the current document. Save xml:base as current document's
8756
                                        // directory and save file in zip as subdir.file_path
8757
                                        if ('..' === substr($doc_info[0], 0, 2)) {
8758
                                            // Relative path going up.
8759
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8760
                                            $current_dir = str_replace('\\', '/', $current_dir);
8761
                                            $file_path = realpath($current_dir.$doc_info[0]);
8762
                                            $file_path = str_replace('\\', '/', $file_path);
8763
                                            if (false !== strstr($file_path, $main_path)) {
8764
                                                // The calculated real path is really inside Chamilo's root path.
8765
                                                // Reduce file path to what's under the DocumentRoot.
8766
8767
                                                $file_path = substr($file_path, strlen($root_path));
8768
                                                $file_path_dest = $file_path;
8769
8770
                                                // File path is courses/CHAMILO/document/....
8771
                                                $info_file_path = explode('/', $file_path);
8772
                                                if ('courses' == $info_file_path[0]) {
8773
                                                    // Add character "/" in file path.
8774
                                                    $file_path_dest = 'document/'.$file_path;
8775
                                                }
8776
                                                $zip_files_abs[] = $file_path;
8777
8778
                                                $link_updates[$my_file_path][] = [
8779
                                                    'orig' => $doc_info[0],
8780
                                                    'dest' => $file_path_dest,
8781
                                                ];
8782
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8783
                                                $my_dep->setAttribute('xml:base', '');
8784
                                            }
8785
                                        } else {
8786
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8787
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
8788
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8789
                                        }
8790
                                        break;
8791
                                    default:
8792
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
8793
                                        $my_dep->setAttribute('xml:base', '');
8794
                                        break;
8795
                                }
8796
                            }
8797
                            $my_dep->appendChild($my_dep_file);
8798
                            $resources->appendChild($my_dep);
8799
                            $dependency = $xmldoc->createElement('dependency');
8800
                            $dependency->setAttribute('identifierref', $res_id);
8801
                            $my_resource->appendChild($dependency);
8802
                            $i++;
8803
                        }
8804
                        $resources->appendChild($my_resource);
8805
                        $zip_files[] = $my_file_path;
8806
                        break;
8807
                    default:
8808
                        // Get the path of the file(s) from the course directory root
8809
                        $my_file_path = 'non_exportable.html';
8810
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
8811
                        $my_xml_file_path = $my_file_path;
8812
                        $my_sub_dir = dirname($my_file_path);
8813
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8814
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
8815
                        $my_xml_sub_dir = $my_sub_dir;
8816
                        // Give a <resource> child to the <resources> element.
8817
                        $my_resource = $xmldoc->createElement('resource');
8818
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8819
                        $my_resource->setAttribute('type', 'webcontent');
8820
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
8821
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8822
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
8823
                        // xml:base is the base directory to find the files declared in this resource.
8824
                        $my_resource->setAttribute('xml:base', '');
8825
                        // Give a <file> child to the <resource> element.
8826
                        $my_file = $xmldoc->createElement('file');
8827
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
8828
                        $my_resource->appendChild($my_file);
8829
                        $resources->appendChild($my_resource);
8830
                        break;
8831
                }
8832
            }
8833
        }
8834
        $organizations->appendChild($organization);
8835
        $root->appendChild($organizations);
8836
        $root->appendChild($resources);
8837
        $xmldoc->appendChild($root);
8838
8839
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
8840
8841
        // then add the file to the zip, then destroy the file (this is done automatically).
8842
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
8843
        foreach ($zip_files as $file_path) {
8844
            if (empty($file_path)) {
8845
                continue;
8846
            }
8847
8848
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
8849
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
8850
8851
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
8852
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
8853
            }
8854
8855
            $this->create_path($dest_file);
8856
            @copy($filePath, $dest_file);
8857
8858
            // Check if the file needs a link update.
8859
            if (in_array($file_path, array_keys($link_updates))) {
8860
                $string = file_get_contents($dest_file);
8861
                unlink($dest_file);
8862
                foreach ($link_updates[$file_path] as $old_new) {
8863
                    // This is an ugly hack that allows .flv files to be found by the flv player that
8864
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
8865
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
8866
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
8867
                    if ('flv' === substr($old_new['dest'], -3) &&
8868
                        'main/' === substr($old_new['dest'], 0, 5)
8869
                    ) {
8870
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
8871
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
8872
                        'video/' === substr($old_new['dest'], 0, 6)
8873
                    ) {
8874
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
8875
                    }
8876
8877
                    // Fix to avoid problems with default_course_document
8878
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
8879
                        $newDestination = $old_new['dest'];
8880
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
8881
                            $newDestination = $old_new['replace'];
8882
                        }
8883
                    } else {
8884
                        $newDestination = str_replace('document/', '', $old_new['dest']);
8885
                    }
8886
                    $string = str_replace($old_new['orig'], $newDestination, $string);
8887
8888
                    // Add files inside the HTMLs
8889
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
8890
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
8891
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
8892
                        copy(
8893
                            $sys_course_path.$new_path,
8894
                            $destinationFile
8895
                        );
8896
                    }
8897
                }
8898
                file_put_contents($dest_file, $string);
8899
            }
8900
8901
            if (file_exists($filePath) && $copyAll) {
8902
                $extension = $this->get_extension($filePath);
8903
                if (in_array($extension, ['html', 'html'])) {
8904
                    $containerOrigin = dirname($filePath);
8905
                    $containerDestination = dirname($dest_file);
8906
8907
                    $finder = new Finder();
8908
                    $finder->files()->in($containerOrigin)
8909
                        ->notName('*_DELETED_*')
8910
                        ->exclude('share_folder')
8911
                        ->exclude('chat_files')
8912
                        ->exclude('certificates')
8913
                    ;
8914
8915
                    if (is_dir($containerOrigin) &&
8916
                        is_dir($containerDestination)
8917
                    ) {
8918
                        $fs = new Filesystem();
8919
                        $fs->mirror(
8920
                            $containerOrigin,
8921
                            $containerDestination,
8922
                            $finder
8923
                        );
8924
                    }
8925
                }
8926
            }
8927
        }
8928
8929
        foreach ($zip_files_abs as $file_path) {
8930
            if (empty($file_path)) {
8931
                continue;
8932
            }
8933
8934
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
8935
                continue;
8936
            }
8937
8938
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
8939
            if (false !== strstr($file_path, 'upload/users')) {
8940
                $pos = strpos($file_path, 'my_files/');
8941
                if (false !== $pos) {
8942
                    $onlyDirectory = str_replace(
8943
                        'upload/users/',
8944
                        '',
8945
                        substr($file_path, $pos, strlen($file_path))
8946
                    );
8947
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
8948
                }
8949
            }
8950
8951
            if (false !== strstr($file_path, 'default_course_document/')) {
8952
                $replace = str_replace('/main', '', $file_path);
8953
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
8954
            }
8955
8956
            if (empty($dest_file)) {
8957
                continue;
8958
            }
8959
8960
            $this->create_path($dest_file);
8961
            copy($main_path.$file_path, $dest_file);
8962
            // Check if the file needs a link update.
8963
            if (in_array($file_path, array_keys($link_updates))) {
8964
                $string = file_get_contents($dest_file);
8965
                unlink($dest_file);
8966
                foreach ($link_updates[$file_path] as $old_new) {
8967
                    // This is an ugly hack that allows .flv files to be found by the flv player that
8968
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
8969
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
8970
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
8971
                    if ('flv' == substr($old_new['dest'], -3) &&
8972
                        'main/' == substr($old_new['dest'], 0, 5)
8973
                    ) {
8974
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
8975
                    }
8976
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
8977
                }
8978
                file_put_contents($dest_file, $string);
8979
            }
8980
        }
8981
8982
        if (is_array($links_to_create)) {
8983
            foreach ($links_to_create as $file => $link) {
8984
                $content = '<!DOCTYPE html><head>
8985
                            <meta charset="'.api_get_language_isocode().'" />
8986
                            <title>'.$link['title'].'</title>
8987
                            </head>
8988
                            <body dir="'.api_get_text_direction().'">
8989
                            <div style="text-align:center">
8990
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
8991
                            </body>
8992
                            </html>';
8993
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
8994
            }
8995
        }
8996
8997
        // Add non exportable message explanation.
8998
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
8999
        $file_content = '<!DOCTYPE html><head>
9000
                        <meta charset="'.api_get_language_isocode().'" />
9001
                        <title>'.$lang_not_exportable.'</title>
9002
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9003
                        </head>
9004
                        <body dir="'.api_get_text_direction().'">';
9005
        $file_content .=
9006
            <<<EOD
9007
                    <style>
9008
            .error-message {
9009
                font-family: arial, verdana, helvetica, sans-serif;
9010
                border-width: 1px;
9011
                border-style: solid;
9012
                left: 50%;
9013
                margin: 10px auto;
9014
                min-height: 30px;
9015
                padding: 5px;
9016
                right: 50%;
9017
                width: 500px;
9018
                background-color: #FFD1D1;
9019
                border-color: #FF0000;
9020
                color: #000;
9021
            }
9022
        </style>
9023
    <body>
9024
        <div class="error-message">
9025
            $lang_not_exportable
9026
        </div>
9027
    </body>
9028
</html>
9029
EOD;
9030
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9031
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9032
        }
9033
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9034
9035
        // Add the extra files that go along with a SCORM package.
9036
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9037
9038
        $fs = new Filesystem();
9039
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9040
9041
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9042
        $manifest = @$xmldoc->saveXML();
9043
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9044
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9045
        $zip_folder->add(
9046
            $archivePath.'/'.$temp_dir_short,
9047
            PCLZIP_OPT_REMOVE_PATH,
9048
            $archivePath.'/'.$temp_dir_short.'/'
9049
        );
9050
9051
        // Clean possible temporary files.
9052
        foreach ($files_cleanup as $file) {
9053
            $res = unlink($file);
9054
            if (false === $res) {
9055
                error_log(
9056
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9057
                    0
9058
                );
9059
            }
9060
        }
9061
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9062
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9063
    }
9064
9065
    /**
9066
     * @param int $lp_id
9067
     *
9068
     * @return bool
9069
     */
9070
    public function scorm_export_to_pdf($lp_id)
9071
    {
9072
        $lp_id = (int) $lp_id;
9073
        $files_to_export = [];
9074
9075
        $sessionId = api_get_session_id();
9076
        $course_data = api_get_course_info($this->cc);
9077
9078
        $lp = Container::getLpRepository()->find($lp_id);
9079
        if (!empty($course_data)) {
9080
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9081
            $list = self::get_flat_ordered_items_list($lp);
9082
            if (!empty($list)) {
9083
                foreach ($list as $item_id) {
9084
                    $item = $this->items[$item_id];
9085
                    switch ($item->type) {
9086
                        case 'document':
9087
                            // Getting documents from a LP with chamilo documents
9088
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9089
                            // Try loading document from the base course.
9090
                            if (empty($file_data) && !empty($sessionId)) {
9091
                                $file_data = DocumentManager::get_document_data_by_id(
9092
                                    $item->path,
9093
                                    $this->cc,
9094
                                    false,
9095
                                    0
9096
                                );
9097
                            }
9098
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9099
                            if (file_exists($file_path)) {
9100
                                $files_to_export[] = [
9101
                                    'title' => $item->get_title(),
9102
                                    'path' => $file_path,
9103
                                ];
9104
                            }
9105
                            break;
9106
                        case 'asset': //commes from a scorm package generated by chamilo
9107
                        case 'sco':
9108
                            $file_path = $scorm_path.'/'.$item->path;
9109
                            if (file_exists($file_path)) {
9110
                                $files_to_export[] = [
9111
                                    'title' => $item->get_title(),
9112
                                    'path' => $file_path,
9113
                                ];
9114
                            }
9115
                            break;
9116
                        case 'dir':
9117
                            $files_to_export[] = [
9118
                                'title' => $item->get_title(),
9119
                                'path' => null,
9120
                            ];
9121
                            break;
9122
                    }
9123
                }
9124
            }
9125
9126
            $pdf = new PDF();
9127
            $result = $pdf->html_to_pdf(
9128
                $files_to_export,
9129
                $this->name,
9130
                $this->cc,
9131
                true,
9132
                true,
9133
                true,
9134
                $this->get_name()
9135
            );
9136
9137
            return $result;
9138
        }
9139
9140
        return false;
9141
    }
9142
9143
    /**
9144
     * Temp function to be moved in main_api or the best place around for this.
9145
     * Creates a file path if it doesn't exist.
9146
     *
9147
     * @param string $path
9148
     */
9149
    public function create_path($path)
9150
    {
9151
        $path_bits = explode('/', dirname($path));
9152
9153
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9154
        $path_built = IS_WINDOWS_OS ? '' : '/';
9155
        foreach ($path_bits as $bit) {
9156
            if (!empty($bit)) {
9157
                $new_path = $path_built.$bit;
9158
                if (is_dir($new_path)) {
9159
                    $path_built = $new_path.'/';
9160
                } else {
9161
                    mkdir($new_path, api_get_permissions_for_new_directories());
9162
                    $path_built = $new_path.'/';
9163
                }
9164
            }
9165
        }
9166
    }
9167
9168
    /**
9169
     * @param int    $lp_id
9170
     * @param string $status
9171
     */
9172
    public function set_autolaunch($lp_id, $status)
9173
    {
9174
        $course_id = api_get_course_int_id();
9175
        $lp_id = (int) $lp_id;
9176
        $status = (int) $status;
9177
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9178
9179
        // Setting everything to autolaunch = 0
9180
        $attributes['autolaunch'] = 0;
9181
        $where = [
9182
            'session_id = ? AND c_id = ? ' => [
9183
                api_get_session_id(),
9184
                $course_id,
9185
            ],
9186
        ];
9187
        Database::update($lp_table, $attributes, $where);
9188
        if (1 == $status) {
9189
            //Setting my lp_id to autolaunch = 1
9190
            $attributes['autolaunch'] = 1;
9191
            $where = [
9192
                'iid = ? AND session_id = ? AND c_id = ?' => [
9193
                    $lp_id,
9194
                    api_get_session_id(),
9195
                    $course_id,
9196
                ],
9197
            ];
9198
            Database::update($lp_table, $attributes, $where);
9199
        }
9200
    }
9201
9202
    /**
9203
     * Gets previous_item_id for the next element of the lp_item table.
9204
     *
9205
     * @author Isaac flores paz
9206
     *
9207
     * @return int Previous item ID
9208
     */
9209
    public function select_previous_item_id()
9210
    {
9211
        $course_id = api_get_course_int_id();
9212
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9213
9214
        // Get the max order of the items
9215
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9216
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9217
        $rs_max_order = Database::query($sql);
9218
        $row_max_order = Database::fetch_object($rs_max_order);
9219
        $max_order = $row_max_order->display_order;
9220
        // Get the previous item ID
9221
        $sql = "SELECT iid as previous FROM $table_lp_item
9222
                WHERE
9223
                    c_id = $course_id AND
9224
                    lp_id = ".$this->lp_id." AND
9225
                    display_order = '$max_order' ";
9226
        $rs_max = Database::query($sql);
9227
        $row_max = Database::fetch_object($rs_max);
9228
9229
        // Return the previous item ID
9230
        return $row_max->previous;
9231
    }
9232
9233
    /**
9234
     * Copies an LP.
9235
     */
9236
    public function copy()
9237
    {
9238
        // Course builder
9239
        $cb = new CourseBuilder();
9240
9241
        //Setting tools that will be copied
9242
        $cb->set_tools_to_build(['learnpaths']);
9243
9244
        //Setting elements that will be copied
9245
        $cb->set_tools_specific_id_list(
9246
            ['learnpaths' => [$this->lp_id]]
9247
        );
9248
9249
        $course = $cb->build();
9250
9251
        //Course restorer
9252
        $course_restorer = new CourseRestorer($course);
9253
        $course_restorer->set_add_text_in_items(true);
9254
        $course_restorer->set_tool_copy_settings(
9255
            ['learnpaths' => ['reset_dates' => true]]
9256
        );
9257
        $course_restorer->restore(
9258
            api_get_course_id(),
9259
            api_get_session_id(),
9260
            false,
9261
            false
9262
        );
9263
    }
9264
9265
    /**
9266
     * Verify document size.
9267
     *
9268
     * @param string $s
9269
     *
9270
     * @return bool
9271
     */
9272
    public static function verify_document_size($s)
9273
    {
9274
        $post_max = ini_get('post_max_size');
9275
        if ('M' == substr($post_max, -1, 1)) {
9276
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9277
        } elseif ('G' == substr($post_max, -1, 1)) {
9278
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9279
        }
9280
        $upl_max = ini_get('upload_max_filesize');
9281
        if ('M' == substr($upl_max, -1, 1)) {
9282
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9283
        } elseif ('G' == substr($upl_max, -1, 1)) {
9284
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9285
        }
9286
9287
        $repo = Container::getDocumentRepository();
9288
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9289
9290
        $course_max_space = DocumentManager::get_course_quota();
9291
        $total_size = filesize($s) + $documents_total_space;
9292
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9293
            return true;
9294
        }
9295
9296
        return false;
9297
    }
9298
9299
    /**
9300
     * Clear LP prerequisites.
9301
     */
9302
    public function clearPrerequisites()
9303
    {
9304
        $course_id = $this->get_course_int_id();
9305
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9306
        $lp_id = $this->get_id();
9307
        // Cleaning prerequisites
9308
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9309
                WHERE lp_id = $lp_id";
9310
        Database::query($sql);
9311
9312
        // Cleaning mastery score for exercises
9313
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9314
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
9315
        Database::query($sql);
9316
    }
9317
9318
    public function set_previous_step_as_prerequisite_for_all_items()
9319
    {
9320
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9321
        $course_id = $this->get_course_int_id();
9322
        $lp_id = $this->get_id();
9323
9324
        if (!empty($this->items)) {
9325
            $previous_item_id = null;
9326
            $previous_item_max = 0;
9327
            $previous_item_type = null;
9328
            $last_item_not_dir = null;
9329
            $last_item_not_dir_type = null;
9330
            $last_item_not_dir_max = null;
9331
9332
            foreach ($this->ordered_items as $itemId) {
9333
                $item = $this->getItem($itemId);
9334
                // if there was a previous item... (otherwise jump to set it)
9335
                if (!empty($previous_item_id)) {
9336
                    $current_item_id = $item->get_id(); //save current id
9337
                    if ('dir' != $item->get_type()) {
9338
                        // Current item is not a folder, so it qualifies to get a prerequisites
9339
                        if ('quiz' == $last_item_not_dir_type) {
9340
                            // if previous is quiz, mark its max score as default score to be achieved
9341
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9342
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9343
                            Database::query($sql);
9344
                        }
9345
                        // now simply update the prerequisite to set it to the last non-chapter item
9346
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9347
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
9348
                        Database::query($sql);
9349
                        // record item as 'non-chapter' reference
9350
                        $last_item_not_dir = $item->get_id();
9351
                        $last_item_not_dir_type = $item->get_type();
9352
                        $last_item_not_dir_max = $item->get_max();
9353
                    }
9354
                } else {
9355
                    if ('dir' != $item->get_type()) {
9356
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9357
                        $last_item_not_dir = $item->get_id();
9358
                        $last_item_not_dir_type = $item->get_type();
9359
                        $last_item_not_dir_max = $item->get_max();
9360
                    }
9361
                }
9362
                // Saving the item as "previous item" for the next loop
9363
                $previous_item_id = $item->get_id();
9364
                $previous_item_max = $item->get_max();
9365
                $previous_item_type = $item->get_type();
9366
            }
9367
        }
9368
    }
9369
9370
    /**
9371
     * @param array $params
9372
     *
9373
     * @return int
9374
     */
9375
    public static function createCategory($params)
9376
    {
9377
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9378
9379
        $item = new CLpCategory();
9380
        $item
9381
            ->setName($params['name'])
9382
            ->setParent($courseEntity)
9383
            ->addCourseLink($courseEntity, api_get_session_entity())
9384
        ;
9385
9386
        $repo = Container::getLpCategoryRepository();
9387
        $repo->create($item);
9388
9389
        return $item->getIid();
9390
    }
9391
9392
    /**
9393
     * @param array $params
9394
     */
9395
    public static function updateCategory($params)
9396
    {
9397
        $em = Database::getManager();
9398
        /** @var CLpCategory $item */
9399
        $item = $em->find(CLpCategory::class, $params['id']);
9400
        if ($item) {
9401
            $item->setName($params['name']);
9402
            $em->persist($item);
9403
            $em->flush();
9404
        }
9405
    }
9406
9407
    /**
9408
     * @param int $id
9409
     */
9410
    public static function moveUpCategory($id)
9411
    {
9412
        $id = (int) $id;
9413
        $em = Database::getManager();
9414
        /** @var CLpCategory $item */
9415
        $item = $em->find(CLpCategory::class, $id);
9416
        if ($item) {
9417
            $position = $item->getPosition() - 1;
9418
            $item->setPosition($position);
9419
            $em->persist($item);
9420
            $em->flush();
9421
        }
9422
    }
9423
9424
    /**
9425
     * @param int $id
9426
     */
9427
    public static function moveDownCategory($id)
9428
    {
9429
        $id = (int) $id;
9430
        $em = Database::getManager();
9431
        /** @var CLpCategory $item */
9432
        $item = $em->find(CLpCategory::class, $id);
9433
        if ($item) {
9434
            $position = $item->getPosition() + 1;
9435
            $item->setPosition($position);
9436
            $em->persist($item);
9437
            $em->flush();
9438
        }
9439
    }
9440
9441
    /**
9442
     * @param int $courseId
9443
     *
9444
     * @return int|mixed
9445
     */
9446
    public static function getCountCategories($courseId)
9447
    {
9448
        if (empty($courseId)) {
9449
            return 0;
9450
        }
9451
        $repo = Container::getLpCategoryRepository();
9452
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9453
        $qb->addSelect('count(resource)');
9454
9455
        return $qb->getSingleScalarResult();
9456
    }
9457
9458
    /**
9459
     * @param int $courseId
9460
     *
9461
     * @return CLpCategory[]
9462
     */
9463
    public static function getCategories($courseId)
9464
    {
9465
        $em = Database::getManager();
9466
9467
        // Using doctrine extensions
9468
        $repo = Container::getLpCategoryRepository();
9469
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9470
9471
        return $qb->getQuery()->getResult();
9472
9473
        //return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9474
    }
9475
9476
    public static function getCategorySessionId($id)
9477
    {
9478
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9479
            return 0;
9480
        }
9481
9482
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9483
        $id = (int) $id;
9484
9485
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9486
        $result = Database::query($sql);
9487
        $result = Database::fetch_array($result, 'ASSOC');
9488
9489
        if ($result) {
9490
            return (int) $result['session_id'];
9491
        }
9492
9493
        return 0;
9494
    }
9495
9496
    /**
9497
     * @param int $id
9498
     */
9499
    public static function deleteCategory($id): bool
9500
    {
9501
        $repo = Container::getLpCategoryRepository();
9502
        /** @var CLpCategory $category */
9503
        $category = $repo->find($id);
9504
        if ($category) {
9505
            $em = Database::getManager();
9506
            $lps = $category->getLps();
9507
9508
            foreach ($lps as $lp) {
9509
                $lp->setCategory(null);
9510
                $em->persist($lp);
9511
            }
9512
9513
            // Removing category.
9514
            $em->remove($category);
9515
            $em->flush();
9516
9517
            return true;
9518
        }
9519
9520
        return false;
9521
    }
9522
9523
    /**
9524
     * @param int  $courseId
9525
     * @param bool $addSelectOption
9526
     *
9527
     * @return mixed
9528
     */
9529
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9530
    {
9531
        $repo = Container::getLpCategoryRepository();
9532
        $items = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9533
        $cats = [];
9534
        if ($addSelectOption) {
9535
            $cats = [get_lang('Select a category')];
9536
        }
9537
9538
        if (!empty($items)) {
9539
            foreach ($items as $cat) {
9540
                $cats[$cat->getIid()] = $cat->getName();
9541
            }
9542
        }
9543
9544
        return $cats;
9545
    }
9546
9547
    /**
9548
     * @param string $courseCode
9549
     * @param int    $lpId
9550
     * @param int    $user_id
9551
     *
9552
     * @return learnpath
9553
     */
9554
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9555
    {
9556
        $debug = 0;
9557
        $learnPath = null;
9558
        $lpObject = Session::read('lpobject');
9559
9560
        $repo = Container::getLpRepository();
9561
        $lp = $repo->find($lpId);
9562
        if (null !== $lpObject) {
9563
            /** @var learnpath $learnPath */
9564
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
9565
            $learnPath->entity = $lp;
9566
            if ($debug) {
9567
                error_log('getLpFromSession: unserialize');
9568
                error_log('------getLpFromSession------');
9569
                error_log('------unserialize------');
9570
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9571
                error_log("api_get_sessionid: ".api_get_session_id());
9572
            }
9573
        }
9574
9575
        if (!is_object($learnPath)) {
9576
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
9577
            if ($debug) {
9578
                error_log('------getLpFromSession------');
9579
                error_log('getLpFromSession: create new learnpath');
9580
                error_log("create new LP with $courseCode - $lpId - $user_id");
9581
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9582
                error_log("api_get_sessionid: ".api_get_session_id());
9583
            }
9584
        }
9585
9586
        return $learnPath;
9587
    }
9588
9589
    /**
9590
     * @param int $itemId
9591
     *
9592
     * @return learnpathItem|false
9593
     */
9594
    public function getItem($itemId)
9595
    {
9596
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
9597
            return $this->items[$itemId];
9598
        }
9599
9600
        return false;
9601
    }
9602
9603
    /**
9604
     * @return int
9605
     */
9606
    public function getCurrentAttempt()
9607
    {
9608
        $attempt = $this->getItem($this->get_current_item_id());
9609
        if ($attempt) {
9610
            return $attempt->get_attempt_id();
9611
        }
9612
9613
        return 0;
9614
    }
9615
9616
    /**
9617
     * @return int
9618
     */
9619
    public function getCategoryId()
9620
    {
9621
        return (int) $this->categoryId;
9622
    }
9623
9624
    /**
9625
     * Get whether this is a learning path with the possibility to subscribe
9626
     * users or not.
9627
     *
9628
     * @return int
9629
     */
9630
    public function getSubscribeUsers()
9631
    {
9632
        return $this->subscribeUsers;
9633
    }
9634
9635
    /**
9636
     * Calculate the count of stars for a user in this LP
9637
     * This calculation is based on the following rules:
9638
     * - the student gets one star when he gets to 50% of the learning path
9639
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
9640
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
9641
     * - the student gets the final star when the score for the *last* test is >= 80%.
9642
     *
9643
     * @param int $sessionId Optional. The session ID
9644
     *
9645
     * @return int The count of stars
9646
     */
9647
    public function getCalculateStars($sessionId = 0)
9648
    {
9649
        $stars = 0;
9650
        $progress = self::getProgress(
9651
            $this->lp_id,
9652
            $this->user_id,
9653
            $this->course_int_id,
9654
            $sessionId
9655
        );
9656
9657
        if ($progress >= 50) {
9658
            $stars++;
9659
        }
9660
9661
        // Calculate stars chapters evaluation
9662
        $exercisesItems = $this->getExercisesItems();
9663
9664
        if (!empty($exercisesItems)) {
9665
            $totalResult = 0;
9666
9667
            foreach ($exercisesItems as $exerciseItem) {
9668
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9669
                    $this->user_id,
9670
                    $exerciseItem->path,
9671
                    $this->course_int_id,
9672
                    $sessionId,
9673
                    $this->lp_id,
9674
                    $exerciseItem->db_id
9675
                );
9676
9677
                $exerciseResultInfo = end($exerciseResultInfo);
9678
9679
                if (!$exerciseResultInfo) {
9680
                    continue;
9681
                }
9682
9683
                if (!empty($exerciseResultInfo['max_score'])) {
9684
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
9685
                } else {
9686
                    $exerciseResult = 0;
9687
                }
9688
                $totalResult += $exerciseResult;
9689
            }
9690
9691
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
9692
9693
            if ($totalExerciseAverage >= 50) {
9694
                $stars++;
9695
            }
9696
9697
            if ($totalExerciseAverage >= 80) {
9698
                $stars++;
9699
            }
9700
        }
9701
9702
        // Calculate star for final evaluation
9703
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9704
9705
        if (!empty($finalEvaluationItem)) {
9706
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9707
                $this->user_id,
9708
                $finalEvaluationItem->path,
9709
                $this->course_int_id,
9710
                $sessionId,
9711
                $this->lp_id,
9712
                $finalEvaluationItem->db_id
9713
            );
9714
9715
            $evaluationResultInfo = end($evaluationResultInfo);
9716
9717
            if ($evaluationResultInfo) {
9718
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
9719
                if ($evaluationResult >= 80) {
9720
                    $stars++;
9721
                }
9722
            }
9723
        }
9724
9725
        return $stars;
9726
    }
9727
9728
    /**
9729
     * Get the items of exercise type.
9730
     *
9731
     * @return array The items. Otherwise return false
9732
     */
9733
    public function getExercisesItems()
9734
    {
9735
        $exercises = [];
9736
        foreach ($this->items as $item) {
9737
            if ('quiz' != $item->type) {
9738
                continue;
9739
            }
9740
            $exercises[] = $item;
9741
        }
9742
9743
        array_pop($exercises);
9744
9745
        return $exercises;
9746
    }
9747
9748
    /**
9749
     * Get the item of exercise type (evaluation type).
9750
     *
9751
     * @return array The final evaluation. Otherwise return false
9752
     */
9753
    public function getFinalEvaluationItem()
9754
    {
9755
        $exercises = [];
9756
        foreach ($this->items as $item) {
9757
            if (TOOL_QUIZ !== $item->type) {
9758
                continue;
9759
            }
9760
9761
            $exercises[] = $item;
9762
        }
9763
9764
        return array_pop($exercises);
9765
    }
9766
9767
    /**
9768
     * Calculate the total points achieved for the current user in this learning path.
9769
     *
9770
     * @param int $sessionId Optional. The session Id
9771
     *
9772
     * @return int
9773
     */
9774
    public function getCalculateScore($sessionId = 0)
9775
    {
9776
        // Calculate stars chapters evaluation
9777
        $exercisesItems = $this->getExercisesItems();
9778
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9779
        $totalExercisesResult = 0;
9780
        $totalEvaluationResult = 0;
9781
9782
        if (false !== $exercisesItems) {
9783
            foreach ($exercisesItems as $exerciseItem) {
9784
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9785
                    $this->user_id,
9786
                    $exerciseItem->path,
9787
                    $this->course_int_id,
9788
                    $sessionId,
9789
                    $this->lp_id,
9790
                    $exerciseItem->db_id
9791
                );
9792
9793
                $exerciseResultInfo = end($exerciseResultInfo);
9794
9795
                if (!$exerciseResultInfo) {
9796
                    continue;
9797
                }
9798
9799
                $totalExercisesResult += $exerciseResultInfo['score'];
9800
            }
9801
        }
9802
9803
        if (!empty($finalEvaluationItem)) {
9804
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9805
                $this->user_id,
9806
                $finalEvaluationItem->path,
9807
                $this->course_int_id,
9808
                $sessionId,
9809
                $this->lp_id,
9810
                $finalEvaluationItem->db_id
9811
            );
9812
9813
            $evaluationResultInfo = end($evaluationResultInfo);
9814
9815
            if ($evaluationResultInfo) {
9816
                $totalEvaluationResult += $evaluationResultInfo['score'];
9817
            }
9818
        }
9819
9820
        return $totalExercisesResult + $totalEvaluationResult;
9821
    }
9822
9823
    /**
9824
     * Check if URL is not allowed to be show in a iframe.
9825
     *
9826
     * @param string $src
9827
     *
9828
     * @return string
9829
     */
9830
    public function fixBlockedLinks($src)
9831
    {
9832
        $urlInfo = parse_url($src);
9833
9834
        $platformProtocol = 'https';
9835
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
9836
            $platformProtocol = 'http';
9837
        }
9838
9839
        $protocolFixApplied = false;
9840
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
9841
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
9842
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
9843
9844
        if ($platformProtocol != $scheme) {
9845
            Session::write('x_frame_source', $src);
9846
            $src = 'blank.php?error=x_frames_options';
9847
            $protocolFixApplied = true;
9848
        }
9849
9850
        if (false == $protocolFixApplied) {
9851
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
9852
                // Check X-Frame-Options
9853
                $ch = curl_init();
9854
                $options = [
9855
                    CURLOPT_URL => $src,
9856
                    CURLOPT_RETURNTRANSFER => true,
9857
                    CURLOPT_HEADER => true,
9858
                    CURLOPT_FOLLOWLOCATION => true,
9859
                    CURLOPT_ENCODING => "",
9860
                    CURLOPT_AUTOREFERER => true,
9861
                    CURLOPT_CONNECTTIMEOUT => 120,
9862
                    CURLOPT_TIMEOUT => 120,
9863
                    CURLOPT_MAXREDIRS => 10,
9864
                ];
9865
9866
                $proxySettings = api_get_configuration_value('proxy_settings');
9867
                if (!empty($proxySettings) &&
9868
                    isset($proxySettings['curl_setopt_array'])
9869
                ) {
9870
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
9871
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
9872
                }
9873
9874
                curl_setopt_array($ch, $options);
9875
                $response = curl_exec($ch);
9876
                $httpCode = curl_getinfo($ch);
9877
                $headers = substr($response, 0, $httpCode['header_size']);
9878
9879
                $error = false;
9880
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
9881
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
9882
                ) {
9883
                    $error = true;
9884
                }
9885
9886
                if ($error) {
9887
                    Session::write('x_frame_source', $src);
9888
                    $src = 'blank.php?error=x_frames_options';
9889
                }
9890
            }
9891
        }
9892
9893
        return $src;
9894
    }
9895
9896
    /**
9897
     * Check if this LP has a created forum in the basis course.
9898
     *
9899
     * @deprecated
9900
     *
9901
     * @return bool
9902
     */
9903
    public function lpHasForum()
9904
    {
9905
        $forumTable = Database::get_course_table(TABLE_FORUM);
9906
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
9907
9908
        $fakeFrom = "
9909
            $forumTable f
9910
            INNER JOIN $itemProperty ip
9911
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
9912
        ";
9913
9914
        $resultData = Database::select(
9915
            'COUNT(f.iid) AS qty',
9916
            $fakeFrom,
9917
            [
9918
                'where' => [
9919
                    'ip.visibility != ? AND ' => 2,
9920
                    'ip.tool = ? AND ' => TOOL_FORUM,
9921
                    'f.c_id = ? AND ' => intval($this->course_int_id),
9922
                    'f.lp_id = ?' => intval($this->lp_id),
9923
                ],
9924
            ],
9925
            'first'
9926
        );
9927
9928
        return $resultData['qty'] > 0;
9929
    }
9930
9931
    /**
9932
     * Get the forum for this learning path.
9933
     *
9934
     * @param int $sessionId
9935
     *
9936
     * @return array
9937
     */
9938
    public function getForum($sessionId = 0)
9939
    {
9940
        $repo = Container::getForumRepository();
9941
9942
        $course = api_get_course_entity();
9943
        $session = api_get_session_entity($sessionId);
9944
        $qb = $repo->getResourcesByCourse($course, $session);
9945
9946
        return $qb->getQuery()->getResult();
9947
    }
9948
9949
    /**
9950
     * Create a forum for this learning path.
9951
     *
9952
     * @return int The forum ID if was created. Otherwise return false
9953
     */
9954
    public function createForum(CForumCategory $forumCategory)
9955
    {
9956
        return store_forum(
9957
            [
9958
                'lp_id' => $this->lp_id,
9959
                'forum_title' => $this->name,
9960
                'forum_comment' => null,
9961
                'forum_category' => $forumCategory->getIid(),
9962
                'students_can_edit_group' => ['students_can_edit' => 0],
9963
                'allow_new_threads_group' => ['allow_new_threads' => 0],
9964
                'default_view_type_group' => ['default_view_type' => 'flat'],
9965
                'group_forum' => 0,
9966
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
9967
            ],
9968
            [],
9969
            true
9970
        );
9971
    }
9972
9973
    /**
9974
     * Get the LP Final Item form.
9975
     *
9976
     * @throws Exception
9977
     *
9978
     *
9979
     * @return string
9980
     */
9981
    public function getFinalItemForm()
9982
    {
9983
        $finalItem = $this->getFinalItem();
9984
        $title = '';
9985
9986
        if ($finalItem) {
9987
            $title = $finalItem->get_title();
9988
            $buttonText = get_lang('Save');
9989
            $content = $this->getSavedFinalItem();
9990
        } else {
9991
            $buttonText = get_lang('Add this document to the course');
9992
            $content = $this->getFinalItemTemplate();
9993
        }
9994
9995
        $editorConfig = [
9996
            'ToolbarSet' => 'LearningPathDocuments',
9997
            'Width' => '100%',
9998
            'Height' => '500',
9999
            'FullPage' => true,
10000
//            'CreateDocumentDir' => $relative_prefix,
10001
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10002
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10003
        ];
10004
10005
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10006
            'type' => 'document',
10007
            'lp_id' => $this->lp_id,
10008
        ]);
10009
10010
        $form = new FormValidator('final_item', 'POST', $url);
10011
        $form->addText('title', get_lang('Title'));
10012
        $form->addButtonSave($buttonText);
10013
        $form->addHtml(
10014
            Display::return_message(
10015
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10016
                'normal',
10017
                false
10018
            )
10019
        );
10020
10021
        $renderer = $form->defaultRenderer();
10022
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10023
10024
        $form->addHtmlEditor(
10025
            'content_lp_certificate',
10026
            null,
10027
            true,
10028
            false,
10029
            $editorConfig,
10030
            true
10031
        );
10032
        $form->addHidden('action', 'add_final_item');
10033
        $form->addHidden('path', Session::read('pathItem'));
10034
        $form->addHidden('previous', $this->get_last());
10035
        $form->setDefaults(
10036
            ['title' => $title, 'content_lp_certificate' => $content]
10037
        );
10038
10039
        if ($form->validate()) {
10040
            $values = $form->exportValues();
10041
            $lastItemId = $this->getLastInFirstLevel();
10042
10043
            if (!$finalItem) {
10044
                $documentId = $this->create_document(
10045
                    $this->course_info,
10046
                    $values['content_lp_certificate'],
10047
                    $values['title']
10048
                );
10049
                $this->add_item(
10050
                    0,
10051
                    $lastItemId,
10052
                    'final_item',
10053
                    $documentId,
10054
                    $values['title'],
10055
                    ''
10056
                );
10057
10058
                Display::addFlash(
10059
                    Display::return_message(get_lang('Added'))
10060
                );
10061
            } else {
10062
                $this->edit_document($this->course_info);
10063
            }
10064
        }
10065
10066
        return $form->returnForm();
10067
    }
10068
10069
    /**
10070
     * Check if the current lp item is first, both, last or none from lp list.
10071
     *
10072
     * @param int $currentItemId
10073
     *
10074
     * @return string
10075
     */
10076
    public function isFirstOrLastItem($currentItemId)
10077
    {
10078
        $lpItemId = [];
10079
        $typeListNotToVerify = self::getChapterTypes();
10080
10081
        // Using get_toc() function instead $this->items because returns the correct order of the items
10082
        foreach ($this->get_toc() as $item) {
10083
            if (!in_array($item['type'], $typeListNotToVerify)) {
10084
                $lpItemId[] = $item['id'];
10085
            }
10086
        }
10087
10088
        $lastLpItemIndex = count($lpItemId) - 1;
10089
        $position = array_search($currentItemId, $lpItemId);
10090
10091
        switch ($position) {
10092
            case 0:
10093
                if (!$lastLpItemIndex) {
10094
                    $answer = 'both';
10095
                    break;
10096
                }
10097
10098
                $answer = 'first';
10099
                break;
10100
            case $lastLpItemIndex:
10101
                $answer = 'last';
10102
                break;
10103
            default:
10104
                $answer = 'none';
10105
        }
10106
10107
        return $answer;
10108
    }
10109
10110
    /**
10111
     * Get whether this is a learning path with the accumulated SCORM time or not.
10112
     *
10113
     * @return int
10114
     */
10115
    public function getAccumulateScormTime()
10116
    {
10117
        return $this->accumulateScormTime;
10118
    }
10119
10120
    /**
10121
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10122
     * the new learning path tool.
10123
     *
10124
     * The function is a big switch on tool type.
10125
     * In each case, we query the corresponding table for information and build the link
10126
     * with that information.
10127
     *
10128
     * @author Yannick Warnier <[email protected]> - rebranding based on
10129
     * previous work (display_addedresource_link_in_learnpath())
10130
     *
10131
     * @param int $course_id      Course code
10132
     * @param int $learningPathId The learning path ID (in lp table)
10133
     * @param int $id_in_path     the unique index in the items table
10134
     * @param int $lpViewId
10135
     *
10136
     * @return string
10137
     */
10138
    public static function rl_get_resource_link_for_learnpath(
10139
        $course_id,
10140
        $learningPathId,
10141
        $id_in_path,
10142
        $lpViewId
10143
    ) {
10144
        $session_id = api_get_session_id();
10145
10146
        $learningPathId = (int) $learningPathId;
10147
        $id_in_path = (int) $id_in_path;
10148
        $lpViewId = (int) $lpViewId;
10149
10150
        $em = Database::getManager();
10151
        $lpItemRepo = $em->getRepository(CLpItem::class);
10152
10153
        /** @var CLpItem $rowItem */
10154
        $rowItem = $lpItemRepo->findOneBy([
10155
            'lp' => $learningPathId,
10156
            'iid' => $id_in_path,
10157
        ]);
10158
10159
        if (!$rowItem) {
10160
            // Try one more time with "id"
10161
            /** @var CLpItem $rowItem */
10162
            $rowItem = $lpItemRepo->findOneBy([
10163
                'lp' => $learningPathId,
10164
                'id' => $id_in_path,
10165
            ]);
10166
10167
            if (!$rowItem) {
10168
                return -1;
10169
            }
10170
        }
10171
10172
        $type = $rowItem->getItemType();
10173
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10174
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10175
        $link = '';
10176
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
10177
10178
        switch ($type) {
10179
            case 'dir':
10180
                return $main_dir_path.'lp/blank.php';
10181
            case TOOL_CALENDAR_EVENT:
10182
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10183
            case TOOL_ANNOUNCEMENT:
10184
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10185
            case TOOL_LINK:
10186
                $linkInfo = Link::getLinkInfo($id);
10187
                if (isset($linkInfo['url'])) {
10188
                    return $linkInfo['url'];
10189
                }
10190
10191
                return '';
10192
            case TOOL_QUIZ:
10193
                if (empty($id)) {
10194
                    return '';
10195
                }
10196
10197
                // Get the lp_item_view with the highest view_count.
10198
                $learnpathItemViewResult = $em
10199
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10200
                    ->findBy(
10201
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getIid(), 'lpViewId' => $lpViewId],
10202
                        ['viewCount' => 'DESC'],
10203
                        1
10204
                    );
10205
                /** @var CLpItemView $learnpathItemViewData */
10206
                $learnpathItemViewData = current($learnpathItemViewResult);
10207
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
10208
10209
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10210
                    .http_build_query([
10211
                        'lp_init' => 1,
10212
                        'learnpath_item_view_id' => $learnpathItemViewId,
10213
                        'learnpath_id' => $learningPathId,
10214
                        'learnpath_item_id' => $id_in_path,
10215
                        'exerciseId' => $id,
10216
                    ]);
10217
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10218
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10219
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10220
                $myrow = Database::fetch_array($result);
10221
                $path = $myrow['path'];
10222
10223
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10224
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10225
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10226
            case TOOL_FORUM:
10227
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10228
            case TOOL_THREAD:
10229
                // forum post
10230
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10231
                if (empty($id)) {
10232
                    return '';
10233
                }
10234
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
10235
                $result = Database::query($sql);
10236
                $myrow = Database::fetch_array($result);
10237
10238
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10239
                    .$extraParams;
10240
            case TOOL_POST:
10241
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10242
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10243
                $myrow = Database::fetch_array($result);
10244
10245
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10246
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10247
            case TOOL_READOUT_TEXT:
10248
                return api_get_path(WEB_CODE_PATH).
10249
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10250
            case TOOL_DOCUMENT:
10251
                $repo = Container::getDocumentRepository();
10252
                $document = $repo->find($rowItem->getPath());
10253
                $params = [
10254
                    'cid' => $course_id,
10255
                    'sid' => $session_id,
10256
                ];
10257
                $file = $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
10258
10259
                return $file;
10260
10261
                $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...
10262
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10263
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10264
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10265
10266
                $openmethod = 2;
10267
                $officedoc = false;
10268
                Session::write('openmethod', $openmethod);
10269
                Session::write('officedoc', $officedoc);
10270
10271
                if ($showDirectUrl) {
10272
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10273
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10274
                        if (Link::isPdfLink($file)) {
10275
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10276
10277
                            return $pdfUrl;
10278
                        }
10279
                    }
10280
10281
                    return $file;
10282
                }
10283
10284
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10285
            case TOOL_LP_FINAL_ITEM:
10286
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10287
                    .$extraParams;
10288
            case 'assignments':
10289
                return $main_dir_path.'work/work.php?'.$extraParams;
10290
            case TOOL_DROPBOX:
10291
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10292
            case 'introduction_text': //DEPRECATED
10293
                return '';
10294
            case TOOL_COURSE_DESCRIPTION:
10295
                return $main_dir_path.'course_description?'.$extraParams;
10296
            case TOOL_GROUP:
10297
                return $main_dir_path.'group/group.php?'.$extraParams;
10298
            case TOOL_USER:
10299
                return $main_dir_path.'user/user.php?'.$extraParams;
10300
            case TOOL_STUDENTPUBLICATION:
10301
                if (!empty($rowItem->getPath())) {
10302
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10303
                }
10304
10305
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10306
        }
10307
10308
        return $link;
10309
    }
10310
10311
    /**
10312
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10313
     *
10314
     * @author Yannick Warnier <[email protected]>
10315
     *
10316
     * @param string $course_code    Course code
10317
     * @param int    $learningPathId
10318
     * @param int    $id_in_path     The resource ID
10319
     *
10320
     * @return string
10321
     */
10322
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10323
    {
10324
        $_course = api_get_course_info($course_code);
10325
        if (empty($_course)) {
10326
            return '';
10327
        }
10328
        $course_id = $_course['real_id'];
10329
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10330
        $learningPathId = (int) $learningPathId;
10331
        $id_in_path = (int) $id_in_path;
10332
10333
        $sql = "SELECT item_type, title, ref
10334
                FROM $tbl_lp_item
10335
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10336
        $res_item = Database::query($sql);
10337
10338
        if (Database::num_rows($res_item) < 1) {
10339
            return '';
10340
        }
10341
        $row_item = Database::fetch_array($res_item);
10342
        $type = strtolower($row_item['item_type']);
10343
        $id = $row_item['ref'];
10344
        $output = '';
10345
10346
        switch ($type) {
10347
            case TOOL_CALENDAR_EVENT:
10348
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10349
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10350
                $myrow = Database::fetch_array($result);
10351
                $output = $myrow['title'];
10352
                break;
10353
            case TOOL_ANNOUNCEMENT:
10354
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10355
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10356
                $myrow = Database::fetch_array($result);
10357
                $output = $myrow['title'];
10358
                break;
10359
            case TOOL_LINK:
10360
                // Doesn't take $target into account.
10361
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10362
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10363
                $myrow = Database::fetch_array($result);
10364
                $output = $myrow['title'];
10365
                break;
10366
            case TOOL_QUIZ:
10367
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10368
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10369
                $myrow = Database::fetch_array($result);
10370
                $output = $myrow['title'];
10371
                break;
10372
            case TOOL_FORUM:
10373
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10374
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10375
                $myrow = Database::fetch_array($result);
10376
                $output = $myrow['forum_name'];
10377
                break;
10378
            case TOOL_THREAD:
10379
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10380
                // Grabbing the title of the post.
10381
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10382
                $result_title = Database::query($sql_title);
10383
                $myrow_title = Database::fetch_array($result_title);
10384
                $output = $myrow_title['post_title'];
10385
                break;
10386
            case TOOL_POST:
10387
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10388
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10389
                $result = Database::query($sql);
10390
                $post = Database::fetch_array($result);
10391
                $output = $post['post_title'];
10392
                break;
10393
            case 'dir':
10394
            case TOOL_DOCUMENT:
10395
                $title = $row_item['title'];
10396
                $output = '-';
10397
                if (!empty($title)) {
10398
                    $output = $title;
10399
                }
10400
                break;
10401
            case 'hotpotatoes':
10402
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10403
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10404
                $myrow = Database::fetch_array($result);
10405
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10406
                $last = count($pathname) - 1; // Making a correct name for the link.
10407
                $filename = $pathname[$last]; // Making a correct name for the link.
10408
                $myrow['path'] = rawurlencode($myrow['path']);
10409
                $output = $filename;
10410
                break;
10411
        }
10412
10413
        return stripslashes($output);
10414
    }
10415
10416
    /**
10417
     * Get the parent names for the current item.
10418
     *
10419
     * @param int $newItemId Optional. The item ID
10420
     *
10421
     * @return array
10422
     */
10423
    public function getCurrentItemParentNames($newItemId = 0)
10424
    {
10425
        $newItemId = $newItemId ?: $this->get_current_item_id();
10426
        $return = [];
10427
        $item = $this->getItem($newItemId);
10428
        $parent = $this->getItem($item->get_parent());
10429
10430
        while ($parent) {
10431
            $return[] = $parent->get_title();
10432
            $parent = $this->getItem($parent->get_parent());
10433
        }
10434
10435
        return array_reverse($return);
10436
    }
10437
10438
    /**
10439
     * Reads and process "lp_subscription_settings" setting.
10440
     *
10441
     * @return array
10442
     */
10443
    public static function getSubscriptionSettings()
10444
    {
10445
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10446
        if (empty($subscriptionSettings)) {
10447
            // By default allow both settings
10448
            $subscriptionSettings = [
10449
                'allow_add_users_to_lp' => true,
10450
                'allow_add_users_to_lp_category' => true,
10451
            ];
10452
        } else {
10453
            $subscriptionSettings = $subscriptionSettings['options'];
10454
        }
10455
10456
        return $subscriptionSettings;
10457
    }
10458
10459
    /**
10460
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10461
     */
10462
    public function exportToCourseBuildFormat()
10463
    {
10464
        if (!api_is_allowed_to_edit()) {
10465
            return false;
10466
        }
10467
10468
        $courseBuilder = new CourseBuilder();
10469
        $itemList = [];
10470
        /** @var learnpathItem $item */
10471
        foreach ($this->items as $item) {
10472
            $itemList[$item->get_type()][] = $item->get_path();
10473
        }
10474
10475
        if (empty($itemList)) {
10476
            return false;
10477
        }
10478
10479
        if (isset($itemList['document'])) {
10480
            // Get parents
10481
            foreach ($itemList['document'] as $documentId) {
10482
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10483
                if (!empty($documentInfo['parents'])) {
10484
                    foreach ($documentInfo['parents'] as $parentInfo) {
10485
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10486
                            continue;
10487
                        }
10488
                        $itemList['document'][] = $parentInfo['iid'];
10489
                    }
10490
                }
10491
            }
10492
10493
            $courseInfo = api_get_course_info();
10494
            foreach ($itemList['document'] as $documentId) {
10495
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10496
                $items = DocumentManager::get_resources_from_source_html(
10497
                    $documentInfo['absolute_path'],
10498
                    true,
10499
                    TOOL_DOCUMENT
10500
                );
10501
10502
                if (!empty($items)) {
10503
                    foreach ($items as $item) {
10504
                        // Get information about source url
10505
                        $url = $item[0]; // url
10506
                        $scope = $item[1]; // scope (local, remote)
10507
                        $type = $item[2]; // type (rel, abs, url)
10508
10509
                        $origParseUrl = parse_url($url);
10510
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10511
10512
                        if ('local' === $scope) {
10513
                            if ('abs' === $type || 'rel' === $type) {
10514
                                $documentFile = strstr($realOrigPath, 'document');
10515
                                if (false !== strpos($realOrigPath, $documentFile)) {
10516
                                    $documentFile = str_replace('document', '', $documentFile);
10517
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10518
                                    // Document found! Add it to the list
10519
                                    if ($itemDocumentId) {
10520
                                        $itemList['document'][] = $itemDocumentId;
10521
                                    }
10522
                                }
10523
                            }
10524
                        }
10525
                    }
10526
                }
10527
            }
10528
10529
            $courseBuilder->build_documents(
10530
                api_get_session_id(),
10531
                $this->get_course_int_id(),
10532
                true,
10533
                $itemList['document']
10534
            );
10535
        }
10536
10537
        if (isset($itemList['quiz'])) {
10538
            $courseBuilder->build_quizzes(
10539
                api_get_session_id(),
10540
                $this->get_course_int_id(),
10541
                true,
10542
                $itemList['quiz']
10543
            );
10544
        }
10545
10546
        /*if (!empty($itemList['thread'])) {
10547
            $postList = [];
10548
            foreach ($itemList['thread'] as $postId) {
10549
                $post = get_post_information($postId);
10550
                if ($post) {
10551
                    if (!isset($itemList['forum'])) {
10552
                        $itemList['forum'] = [];
10553
                    }
10554
                    $itemList['forum'][] = $post['forum_id'];
10555
                    $postList[] = $postId;
10556
                }
10557
            }
10558
10559
            if (!empty($postList)) {
10560
                $courseBuilder->build_forum_posts(
10561
                    $this->get_course_int_id(),
10562
                    null,
10563
                    null,
10564
                    $postList
10565
                );
10566
            }
10567
        }*/
10568
10569
        if (!empty($itemList['thread'])) {
10570
            $threadList = [];
10571
            $em = Database::getManager();
10572
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
10573
            foreach ($itemList['thread'] as $threadId) {
10574
                /** @var CForumThread $thread */
10575
                $thread = $repo->find($threadId);
10576
                if ($thread) {
10577
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
10578
                    $threadList[] = $thread->getIid();
10579
                }
10580
            }
10581
10582
            if (!empty($threadList)) {
10583
                $courseBuilder->build_forum_topics(
10584
                    api_get_session_id(),
10585
                    $this->get_course_int_id(),
10586
                    null,
10587
                    $threadList
10588
                );
10589
            }
10590
        }
10591
10592
        $forumCategoryList = [];
10593
        if (isset($itemList['forum'])) {
10594
            foreach ($itemList['forum'] as $forumId) {
10595
                $forumInfo = get_forums($forumId);
10596
                $forumCategoryList[] = $forumInfo['forum_category'];
10597
            }
10598
        }
10599
10600
        if (!empty($forumCategoryList)) {
10601
            $courseBuilder->build_forum_category(
10602
                api_get_session_id(),
10603
                $this->get_course_int_id(),
10604
                true,
10605
                $forumCategoryList
10606
            );
10607
        }
10608
10609
        if (!empty($itemList['forum'])) {
10610
            $courseBuilder->build_forums(
10611
                api_get_session_id(),
10612
                $this->get_course_int_id(),
10613
                true,
10614
                $itemList['forum']
10615
            );
10616
        }
10617
10618
        if (isset($itemList['link'])) {
10619
            $courseBuilder->build_links(
10620
                api_get_session_id(),
10621
                $this->get_course_int_id(),
10622
                true,
10623
                $itemList['link']
10624
            );
10625
        }
10626
10627
        if (!empty($itemList['student_publication'])) {
10628
            $courseBuilder->build_works(
10629
                api_get_session_id(),
10630
                $this->get_course_int_id(),
10631
                true,
10632
                $itemList['student_publication']
10633
            );
10634
        }
10635
10636
        $courseBuilder->build_learnpaths(
10637
            api_get_session_id(),
10638
            $this->get_course_int_id(),
10639
            true,
10640
            [$this->get_id()],
10641
            false
10642
        );
10643
10644
        $courseBuilder->restoreDocumentsFromList();
10645
10646
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
10647
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
10648
        $result = DocumentManager::file_send_for_download(
10649
            $zipPath,
10650
            true,
10651
            $this->get_name().'.zip'
10652
        );
10653
10654
        if ($result) {
10655
            api_not_allowed();
10656
        }
10657
10658
        return true;
10659
    }
10660
10661
    /**
10662
     * Get whether this is a learning path with the accumulated work time or not.
10663
     *
10664
     * @return int
10665
     */
10666
    public function getAccumulateWorkTime()
10667
    {
10668
        return (int) $this->accumulateWorkTime;
10669
    }
10670
10671
    /**
10672
     * Get whether this is a learning path with the accumulated work time or not.
10673
     *
10674
     * @return int
10675
     */
10676
    public function getAccumulateWorkTimeTotalCourse()
10677
    {
10678
        $table = Database::get_course_table(TABLE_LP_MAIN);
10679
        $sql = "SELECT SUM(accumulate_work_time) AS total
10680
                FROM $table
10681
                WHERE c_id = ".$this->course_int_id;
10682
        $result = Database::query($sql);
10683
        $row = Database::fetch_array($result);
10684
10685
        return (int) $row['total'];
10686
    }
10687
10688
    /**
10689
     * @param int $lpId
10690
     * @param int $courseId
10691
     *
10692
     * @return mixed
10693
     */
10694
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
10695
    {
10696
        $lpId = (int) $lpId;
10697
        $table = Database::get_course_table(TABLE_LP_MAIN);
10698
        $sql = "SELECT accumulate_work_time
10699
                FROM $table
10700
                WHERE iid = $lpId";
10701
        $result = Database::query($sql);
10702
        $row = Database::fetch_array($result);
10703
10704
        return $row['accumulate_work_time'];
10705
    }
10706
10707
    /**
10708
     * @param int $courseId
10709
     *
10710
     * @return int
10711
     */
10712
    public static function getAccumulateWorkTimeTotal($courseId)
10713
    {
10714
        $table = Database::get_course_table(TABLE_LP_MAIN);
10715
        $courseId = (int) $courseId;
10716
        $sql = "SELECT SUM(accumulate_work_time) AS total
10717
                FROM $table
10718
                WHERE c_id = $courseId";
10719
        $result = Database::query($sql);
10720
        $row = Database::fetch_array($result);
10721
10722
        return (int) $row['total'];
10723
    }
10724
10725
    /**
10726
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
10727
     * and put the images in.
10728
     *
10729
     * @return array
10730
     */
10731
    public static function getIconSelect()
10732
    {
10733
        $theme = api_get_visual_theme();
10734
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
10735
        $icons = ['' => get_lang('Please select an option')];
10736
10737
        if (is_dir($path)) {
10738
            $finder = new Finder();
10739
            $finder->files()->in($path);
10740
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
10741
            /** @var SplFileInfo $file */
10742
            foreach ($finder as $file) {
10743
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
10744
                    $icons[$file->getFilename()] = $file->getFilename();
10745
                }
10746
            }
10747
        }
10748
10749
        return $icons;
10750
    }
10751
10752
    /**
10753
     * @param int $lpId
10754
     *
10755
     * @return string
10756
     */
10757
    public static function getSelectedIcon($lpId)
10758
    {
10759
        $extraFieldValue = new ExtraFieldValue('lp');
10760
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
10761
        $icon = '';
10762
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
10763
            $icon = $lpIcon['value'];
10764
        }
10765
10766
        return $icon;
10767
    }
10768
10769
    /**
10770
     * @param int $lpId
10771
     *
10772
     * @return string
10773
     */
10774
    public static function getSelectedIconHtml($lpId)
10775
    {
10776
        $icon = self::getSelectedIcon($lpId);
10777
10778
        if (empty($icon)) {
10779
            return '';
10780
        }
10781
10782
        $theme = api_get_visual_theme();
10783
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
10784
10785
        return Display::img($path);
10786
    }
10787
10788
    /**
10789
     * @param string $value
10790
     *
10791
     * @return string
10792
     */
10793
    public function cleanItemTitle($value)
10794
    {
10795
        $value = Security::remove_XSS(strip_tags($value));
10796
10797
        return $value;
10798
    }
10799
10800
    public function setItemTitle(FormValidator $form)
10801
    {
10802
        if (api_get_configuration_value('save_titles_as_html')) {
10803
            $form->addHtmlEditor(
10804
                'title',
10805
                get_lang('Title'),
10806
                true,
10807
                false,
10808
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
10809
            );
10810
        } else {
10811
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
10812
            $form->applyFilter('title', 'trim');
10813
            $form->applyFilter('title', 'html_filter');
10814
        }
10815
    }
10816
10817
    /**
10818
     * @return array
10819
     */
10820
    public function getItemsForForm($addParentCondition = false)
10821
    {
10822
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10823
10824
        $sql = "SELECT * FROM $tbl_lp_item
10825
                WHERE path <> 'root' AND lp_id = ".$this->lp_id;
10826
10827
        if ($addParentCondition) {
10828
            $sql .= ' AND parent_item_id IS NULL ';
10829
        }
10830
        $sql .= ' ORDER BY display_order ASC';
10831
10832
        $result = Database::query($sql);
10833
        $arrLP = [];
10834
        while ($row = Database::fetch_array($result)) {
10835
            $arrLP[] = [
10836
                'iid' => $row['iid'],
10837
                'id' => $row['iid'],
10838
                'item_type' => $row['item_type'],
10839
                'title' => $this->cleanItemTitle($row['title']),
10840
                'title_raw' => $row['title'],
10841
                'path' => $row['path'],
10842
                'description' => Security::remove_XSS($row['description']),
10843
                'parent_item_id' => $row['parent_item_id'],
10844
                'previous_item_id' => $row['previous_item_id'],
10845
                'next_item_id' => $row['next_item_id'],
10846
                'display_order' => $row['display_order'],
10847
                'max_score' => $row['max_score'],
10848
                'min_score' => $row['min_score'],
10849
                'mastery_score' => $row['mastery_score'],
10850
                'prerequisite' => $row['prerequisite'],
10851
                'max_time_allowed' => $row['max_time_allowed'],
10852
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10853
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10854
            ];
10855
        }
10856
10857
        return $arrLP;
10858
    }
10859
10860
    /**
10861
     * Gets whether this SCORM learning path has been marked to use the score
10862
     * as progress. Takes into account whether the learnpath matches (SCORM
10863
     * content + less than 2 items).
10864
     *
10865
     * @return bool True if the score should be used as progress, false otherwise
10866
     */
10867
    public function getUseScoreAsProgress()
10868
    {
10869
        // If not a SCORM, we don't care about the setting
10870
        if (2 != $this->get_type()) {
10871
            return false;
10872
        }
10873
        // If more than one step in the SCORM, we don't care about the setting
10874
        if ($this->get_total_items_count() > 1) {
10875
            return false;
10876
        }
10877
        $extraFieldValue = new ExtraFieldValue('lp');
10878
        $doUseScore = false;
10879
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
10880
            $this->get_id(),
10881
            'use_score_as_progress'
10882
        );
10883
        if (!empty($useScore) && isset($useScore['value'])) {
10884
            $doUseScore = $useScore['value'];
10885
        }
10886
10887
        return $doUseScore;
10888
    }
10889
10890
    /**
10891
     * Get the user identifier (user_id or username
10892
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
10893
     *
10894
     * @return string User ID or username, depending on configuration setting
10895
     */
10896
    public static function getUserIdentifierForExternalServices()
10897
    {
10898
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
10899
            return api_get_user_info(api_get_user_id())['username'];
10900
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
10901
            $extraFieldValue = new ExtraFieldValue('user');
10902
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
10903
                api_get_user_id(),
10904
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
10905
            );
10906
10907
            return $extrafield['value'];
10908
        } else {
10909
            return api_get_user_id();
10910
        }
10911
    }
10912
10913
    /**
10914
     * Save the new order for learning path items.
10915
     *
10916
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
10917
     *
10918
     * @param array $orderList A associative array with item ID as key and parent ID as value.
10919
     */
10920
    public function sortItemByOrderList(array $orderList)
10921
    {
10922
        $lpItemRepo = Container::getLpItemRepository();
10923
        $rootParent = $lpItemRepo->getItemRoot($this->get_id());
10924
10925
        /*$previous = 2;
10926
        $next = 0;
10927
        $rootParent->setPreviousItemId(1);
10928
        $last = $this->updateList($orderList, $rootParent, $previous, $next);
10929
        $rootParent->setNextItemId($last + 1);
10930
*/
10931
        $em = Database::getManager();
10932
        echo '<pre>';
10933
        //var_dump($orderList);
10934
//        $em->persist($rootParent);
10935
10936
        /*$node = new \Tree\Node\Node($rootParent->getIid());
10937
        $parentList[$rootParent->getIid()] = $node;
10938
10939
        foreach ($orderList as $item) {
10940
            $itemId = $item->id ?? 0;
10941
            if (empty($itemId)) {
10942
                continue;
10943
            }
10944
            $parentId = $item->parent_id ?? 0;
10945
10946
            if (empty($parentId)) {
10947
                $parent = $rootParent;
10948
                $parentId = $rootParent->getIid();
10949
            } else {
10950
                //$parent = $lpItemRepo->find($parentId);
10951
            }
10952
            //$parentList[$itemId] = ;
10953
10954
            $child = new \Tree\Node\Node($itemId);
10955
            $parentList[$itemId] = $child;
10956
            $parentList[$parentId]->addChild($child);
10957
        }
10958
10959
        $builder = new Tree\Builder\NodeBuilder;
10960
        echo '<pre>';
10961
        foreach ($node->getChildren() as $child) {
10962
            var_dump($child->getValue().'-'.$child->getDepth().'-'.$child->getSize());
10963
10964
            var_dump("---");
10965
            foreach ($child->getNeighbors() as $neighbor) {
10966
10967
                var_dump($neighbor->getValue(), $child->getSize(), $neighbor->getDepth());
10968
            }
10969
        }
10970
exit;*/
10971
10972
        $counter = 2;
10973
        /*$rootParent->setPreviousItemId(1);
10974
        $rootParent->setDisplayOrder(0);
10975
        $rootParent->setLaunchData(1);*/
10976
        echo '<pre>';
10977
        $rootParent->setDisplayOrder(1);
10978
        foreach ($orderList as $item) {
10979
            $itemId  = $item->id ?? 0;
10980
            if (empty($itemId)) {
10981
                continue;
10982
            }
10983
            $parentId = $item->parent_id ?? 0;
10984
            if (empty($parentId)) {
10985
                $parent = $rootParent;
10986
            } else {
10987
                $parent = $lpItemRepo->find($parentId);
10988
            }
10989
10990
            if (isset($parentOrder[$parent->getIid()])) {
10991
                $parentOrder[$parent->getIid()]++;
10992
            } else {
10993
                $parentOrder[$parent->getIid()] = 0;
10994
            }
10995
            /** @var CLpItem $item */
10996
            $item = $lpItemRepo->find($itemId);
10997
10998
            $previousId = 2;
10999
            if (isset($previous[$counter - 1])) {
11000
                $previousId = $previous[$counter - 1];
11001
            }
11002
            $previous[$counter] = $counter + 2;
11003
11004
            $item->setParent($parent);
11005
            //$item->setPreviousItemId($counter);
11006
            //$item->setNextItemId($counter + 1);
11007
            //$item->setDisplayOrder($parentOrder[$parent->getIid()]);
11008
            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...
11009
            //$item->setPreviousItemId(0);
11010
            //$item->setNextItemId(0);
11011
            $item->setDisplayOrder($counter);
11012
            $em->persist($item);
11013
            $counter++;
11014
        }
11015
        //$rootParent->setNextItemId($counter+1);
11016
        $em->persist($rootParent);
11017
        $em->flush();
11018
        var_dump($lpItemRepo->verify());
11019
        $lpItemRepo->reorder($rootParent, 'displayOrder');
11020
11021
        var_dump($lpItemRepo->verify());
11022
        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...
11023
        $lpItemRepo->recoverNode($rootParent);
0 ignored issues
show
Unused Code introduced by
$lpItemRepo->recoverNode($rootParent) 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...
11024
        var_dump($lpItemRepo->verify());
11025
11026
        $em->flush();
11027
        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...
11028
    }
11029
11030
    private static function updateList($list, $parent, &$previous, &$next)
11031
    {
11032
        //echo '<pre>';        var_dump($list);exit;
11033
        $lpItemRepo = Container::getLpItemRepository();
11034
        $em = Database::getManager();
11035
        $counter = 0;
11036
        foreach ($list as $item) {
11037
            if (empty($item) || !isset($item->id)) {
11038
                continue;
11039
            }
11040
            $id = $item->id;
11041
            $children = $item->children;
11042
11043
            /** @var CLpItem $lpItem */
11044
            $lpItem = $lpItemRepo->find($id);
11045
            $lpItem
11046
                ->setParent($parent)
11047
                ->setDisplayOrder($counter)
11048
                ->setPreviousItemId($previous);
11049
            ;
11050
            $previous++;
11051
            $counter++;
11052
            $childExists = false;
11053
            foreach ($children as $child) {
11054
                if (isset($child->id)) {
11055
                    $childExists = true;
11056
                    break;
11057
                }
11058
            }
11059
11060
            if ($childExists) {
11061
                $next = self::updateList($children, $lpItem, $previous, $next);
11062
                $lpItem->setNextItemId($next + 1);
11063
                $previous++;
11064
            } else {
11065
                $next = $previous;
11066
                $previous++;
11067
                $lpItem->setNextItemId($next);
11068
            }
11069
            $em->persist($lpItem);
11070
        }
11071
11072
        return $next;
11073
    }
11074
11075
    /**
11076
     * Get the depth level of LP item.
11077
     *
11078
     * @param array $items
11079
     * @param int   $currentItemId
11080
     *
11081
     * @return int
11082
     */
11083
    private static function get_level_for_item($items, $currentItemId)
11084
    {
11085
        $parentItemId = 0;
11086
        if (isset($items[$currentItemId])) {
11087
            $parentItemId = $items[$currentItemId]->parent;
11088
        }
11089
11090
        if (0 == $parentItemId) {
11091
            return 0;
11092
        }
11093
11094
        return self::get_level_for_item($items, $parentItemId) + 1;
11095
    }
11096
11097
    /**
11098
     * Generate the link for a learnpath category as course tool.
11099
     *
11100
     * @param int $categoryId
11101
     *
11102
     * @return string
11103
     */
11104
    private static function getCategoryLinkForTool($categoryId)
11105
    {
11106
        $categoryId = (int) $categoryId;
11107
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
11108
            .http_build_query(
11109
                [
11110
                    'action' => 'view_category',
11111
                    'id' => $categoryId,
11112
                ]
11113
            );
11114
    }
11115
11116
    /**
11117
     * Return the scorm item type object with spaces replaced with _
11118
     * The return result is use to build a css classname like scorm_type_$return.
11119
     *
11120
     * @param $in_type
11121
     *
11122
     * @return mixed
11123
     */
11124
    private static function format_scorm_type_item($in_type)
11125
    {
11126
        return str_replace(' ', '_', $in_type);
11127
    }
11128
11129
    /**
11130
     * Check and obtain the lp final item if exist.
11131
     *
11132
     * @return learnpathItem
11133
     */
11134
    private function getFinalItem()
11135
    {
11136
        if (empty($this->items)) {
11137
            return null;
11138
        }
11139
11140
        foreach ($this->items as $item) {
11141
            if ('final_item' !== $item->type) {
11142
                continue;
11143
            }
11144
11145
            return $item;
11146
        }
11147
    }
11148
11149
    /**
11150
     * Get the LP Final Item Template.
11151
     *
11152
     * @return string
11153
     */
11154
    private function getFinalItemTemplate()
11155
    {
11156
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11157
    }
11158
11159
    /**
11160
     * Get the LP Final Item Url.
11161
     *
11162
     * @return string
11163
     */
11164
    private function getSavedFinalItem()
11165
    {
11166
        $finalItem = $this->getFinalItem();
11167
11168
        $repo = Container::getDocumentRepository();
11169
        /** @var CDocument $document */
11170
        $document = $repo->find($finalItem->path);
11171
11172
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11173
            return $repo->getResourceFileContent($document);
11174
        }
11175
11176
        return '';
11177
    }
11178
}
11179