Passed
Push — master ( 088f37...4791a8 )
by Julito
09:08
created

learnpath::get_common_index_terms_by_prefix()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 2
nop 1
dl 0
loc 16
rs 9.9332
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\Framework\Container;
8
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
11
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
12
use Chamilo\CourseBundle\Entity\CDocument;
13
use Chamilo\CourseBundle\Entity\CForumCategory;
14
use Chamilo\CourseBundle\Entity\CForumThread;
15
use Chamilo\CourseBundle\Entity\CLink;
16
use Chamilo\CourseBundle\Entity\CLp;
17
use Chamilo\CourseBundle\Entity\CLpCategory;
18
use Chamilo\CourseBundle\Entity\CLpItem;
19
use Chamilo\CourseBundle\Entity\CLpItemView;
20
use Chamilo\CourseBundle\Entity\CQuiz;
21
use Chamilo\CourseBundle\Entity\CStudentPublication;
22
use Chamilo\CourseBundle\Entity\CTool;
23
use ChamiloSession as Session;
24
use Doctrine\Common\Collections\Criteria;
25
use Symfony\Component\Filesystem\Filesystem;
26
use Symfony\Component\Finder\Finder;
27
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
28
29
/**
30
 * Class learnpath
31
 * This class defines the parent attributes and methods for Chamilo learnpaths
32
 * and SCORM learnpaths. It is used by the scorm class.
33
 *
34
 * @todo decouple class
35
 *
36
 * @author  Yannick Warnier <[email protected]>
37
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
38
 */
39
class learnpath
40
{
41
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
42
    public const STATUS_CSS_CLASS_NAME = [
43
        'not attempted' => 'scorm_not_attempted',
44
        'incomplete' => 'scorm_not_attempted',
45
        'failed' => 'scorm_failed',
46
        'completed' => 'scorm_completed',
47
        'passed' => 'scorm_completed',
48
        'succeeded' => 'scorm_completed',
49
        'browsed' => 'scorm_completed',
50
    ];
51
52
    public $attempt = 0; // The number for the current ID view.
53
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
54
    public $current; // Id of the current item the user is viewing.
55
    public $current_score; // The score of the current item.
56
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
57
    public $current_time_stop; // The time the user closed this resource.
58
    public $default_status = 'not attempted';
59
    public $encoding = 'UTF-8';
60
    public $error = '';
61
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
62
    public $index; // The index of the active learnpath_item in $ordered_items array.
63
    /** @var learnpathItem[] */
64
    public $items = [];
65
    public $last; // item_id of last item viewed in the learning path.
66
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
67
    public $license; // Which license this course has been given - not used yet on 20060522.
68
    public $lp_id; // DB iid for this learnpath.
69
    public $lp_view_id; // DB ID for lp_view
70
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
71
    public $message = '';
72
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
73
    public $name; // Learnpath name (they generally have one).
74
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
75
    public $path = ''; // Path inside the scorm directory (if scorm).
76
    public $theme; // The current theme of the learning path.
77
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
78
    public $accumulateWorkTime; // The min time of learnpath
79
80
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
81
    public $prevent_reinit = 1;
82
83
    // Describes the mode of progress bar display.
84
    public $seriousgame_mode = 0;
85
    public $progress_bar_mode = '%';
86
87
    // Percentage progress as saved in the db.
88
    public $progress_db = 0;
89
    public $proximity; // Wether the content is distant or local or unknown.
90
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
91
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
92
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
93
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
94
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
95
    public $user_id; //ID of the user that is viewing/using the course
96
    public $update_queue = [];
97
    public $scorm_debug = 0;
98
    public $arrMenu = []; // Array for the menu items.
99
    public $debug = 0; // Logging level.
100
    public $lp_session_id = 0;
101
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
102
    public $prerequisite = 0;
103
    public $use_max_score = 1; // 1 or 0
104
    public $subscribeUsers = 0; // Subscribe users or not
105
    public $created_on = '';
106
    public $modified_on = '';
107
    public $publicated_on = '';
108
    public $expired_on = '';
109
    public $ref;
110
    public $course_int_id;
111
    public $course_info;
112
    public $categoryId;
113
    public $scormUrl;
114
    public $entity;
115
116
    public function __construct(CLp $entity, $course_info, $user_id)
117
    {
118
        $debug = $this->debug;
119
        $user_id = (int) $user_id;
120
        $this->encoding = api_get_system_encoding();
121
        $lp_id = (int) $entity->getIid();
122
        $course_info = empty($course_info) ? api_get_course_info() : $course_info;
123
        $course_id = (int) $course_info['real_id'];
124
        $this->course_info = $course_info;
125
        $this->set_course_int_id($course_id);
126
        if (empty($lp_id) || empty($course_id)) {
127
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
128
        } else {
129
            $this->entity = $entity;
130
            $this->lp_id = $lp_id;
131
            $this->type = $entity->getLpType();
132
            $this->name = stripslashes($entity->getName());
133
            $this->proximity = $entity->getContentLocal();
134
            $this->theme = $entity->getTheme();
135
            $this->maker = $entity->getContentLocal();
136
            $this->prevent_reinit = $entity->getPreventReinit();
137
            $this->seriousgame_mode = $entity->getSeriousgameMode();
138
            $this->license = $entity->getContentLicense();
139
            $this->scorm_debug = $entity->getDebug();
140
            $this->js_lib = $entity->getJsLib();
141
            $this->path = $entity->getPath();
142
            $this->author = $entity->getAuthor();
143
            $this->hide_toc_frame = $entity->getHideTocFrame();
144
            //$this->lp_session_id = $entity->getSessionId();
145
            $this->use_max_score = $entity->getUseMaxScore();
146
            $this->subscribeUsers = $entity->getSubscribeUsers();
147
            $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
148
            $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
149
            $this->ref = $entity->getRef();
150
            $this->categoryId = 0;
151
            if ($entity->getCategory()) {
152
                $this->categoryId = $entity->getCategory()->getIid();
153
            }
154
155
            if ($entity->hasAsset()) {
156
                $asset = $entity->getAsset();
157
                $this->scormUrl = Container::getAssetRepository()->getAssetUrl($asset).'/';
158
            }
159
160
            $this->accumulateScormTime = $entity->getAccumulateWorkTime();
161
162
            if (!empty($entity->getPublicatedOn())) {
163
                $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
164
            }
165
166
            if (!empty($entity->getExpiredOn())) {
167
                $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
168
            }
169
            if (2 == $this->type) {
170
                if (1 == $entity->getForceCommit()) {
171
                    $this->force_commit = true;
172
                }
173
            }
174
            $this->mode = $entity->getDefaultViewMod();
175
176
            // Check user ID.
177
            if (empty($user_id)) {
178
                $this->error = 'User ID is empty';
179
            } else {
180
                $this->user_id = $user_id;
181
            }
182
183
            // End of variables checking.
184
            $session_id = api_get_session_id();
185
            //  Get the session condition for learning paths of the base + session.
186
            $session = api_get_session_condition($session_id);
187
            // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
188
            $lp_table = Database::get_course_table(TABLE_LP_VIEW);
189
190
            // Selecting by view_count descending allows to get the highest view_count first.
191
            $sql = "SELECT * FROM $lp_table
192
                    WHERE
193
                        c_id = $course_id AND
194
                        lp_id = $lp_id AND
195
                        user_id = $user_id
196
                        $session
197
                    ORDER BY view_count DESC";
198
            $res = Database::query($sql);
199
200
            if (Database::num_rows($res) > 0) {
201
                $row = Database::fetch_array($res);
202
                $this->attempt = $row['view_count'];
203
                $this->lp_view_id = $row['iid'];
204
                $this->last_item_seen = $row['last_item'];
205
                $this->progress_db = $row['progress'];
206
                $this->lp_view_session_id = $row['session_id'];
207
            } elseif (!api_is_invitee()) {
208
                $this->attempt = 1;
209
                $params = [
210
                    'c_id' => $course_id,
211
                    'lp_id' => $lp_id,
212
                    'user_id' => $user_id,
213
                    'view_count' => 1,
214
                    //'session_id' => $session_id,
215
                    'last_item' => 0,
216
                ];
217
                if (!empty($session_id)) {
218
                    $params['session_id'] = $session_id;
219
                }
220
                $this->last_item_seen = 0;
221
                $this->lp_view_session_id = $session_id;
222
                $this->lp_view_id = Database::insert($lp_table, $params);
223
            }
224
225
            $criteria = Criteria::create()
226
                ->orderBy(
227
                    [
228
                        'parent' => Criteria::ASC,
229
                        'displayOrder' => Criteria::ASC,
230
                    ]
231
                );
232
            $items = $this->entity->getItems()->matching($criteria);
233
            $lp_item_id_list = [];
234
            foreach ($items as $item) {
235
                $itemId = $item->getIid();
236
                $lp_item_id_list[] = $itemId;
237
                switch ($this->type) {
238
                    case 3: //aicc
239
                        $oItem = new aiccItem('db', $itemId, $course_id);
240
                        if (is_object($oItem)) {
241
                            $my_item_id = $oItem->get_id();
242
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
243
                            $oItem->set_prevent_reinit($this->prevent_reinit);
244
                            // Don't use reference here as the next loop will make the pointed object change.
245
                            $this->items[$my_item_id] = $oItem;
246
                            $this->refs_list[$oItem->ref] = $my_item_id;
247
                        }
248
                        break;
249
                    case 2:
250
                        $oItem = new scormItem('db', $itemId, $course_id);
251
                        if (is_object($oItem)) {
252
                            $my_item_id = $oItem->get_id();
253
                            $oItem->set_lp_view($this->lp_view_id, $course_id);
254
                            $oItem->set_prevent_reinit($this->prevent_reinit);
255
                            // Don't use reference here as the next loop will make the pointed object change.
256
                            $this->items[$my_item_id] = $oItem;
257
                            $this->refs_list[$oItem->ref] = $my_item_id;
258
                        }
259
                        break;
260
                    case 1:
261
                    default:
262
                        $oItem = new learnpathItem($itemId, $course_id, $item);
263
                        if (is_object($oItem)) {
264
                            $my_item_id = $oItem->get_id();
265
                            // Moved down to when we are sure the item_view exists.
266
                            //$oItem->set_lp_view($this->lp_view_id);
267
                            $oItem->set_prevent_reinit($this->prevent_reinit);
268
                            // Don't use reference here as the next loop will make the pointed object change.
269
                            $this->items[$my_item_id] = $oItem;
270
                            $this->refs_list[$my_item_id] = $my_item_id;
271
                        }
272
                        break;
273
                }
274
275
                // Setting the object level with variable $this->items[$i][parent]
276
                foreach ($this->items as $itemLPObject) {
277
                    $level = self::get_level_for_item($this->items, $itemLPObject->db_id);
278
                    $itemLPObject->level = $level;
279
                }
280
281
                // Setting the view in the item object.
282
                if (is_object($this->items[$itemId])) {
283
                    $this->items[$itemId]->set_lp_view($this->lp_view_id, $course_id);
284
                    if (TOOL_HOTPOTATOES == $this->items[$itemId]->get_type()) {
285
                        $this->items[$itemId]->current_start_time = 0;
286
                        $this->items[$itemId]->current_stop_time = 0;
287
                    }
288
                }
289
            }
290
291
            if (!empty($lp_item_id_list)) {
292
                $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
293
                if (!empty($lp_item_id_list_to_string)) {
294
                    // Get last viewing vars.
295
                    $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
296
                    // This query should only return one or zero result.
297
                    $sql = "SELECT lp_item_id, status
298
                            FROM $itemViewTable
299
                            WHERE
300
                                lp_view_id = ".$this->get_view_id()." AND
301
                                lp_item_id IN ('".$lp_item_id_list_to_string."')
302
                            ORDER BY view_count DESC ";
303
                    $status_list = [];
304
                    $res = Database::query($sql);
305
                    while ($row = Database:: fetch_array($res)) {
306
                        $status_list[$row['lp_item_id']] = $row['status'];
307
                    }
308
309
                    foreach ($lp_item_id_list as $item_id) {
310
                        if (isset($status_list[$item_id])) {
311
                            $status = $status_list[$item_id];
312
                            if (is_object($this->items[$item_id])) {
313
                                $this->items[$item_id]->set_status($status);
314
                                if (empty($status)) {
315
                                    $this->items[$item_id]->set_status(
316
                                        $this->default_status
317
                                    );
318
                                }
319
                            }
320
                        } else {
321
                            if (!api_is_invitee()) {
322
                                if (is_object($this->items[$item_id])) {
323
                                    $this->items[$item_id]->set_status(
324
                                        $this->default_status
325
                                    );
326
                                }
327
328
                                if (!empty($this->lp_view_id)) {
329
                                    // Add that row to the lp_item_view table so that
330
                                    // we have something to show in the stats page.
331
                                    $params = [
332
                                        'lp_item_id' => $item_id,
333
                                        'lp_view_id' => $this->lp_view_id,
334
                                        'view_count' => 1,
335
                                        'status' => 'not attempted',
336
                                        'start_time' => time(),
337
                                        'total_time' => 0,
338
                                        'score' => 0,
339
                                    ];
340
                                    Database::insert($itemViewTable, $params);
341
342
                                    $this->items[$item_id]->set_lp_view(
343
                                        $this->lp_view_id,
344
                                        $course_id
345
                                    );
346
                                }
347
                            }
348
                        }
349
                    }
350
                }
351
            }
352
353
            $this->ordered_items = self::get_flat_ordered_items_list($this->entity, null);
354
            $this->max_ordered_items = 0;
355
            foreach ($this->ordered_items as $index => $dummy) {
356
                if ($index > $this->max_ordered_items && !empty($dummy)) {
357
                    $this->max_ordered_items = $index;
358
                }
359
            }
360
            // TODO: Define the current item better.
361
            $this->first();
362
            if ($debug) {
363
                error_log('lp_view_session_id '.$this->lp_view_session_id);
364
                error_log('End of learnpath constructor for learnpath '.$this->get_id());
365
            }
366
        }
367
    }
368
369
    /**
370
     * @return string
371
     */
372
    public function getCourseCode()
373
    {
374
        return $this->cc;
375
    }
376
377
    /**
378
     * @return int
379
     */
380
    public function get_course_int_id()
381
    {
382
        return $this->course_int_id ?? api_get_course_int_id();
383
    }
384
385
    /**
386
     * @param $course_id
387
     *
388
     * @return int
389
     */
390
    public function set_course_int_id($course_id)
391
    {
392
        return $this->course_int_id = (int) $course_id;
393
    }
394
395
    /**
396
     * Function rewritten based on old_add_item() from Yannick Warnier.
397
     * Due the fact that users can decide where the item should come, I had to overlook this function and
398
     * I found it better to rewrite it. Old function is still available.
399
     * Added also the possibility to add a description.
400
     *
401
     * @param CLpItem $parent
402
     * @param int     $previous
403
     * @param string  $type
404
     * @param int     $id resource ID (ref)
405
     * @param string  $title
406
     * @param string  $description
407
     * @param int     $prerequisites
408
     * @param int     $max_time_allowed
409
     * @param int     $userId
410
     *
411
     * @return int
412
     */
413
    public function add_item(
414
        ?CLpItem $parent,
415
        $previous,
416
        $type,
417
        $id,
418
        $title,
419
        $description = '',
420
        $prerequisites = 0,
421
        $max_time_allowed = 0,
422
        $userId = 0
423
    ) {
424
        $type = empty($type) ? 'dir' : $type;
425
        $course_id = $this->course_info['real_id'];
426
        if (empty($course_id)) {
427
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
428
            $this->course_info = api_get_course_info($this->cc);
429
            $course_id = $this->course_info['real_id'];
430
        }
431
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
432
433
        $previous = (int) $previous;
434
        $id = (int) $id;
435
        $max_time_allowed = (int) $max_time_allowed;
436
        if (empty($max_time_allowed)) {
437
            $max_time_allowed = 0;
438
        }
439
        $lpId = $this->get_id();
440
        $parentCondition = ' AND parent_item_id IS NULL ';
441
        $parentId = 0;
442
        if (!empty($parent)) {
443
            $parentId = $parent->getIid();
444
            $parentCondition = " AND parent_item_id = $parentId  ";
445
        }
446
447
        $sql = "SELECT COUNT(iid) AS num
448
                FROM $tbl_lp_item
449
                WHERE
450
                    lp_id = $lpId
451
                    $parentCondition ";
452
453
        $res_count = Database::query($sql);
454
        $row = Database::fetch_array($res_count);
455
        $num = $row['num'];
456
457
        $tmp_previous = 0;
458
        $display_order = 0;
459
        $next = 0;
460
        if ($num > 0) {
461
            if (empty($previous)) {
462
                $sql = "SELECT iid, next_item_id, display_order
463
                        FROM $tbl_lp_item
464
                        WHERE
465
                            lp_id = $lpId AND
466
                            (previous_item_id = 0 OR
467
                            previous_item_id = $parentId)
468
                            $parentCondition";
469
                $result = Database::query($sql);
470
                $row = Database::fetch_array($result);
471
                if ($row) {
472
                    $next = $row['iid'];
473
                }
474
            } else {
475
                $previous = (int) $previous;
476
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
477
						FROM $tbl_lp_item
478
                        WHERE
479
                            lp_id = $lpId AND
480
                            iid = $previous";
481
                $result = Database::query($sql);
482
                $row = Database::fetch_array($result);
483
                if ($row) {
484
                    $tmp_previous = $row['iid'];
485
                    $next = $row['next_item_id'];
486
                    $display_order = $row['display_order'];
487
                }
488
            }
489
        }
490
491
        $id = (int) $id;
492
        $max_score = 100;
493
        if ('quiz' === $type && $id) {
494
            $sql = 'SELECT SUM(ponderation)
495
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as q
496
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
497
                    ON
498
                        q.iid = quiz_rel_question.question_id
499
                    WHERE
500
                        quiz_rel_question.quiz_id = '.$id;
501
            $rsQuiz = Database::query($sql);
502
            $max_score = Database::result($rsQuiz, 0, 0);
503
504
            // Disabling the exercise if we add it inside a LP
505
            $exercise = new Exercise($course_id);
506
            $exercise->read($id);
507
            $exercise->disable();
508
            $exercise->save();
509
            $title = $exercise->get_formated_title();
510
        }
511
512
        $lpItem = new CLpItem();
513
        $lpItem
514
            ->setTitle($title)
515
            ->setDescription($description)
516
            ->setPath($id)
517
            ->setLp($this->entity)
518
            ->setItemType($type)
519
            ->setMaxScore($max_score)
520
            ->setMaxTimeAllowed($max_time_allowed)
521
            ->setPrerequisite($prerequisites)
522
            ->setDisplayOrder($display_order + 1)
523
            ->setNextItemId((int) $next)
524
            ->setParent($parent)
525
            ->setPreviousItemId($previous)
526
        ;
527
        $em = Database::getManager();
528
        $em->persist($lpItem);
529
        $em->flush();
530
531
        $new_item_id = $lpItem->getIid();
532
        if ($new_item_id) {
533
            if (!empty($next)) {
534
                $sql = "UPDATE $tbl_lp_item
535
                        SET previous_item_id = $new_item_id
536
                        WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
537
                Database::query($sql);
538
            }
539
540
            // Update the item that should be before the new item.
541
            if (!empty($tmp_previous)) {
542
                $sql = "UPDATE $tbl_lp_item
543
                        SET next_item_id = $new_item_id
544
                        WHERE iid = $tmp_previous";
545
                Database::query($sql);
546
            }
547
548
            $parentCondition = ' AND parent_item_id IS NULL ';
549
            if (!empty($lpItem->getParentItemId())) {
550
                $parentId = $lpItem->getParentItemId();
551
                $parentCondition = " AND parent_item_id = $parentId  ";
552
            }
553
554
            // Update all the items after the new item.
555
            $sql = "UPDATE $tbl_lp_item
556
                        SET display_order = display_order + 1
557
                    WHERE
558
                        lp_id = $lpId AND
559
                        iid <> $new_item_id AND
560
                        display_order > $display_order
561
                        $parentCondition
562
                    ";
563
            Database::query($sql);
564
565
            // Update the item that should come after the new item.
566
            $sql = "UPDATE $tbl_lp_item
567
                    SET ref = $new_item_id
568
                    WHERE iid = $new_item_id";
569
            Database::query($sql);
570
571
            $sql = "UPDATE $tbl_lp_item
572
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
573
                    WHERE lp_id = $lpId AND item_type = '".TOOL_LP_FINAL_ITEM."'";
574
            Database::query($sql);
575
576
            // Upload audio.
577
            if (!empty($_FILES['mp3']['name'])) {
578
                // Create the audio folder if it does not exist yet.
579
                /*$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
580
                if (!is_dir($filepath.'audio')) {
581
                    mkdir(
582
                        $filepath.'audio',
583
                        api_get_permissions_for_new_directories()
584
                    );
585
                    DocumentManager::addDocument(
586
                        $_course,
587
                        '/audio',
588
                        'folder',
589
                        0,
590
                        'audio',
591
                        '',
592
                        0,
593
                        true,
594
                        null,
595
                        $sessionId,
596
                        $userId
597
                    );
598
                }
599
600
                $file_path = handle_uploaded_document(
601
                    $_course,
602
                    $_FILES['mp3'],
603
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
604
                    '/audio',
605
                    $userId,
606
                    '',
607
                    '',
608
                    '',
609
                    '',
610
                    false
611
                );
612
613
                // Getting the filename only.
614
                $file_components = explode('/', $file_path);
615
                $file = $file_components[count($file_components) - 1];
616
617
                // Store the mp3 file in the lp_item table.
618
                $sql = "UPDATE $tbl_lp_item SET
619
                          audio = '".Database::escape_string($file)."'
620
                        WHERE iid = '".intval($new_item_id)."'";
621
                Database::query($sql);*/
622
            }
623
        }
624
625
        return $new_item_id;
626
    }
627
628
    /**
629
     * Static admin function allowing addition of a learnpath to a course.
630
     *
631
     * @param string $courseCode
632
     * @param string $name
633
     * @param string $description
634
     * @param string $learnpath
635
     * @param string $origin
636
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
637
     * @param string $publicated_on
638
     * @param string $expired_on
639
     * @param int    $categoryId
640
     * @param int    $userId
641
     *
642
     * @return CLp
643
     */
644
    public static function add_lp(
645
        $courseCode,
646
        $name,
647
        $description = '',
648
        $learnpath = 'guess',
649
        $origin = 'zip',
650
        $zipname = '',
651
        $publicated_on = '',
652
        $expired_on = '',
653
        $categoryId = 0,
654
        $userId = 0
655
    ) {
656
        global $charset;
657
658
        if (!empty($courseCode)) {
659
            $courseInfo = api_get_course_info($courseCode);
660
            $course_id = $courseInfo['real_id'];
661
        } else {
662
            $course_id = api_get_course_int_id();
663
            $courseInfo = api_get_course_info();
664
        }
665
666
        $categoryId = (int) $categoryId;
667
668
        if (empty($publicated_on)) {
669
            $publicated_on = null;
670
        } else {
671
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
672
        }
673
674
        if (empty($expired_on)) {
675
            $expired_on = null;
676
        } else {
677
            $expired_on = api_get_utc_datetime($expired_on, true, true);
678
        }
679
680
        /*$check_name = "SELECT * FROM $tbl_lp
681
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
682
        $res_name = Database::query($check_name);
683
684
        while (Database::num_rows($res_name)) {
685
            // There is already one such name, update the current one a bit.
686
            $i++;
687
            $name = $name.' - '.$i;
688
            $check_name = "SELECT * FROM $tbl_lp
689
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
690
            $res_name = Database::query($check_name);
691
        }*/
692
        // New name does not exist yet; keep it.
693
        // Escape description.
694
        // Kevin: added htmlentities().
695
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
696
        $type = 1;
697
        switch ($learnpath) {
698
            case 'guess':
699
            case 'aicc':
700
                break;
701
            case 'dokeos':
702
            case 'chamilo':
703
                $type = 1;
704
                break;
705
        }
706
707
        $id = null;
708
        $sessionEntity = api_get_session_entity();
709
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
710
        $lp = null;
711
        switch ($origin) {
712
            case 'zip':
713
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
714
                break;
715
            case 'manual':
716
            default:
717
                /*$get_max = "SELECT MAX(display_order)
718
                            FROM $tbl_lp WHERE c_id = $course_id";
719
                $res_max = Database::query($get_max);
720
                if (Database::num_rows($res_max) < 1) {
721
                    $dsp = 1;
722
                } else {
723
                    $row = Database::fetch_array($res_max);
724
                    $dsp = $row[0] + 1;
725
                }*/
726
727
                $dsp = 1;
728
                $category = null;
729
                if (!empty($categoryId)) {
730
                    $category = Container::getLpCategoryRepository()->find($categoryId);
731
                }
732
733
                $lp = new CLp();
734
                $lp
735
                    ->setLpType($type)
736
                    ->setName($name)
737
                    ->setDescription($description)
738
                    ->setDisplayOrder($dsp)
739
                    ->setCategory($category)
740
                    ->setPublicatedOn($publicated_on)
741
                    ->setExpiredOn($expired_on)
742
                    ->setParent($courseEntity)
743
                    ->addCourseLink($courseEntity, $sessionEntity)
744
                ;
745
746
                $em = Database::getManager();
747
                $em->persist($lp);
748
                $em->flush();
749
                break;
750
        }
751
752
        return $lp;
753
    }
754
755
    /**
756
     * Auto completes the parents of an item in case it's been completed or passed.
757
     *
758
     * @param int $item Optional ID of the item from which to look for parents
759
     */
760
    public function autocomplete_parents($item)
761
    {
762
        $debug = $this->debug;
763
764
        if (empty($item)) {
765
            $item = $this->current;
766
        }
767
768
        $currentItem = $this->getItem($item);
769
        if ($currentItem) {
770
            $parent_id = $currentItem->get_parent();
771
            $parent = $this->getItem($parent_id);
772
            if ($parent) {
773
                // if $item points to an object and there is a parent.
774
                if ($debug) {
775
                    error_log(
776
                        'Autocompleting parent of item '.$item.' '.
777
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
778
                        0
779
                    );
780
                }
781
782
                // New experiment including failed and browsed in completed status.
783
                //$current_status = $currentItem->get_status();
784
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
785
                // Fixes chapter auto complete
786
                if (true) {
787
                    // If the current item is completed or passes or succeeded.
788
                    $updateParentStatus = true;
789
                    if ($debug) {
790
                        error_log('Status of current item is alright');
791
                    }
792
793
                    foreach ($parent->get_children() as $childItemId) {
794
                        $childItem = $this->getItem($childItemId);
795
796
                        // If children was not set try to get the info
797
                        if (empty($childItem->db_item_view_id)) {
798
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
799
                        }
800
801
                        // Check all his brothers (parent's children) for completion status.
802
                        if ($childItemId != $item) {
803
                            if ($debug) {
804
                                error_log(
805
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
806
                                    0
807
                                );
808
                            }
809
                            // Trying completing parents of failed and browsed items as well.
810
                            if ($childItem->status_is(
811
                                [
812
                                    'completed',
813
                                    'passed',
814
                                    'succeeded',
815
                                    'browsed',
816
                                    'failed',
817
                                ]
818
                            )
819
                            ) {
820
                                // Keep completion status to true.
821
                                continue;
822
                            } else {
823
                                if ($debug > 2) {
824
                                    error_log(
825
                                        '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,
826
                                        0
827
                                    );
828
                                }
829
                                $updateParentStatus = false;
830
                                break;
831
                            }
832
                        }
833
                    }
834
835
                    if ($updateParentStatus) {
836
                        // If all the children were completed:
837
                        $parent->set_status('completed');
838
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
839
                        // Force the status to "completed"
840
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
841
                        $this->update_queue[$parent->get_id()] = 'completed';
842
                        if ($debug) {
843
                            error_log(
844
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
845
                                print_r($this->update_queue, 1),
846
                                0
847
                            );
848
                        }
849
                        // Recursive call.
850
                        $this->autocomplete_parents($parent->get_id());
851
                    }
852
                }
853
            } else {
854
                if ($debug) {
855
                    error_log("Parent #$parent_id does not exists");
856
                }
857
            }
858
        } else {
859
            if ($debug) {
860
                error_log("#$item is an item that doesn't have parents");
861
            }
862
        }
863
    }
864
865
    /**
866
     * Closes the current resource.
867
     *
868
     * Stops the timer
869
     * Saves into the database if required
870
     * Clears the current resource data from this object
871
     *
872
     * @return bool True on success, false on failure
873
     */
874
    public function close()
875
    {
876
        if (empty($this->lp_id)) {
877
            $this->error = 'Trying to close this learnpath but no ID is set';
878
879
            return false;
880
        }
881
        $this->current_time_stop = time();
882
        $this->ordered_items = [];
883
        $this->index = 0;
884
        unset($this->lp_id);
885
        //unset other stuff
886
        return true;
887
    }
888
889
    /**
890
     * Static admin function allowing removal of a learnpath.
891
     *
892
     * @param array  $courseInfo
893
     * @param int    $id         Learnpath ID
894
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
895
     *
896
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
897
     */
898
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
899
    {
900
        $course_id = api_get_course_int_id();
901
        if (!empty($courseInfo)) {
902
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
903
        }
904
905
        // TODO: Implement a way of getting this to work when the current object is not set.
906
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
907
        // If an ID is specifically given and the current LP is not the same, prevent delete.
908
        if (!empty($id) && ($id != $this->lp_id)) {
909
            return false;
910
        }
911
912
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
913
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
914
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
915
916
        // Delete lp item id.
917
        foreach ($this->items as $lpItemId => $dummy) {
918
            $sql = "DELETE FROM $lp_item_view
919
                    WHERE lp_item_id = '".$lpItemId."'";
920
            Database::query($sql);
921
        }
922
923
        // Proposed by Christophe (nickname: clefevre)
924
        $sql = "DELETE FROM $lp_item
925
                WHERE lp_id = ".$this->lp_id;
926
        Database::query($sql);
927
928
        $sql = "DELETE FROM $lp_view
929
                WHERE lp_id = ".$this->lp_id;
930
        Database::query($sql);
931
932
        //self::toggleVisibility($this->lp_id, 0);
933
934
        /*if (2 == $this->type || 3 == $this->type) {
935
            // This is a scorm learning path, delete the files as well.
936
            $sql = "SELECT path FROM $lp
937
                    WHERE iid = ".$this->lp_id;
938
            $res = Database::query($sql);
939
            if (Database::num_rows($res) > 0) {
940
                $row = Database::fetch_array($res);
941
                $path = $row['path'];
942
                $sql = "SELECT iid FROM $lp
943
                        WHERE
944
                            c_id = $course_id AND
945
                            path = '$path' AND
946
                            iid != ".$this->lp_id;
947
                $res = Database::query($sql);
948
                if (Database::num_rows($res) > 0) {
949
                    // Another learning path uses this directory, so don't delete it.
950
                    if ($this->debug > 2) {
951
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
952
                    }
953
                } else {
954
                    // No other LP uses that directory, delete it.
955
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
956
                    // The absolute system path for this course.
957
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
958
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
959
                        if ($this->debug > 2) {
960
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
961
                        }
962
                        // Proposed by Christophe (clefevre).
963
                        if (0 == strcmp(substr($path, -2), "/.")) {
964
                            $path = substr($path, 0, -1); // Remove "." at the end.
965
                        }
966
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
967
                        rmdirr($course_scorm_dir.$path);
968
                    }
969
                }
970
            }
971
        }*/
972
973
        $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
974
        $sql = "DELETE FROM $table
975
                WHERE
976
                    lp_id = {$this->lp_id}";
977
        Database::query($sql);
978
979
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
980
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
981
        // Delete tools
982
        $sql = "DELETE FROM $tbl_tool
983
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
984
        Database::query($sql);*/
985
986
        $repo = Container::getLpRepository();
987
        $lp = $repo->find($this->lp_id);
988
        Database::getManager()->remove($lp);
989
        Database::getManager()->flush();
990
991
        // Updates the display order of all lps.
992
        $this->update_display_order();
993
994
        $link_info = GradebookUtils::isResourceInCourseGradebook(
995
            api_get_course_id(),
996
            4,
997
            $id,
998
            api_get_session_id()
999
        );
1000
1001
        if (false !== $link_info) {
1002
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1003
        }
1004
1005
        if ('true' === api_get_setting('search_enabled')) {
1006
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1007
        }
1008
    }
1009
1010
    /**
1011
     * Removes all the children of one item - dangerous!
1012
     *
1013
     * @param int $id Element ID of which children have to be removed
1014
     *
1015
     * @return int Total number of children removed
1016
     */
1017
    public function delete_children_items($id)
1018
    {
1019
        $course_id = $this->course_info['real_id'];
1020
1021
        $num = 0;
1022
        $id = (int) $id;
1023
        if (empty($id) || empty($course_id)) {
1024
            return false;
1025
        }
1026
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1027
        $sql = "SELECT * FROM $lp_item
1028
                WHERE parent_item_id = $id";
1029
        $res = Database::query($sql);
1030
        while ($row = Database::fetch_array($res)) {
1031
            $num += $this->delete_children_items($row['iid']);
1032
            $sql = "DELETE FROM $lp_item
1033
                    WHERE iid = ".$row['iid'];
1034
            Database::query($sql);
1035
            $num++;
1036
        }
1037
1038
        return $num;
1039
    }
1040
1041
    /**
1042
     * Removes an item from the current learnpath.
1043
     *
1044
     * @param int $id Elem ID (0 if first)
1045
     *
1046
     * @return int Number of elements moved
1047
     *
1048
     * @todo implement resource removal
1049
     */
1050
    public function delete_item($id)
1051
    {
1052
        $course_id = api_get_course_int_id();
1053
        $id = (int) $id;
1054
        // TODO: Implement the resource removal.
1055
        if (empty($id) || empty($course_id)) {
1056
            return false;
1057
        }
1058
        // First select item to get previous, next, and display order.
1059
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1060
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1061
        $res_sel = Database::query($sql_sel);
1062
        if (Database::num_rows($res_sel) < 1) {
1063
            return false;
1064
        }
1065
        $row = Database::fetch_array($res_sel);
1066
        $previous = $row['previous_item_id'];
1067
        $next = $row['next_item_id'];
1068
        $display = $row['display_order'];
1069
        $parent = $row['parent_item_id'];
1070
        $lp = $row['lp_id'];
1071
        // Delete children items.
1072
        $this->delete_children_items($id);
1073
        // Now delete the item.
1074
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1075
        Database::query($sql_del);
1076
        // Now update surrounding items.
1077
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1078
                    WHERE iid = $previous";
1079
        Database::query($sql_upd);
1080
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1081
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1082
        Database::query($sql_upd);
1083
1084
        $parentCondition = ' AND parent_item_id IS NULL ';
1085
        if (!empty($parent)) {
1086
            $parent = (int) $parent;
1087
            $parentCondition = " AND parent_item_id = $parent  ";
1088
        }
1089
1090
        // Now update all following items with new display order.
1091
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1092
                    WHERE
1093
                        lp_id = $lp AND
1094
                        display_order > $display
1095
                        $parentCondition
1096
                    ";
1097
        Database::query($sql_all);
1098
1099
        //Removing prerequisites since the item will not longer exist
1100
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1101
                    WHERE prerequisite = '$id'";
1102
        Database::query($sql_all);
1103
1104
        $sql = "UPDATE $lp_item
1105
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1106
                WHERE lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1107
        Database::query($sql);
1108
1109
        // Remove from search engine if enabled.
1110
        if ('true' === api_get_setting('search_enabled')) {
1111
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1112
            $sql = 'SELECT * FROM %s
1113
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1114
                    LIMIT 1';
1115
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1116
            $res = Database::query($sql);
1117
            if (Database::num_rows($res) > 0) {
1118
                $row2 = Database::fetch_array($res);
1119
                $di = new ChamiloIndexer();
1120
                $di->remove_document($row2['search_did']);
1121
            }
1122
            $sql = 'DELETE FROM %s
1123
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1124
                    LIMIT 1';
1125
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1126
            Database::query($sql);
1127
        }
1128
    }
1129
1130
    /**
1131
     * Updates an item's content in place.
1132
     *
1133
     * @param int    $id               Element ID
1134
     * @param int    $parent           Parent item ID
1135
     * @param int    $previous         Previous item ID
1136
     * @param string $title            Item title
1137
     * @param string $description      Item description
1138
     * @param string $prerequisites    Prerequisites (optional)
1139
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1140
     * @param int    $max_time_allowed
1141
     * @param string $url
1142
     *
1143
     * @return bool True on success, false on error
1144
     */
1145
    public function edit_item(
1146
        $id,
1147
        $parent,
1148
        $previous,
1149
        $title,
1150
        $description,
1151
        $prerequisites = '0',
1152
        $audio = [],
1153
        $max_time_allowed = 0,
1154
        $url = ''
1155
    ) {
1156
        $course_id = api_get_course_int_id();
1157
        $_course = api_get_course_info();
1158
        $id = (int) $id;
1159
1160
        if (empty($max_time_allowed)) {
1161
            $max_time_allowed = 0;
1162
        }
1163
1164
        if (empty($id) || empty($_course)) {
1165
            return false;
1166
        }
1167
1168
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1169
        $sql = "SELECT * FROM $tbl_lp_item
1170
                WHERE iid = $id";
1171
        $res_select = Database::query($sql);
1172
        $row_select = Database::fetch_array($res_select);
1173
        $audio_update_sql = '';
1174
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1175
            // Create the audio folder if it does not exist yet.
1176
            //$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1177
            if (!is_dir($filepath.'audio')) {
1178
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1179
                $audio_id = DocumentManager::addDocument(
1180
                    $_course,
1181
                    '/audio',
1182
                    'folder',
1183
                    0,
1184
                    'audio'
1185
                );
1186
            }
1187
1188
            // Upload file in documents.
1189
            $pi = pathinfo($audio['name']);
1190
            if ('mp3' === $pi['extension']) {
1191
                $c_det = api_get_course_info($this->cc);
1192
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1193
                $path = handle_uploaded_document(
1194
                    $c_det,
1195
                    $audio,
1196
                    $bp,
1197
                    '/audio',
1198
                    api_get_user_id(),
1199
                    0,
1200
                    null,
1201
                    0,
1202
                    'rename',
1203
                    false,
1204
                    0
1205
                );
1206
                $path = substr($path, 7);
1207
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1208
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1209
            }
1210
        }
1211
1212
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1213
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1214
1215
        // TODO: htmlspecialchars to be checked for encoding related problems.
1216
        if ($same_parent && $same_previous) {
1217
            // Only update title and description.
1218
            $sql = "UPDATE $tbl_lp_item
1219
                    SET title = '".Database::escape_string($title)."',
1220
                        prerequisite = '".$prerequisites."',
1221
                        description = '".Database::escape_string($description)."'
1222
                        ".$audio_update_sql.",
1223
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1224
                    WHERE iid = $id";
1225
            Database::query($sql);
1226
        } else {
1227
            $old_parent = $row_select['parent_item_id'];
1228
            $old_previous = $row_select['previous_item_id'];
1229
            $old_next = $row_select['next_item_id'];
1230
            $old_order = $row_select['display_order'];
1231
            $old_prerequisite = $row_select['prerequisite'];
1232
            $old_max_time_allowed = $row_select['max_time_allowed'];
1233
1234
            /* BEGIN -- virtually remove the current item id */
1235
            /* for the next and previous item it is like the current item doesn't exist anymore */
1236
            if (0 != $old_previous) {
1237
                // Next
1238
                $sql = "UPDATE $tbl_lp_item
1239
                        SET next_item_id = $old_next
1240
                        WHERE iid = $old_previous";
1241
                Database::query($sql);
1242
            }
1243
1244
            if (!empty($old_next)) {
1245
                // Previous
1246
                $sql = "UPDATE $tbl_lp_item
1247
                        SET previous_item_id = $old_previous
1248
                        WHERE iid = $old_next";
1249
                Database::query($sql);
1250
            }
1251
1252
            // display_order - 1 for every item with a display_order
1253
            // bigger then the display_order of the current item.
1254
            $sql = "UPDATE $tbl_lp_item
1255
                    SET display_order = display_order - 1
1256
                    WHERE
1257
                        c_id = $course_id AND
1258
                        display_order > $old_order AND
1259
                        lp_id = ".$this->lp_id." AND
1260
                        parent_item_id = $old_parent";
1261
            Database::query($sql);
1262
            /* END -- virtually remove the current item id */
1263
1264
            /* BEGIN -- update the current item id to his new location */
1265
            if (0 == $previous) {
1266
                // Select the data of the item that should come after the current item.
1267
                $sql = "SELECT id, display_order
1268
                        FROM $tbl_lp_item
1269
                        WHERE
1270
                            c_id = $course_id AND
1271
                            lp_id = ".$this->lp_id." AND
1272
                            parent_item_id = $parent AND
1273
                            previous_item_id = $previous";
1274
                $res_select_old = Database::query($sql);
1275
                $row_select_old = Database::fetch_array($res_select_old);
1276
1277
                // If the new parent didn't have children before.
1278
                if (0 == Database::num_rows($res_select_old)) {
1279
                    $new_next = 0;
1280
                    $new_order = 1;
1281
                } else {
1282
                    $new_next = $row_select_old['id'];
1283
                    $new_order = $row_select_old['display_order'];
1284
                }
1285
            } else {
1286
                // Select the data of the item that should come before the current item.
1287
                $sql = "SELECT next_item_id, display_order
1288
                        FROM $tbl_lp_item
1289
                        WHERE iid = $previous";
1290
                $res_select_old = Database::query($sql);
1291
                $row_select_old = Database::fetch_array($res_select_old);
1292
                $new_next = $row_select_old['next_item_id'];
1293
                $new_order = $row_select_old['display_order'] + 1;
1294
            }
1295
1296
            // TODO: htmlspecialchars to be checked for encoding related problems.
1297
            // Update the current item with the new data.
1298
            $sql = "UPDATE $tbl_lp_item
1299
                    SET
1300
                        title = '".Database::escape_string($title)."',
1301
                        description = '".Database::escape_string($description)."',
1302
                        parent_item_id = $parent,
1303
                        previous_item_id = $previous,
1304
                        next_item_id = $new_next,
1305
                        display_order = $new_order
1306
                        $audio_update_sql
1307
                    WHERE iid = $id";
1308
            Database::query($sql);
1309
1310
            if (0 != $previous) {
1311
                // Update the previous item's next_item_id.
1312
                $sql = "UPDATE $tbl_lp_item
1313
                        SET next_item_id = $id
1314
                        WHERE iid = $previous";
1315
                Database::query($sql);
1316
            }
1317
1318
            if (!empty($new_next)) {
1319
                // Update the next item's previous_item_id.
1320
                $sql = "UPDATE $tbl_lp_item
1321
                        SET previous_item_id = $id
1322
                        WHERE iid = $new_next";
1323
                Database::query($sql);
1324
            }
1325
1326
            if ($old_prerequisite != $prerequisites) {
1327
                $sql = "UPDATE $tbl_lp_item
1328
                        SET prerequisite = '$prerequisites'
1329
                        WHERE iid = $id";
1330
                Database::query($sql);
1331
            }
1332
1333
            if ($old_max_time_allowed != $max_time_allowed) {
1334
                // update max time allowed
1335
                $sql = "UPDATE $tbl_lp_item
1336
                        SET max_time_allowed = $max_time_allowed
1337
                        WHERE iid = $id";
1338
                Database::query($sql);
1339
            }
1340
1341
            // Update all the items with the same or a bigger display_order than the current item.
1342
            $sql = "UPDATE $tbl_lp_item
1343
                    SET display_order = display_order + 1
1344
                    WHERE
1345
                       c_id = $course_id AND
1346
                       lp_id = ".$this->get_id()." AND
1347
                       iid <> $id AND
1348
                       parent_item_id = $parent AND
1349
                       display_order >= $new_order";
1350
            Database::query($sql);
1351
        }
1352
1353
        if ('link' === $row_select['item_type']) {
1354
            $link = new Link();
1355
            $linkId = $row_select['path'];
1356
            $link->updateLink($linkId, $url);
1357
        }
1358
    }
1359
1360
    /**
1361
     * Updates an item's prereq in place.
1362
     *
1363
     * @param int    $id              Element ID
1364
     * @param string $prerequisite_id Prerequisite Element ID
1365
     * @param int    $minScore        Prerequisite min score
1366
     * @param int    $maxScore        Prerequisite max score
1367
     *
1368
     * @return bool True on success, false on error
1369
     */
1370
    public function edit_item_prereq($id, $prerequisite_id, $minScore = 0, $maxScore = 100)
1371
    {
1372
        $id = (int) $id;
1373
1374
        if (empty($id)) {
1375
            return false;
1376
        }
1377
        $prerequisite_id = (int) $prerequisite_id;
1378
1379
        if (empty($minScore) || $minScore < 0) {
1380
            $minScore = 0;
1381
        }
1382
1383
        if (empty($maxScore) || $maxScore < 0) {
1384
            $maxScore = 100;
1385
        }
1386
1387
        $minScore = (float) $minScore;
1388
        $maxScore = (float) $maxScore;
1389
1390
        if (empty($prerequisite_id)) {
1391
            $prerequisite_id = 'NULL';
1392
            $minScore = 0;
1393
            $maxScore = 100;
1394
        }
1395
1396
        $table = Database::get_course_table(TABLE_LP_ITEM);
1397
        $sql = " UPDATE $table
1398
                 SET
1399
                    prerequisite = $prerequisite_id ,
1400
                    prerequisite_min_score = $minScore ,
1401
                    prerequisite_max_score = $maxScore
1402
                 WHERE iid = $id";
1403
        Database::query($sql);
1404
1405
        return true;
1406
    }
1407
1408
    /**
1409
     * Get the specific prefix index terms of this learning path.
1410
     *
1411
     * @param string $prefix
1412
     *
1413
     * @return array Array of terms
1414
     */
1415
    public function get_common_index_terms_by_prefix($prefix)
1416
    {
1417
        $terms = get_specific_field_values_list_by_prefix(
1418
            $prefix,
1419
            $this->cc,
1420
            TOOL_LEARNPATH,
1421
            $this->lp_id
1422
        );
1423
        $prefix_terms = [];
1424
        if (!empty($terms)) {
1425
            foreach ($terms as $term) {
1426
                $prefix_terms[] = $term['value'];
1427
            }
1428
        }
1429
1430
        return $prefix_terms;
1431
    }
1432
1433
    /**
1434
     * Gets the number of items currently completed.
1435
     *
1436
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1437
     *
1438
     * @return int The number of items currently completed
1439
     */
1440
    public function get_complete_items_count($failedStatusException = false)
1441
    {
1442
        $i = 0;
1443
        $completedStatusList = [
1444
            'completed',
1445
            'passed',
1446
            'succeeded',
1447
            'browsed',
1448
        ];
1449
1450
        if (!$failedStatusException) {
1451
            $completedStatusList[] = 'failed';
1452
        }
1453
1454
        foreach ($this->items as $id => $dummy) {
1455
            // Trying failed and browsed considered "progressed" as well.
1456
            if ($this->items[$id]->status_is($completedStatusList) &&
1457
                'dir' !== $this->items[$id]->get_type()
1458
            ) {
1459
                $i++;
1460
            }
1461
        }
1462
1463
        return $i;
1464
    }
1465
1466
    /**
1467
     * Gets the current item ID.
1468
     *
1469
     * @return int The current learnpath item id
1470
     */
1471
    public function get_current_item_id()
1472
    {
1473
        $current = 0;
1474
        if (!empty($this->current)) {
1475
            $current = (int) $this->current;
1476
        }
1477
1478
        return $current;
1479
    }
1480
1481
    /**
1482
     * Force to get the first learnpath item id.
1483
     *
1484
     * @return int The current learnpath item id
1485
     */
1486
    public function get_first_item_id()
1487
    {
1488
        $current = 0;
1489
        if (is_array($this->ordered_items)) {
1490
            $current = $this->ordered_items[0];
1491
        }
1492
1493
        return $current;
1494
    }
1495
1496
    /**
1497
     * Gets the total number of items available for viewing in this SCORM.
1498
     *
1499
     * @return int The total number of items
1500
     */
1501
    public function get_total_items_count()
1502
    {
1503
        return count($this->items);
1504
    }
1505
1506
    /**
1507
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1508
     *
1509
     * @return int The total no-chapters number of items
1510
     */
1511
    public function getTotalItemsCountWithoutDirs()
1512
    {
1513
        $total = 0;
1514
        $typeListNotToCount = self::getChapterTypes();
1515
        foreach ($this->items as $temp2) {
1516
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1517
                $total++;
1518
            }
1519
        }
1520
1521
        return $total;
1522
    }
1523
1524
    /**
1525
     *  Sets the first element URL.
1526
     */
1527
    public function first()
1528
    {
1529
        if ($this->debug > 0) {
1530
            error_log('In learnpath::first()', 0);
1531
            error_log('$this->last_item_seen '.$this->last_item_seen);
1532
        }
1533
1534
        // Test if the last_item_seen exists and is not a dir.
1535
        if (0 == count($this->ordered_items)) {
1536
            $this->index = 0;
1537
        }
1538
1539
        if (!empty($this->last_item_seen) &&
1540
            !empty($this->items[$this->last_item_seen]) &&
1541
            'dir' != $this->items[$this->last_item_seen]->get_type()
1542
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1543
            //&& !$this->items[$this->last_item_seen]->is_done()
1544
        ) {
1545
            if ($this->debug > 2) {
1546
                error_log(
1547
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1548
                    $this->items[$this->last_item_seen]->get_type()
1549
                );
1550
            }
1551
            $index = -1;
1552
            foreach ($this->ordered_items as $myindex => $item_id) {
1553
                if ($item_id == $this->last_item_seen) {
1554
                    $index = $myindex;
1555
                    break;
1556
                }
1557
            }
1558
            if (-1 == $index) {
1559
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1560
                if ($this->debug > 2) {
1561
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1562
                }
1563
1564
                return false;
1565
            } else {
1566
                $this->last = $this->last_item_seen;
1567
                $this->current = $this->last_item_seen;
1568
                $this->index = $index;
1569
            }
1570
        } else {
1571
            if ($this->debug > 2) {
1572
                error_log('In learnpath::first() - No last item seen', 0);
1573
            }
1574
            $index = 0;
1575
            // Loop through all ordered items and stop at the first item that is
1576
            // not a directory *and* that has not been completed yet.
1577
            while (!empty($this->ordered_items[$index]) &&
1578
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1579
                (
1580
                    'dir' === $this->items[$this->ordered_items[$index]]->get_type() ||
1581
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1582
                ) && $index < $this->max_ordered_items) {
1583
                $index++;
1584
            }
1585
1586
            $this->last = $this->current;
1587
            // current is
1588
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1589
            $this->index = $index;
1590
            if ($this->debug > 2) {
1591
                error_log('$index '.$index);
1592
                error_log('In learnpath::first() - No last item seen');
1593
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1594
            }
1595
        }
1596
        if ($this->debug > 2) {
1597
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1598
        }
1599
    }
1600
1601
    /**
1602
     * Gets the js library from the database.
1603
     *
1604
     * @return string The name of the javascript library to be used
1605
     */
1606
    public function get_js_lib()
1607
    {
1608
        $lib = '';
1609
        if (!empty($this->js_lib)) {
1610
            $lib = $this->js_lib;
1611
        }
1612
1613
        return $lib;
1614
    }
1615
1616
    /**
1617
     * Gets the learnpath database ID.
1618
     *
1619
     * @return int Learnpath ID in the lp table
1620
     */
1621
    public function get_id()
1622
    {
1623
        if (!empty($this->lp_id)) {
1624
            return (int) $this->lp_id;
1625
        }
1626
1627
        return 0;
1628
    }
1629
1630
    /**
1631
     * Gets the last element URL.
1632
     *
1633
     * @return string URL to load into the viewer
1634
     */
1635
    public function get_last()
1636
    {
1637
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1638
        if (count($this->ordered_items) > 0) {
1639
            $this->index = count($this->ordered_items) - 1;
1640
1641
            return $this->ordered_items[$this->index];
1642
        }
1643
1644
        return false;
1645
    }
1646
1647
    /**
1648
     * Get the last element in the first level.
1649
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1650
     *
1651
     * @return mixed
1652
     */
1653
    public function getLastInFirstLevel()
1654
    {
1655
        try {
1656
            $lastId = Database::getManager()
1657
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1658
                WHERE i.lpId = :lp AND i.parent IS NULL AND i.itemType != :type ORDER BY i.displayOrder DESC')
1659
                ->setMaxResults(1)
1660
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1661
                ->getSingleScalarResult();
1662
1663
            return $lastId;
1664
        } catch (Exception $exception) {
1665
            return 0;
1666
        }
1667
    }
1668
1669
    /**
1670
     * Gets the navigation bar for the learnpath display screen.
1671
     *
1672
     * @param string $barId
1673
     *
1674
     * @return string The HTML string to use as a navigation bar
1675
     */
1676
    public function get_navigation_bar($barId = '')
1677
    {
1678
        if (empty($barId)) {
1679
            $barId = 'control-top';
1680
        }
1681
        $lpId = $this->lp_id;
1682
        $mycurrentitemid = $this->get_current_item_id();
1683
        $reportingText = get_lang('Reporting');
1684
        $previousText = get_lang('Previous');
1685
        $nextText = get_lang('Next');
1686
        $fullScreenText = get_lang('Back to normal screen');
1687
1688
        $settings = api_get_configuration_value('lp_view_settings');
1689
        $display = isset($settings['display']) ? $settings['display'] : false;
1690
        $reportingIcon = '
1691
            <a class="icon-toolbar"
1692
                id="stats_link"
1693
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1694
                onclick="window.parent.API.save_asset(); return true;"
1695
                target="content_name" title="'.$reportingText.'">
1696
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1697
            </a>';
1698
1699
        if (!empty($display)) {
1700
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1701
            if (false === $showReporting) {
1702
                $reportingIcon = '';
1703
            }
1704
        }
1705
1706
        $hideArrows = false;
1707
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1708
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1709
        }
1710
1711
        $previousIcon = '';
1712
        $nextIcon = '';
1713
        if (false === $hideArrows) {
1714
            $previousIcon = '
1715
                <a class="icon-toolbar" id="scorm-previous" href="#"
1716
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1717
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1718
                </a>';
1719
1720
            $nextIcon = '
1721
                <a class="icon-toolbar" id="scorm-next" href="#"
1722
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1723
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1724
                </a>';
1725
        }
1726
1727
        if ('fullscreen' === $this->mode) {
1728
            $navbar = '
1729
                  <span id="'.$barId.'" class="buttons">
1730
                    '.$reportingIcon.'
1731
                    '.$previousIcon.'
1732
                    '.$nextIcon.'
1733
                    <a class="icon-toolbar" id="view-embedded"
1734
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1735
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1736
                    </a>
1737
                  </span>';
1738
        } else {
1739
            $navbar = '
1740
                 <span id="'.$barId.'" class="buttons text-right">
1741
                    '.$reportingIcon.'
1742
                    '.$previousIcon.'
1743
                    '.$nextIcon.'
1744
                </span>';
1745
        }
1746
1747
        return $navbar;
1748
    }
1749
1750
    /**
1751
     * Gets the next resource in queue (url).
1752
     *
1753
     * @return string URL to load into the viewer
1754
     */
1755
    public function get_next_index()
1756
    {
1757
        // TODO
1758
        $index = $this->index;
1759
        $index++;
1760
        while (
1761
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1762
            $index < $this->max_ordered_items
1763
        ) {
1764
            $index++;
1765
            if ($index == $this->max_ordered_items) {
1766
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1767
                    return $this->index;
1768
                }
1769
1770
                return $index;
1771
            }
1772
        }
1773
        if (empty($this->ordered_items[$index])) {
1774
            return $this->index;
1775
        }
1776
1777
        return $index;
1778
    }
1779
1780
    /**
1781
     * Gets item_id for the next element.
1782
     *
1783
     * @return int Next item (DB) ID
1784
     */
1785
    public function get_next_item_id()
1786
    {
1787
        $new_index = $this->get_next_index();
1788
        if (!empty($new_index)) {
1789
            if (isset($this->ordered_items[$new_index])) {
1790
                return $this->ordered_items[$new_index];
1791
            }
1792
        }
1793
1794
        return 0;
1795
    }
1796
1797
    /**
1798
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1799
     *
1800
     * Generally, the package provided is in the form of a zip file, so the function
1801
     * has been written to test a zip file. If not a zip, the function will return the
1802
     * default return value: ''
1803
     *
1804
     * @param string $file_path the path to the file
1805
     * @param string $file_name the original name of the file
1806
     *
1807
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package'
1808
     *                if the package is empty, or '' if the package cannot be recognized
1809
     */
1810
    public static function getPackageType($file_path, $file_name)
1811
    {
1812
        // Get name of the zip file without the extension.
1813
        $file_info = pathinfo($file_name);
1814
        $extension = $file_info['extension']; // Extension only.
1815
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1816
                'dll',
1817
                'exe',
1818
            ])) {
1819
            return 'oogie';
1820
        }
1821
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1822
                'dll',
1823
                'exe',
1824
            ])) {
1825
            return 'woogie';
1826
        }
1827
1828
        $zipFile = new PclZip($file_path);
1829
        // Check the zip content (real size and file extension).
1830
        $zipContentArray = $zipFile->listContent();
1831
        $package_type = '';
1832
        $manifest = '';
1833
        $aicc_match_crs = 0;
1834
        $aicc_match_au = 0;
1835
        $aicc_match_des = 0;
1836
        $aicc_match_cst = 0;
1837
        $countItems = 0;
1838
1839
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1840
        if (is_array($zipContentArray)) {
1841
            $countItems = count($zipContentArray);
1842
            if ($countItems > 0) {
1843
                foreach ($zipContentArray as $thisContent) {
1844
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1845
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1846
                    } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1847
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1848
                        $package_type = 'scorm';
1849
                        break; // Exit the foreach loop.
1850
                    } elseif (
1851
                        preg_match('/aicc\//i', $thisContent['filename']) ||
1852
                        in_array(
1853
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1854
                            ['crs', 'au', 'des', 'cst']
1855
                        )
1856
                    ) {
1857
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1858
                        switch ($ext) {
1859
                            case 'crs':
1860
                                $aicc_match_crs = 1;
1861
                                break;
1862
                            case 'au':
1863
                                $aicc_match_au = 1;
1864
                                break;
1865
                            case 'des':
1866
                                $aicc_match_des = 1;
1867
                                break;
1868
                            case 'cst':
1869
                                $aicc_match_cst = 1;
1870
                                break;
1871
                            default:
1872
                                break;
1873
                        }
1874
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1875
                    } else {
1876
                        $package_type = '';
1877
                    }
1878
                }
1879
            }
1880
        }
1881
1882
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1883
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1884
            $package_type = 'aicc';
1885
        }
1886
1887
        // Try with chamilo course builder
1888
        if (empty($package_type)) {
1889
            // Sometimes users will try to upload an empty zip, or a zip with
1890
            // only a folder. Catch that and make the calling function aware.
1891
            // If the single file was the imsmanifest.xml, then $package_type
1892
            // would be 'scorm' and we wouldn't be here.
1893
            if ($countItems < 2) {
1894
                return 'error-empty-package';
1895
            }
1896
            $package_type = 'chamilo';
1897
        }
1898
1899
        return $package_type;
1900
    }
1901
1902
    /**
1903
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1904
     *
1905
     * @return string URL to load into the viewer
1906
     */
1907
    public function get_previous_index()
1908
    {
1909
        $index = $this->index;
1910
        if (isset($this->ordered_items[$index - 1])) {
1911
            $index--;
1912
            while (isset($this->ordered_items[$index]) &&
1913
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1914
            ) {
1915
                $index--;
1916
                if ($index < 0) {
1917
                    return $this->index;
1918
                }
1919
            }
1920
        }
1921
1922
        return $index;
1923
    }
1924
1925
    /**
1926
     * Gets item_id for the next element.
1927
     *
1928
     * @return int Previous item (DB) ID
1929
     */
1930
    public function get_previous_item_id()
1931
    {
1932
        $index = $this->get_previous_index();
1933
1934
        return $this->ordered_items[$index];
1935
    }
1936
1937
    /**
1938
     * Returns the HTML necessary to print a mediaplayer block inside a page.
1939
     *
1940
     * @param int    $lpItemId
1941
     * @param string $autostart
1942
     *
1943
     * @return string The mediaplayer HTML
1944
     */
1945
    public function get_mediaplayer($lpItemId, $autostart = 'true')
1946
    {
1947
        $course_id = api_get_course_int_id();
1948
        $courseInfo = api_get_course_info();
1949
        $lpItemId = (int) $lpItemId;
1950
1951
        if (empty($courseInfo) || empty($lpItemId)) {
1952
            return '';
1953
        }
1954
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
1955
1956
        if (empty($item)) {
1957
            return '';
1958
        }
1959
1960
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1961
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1962
        $itemViewId = (int) $item->db_item_view_id;
1963
1964
        // Getting all the information about the item.
1965
        $sql = "SELECT lp_view.status
1966
                FROM $tbl_lp_item as lpi
1967
                INNER JOIN $tbl_lp_item_view as lp_view
1968
                ON (lpi.iid = lp_view.lp_item_id)
1969
                WHERE
1970
                    lp_view.iid = $itemViewId AND
1971
                    lpi.iid = $lpItemId
1972
                ";
1973
        $result = Database::query($sql);
1974
        $row = Database::fetch_assoc($result);
1975
        $output = '';
1976
        $audio = $item->audio;
1977
1978
        if (!empty($audio)) {
1979
            $list = $_SESSION['oLP']->get_toc();
1980
1981
            switch ($item->get_type()) {
1982
                case 'quiz':
1983
                    $type_quiz = false;
1984
                    foreach ($list as $toc) {
1985
                        if ($toc['id'] == $_SESSION['oLP']->current) {
1986
                            $type_quiz = true;
1987
                        }
1988
                    }
1989
1990
                    if ($type_quiz) {
1991
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
1992
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
1993
                        } else {
1994
                            $autostart_audio = $autostart;
1995
                        }
1996
                    }
1997
                    break;
1998
                case TOOL_READOUT_TEXT:
1999
                    $autostart_audio = 'false';
2000
                    break;
2001
                default:
2002
                    $autostart_audio = 'true';
2003
            }
2004
2005
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2006
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2007
2008
            $player = Display::getMediaPlayer(
2009
                $file,
2010
                [
2011
                    'id' => 'lp_audio_media_player',
2012
                    'url' => $url,
2013
                    'autoplay' => $autostart_audio,
2014
                    'width' => '100%',
2015
                ]
2016
            );
2017
2018
            // The mp3 player.
2019
            $output = '<div id="container">';
2020
            $output .= $player;
2021
            $output .= '</div>';
2022
        }
2023
2024
        return $output;
2025
    }
2026
2027
    /**
2028
     * @param int    $studentId
2029
     * @param int    $prerequisite
2030
     * @param Course $course
2031
     * @param int    $sessionId
2032
     *
2033
     * @return bool
2034
     */
2035
    public static function isBlockedByPrerequisite(
2036
        $studentId,
2037
        $prerequisite,
2038
        Course $course,
2039
        $sessionId
2040
    ) {
2041
        if (null === $course) {
2042
            return false;
2043
        }
2044
2045
        $courseId = $course->getId();
2046
2047
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2048
        if ($allow) {
2049
            if (api_is_allowed_to_edit() ||
2050
                api_is_platform_admin(true) ||
2051
                api_is_drh() ||
2052
                api_is_coach($sessionId, $courseId, false)
2053
            ) {
2054
                return false;
2055
            }
2056
        }
2057
2058
        $isBlocked = false;
2059
        if (!empty($prerequisite)) {
2060
            $progress = self::getProgress(
2061
                $prerequisite,
2062
                $studentId,
2063
                $courseId,
2064
                $sessionId
2065
            );
2066
            if ($progress < 100) {
2067
                $isBlocked = true;
2068
            }
2069
2070
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2071
                // Block if it does not exceed minimum time
2072
                // Minimum time (in minutes) to pass the learning path
2073
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2074
2075
                if ($accumulateWorkTime > 0) {
2076
                    // Total time in course (sum of times in learning paths from course)
2077
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2078
2079
                    // Connect with the plugin_licences_course_session table
2080
                    // which indicates what percentage of the time applies
2081
                    // Minimum connection percentage
2082
                    $perc = 100;
2083
                    // Time from the course
2084
                    $tc = $accumulateWorkTimeTotal;
2085
2086
                    // Percentage of the learning paths
2087
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2088
                    // Minimum time for each learning path
2089
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2090
2091
                    // Spent time (in seconds) so far in the learning path
2092
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2093
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2094
2095
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2096
                        $isBlocked = true;
2097
                    }
2098
                }
2099
            }
2100
        }
2101
2102
        return $isBlocked;
2103
    }
2104
2105
    /**
2106
     * Checks if the learning path is visible for student after the progress
2107
     * of its prerequisite is completed, considering the time availability and
2108
     * the LP visibility.
2109
     */
2110
    public static function is_lp_visible_for_student(CLp $lp, $student_id, Course $course, \Chamilo\CoreBundle\Entity\Session $session = null): bool
2111
    {
2112
        if (null === $course) {
2113
            return false;
2114
        }
2115
2116
        $sessionId = $session ? $session->getId() : 0;
2117
        $courseId = $course->getId();
2118
2119
        /*$itemInfo = api_get_item_property_info(
2120
            $courseId,
2121
            TOOL_LEARNPATH,
2122
            $lp_id,
2123
            $sessionId
2124
        );*/
2125
2126
        $visibility = $lp->isVisible($course, $session);
2127
2128
        // If the item was deleted.
2129
        if (false === $visibility) {
2130
            return false;
2131
        }
2132
2133
        $now = time();
2134
        if ($lp->hasCategory()) {
2135
            $category = $lp->getCategory();
2136
2137
            if (false === self::categoryIsVisibleForStudent(
2138
                    $category,
2139
                    api_get_user_entity($student_id),
2140
                    $course,
2141
                    $session
2142
                )) {
2143
                return false;
2144
            }
2145
2146
            $prerequisite = $lp->getPrerequisite();
2147
            $is_visible = true;
2148
2149
            $isBlocked = self::isBlockedByPrerequisite(
2150
                $student_id,
2151
                $prerequisite,
2152
                $course,
2153
                $sessionId
2154
            );
2155
2156
            if ($isBlocked) {
2157
                $is_visible = false;
2158
            }
2159
2160
            // Also check the time availability of the LP
2161
            if ($is_visible) {
2162
                // Adding visibility restrictions
2163
                if (null !== $lp->getPublicatedOn()) {
2164
                    if ($now < $lp->getPublicatedOn()->getTimestamp()) {
2165
                        $is_visible = false;
2166
                    }
2167
                }
2168
                // Blocking empty start times see BT#2800
2169
                global $_custom;
2170
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2171
                    $_custom['lps_hidden_when_no_start_date']
2172
                ) {
2173
                    if (null !== $lp->getPublicatedOn()) {
2174
                        $is_visible = false;
2175
                    }
2176
                }
2177
2178
                if (null !== $lp->getExpiredOn()) {
2179
                    if ($now > $lp->getExpiredOn()->getTimestamp()) {
2180
                        $is_visible = false;
2181
                    }
2182
                }
2183
            }
2184
2185
            if ($is_visible) {
2186
                $subscriptionSettings = self::getSubscriptionSettings();
2187
2188
                // Check if the subscription users/group to a LP is ON
2189
                if (1 == $lp->getSubscribeUsers() &&
2190
                    true === $subscriptionSettings['allow_add_users_to_lp']
2191
                ) {
2192
                    // Try group
2193
                    $is_visible = false;
2194
                    // Checking only the user visibility
2195
                    // @todo fix visibility
2196
                    $userVisibility = 1;
2197
                    if (1 == $userVisibility) {
2198
                        $is_visible = true;
2199
                    } else {
2200
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2201
                        if (!empty($userGroups)) {
2202
                            foreach ($userGroups as $groupInfo) {
2203
                                $groupId = $groupInfo['iid'];
2204
                                // @todo fix visibility.
2205
                                $userVisibility = 1;
2206
                                if (1 == $userVisibility) {
2207
                                    $is_visible = true;
2208
                                    break;
2209
                                }
2210
                            }
2211
                        }
2212
                    }
2213
                }
2214
            }
2215
2216
            return $is_visible;
2217
        }
2218
2219
        return true;
2220
    }
2221
2222
    /**
2223
     * @param int $lpId
2224
     * @param int $userId
2225
     * @param int $courseId
2226
     * @param int $sessionId
2227
     *
2228
     * @return int
2229
     */
2230
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2231
    {
2232
        $lpId = (int) $lpId;
2233
        $userId = (int) $userId;
2234
        $courseId = (int) $courseId;
2235
        $sessionId = (int) $sessionId;
2236
2237
        $sessionCondition = api_get_session_condition($sessionId);
2238
        $table = Database::get_course_table(TABLE_LP_VIEW);
2239
        $sql = "SELECT progress FROM $table
2240
                WHERE
2241
                    c_id = $courseId AND
2242
                    lp_id = $lpId AND
2243
                    user_id = $userId $sessionCondition ";
2244
        $res = Database::query($sql);
2245
2246
        $progress = 0;
2247
        if (Database::num_rows($res) > 0) {
2248
            $row = Database::fetch_array($res);
2249
            $progress = (int) $row['progress'];
2250
        }
2251
2252
        return $progress;
2253
    }
2254
2255
    /**
2256
     * @param array $lpList
2257
     * @param int   $userId
2258
     * @param int   $courseId
2259
     * @param int   $sessionId
2260
     *
2261
     * @return array
2262
     */
2263
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2264
    {
2265
        $lpList = array_map('intval', $lpList);
2266
        if (empty($lpList)) {
2267
            return [];
2268
        }
2269
2270
        $lpList = implode("','", $lpList);
2271
2272
        $userId = (int) $userId;
2273
        $courseId = (int) $courseId;
2274
        $sessionId = (int) $sessionId;
2275
2276
        $sessionCondition = api_get_session_condition($sessionId);
2277
        $table = Database::get_course_table(TABLE_LP_VIEW);
2278
        $sql = "SELECT lp_id, progress FROM $table
2279
                WHERE
2280
                    c_id = $courseId AND
2281
                    lp_id IN ('".$lpList."') AND
2282
                    user_id = $userId $sessionCondition ";
2283
        $res = Database::query($sql);
2284
2285
        if (Database::num_rows($res) > 0) {
2286
            $list = [];
2287
            while ($row = Database::fetch_array($res)) {
2288
                $list[$row['lp_id']] = $row['progress'];
2289
            }
2290
2291
            return $list;
2292
        }
2293
2294
        return [];
2295
    }
2296
2297
    /**
2298
     * Displays a progress bar
2299
     * completed so far.
2300
     *
2301
     * @param int    $percentage Progress value to display
2302
     * @param string $text_add   Text to display near the progress value
2303
     *
2304
     * @return string HTML string containing the progress bar
2305
     */
2306
    public static function get_progress_bar($percentage = -1, $text_add = '')
2307
    {
2308
        $text = $percentage.$text_add;
2309
2310
        return '<div class="progress">
2311
            <div id="progress_bar_value"
2312
                class="progress-bar progress-bar-success" role="progressbar"
2313
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2314
            '.$text.'
2315
            </div>
2316
        </div>';
2317
    }
2318
2319
    /**
2320
     * @param string $mode can be '%' or 'abs'
2321
     *                     otherwise this value will be used $this->progress_bar_mode
2322
     *
2323
     * @return string
2324
     */
2325
    public function getProgressBar($mode = null)
2326
    {
2327
        [$percentage, $text_add] = $this->get_progress_bar_text($mode);
2328
2329
        return self::get_progress_bar($percentage, $text_add);
2330
    }
2331
2332
    /**
2333
     * Gets the progress bar info to display inside the progress bar.
2334
     * Also used by scorm_api.php.
2335
     *
2336
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2337
     *                     we display a number of completed elements per total elements
2338
     * @param int    $add  Additional steps to fake as completed
2339
     *
2340
     * @return array Percentage or number and symbol (% or /xx)
2341
     */
2342
    public function get_progress_bar_text($mode = '', $add = 0)
2343
    {
2344
        if (empty($mode)) {
2345
            $mode = $this->progress_bar_mode;
2346
        }
2347
        $text = '';
2348
        $percentage = 0;
2349
        // If the option to use the score as progress is set for this learning
2350
        // path, then the rules are completely different: we assume only one
2351
        // item exists and the progress of the LP depends on the score
2352
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2353
        if (true === $scoreAsProgressSetting) {
2354
            $scoreAsProgress = $this->getUseScoreAsProgress();
2355
            if ($scoreAsProgress) {
2356
                // Get single item's score
2357
                $itemId = $this->get_current_item_id();
2358
                $item = $this->getItem($itemId);
2359
                $score = $item->get_score();
2360
                $maxScore = $item->get_max();
2361
                if ($mode = '%') {
2362
                    if (!empty($maxScore)) {
2363
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2364
                    }
2365
                    $percentage = number_format($percentage, 0);
2366
                    $text = '%';
2367
                } else {
2368
                    $percentage = $score;
2369
                    $text = '/'.$maxScore;
2370
                }
2371
2372
                return [$percentage, $text];
2373
            }
2374
        }
2375
        // otherwise just continue the normal processing of progress
2376
        $total_items = $this->getTotalItemsCountWithoutDirs();
2377
        $completeItems = $this->get_complete_items_count();
2378
        if (0 != $add) {
2379
            $completeItems += $add;
2380
        }
2381
        if ($completeItems > $total_items) {
2382
            $completeItems = $total_items;
2383
        }
2384
        if ('%' === $mode) {
2385
            if ($total_items > 0) {
2386
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2387
            }
2388
            $percentage = number_format($percentage, 0);
2389
            $text = '%';
2390
        } elseif ('abs' === $mode) {
2391
            $percentage = $completeItems;
2392
            $text = '/'.$total_items;
2393
        }
2394
2395
        return [
2396
            $percentage,
2397
            $text,
2398
        ];
2399
    }
2400
2401
    /**
2402
     * Gets the progress bar mode.
2403
     *
2404
     * @return string The progress bar mode attribute
2405
     */
2406
    public function get_progress_bar_mode()
2407
    {
2408
        if (!empty($this->progress_bar_mode)) {
2409
            return $this->progress_bar_mode;
2410
        }
2411
2412
        return '%';
2413
    }
2414
2415
    /**
2416
     * Gets the learnpath theme (remote or local).
2417
     *
2418
     * @return string Learnpath theme
2419
     */
2420
    public function get_theme()
2421
    {
2422
        if (!empty($this->theme)) {
2423
            return $this->theme;
2424
        }
2425
2426
        return '';
2427
    }
2428
2429
    /**
2430
     * Gets the learnpath session id.
2431
     *
2432
     * @return int
2433
     */
2434
    public function get_lp_session_id()
2435
    {
2436
        if (!empty($this->lp_session_id)) {
2437
            return (int) $this->lp_session_id;
2438
        }
2439
2440
        return 0;
2441
    }
2442
2443
    /**
2444
     * Gets the learnpath author.
2445
     *
2446
     * @return string LP's author
2447
     */
2448
    public function get_author()
2449
    {
2450
        if (!empty($this->author)) {
2451
            return $this->author;
2452
        }
2453
2454
        return '';
2455
    }
2456
2457
    /**
2458
     * Gets hide table of contents.
2459
     *
2460
     * @return int
2461
     */
2462
    public function getHideTableOfContents()
2463
    {
2464
        return (int) $this->hide_toc_frame;
2465
    }
2466
2467
    /**
2468
     * Generate a new prerequisites string for a given item. If this item was a sco and
2469
     * its prerequisites were strings (instead of IDs), then transform those strings into
2470
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2471
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2472
     * same rule as the scormExport() method.
2473
     *
2474
     * @param int $item_id Item ID
2475
     *
2476
     * @return string Prerequisites string ready for the export as SCORM
2477
     */
2478
    public function get_scorm_prereq_string($item_id)
2479
    {
2480
        if ($this->debug > 0) {
2481
            error_log('In learnpath::get_scorm_prereq_string()');
2482
        }
2483
        if (!is_object($this->items[$item_id])) {
2484
            return false;
2485
        }
2486
        /** @var learnpathItem $oItem */
2487
        $oItem = $this->items[$item_id];
2488
        $prereq = $oItem->get_prereq_string();
2489
2490
        if (empty($prereq)) {
2491
            return '';
2492
        }
2493
        if (preg_match('/^\d+$/', $prereq) &&
2494
            isset($this->items[$prereq]) &&
2495
            is_object($this->items[$prereq])
2496
        ) {
2497
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2498
            // then simply return it (with the ITEM_ prefix).
2499
            //return 'ITEM_' . $prereq;
2500
            return $this->items[$prereq]->ref;
2501
        } else {
2502
            if (isset($this->refs_list[$prereq])) {
2503
                // It's a simple string item from which the ID can be found in the refs list,
2504
                // so we can transform it directly to an ID for export.
2505
                return $this->items[$this->refs_list[$prereq]]->ref;
2506
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2507
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2508
            } else {
2509
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2510
                // and replace them, one by one, by the internal IDs (chamilo db)
2511
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2512
                // by a space as well.
2513
                $find = [
2514
                    '&',
2515
                    '|',
2516
                    '~',
2517
                    '=',
2518
                    '<>',
2519
                    '{',
2520
                    '}',
2521
                    '*',
2522
                    '(',
2523
                    ')',
2524
                ];
2525
                $replace = [
2526
                    ' ',
2527
                    ' ',
2528
                    ' ',
2529
                    ' ',
2530
                    ' ',
2531
                    ' ',
2532
                    ' ',
2533
                    ' ',
2534
                    ' ',
2535
                    ' ',
2536
                ];
2537
                $prereq_mod = str_replace($find, $replace, $prereq);
2538
                $ids = explode(' ', $prereq_mod);
2539
                foreach ($ids as $id) {
2540
                    $id = trim($id);
2541
                    if (isset($this->refs_list[$id])) {
2542
                        $prereq = preg_replace(
2543
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2544
                            'ITEM_'.$this->refs_list[$id],
2545
                            $prereq
2546
                        );
2547
                    }
2548
                }
2549
2550
                return $prereq;
2551
            }
2552
        }
2553
    }
2554
2555
    /**
2556
     * Returns the XML DOM document's node.
2557
     *
2558
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2559
     * @param string   $id       The identifier to look for
2560
     *
2561
     * @return mixed The reference to the element found with that identifier. False if not found
2562
     */
2563
    public function get_scorm_xml_node(&$children, $id)
2564
    {
2565
        for ($i = 0; $i < $children->length; $i++) {
2566
            $item_temp = $children->item($i);
2567
            if ('item' === $item_temp->nodeName) {
2568
                if ($item_temp->getAttribute('identifier') == $id) {
2569
                    return $item_temp;
2570
                }
2571
            }
2572
            $subchildren = $item_temp->childNodes;
2573
            if ($subchildren && $subchildren->length > 0) {
2574
                $val = $this->get_scorm_xml_node($subchildren, $id);
2575
                if (is_object($val)) {
2576
                    return $val;
2577
                }
2578
            }
2579
        }
2580
2581
        return false;
2582
    }
2583
2584
    /**
2585
     * Gets the status list for all LP's items.
2586
     *
2587
     * @return array Array of [index] => [item ID => current status]
2588
     */
2589
    public function get_items_status_list()
2590
    {
2591
        $list = [];
2592
        foreach ($this->ordered_items as $item_id) {
2593
            $list[] = [
2594
                $item_id => $this->items[$item_id]->get_status(),
2595
            ];
2596
        }
2597
2598
        return $list;
2599
    }
2600
2601
    /**
2602
     * Return the number of interactions for the given learnpath Item View ID.
2603
     * This method can be used as static.
2604
     *
2605
     * @param int $lp_iv_id  Item View ID
2606
     * @param int $course_id course id
2607
     *
2608
     * @return int
2609
     */
2610
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2611
    {
2612
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2613
        $lp_iv_id = (int) $lp_iv_id;
2614
        $course_id = (int) $course_id;
2615
2616
        $sql = "SELECT count(*) FROM $table
2617
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2618
        $res = Database::query($sql);
2619
        $num = 0;
2620
        if (Database::num_rows($res)) {
2621
            $row = Database::fetch_array($res);
2622
            $num = $row[0];
2623
        }
2624
2625
        return $num;
2626
    }
2627
2628
    /**
2629
     * Return the interactions as an array for the given lp_iv_id.
2630
     * This method can be used as static.
2631
     *
2632
     * @param int $lp_iv_id Learnpath Item View ID
2633
     *
2634
     * @return array
2635
     *
2636
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2637
     */
2638
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2639
    {
2640
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2641
        $list = [];
2642
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2643
        $lp_iv_id = (int) $lp_iv_id;
2644
2645
        if (empty($lp_iv_id) || empty($course_id)) {
2646
            return [];
2647
        }
2648
2649
        $sql = "SELECT * FROM $table
2650
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2651
                ORDER BY order_id ASC";
2652
        $res = Database::query($sql);
2653
        $num = Database::num_rows($res);
2654
        if ($num > 0) {
2655
            $list[] = [
2656
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2657
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2658
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2659
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2660
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2661
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2662
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2663
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2664
                'student_response_formatted' => '',
2665
            ];
2666
            while ($row = Database::fetch_array($res)) {
2667
                $studentResponseFormatted = urldecode($row['student_response']);
2668
                $content_student_response = explode('__|', $studentResponseFormatted);
2669
                if (count($content_student_response) > 0) {
2670
                    if (count($content_student_response) >= 3) {
2671
                        // Pop the element off the end of array.
2672
                        array_pop($content_student_response);
2673
                    }
2674
                    $studentResponseFormatted = implode(',', $content_student_response);
2675
                }
2676
2677
                $list[] = [
2678
                    'order_id' => $row['order_id'] + 1,
2679
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2680
                    'type' => $row['interaction_type'],
2681
                    'time' => $row['completion_time'],
2682
                    'correct_responses' => '', // Hide correct responses from students.
2683
                    'student_response' => $row['student_response'],
2684
                    'result' => $row['result'],
2685
                    'latency' => $row['latency'],
2686
                    'student_response_formatted' => $studentResponseFormatted,
2687
                ];
2688
            }
2689
        }
2690
2691
        return $list;
2692
    }
2693
2694
    /**
2695
     * Return the number of objectives for the given learnpath Item View ID.
2696
     * This method can be used as static.
2697
     *
2698
     * @param int $lp_iv_id  Item View ID
2699
     * @param int $course_id Course ID
2700
     *
2701
     * @return int Number of objectives
2702
     */
2703
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2704
    {
2705
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2706
        $course_id = (int) $course_id;
2707
        $lp_iv_id = (int) $lp_iv_id;
2708
        $sql = "SELECT count(*) FROM $table
2709
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2710
        //@todo seems that this always returns 0
2711
        $res = Database::query($sql);
2712
        $num = 0;
2713
        if (Database::num_rows($res)) {
2714
            $row = Database::fetch_array($res);
2715
            $num = $row[0];
2716
        }
2717
2718
        return $num;
2719
    }
2720
2721
    /**
2722
     * Return the objectives as an array for the given lp_iv_id.
2723
     * This method can be used as static.
2724
     *
2725
     * @param int $lpItemViewId Learnpath Item View ID
2726
     * @param int $course_id
2727
     *
2728
     * @return array
2729
     *
2730
     * @todo    Translate labels
2731
     */
2732
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2733
    {
2734
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2735
        $lpItemViewId = (int) $lpItemViewId;
2736
2737
        if (empty($course_id) || empty($lpItemViewId)) {
2738
            return [];
2739
        }
2740
2741
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2742
        $sql = "SELECT * FROM $table
2743
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2744
                ORDER BY order_id ASC";
2745
        $res = Database::query($sql);
2746
        $num = Database::num_rows($res);
2747
        $list = [];
2748
        if ($num > 0) {
2749
            $list[] = [
2750
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2751
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2752
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2753
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2754
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2755
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2756
            ];
2757
            while ($row = Database::fetch_array($res)) {
2758
                $list[] = [
2759
                    'order_id' => $row['order_id'] + 1,
2760
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2761
                    'score_raw' => $row['score_raw'],
2762
                    'score_max' => $row['score_max'],
2763
                    'score_min' => $row['score_min'],
2764
                    'status' => $row['status'],
2765
                ];
2766
            }
2767
        }
2768
2769
        return $list;
2770
    }
2771
2772
    /**
2773
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2774
     * used by get_html_toc() to be ready to display.
2775
     *
2776
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2777
     */
2778
    public function get_toc()
2779
    {
2780
        $toc = [];
2781
        foreach ($this->ordered_items as $item_id) {
2782
            // TODO: Change this link generation and use new function instead.
2783
            $toc[] = [
2784
                'id' => $item_id,
2785
                'title' => $this->items[$item_id]->get_title(),
2786
                'status' => $this->items[$item_id]->get_status(),
2787
                'level' => $this->items[$item_id]->get_level(),
2788
                'type' => $this->items[$item_id]->get_type(),
2789
                'description' => $this->items[$item_id]->get_description(),
2790
                'path' => $this->items[$item_id]->get_path(),
2791
                'parent' => $this->items[$item_id]->get_parent(),
2792
            ];
2793
        }
2794
2795
        return $toc;
2796
    }
2797
2798
    /**
2799
     * Returns the CSS class name associated with a given item status.
2800
     *
2801
     * @param $status string an item status
2802
     *
2803
     * @return string CSS class name
2804
     */
2805
    public static function getStatusCSSClassName($status)
2806
    {
2807
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
2808
            return self::STATUS_CSS_CLASS_NAME[$status];
2809
        }
2810
2811
        return '';
2812
    }
2813
2814
    /**
2815
     * Generate the tree of contents for this learnpath as an associative array tree
2816
     * with keys id, title, status, type, description, path, parent_id, children
2817
     * (title and descriptions as secured)
2818
     * and clues for CSS class composition:
2819
     *  - booleans is_current, is_parent_of_current, is_chapter
2820
     *  - string status_css_class_name.
2821
     *
2822
     * @param $parentId int restrict returned list to children of this parent
2823
     *
2824
     * @return array TOC as a table
2825
     */
2826
    public function getTOCTree($parentId = 0)
2827
    {
2828
        $toc = [];
2829
        $currentItemId = $this->get_current_item_id();
2830
2831
        foreach ($this->ordered_items as $itemId) {
2832
            $item = $this->items[$itemId];
2833
            if ($item->get_parent() == $parentId) {
2834
                $title = $item->get_title();
2835
                if (empty($title)) {
2836
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
2837
                }
2838
2839
                $itemData = [
2840
                    'id' => $itemId,
2841
                    'title' => Security::remove_XSS($title),
2842
                    'status' => $item->get_status(),
2843
                    'level' => $item->get_level(), // FIXME should not be needed
2844
                    'type' => $item->get_type(),
2845
                    'description' => Security::remove_XSS($item->get_description()),
2846
                    'path' => $item->get_path(),
2847
                    'parent_id' => $item->get_parent(),
2848
                    'children' => $this->getTOCTree($itemId),
2849
                    'is_current' => ($itemId == $currentItemId),
2850
                    'is_parent_of_current' => false,
2851
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
2852
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
2853
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
2854
                ];
2855
2856
                if (!empty($itemData['children'])) {
2857
                    foreach ($itemData['children'] as $child) {
2858
                        if ($child['is_current'] || $child['is_parent_of_current']) {
2859
                            $itemData['is_parent_of_current'] = true;
2860
                            break;
2861
                        }
2862
                    }
2863
                }
2864
2865
                $toc[] = $itemData;
2866
            }
2867
        }
2868
2869
        return $toc;
2870
    }
2871
2872
    /**
2873
     * Generate and return the table of contents for this learnpath. The JS
2874
     * table returned is used inside of scorm_api.php.
2875
     *
2876
     * @param string $varname
2877
     *
2878
     * @return string A JS array variable construction
2879
     */
2880
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2881
    {
2882
        $toc = $varname.' = new Array();';
2883
        foreach ($this->ordered_items as $item_id) {
2884
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2885
        }
2886
2887
        return $toc;
2888
    }
2889
2890
    /**
2891
     * Gets the learning path type.
2892
     *
2893
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2894
     *
2895
     * @return mixed Type ID or name, depending on the parameter
2896
     */
2897
    public function get_type($get_name = false)
2898
    {
2899
        $res = false;
2900
        if (!empty($this->type) && (!$get_name)) {
2901
            $res = $this->type;
2902
        }
2903
2904
        return $res;
2905
    }
2906
2907
    /**
2908
     * Gets the learning path type as static method.
2909
     *
2910
     * @param int $lp_id
2911
     *
2912
     * @return mixed Type ID or name, depending on the parameter
2913
     */
2914
    public static function get_type_static($lp_id = 0)
2915
    {
2916
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2917
        $lp_id = (int) $lp_id;
2918
        $sql = "SELECT lp_type FROM $tbl_lp
2919
                WHERE iid = $lp_id";
2920
        $res = Database::query($sql);
2921
        if (false === $res) {
2922
            return null;
2923
        }
2924
        if (Database::num_rows($res) <= 0) {
2925
            return null;
2926
        }
2927
        $row = Database::fetch_array($res);
2928
2929
        return $row['lp_type'];
2930
    }
2931
2932
    /**
2933
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2934
     * This method can be used as abstract and is recursive.
2935
     *
2936
     * @param CLp $lp
2937
     * @param int $parent    Parent ID of the items to look for
2938
     *
2939
     * @return array Ordered list of item IDs (empty array on error)
2940
     */
2941
    public static function get_flat_ordered_items_list(CLp $lp, $parent = 0)
2942
    {
2943
        $parent = (int) $parent;
2944
2945
        $criteria = Criteria::create()
2946
            ->orderBy(
2947
                [
2948
                    'displayOrder' => Criteria::ASC,
2949
                ]
2950
            );
2951
        $items = $lp->getItems()->matching($criteria);
2952
        $items = $items->filter(
2953
            function (CLpItem $element) use ($parent) {
2954
                if (empty($parent)) {
2955
                    $parent = null;
2956
                    return $element->getParent() === $parent;
2957
                } else {
2958
                    if (null !== $element->getParent()) {
2959
                        return $element->getParent()->getIid() === $parent;
2960
                    }
2961
                    return false;
2962
                }
2963
            }
2964
        );
2965
2966
        /*
2967
         $lp = (int) $lp;
2968
        $parent = (int) $parent;
2969
2970
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2971
        $sql = "SELECT iid FROM $tbl_lp_item
2972
                WHERE lp_id = $lp AND parent_item_id = $parent
2973
                ORDER BY display_order";
2974
2975
        $res = Database::query($sql);
2976
        while ($row = Database::fetch_array($res)) {
2977
            $sublist = self::get_flat_ordered_items_list(
2978
                $lp,
2979
                $row['iid'],
2980
                $course_id
2981
            );
2982
            $list[] = $row['iid'];
2983
            foreach ($sublist as $item) {
2984
                $list[] = $item;
2985
            }
2986
        }
2987
        */
2988
2989
        $list = [];
2990
        foreach ($items as $item) {
2991
            $itemId = $item->getIid();
2992
            $sublist = self::get_flat_ordered_items_list($lp, $itemId);
2993
            $list[] = $itemId;
2994
            foreach ($sublist as $subItem) {
2995
                $list[] = $subItem;
2996
            }
2997
        }
2998
2999
        return $list;
3000
    }
3001
3002
    /**
3003
     * @return array
3004
     */
3005
    public static function getChapterTypes()
3006
    {
3007
        return [
3008
            'dir',
3009
        ];
3010
    }
3011
3012
    /**
3013
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3014
     *
3015
     * @param $tree
3016
     *
3017
     * @return array HTML TOC ready to display
3018
     */
3019
    public function getParentToc($tree)
3020
    {
3021
        if (empty($tree)) {
3022
            $tree = $this->get_toc();
3023
        }
3024
        $dirTypes = self::getChapterTypes();
3025
        $myCurrentId = $this->get_current_item_id();
3026
        $listParent = [];
3027
        $listChildren = [];
3028
        $listNotParent = [];
3029
        $list = [];
3030
        foreach ($tree as $subtree) {
3031
            if (in_array($subtree['type'], $dirTypes)) {
3032
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3033
                $subtree['children'] = $listChildren;
3034
                if (!empty($subtree['children'])) {
3035
                    foreach ($subtree['children'] as $subItem) {
3036
                        if ($subItem['id'] == $this->current) {
3037
                            $subtree['parent_current'] = 'in';
3038
                            $subtree['current'] = 'on';
3039
                        }
3040
                    }
3041
                }
3042
                $listParent[] = $subtree;
3043
            }
3044
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3045
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3046
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3047
                }
3048
3049
                $title = Security::remove_XSS($subtree['title']);
3050
                unset($subtree['title']);
3051
3052
                if (empty($title)) {
3053
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3054
                }
3055
                $classStyle = null;
3056
                if ($subtree['id'] == $this->current) {
3057
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3058
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3059
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3060
                }
3061
                $subtree['title'] = $title;
3062
                $subtree['class'] = $classStyle.' '.$cssStatus;
3063
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3064
                $subtree['current_id'] = $myCurrentId;
3065
                $listNotParent[] = $subtree;
3066
            }
3067
        }
3068
3069
        $list['are_parents'] = $listParent;
3070
        $list['not_parents'] = $listNotParent;
3071
3072
        return $list;
3073
    }
3074
3075
    /**
3076
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3077
     *
3078
     * @param array $tree
3079
     * @param int   $id
3080
     * @param bool  $parent
3081
     *
3082
     * @return array HTML TOC ready to display
3083
     */
3084
    public function getChildrenToc($tree, $id, $parent = true)
3085
    {
3086
        if (empty($tree)) {
3087
            $tree = $this->get_toc();
3088
        }
3089
3090
        $dirTypes = self::getChapterTypes();
3091
        $currentItemId = $this->get_current_item_id();
3092
        $list = [];
3093
3094
        foreach ($tree as $subtree) {
3095
            $subtree['tree'] = null;
3096
3097
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3098
                if ($subtree['id'] == $this->current) {
3099
                    $subtree['current'] = 'active';
3100
                } else {
3101
                    $subtree['current'] = null;
3102
                }
3103
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3104
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3105
                }
3106
3107
                $title = Security::remove_XSS($subtree['title']);
3108
                unset($subtree['title']);
3109
                if (empty($title)) {
3110
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3111
                }
3112
3113
                $classStyle = null;
3114
                if ($subtree['id'] == $this->current) {
3115
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3116
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3117
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3118
                }
3119
3120
                if (in_array($subtree['type'], $dirTypes)) {
3121
                    $subtree['title'] = stripslashes($title);
3122
                } else {
3123
                    $subtree['title'] = $title;
3124
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3125
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3126
                    $subtree['current_id'] = $currentItemId;
3127
                }
3128
                $list[] = $subtree;
3129
            }
3130
        }
3131
3132
        return $list;
3133
    }
3134
3135
    /**
3136
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3137
     *
3138
     * @param array $toc_list
3139
     *
3140
     * @return array HTML TOC ready to display
3141
     */
3142
    public function getListArrayToc($toc_list = [])
3143
    {
3144
        if (empty($toc_list)) {
3145
            $toc_list = $this->get_toc();
3146
        }
3147
        // Temporary variables.
3148
        $currentItemId = $this->get_current_item_id();
3149
        $list = [];
3150
        $arrayList = [];
3151
3152
        foreach ($toc_list as $item) {
3153
            $list['id'] = $item['id'];
3154
            $list['status'] = $item['status'];
3155
            $cssStatus = null;
3156
3157
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3158
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3159
            }
3160
3161
            $classStyle = ' ';
3162
            $dirTypes = self::getChapterTypes();
3163
3164
            if (in_array($item['type'], $dirTypes)) {
3165
                $classStyle = 'scorm_item_section ';
3166
            }
3167
            if ($item['id'] == $this->current) {
3168
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3169
            } elseif (!in_array($item['type'], $dirTypes)) {
3170
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3171
            }
3172
            $title = $item['title'];
3173
            if (empty($title)) {
3174
                $title = self::rl_get_resource_name(
3175
                    api_get_course_id(),
3176
                    $this->get_id(),
3177
                    $item['id']
3178
                );
3179
            }
3180
            $title = Security::remove_XSS($item['title']);
3181
3182
            if (empty($item['description'])) {
3183
                $list['description'] = $title;
3184
            } else {
3185
                $list['description'] = $item['description'];
3186
            }
3187
3188
            $list['class'] = $classStyle.' '.$cssStatus;
3189
            $list['level'] = $item['level'];
3190
            $list['type'] = $item['type'];
3191
3192
            if (in_array($item['type'], $dirTypes)) {
3193
                $list['css_level'] = 'level_'.$item['level'];
3194
            } else {
3195
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3196
            }
3197
3198
            if (in_array($item['type'], $dirTypes)) {
3199
                $list['title'] = stripslashes($title);
3200
            } else {
3201
                $list['title'] = stripslashes($title);
3202
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3203
                $list['current_id'] = $currentItemId;
3204
            }
3205
            $arrayList[] = $list;
3206
        }
3207
3208
        return $arrayList;
3209
    }
3210
3211
    /**
3212
     * Returns an HTML-formatted string ready to display with teacher buttons
3213
     * in LP view menu.
3214
     *
3215
     * @return string HTML TOC ready to display
3216
     */
3217
    public function get_teacher_toc_buttons()
3218
    {
3219
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3220
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3221
        $html = '';
3222
        if ($isAllow && false == $hideIcons) {
3223
            if ($this->get_lp_session_id() == api_get_session_id()) {
3224
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3225
                $html .= '<div class="btn-group">';
3226
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3227
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3228
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3229
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3230
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3231
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3232
                $html .= '</div>';
3233
                $html .= '</div>';
3234
            }
3235
        }
3236
3237
        return $html;
3238
    }
3239
3240
    /**
3241
     * Gets the learnpath maker name - generally the editor's name.
3242
     *
3243
     * @return string Learnpath maker name
3244
     */
3245
    public function get_maker()
3246
    {
3247
        if (!empty($this->maker)) {
3248
            return $this->maker;
3249
        }
3250
3251
        return '';
3252
    }
3253
3254
    /**
3255
     * Gets the learnpath name/title.
3256
     *
3257
     * @return string Learnpath name/title
3258
     */
3259
    public function get_name()
3260
    {
3261
        if (!empty($this->name)) {
3262
            return $this->name;
3263
        }
3264
3265
        return 'N/A';
3266
    }
3267
3268
    /**
3269
     * @return string
3270
     */
3271
    public function getNameNoTags()
3272
    {
3273
        return strip_tags($this->get_name());
3274
    }
3275
3276
    /**
3277
     * Gets a link to the resource from the present location, depending on item ID.
3278
     *
3279
     * @param string $type         Type of link expected
3280
     * @param int    $item_id      Learnpath item ID
3281
     * @param bool   $provided_toc
3282
     *
3283
     * @return string $provided_toc Link to the lp_item resource
3284
     */
3285
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3286
    {
3287
        $course_id = $this->get_course_int_id();
3288
        $item_id = (int) $item_id;
3289
3290
        if (empty($item_id)) {
3291
            $item_id = $this->get_current_item_id();
3292
3293
            if (empty($item_id)) {
3294
                //still empty, this means there was no item_id given and we are not in an object context or
3295
                //the object property is empty, return empty link
3296
                $this->first();
3297
3298
                return '';
3299
            }
3300
        }
3301
3302
        $file = '';
3303
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3304
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3305
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3306
3307
        $sql = "SELECT
3308
                    l.lp_type as ltype,
3309
                    l.path as lpath,
3310
                    li.item_type as litype,
3311
                    li.path as lipath,
3312
                    li.parameters as liparams
3313
        		FROM $lp_table l
3314
                INNER JOIN $lp_item_table li
3315
                ON (li.lp_id = l.iid)
3316
        		WHERE
3317
        		    li.iid = $item_id
3318
        		";
3319
        $res = Database::query($sql);
3320
        if (Database::num_rows($res) > 0) {
3321
            $row = Database::fetch_array($res);
3322
            $lp_type = $row['ltype'];
3323
            $lp_path = $row['lpath'];
3324
            $lp_item_type = $row['litype'];
3325
            $lp_item_path = $row['lipath'];
3326
            $lp_item_params = $row['liparams'];
3327
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3328
                [$lp_item_path, $lp_item_params] = explode('?', $lp_item_path);
3329
            }
3330
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3331
            if ('http' === $type) {
3332
                //web path
3333
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3334
            } else {
3335
                //$course_path = $sys_course_path; //system path
3336
            }
3337
3338
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3339
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3340
            if (in_array(
3341
                $lp_item_type,
3342
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3343
            )
3344
            ) {
3345
                $lp_type = CLp::LP_TYPE;
3346
            }
3347
3348
            // Now go through the specific cases to get the end of the path
3349
            // @todo Use constants instead of int values.
3350
            switch ($lp_type) {
3351
                case CLp::LP_TYPE:
3352
                    $file = self::rl_get_resource_link_for_learnpath(
3353
                        $course_id,
3354
                        $this->get_id(),
3355
                        $item_id,
3356
                        $this->get_view_id()
3357
                    );
3358
                    switch ($lp_item_type) {
3359
                        case 'document':
3360
                            // Shows a button to download the file instead of just downloading the file directly.
3361
                            $documentPathInfo = pathinfo($file);
3362
                            if (isset($documentPathInfo['extension'])) {
3363
                                $parsed = parse_url($documentPathInfo['extension']);
3364
                                if (isset($parsed['path'])) {
3365
                                    $extension = $parsed['path'];
3366
                                    $extensionsToDownload = [
3367
                                        'zip',
3368
                                        'ppt',
3369
                                        'pptx',
3370
                                        'ods',
3371
                                        'xlsx',
3372
                                        'xls',
3373
                                        'csv',
3374
                                        'doc',
3375
                                        'docx',
3376
                                        'dot',
3377
                                    ];
3378
3379
                                    if (in_array($extension, $extensionsToDownload)) {
3380
                                        $file = api_get_path(WEB_CODE_PATH).
3381
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3382
                                    }
3383
                                }
3384
                            }
3385
                            break;
3386
                        case 'dir':
3387
                            $file = 'lp_content.php?type=dir';
3388
                            break;
3389
                        case 'link':
3390
                            if (Link::is_youtube_link($file)) {
3391
                                $src = Link::get_youtube_video_id($file);
3392
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3393
                            } elseif (Link::isVimeoLink($file)) {
3394
                                $src = Link::getVimeoLinkId($file);
3395
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3396
                            } else {
3397
                                // If the current site is HTTPS and the link is
3398
                                // HTTP, browsers will refuse opening the link
3399
                                $urlId = api_get_current_access_url_id();
3400
                                $url = api_get_access_url($urlId, false);
3401
                                $protocol = substr($url['url'], 0, 5);
3402
                                if ('https' === $protocol) {
3403
                                    $linkProtocol = substr($file, 0, 5);
3404
                                    if ('http:' === $linkProtocol) {
3405
                                        //this is the special intervention case
3406
                                        $file = api_get_path(WEB_CODE_PATH).
3407
                                            'lp/embed.php?type=nonhttps&source='.urlencode($file);
3408
                                    }
3409
                                }
3410
                            }
3411
                            break;
3412
                        case 'quiz':
3413
                            // Check how much attempts of a exercise exits in lp
3414
                            $lp_item_id = $this->get_current_item_id();
3415
                            $lp_view_id = $this->get_view_id();
3416
3417
                            $prevent_reinit = null;
3418
                            if (isset($this->items[$this->current])) {
3419
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3420
                            }
3421
3422
                            if (empty($provided_toc)) {
3423
                                $list = $this->get_toc();
3424
                            } else {
3425
                                $list = $provided_toc;
3426
                            }
3427
3428
                            $type_quiz = false;
3429
                            foreach ($list as $toc) {
3430
                                if ($toc['id'] == $lp_item_id && 'quiz' === $toc['type']) {
3431
                                    $type_quiz = true;
3432
                                }
3433
                            }
3434
3435
                            if ($type_quiz) {
3436
                                $lp_item_id = (int) $lp_item_id;
3437
                                $lp_view_id = (int) $lp_view_id;
3438
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3439
                                        WHERE
3440
                                            c_id = $course_id AND
3441
                                            lp_item_id='".$lp_item_id."' AND
3442
                                            lp_view_id ='".$lp_view_id."' AND
3443
                                            status='completed'";
3444
                                $result = Database::query($sql);
3445
                                $row_count = Database:: fetch_row($result);
3446
                                $count_item_view = (int) $row_count[0];
3447
                                $not_multiple_attempt = 0;
3448
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3449
                                    $not_multiple_attempt = 1;
3450
                                }
3451
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3452
                            }
3453
                            break;
3454
                    }
3455
3456
                    $tmp_array = explode('/', $file);
3457
                    $document_name = $tmp_array[count($tmp_array) - 1];
3458
                    if (strpos($document_name, '_DELETED_')) {
3459
                        $file = 'blank.php?error=document_deleted';
3460
                    }
3461
                    break;
3462
                case CLp::SCORM_TYPE:
3463
                    if ('dir' !== $lp_item_type) {
3464
                        // Quite complex here:
3465
                        // We want to make sure 'http://' (and similar) links can
3466
                        // be loaded as is (withouth the Chamilo path in front) but
3467
                        // some contents use this form: resource.htm?resource=http://blablabla
3468
                        // which means we have to find a protocol at the path's start, otherwise
3469
                        // it should not be considered as an external URL.
3470
                        // if ($this->prerequisites_match($item_id)) {
3471
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3472
                            if ($this->debug > 2) {
3473
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3474
                            }
3475
                            // Distant url, return as is.
3476
                            $file = $lp_item_path;
3477
                        } else {
3478
                            if ($this->debug > 2) {
3479
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path);
3480
                            }
3481
                            // Prevent getting untranslatable urls.
3482
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3483
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3484
3485
                            /*$asset = $this->getEntity()->getAsset();
3486
                            $folder = Container::getAssetRepository()->getFolder($asset);
3487
                            $hasFile = Container::getAssetRepository()->getFileSystem()->has($folder.$lp_item_path);
3488
                            $file = null;
3489
                            if ($hasFile) {
3490
                                $file = Container::getAssetRepository()->getAssetUrl($asset).'/'.$lp_item_path;
3491
                            }*/
3492
                            $file = $this->scormUrl.$lp_item_path;
3493
3494
                            // Prepare the path.
3495
                            /*$file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3496
                            // TODO: Fix this for urls with protocol header.
3497
                            $file = str_replace('//', '/', $file);
3498
                            $file = str_replace(':/', '://', $file);
3499
                            if ('/' === substr($lp_path, -1)) {
3500
                                $lp_path = substr($lp_path, 0, -1);
3501
                            }*/
3502
                            /*if (!$hasFile) {
3503
                                // if file not found.
3504
                                $decoded = html_entity_decode($lp_item_path);
3505
                                [$decoded] = explode('?', $decoded);
3506
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3507
                                    $file = self::rl_get_resource_link_for_learnpath(
3508
                                        $course_id,
3509
                                        $this->get_id(),
3510
                                        $item_id,
3511
                                        $this->get_view_id()
3512
                                    );
3513
                                    if (empty($file)) {
3514
                                        $file = 'blank.php?error=document_not_found';
3515
                                    } else {
3516
                                        $tmp_array = explode('/', $file);
3517
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3518
                                        if (strpos($document_name, '_DELETED_')) {
3519
                                            $file = 'blank.php?error=document_deleted';
3520
                                        } else {
3521
                                            $file = 'blank.php?error=document_not_found';
3522
                                        }
3523
                                    }
3524
                                } else {
3525
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3526
                                }
3527
                            }*/
3528
                        }
3529
3530
                        // We want to use parameters if they were defined in the imsmanifest
3531
                        if (false === strpos($file, 'blank.php')) {
3532
                            $lp_item_params = ltrim($lp_item_params, '?');
3533
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3534
                        }
3535
                    } else {
3536
                        $file = 'lp_content.php?type=dir';
3537
                    }
3538
                    break;
3539
                case CLp::AICC_TYPE:
3540
                    // Formatting AICC HACP append URL.
3541
                    $aicc_append = '?aicc_sid='.
3542
                        urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3543
                    if (!empty($lp_item_params)) {
3544
                        $aicc_append .= $lp_item_params.'&';
3545
                    }
3546
                    if ('dir' !== $lp_item_type) {
3547
                        // Quite complex here:
3548
                        // We want to make sure 'http://' (and similar) links can
3549
                        // be loaded as is (withouth the Chamilo path in front) but
3550
                        // some contents use this form: resource.htm?resource=http://blablabla
3551
                        // which means we have to find a protocol at the path's start, otherwise
3552
                        // it should not be considered as an external URL.
3553
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3554
                            if ($this->debug > 2) {
3555
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3556
                            }
3557
                            // Distant url, return as is.
3558
                            $file = $lp_item_path;
3559
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3560
                            /*
3561
                            if (stristr($file,'<servername>') !== false) {
3562
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3563
                            }
3564
                            */
3565
                            if (false !== stripos($file, '<servername>')) {
3566
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3567
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3568
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3569
                            }
3570
3571
                            $file .= $aicc_append;
3572
                        } else {
3573
                            if ($this->debug > 2) {
3574
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3575
                            }
3576
                            // Prevent getting untranslatable urls.
3577
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3578
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3579
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3580
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3581
                            // TODO: Fix this for urls with protocol header.
3582
                            $file = str_replace('//', '/', $file);
3583
                            $file = str_replace(':/', '://', $file);
3584
                            $file .= $aicc_append;
3585
                        }
3586
                    } else {
3587
                        $file = 'lp_content.php?type=dir';
3588
                    }
3589
                    break;
3590
                case 4:
3591
                default:
3592
                    break;
3593
            }
3594
            // Replace &amp; by & because &amp; will break URL with params
3595
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3596
        }
3597
        if ($this->debug > 2) {
3598
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3599
        }
3600
3601
        return $file;
3602
    }
3603
3604
    /**
3605
     * Gets the latest usable view or generate a new one.
3606
     *
3607
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3608
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3609
     *
3610
     * @return int DB lp_view id
3611
     */
3612
    public function get_view($attempt_num = 0, $userId = null)
3613
    {
3614
        $search = '';
3615
        $attempt_num = (int) $attempt_num;
3616
        // Use $attempt_num to enable multi-views management (disabled so far).
3617
        if (!empty($attempt_num)) {
3618
            $search = 'AND view_count = '.$attempt_num;
3619
        }
3620
3621
        $course_id = api_get_course_int_id();
3622
        $sessionId = api_get_session_id();
3623
3624
        // Check user ID.
3625
        if (empty($userId)) {
3626
            if (empty($this->get_user_id())) {
3627
                $this->error = 'User ID is empty in learnpath::get_view()';
3628
3629
                return null;
3630
            } else {
3631
                $userId = $this->get_user_id();
3632
            }
3633
        }
3634
        $sessionCondition = api_get_session_condition($sessionId);
3635
3636
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3637
        $table = Database::get_course_table(TABLE_LP_VIEW);
3638
        $sql = "SELECT iid FROM $table
3639
        		WHERE
3640
        		    c_id = $course_id AND
3641
        		    lp_id = ".$this->get_id()." AND
3642
        		    user_id = ".$userId."
3643
        		    $sessionCondition
3644
        		    $search
3645
                ORDER BY view_count DESC";
3646
        $res = Database::query($sql);
3647
        if (Database::num_rows($res) > 0) {
3648
            $row = Database::fetch_array($res);
3649
            $this->lp_view_id = $row['iid'];
3650
        } elseif (!api_is_invitee()) {
3651
            $params = [
3652
                'c_id' => $course_id,
3653
                'lp_id' => $this->get_id(),
3654
                'user_id' => $this->get_user_id(),
3655
                'view_count' => 1,
3656
                'last_item' => 0,
3657
            ];
3658
            if (!empty($sessionId)) {
3659
                $params['session_id']  = $sessionId;
3660
            }
3661
            $this->lp_view_id = Database::insert($table, $params);
3662
        }
3663
3664
        return $this->lp_view_id;
3665
    }
3666
3667
    /**
3668
     * Gets the current view id.
3669
     *
3670
     * @return int View ID (from lp_view)
3671
     */
3672
    public function get_view_id()
3673
    {
3674
        if (!empty($this->lp_view_id)) {
3675
            return (int) $this->lp_view_id;
3676
        }
3677
3678
        return 0;
3679
    }
3680
3681
    /**
3682
     * Gets the update queue.
3683
     *
3684
     * @return array Array containing IDs of items to be updated by JavaScript
3685
     */
3686
    public function get_update_queue()
3687
    {
3688
        return $this->update_queue;
3689
    }
3690
3691
    /**
3692
     * Gets the user ID.
3693
     *
3694
     * @return int User ID
3695
     */
3696
    public function get_user_id()
3697
    {
3698
        if (!empty($this->user_id)) {
3699
            return (int) $this->user_id;
3700
        }
3701
3702
        return false;
3703
    }
3704
3705
    /**
3706
     * Checks if any of the items has an audio element attached.
3707
     *
3708
     * @return bool True or false
3709
     */
3710
    public function has_audio()
3711
    {
3712
        $has = false;
3713
        foreach ($this->items as $i => $item) {
3714
            if (!empty($this->items[$i]->audio)) {
3715
                $has = true;
3716
                break;
3717
            }
3718
        }
3719
3720
        return $has;
3721
    }
3722
3723
    /**
3724
     * Moves an item up and down at its level.
3725
     *
3726
     * @param int    $id        Item to move up and down
3727
     * @param string $direction Direction 'up' or 'down'
3728
     *
3729
     * @return bool|int
3730
     */
3731
    public function move_item($id, $direction)
3732
    {
3733
        $course_id = api_get_course_int_id();
3734
        if (empty($id) || empty($direction)) {
3735
            return false;
3736
        }
3737
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3738
        $sql_sel = "SELECT *
3739
                    FROM $tbl_lp_item
3740
                    WHERE
3741
                        iid = $id
3742
                    ";
3743
        $res_sel = Database::query($sql_sel);
3744
        // Check if elem exists.
3745
        if (Database::num_rows($res_sel) < 1) {
3746
            return false;
3747
        }
3748
        // Gather data.
3749
        $row = Database::fetch_array($res_sel);
3750
        $previous = $row['previous_item_id'];
3751
        $next = $row['next_item_id'];
3752
        $display = $row['display_order'];
3753
        $parent = $row['parent_item_id'];
3754
        $lp = $row['lp_id'];
3755
        // Update the item (switch with previous/next one).
3756
        switch ($direction) {
3757
            case 'up':
3758
                if ($display > 1) {
3759
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3760
                                 WHERE iid = $previous";
3761
                    $res_sel2 = Database::query($sql_sel2);
3762
                    if (Database::num_rows($res_sel2) < 1) {
3763
                        $previous_previous = 0;
3764
                    }
3765
                    // Gather data.
3766
                    $row2 = Database::fetch_array($res_sel2);
3767
                    $previous_previous = $row2['previous_item_id'];
3768
                    // Update previous_previous item (switch "next" with current).
3769
                    if (0 != $previous_previous) {
3770
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3771
                                        next_item_id = $id
3772
                                    WHERE iid = $previous_previous";
3773
                        Database::query($sql_upd2);
3774
                    }
3775
                    // Update previous item (switch with current).
3776
                    if (0 != $previous) {
3777
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3778
                                    next_item_id = $next,
3779
                                    previous_item_id = $id,
3780
                                    display_order = display_order +1
3781
                                    WHERE iid = $previous";
3782
                        Database::query($sql_upd2);
3783
                    }
3784
3785
                    // Update current item (switch with previous).
3786
                    if (0 != $id) {
3787
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3788
                                        next_item_id = $previous,
3789
                                        previous_item_id = $previous_previous,
3790
                                        display_order = display_order-1
3791
                                    WHERE c_id = ".$course_id." AND id = $id";
3792
                        Database::query($sql_upd2);
3793
                    }
3794
                    // Update next item (new previous item).
3795
                    if (!empty($next)) {
3796
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3797
                                     WHERE iid = $next";
3798
                        Database::query($sql_upd2);
3799
                    }
3800
                    $display = $display - 1;
3801
                }
3802
                break;
3803
            case 'down':
3804
                if (0 != $next) {
3805
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3806
                                 WHERE iid = $next";
3807
                    $res_sel2 = Database::query($sql_sel2);
3808
                    if (Database::num_rows($res_sel2) < 1) {
3809
                        $next_next = 0;
3810
                    }
3811
                    // Gather data.
3812
                    $row2 = Database::fetch_array($res_sel2);
3813
                    $next_next = $row2['next_item_id'];
3814
                    // Update previous item (switch with current).
3815
                    if (0 != $previous) {
3816
                        $sql_upd2 = "UPDATE $tbl_lp_item
3817
                                     SET next_item_id = $next
3818
                                     WHERE iid = $previous";
3819
                        Database::query($sql_upd2);
3820
                    }
3821
                    // Update current item (switch with previous).
3822
                    if (0 != $id) {
3823
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3824
                                     previous_item_id = $next,
3825
                                     next_item_id = $next_next,
3826
                                     display_order = display_order + 1
3827
                                     WHERE iid = $id";
3828
                        Database::query($sql_upd2);
3829
                    }
3830
3831
                    // Update next item (new previous item).
3832
                    if (0 != $next) {
3833
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3834
                                     previous_item_id = $previous,
3835
                                     next_item_id = $id,
3836
                                     display_order = display_order-1
3837
                                     WHERE iid = $next";
3838
                        Database::query($sql_upd2);
3839
                    }
3840
3841
                    // Update next_next item (switch "previous" with current).
3842
                    if (0 != $next_next) {
3843
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3844
                                     previous_item_id = $id
3845
                                     WHERE iid = $next_next";
3846
                        Database::query($sql_upd2);
3847
                    }
3848
                    $display = $display + 1;
3849
                }
3850
                break;
3851
            default:
3852
                return false;
3853
        }
3854
3855
        return $display;
3856
    }
3857
3858
    /**
3859
     * Move a LP up (display_order).
3860
     *
3861
     * @param int $lp_id      Learnpath ID
3862
     * @param int $categoryId Category ID
3863
     *
3864
     * @return bool
3865
     */
3866
    public static function move_up($lp_id, $categoryId = 0)
3867
    {
3868
        $courseId = api_get_course_int_id();
3869
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3870
3871
        $categoryCondition = '';
3872
        if (!empty($categoryId)) {
3873
            $categoryId = (int) $categoryId;
3874
            $categoryCondition = " AND category_id = $categoryId";
3875
        }
3876
        $sql = "SELECT * FROM $lp_table
3877
                WHERE c_id = $courseId
3878
                $categoryCondition
3879
                ORDER BY display_order";
3880
        $res = Database::query($sql);
3881
        if (false === $res) {
3882
            return false;
3883
        }
3884
3885
        $lps = [];
3886
        $lp_order = [];
3887
        $num = Database::num_rows($res);
3888
        // First check the order is correct, globally (might be wrong because
3889
        // of versions < 1.8.4)
3890
        if ($num > 0) {
3891
            $i = 1;
3892
            while ($row = Database::fetch_array($res)) {
3893
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3894
                    $sql = "UPDATE $lp_table SET display_order = $i
3895
                            WHERE iid = ".$row['iid'];
3896
                    Database::query($sql);
3897
                }
3898
                $row['display_order'] = $i;
3899
                $lps[$row['iid']] = $row;
3900
                $lp_order[$i] = $row['iid'];
3901
                $i++;
3902
            }
3903
        }
3904
        if ($num > 1) { // If there's only one element, no need to sort.
3905
            $order = $lps[$lp_id]['display_order'];
3906
            if ($order > 1) { // If it's the first element, no need to move up.
3907
                $sql = "UPDATE $lp_table SET display_order = $order
3908
                        WHERE iid = ".$lp_order[$order - 1];
3909
                Database::query($sql);
3910
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3911
                        WHERE iid = $lp_id";
3912
                Database::query($sql);
3913
            }
3914
        }
3915
3916
        return true;
3917
    }
3918
3919
    /**
3920
     * Move a learnpath down (display_order).
3921
     *
3922
     * @param int $lp_id      Learnpath ID
3923
     * @param int $categoryId Category ID
3924
     *
3925
     * @return bool
3926
     */
3927
    public static function move_down($lp_id, $categoryId = 0)
3928
    {
3929
        $courseId = api_get_course_int_id();
3930
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3931
3932
        $categoryCondition = '';
3933
        if (!empty($categoryId)) {
3934
            $categoryId = (int) $categoryId;
3935
            $categoryCondition = " AND category_id = $categoryId";
3936
        }
3937
3938
        $sql = "SELECT * FROM $lp_table
3939
                WHERE c_id = $courseId
3940
                $categoryCondition
3941
                ORDER BY display_order";
3942
        $res = Database::query($sql);
3943
        if (false === $res) {
3944
            return false;
3945
        }
3946
        $lps = [];
3947
        $lp_order = [];
3948
        $num = Database::num_rows($res);
3949
        $max = 0;
3950
        // First check the order is correct, globally (might be wrong because
3951
        // of versions < 1.8.4).
3952
        if ($num > 0) {
3953
            $i = 1;
3954
            while ($row = Database::fetch_array($res)) {
3955
                $max = $i;
3956
                if ($row['display_order'] != $i) {
3957
                    // If we find a gap in the order, we need to fix it.
3958
                    $sql = "UPDATE $lp_table SET display_order = $i
3959
                              WHERE iid = ".$row['iid'];
3960
                    Database::query($sql);
3961
                }
3962
                $row['display_order'] = $i;
3963
                $lps[$row['iid']] = $row;
3964
                $lp_order[$i] = $row['iid'];
3965
                $i++;
3966
            }
3967
        }
3968
        if ($num > 1) { // If there's only one element, no need to sort.
3969
            $order = $lps[$lp_id]['display_order'];
3970
            if ($order < $max) { // If it's the first element, no need to move up.
3971
                $sql = "UPDATE $lp_table SET display_order = $order
3972
                        WHERE iid = ".$lp_order[$order + 1];
3973
                Database::query($sql);
3974
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
3975
                        WHERE iid = $lp_id";
3976
                Database::query($sql);
3977
            }
3978
        }
3979
3980
        return true;
3981
    }
3982
3983
    /**
3984
     * Updates learnpath attributes to point to the next element
3985
     * The last part is similar to set_current_item but processing the other way around.
3986
     */
3987
    public function next()
3988
    {
3989
        if ($this->debug > 0) {
3990
            error_log('In learnpath::next()', 0);
3991
        }
3992
        $this->last = $this->get_current_item_id();
3993
        $this->items[$this->last]->save(
3994
            false,
3995
            $this->prerequisites_match($this->last)
3996
        );
3997
        $this->autocomplete_parents($this->last);
3998
        $new_index = $this->get_next_index();
3999
        if ($this->debug > 2) {
4000
            error_log('New index: '.$new_index, 0);
4001
        }
4002
        $this->index = $new_index;
4003
        if ($this->debug > 2) {
4004
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4005
        }
4006
        $this->current = $this->ordered_items[$new_index];
4007
        if ($this->debug > 2) {
4008
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4009
        }
4010
    }
4011
4012
    /**
4013
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4014
     * class, this might be redefined to allow several behaviours depending on the document type.
4015
     *
4016
     * @param int $id Resource ID
4017
     */
4018
    public function open($id)
4019
    {
4020
        // TODO:
4021
        // set the current resource attribute to this resource
4022
        // switch on element type (redefine in child class?)
4023
        // set status for this item to "opened"
4024
        // start timer
4025
        // initialise score
4026
        $this->index = 0; //or = the last item seen (see $this->last)
4027
    }
4028
4029
    /**
4030
     * Check that all prerequisites are fulfilled. Returns true and an
4031
     * empty string on success, returns false
4032
     * and the prerequisite string on error.
4033
     * This function is based on the rules for aicc_script language as
4034
     * described in the SCORM 1.2 CAM documentation page 108.
4035
     *
4036
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4037
     *
4038
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4039
     *              string otherwise
4040
     */
4041
    public function prerequisites_match($itemId = null)
4042
    {
4043
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4044
        if ($allow) {
4045
            if (api_is_allowed_to_edit() ||
4046
                api_is_platform_admin(true) ||
4047
                api_is_drh() ||
4048
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4049
            ) {
4050
                return true;
4051
            }
4052
        }
4053
4054
        $debug = $this->debug;
4055
        if ($debug > 0) {
4056
            error_log('In learnpath::prerequisites_match()');
4057
        }
4058
4059
        if (empty($itemId)) {
4060
            $itemId = $this->current;
4061
        }
4062
4063
        $currentItem = $this->getItem($itemId);
4064
4065
        if ($currentItem) {
4066
            if (2 == $this->type) {
4067
                // Getting prereq from scorm
4068
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4069
            } else {
4070
                $prereq_string = $currentItem->get_prereq_string();
4071
            }
4072
4073
            if (empty($prereq_string)) {
4074
                if ($debug > 0) {
4075
                    error_log('Found prereq_string is empty return true');
4076
                }
4077
4078
                return true;
4079
            }
4080
4081
            // Clean spaces.
4082
            $prereq_string = str_replace(' ', '', $prereq_string);
4083
            if ($debug > 0) {
4084
                error_log('Found prereq_string: '.$prereq_string, 0);
4085
            }
4086
4087
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4088
            $result = $currentItem->parse_prereq(
4089
                $prereq_string,
4090
                $this->items,
4091
                $this->refs_list,
4092
                $this->get_user_id()
4093
            );
4094
4095
            if (false === $result) {
4096
                $this->set_error_msg($currentItem->prereq_alert);
4097
            }
4098
        } else {
4099
            $result = true;
4100
            if ($debug > 1) {
4101
                error_log('$this->items['.$itemId.'] was not an object', 0);
4102
            }
4103
        }
4104
4105
        if ($debug > 1) {
4106
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4107
        }
4108
4109
        return $result;
4110
    }
4111
4112
    /**
4113
     * Updates learnpath attributes to point to the previous element
4114
     * The last part is similar to set_current_item but processing the other way around.
4115
     */
4116
    public function previous()
4117
    {
4118
        $this->last = $this->get_current_item_id();
4119
        $this->items[$this->last]->save(
4120
            false,
4121
            $this->prerequisites_match($this->last)
4122
        );
4123
        $this->autocomplete_parents($this->last);
4124
        $new_index = $this->get_previous_index();
4125
        $this->index = $new_index;
4126
        $this->current = $this->ordered_items[$new_index];
4127
    }
4128
4129
    /**
4130
     * Publishes a learnpath. This basically means show or hide the learnpath
4131
     * to normal users.
4132
     * Can be used as abstract.
4133
     *
4134
     * @param int $id         Learnpath ID
4135
     * @param int $visibility New visibility (1 = visible/published, 0= invisible/draft)
4136
     *
4137
     * @return bool
4138
     */
4139
    public static function toggleVisibility($id, $visibility = 1)
4140
    {
4141
        $repo = Container::getLpRepository();
4142
        $lp = $repo->find($id);
4143
4144
        if (!$lp) {
4145
            return false;
4146
        }
4147
4148
        $visibility = (int) $visibility;
4149
4150
        if (1 === $visibility) {
4151
            $repo->setVisibilityPublished($lp);
4152
        } else {
4153
            $repo->setVisibilityDraft($lp);
4154
        }
4155
4156
        return true;
4157
4158
        /*$action = 'visible';
4159
        if (1 != $set_visibility) {
4160
            $action = 'invisible';
4161
            self::toggle_publish($lp_id, 'i');
4162
        }
4163
4164
        return api_item_property_update(
4165
            api_get_course_info(),
4166
            TOOL_LEARNPATH,
4167
            $lp_id,
4168
            $action,
4169
            api_get_user_id()
4170
        );*/
4171
    }
4172
4173
    /**
4174
     * Publishes a learnpath category.
4175
     * This basically means show or hide the learnpath category to normal users.
4176
     *
4177
     * @param int $id
4178
     * @param int $visibility
4179
     *
4180
     * @return bool
4181
     */
4182
    public static function toggleCategoryVisibility($id, $visibility = 1)
4183
    {
4184
        $repo = Container::getLpCategoryRepository();
4185
        $resource = $repo->find($id);
4186
4187
        if (!$resource) {
4188
            return false;
4189
        }
4190
4191
        $visibility = (int) $visibility;
4192
4193
        if (1 === $visibility) {
4194
            $repo->setVisibilityPublished($resource);
4195
        } else {
4196
            $repo->setVisibilityDraft($resource);
4197
            self::toggleCategoryPublish($id, 0);
4198
        }
4199
4200
        return false;
4201
        /*
4202
        $action = 'visible';
4203
        if (1 != $visibility) {
4204
            self::toggleCategoryPublish($id, 0);
4205
            $action = 'invisible';
4206
        }
4207
4208
        return api_item_property_update(
4209
            api_get_course_info(),
4210
            TOOL_LEARNPATH_CATEGORY,
4211
            $id,
4212
            $action,
4213
            api_get_user_id()
4214
        );*/
4215
    }
4216
4217
    /**
4218
     * Publishes a learnpath. This basically means show or hide the learnpath
4219
     * on the course homepage.
4220
     *
4221
     * @param int    $id            Learnpath id
4222
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4223
     *
4224
     * @return bool
4225
     */
4226
    public static function togglePublish($id, $setVisibility = 'v')
4227
    {
4228
        $addShortcut = false;
4229
        if ('v' === $setVisibility) {
4230
            $addShortcut = true;
4231
        }
4232
        $repo = Container::getLpRepository();
4233
        /** @var CLp $lp */
4234
        $lp = $repo->find($id);
4235
        if (null === $lp) {
4236
            return false;
4237
        }
4238
        $repoShortcut = Container::getShortcutRepository();
4239
        $courseEntity = api_get_course_entity();
4240
4241
        if ($addShortcut) {
4242
            $repoShortcut->addShortCut($lp, $courseEntity, $courseEntity, api_get_session_entity());
4243
        } else {
4244
            $repoShortcut->removeShortCut($lp);
4245
        }
4246
4247
        return true;
4248
4249
        /*
4250
        $course_id = api_get_course_int_id();
4251
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4252
        $lp_id = (int) $lp_id;
4253
        $sql = "SELECT * FROM $tbl_lp
4254
                WHERE iid = $lp_id";
4255
        $result = Database::query($sql);
4256
4257
        if (Database::num_rows($result)) {
4258
            $row = Database::fetch_array($result);
4259
            $name = Database::escape_string($row['name']);
4260
            if ($set_visibility == 'i') {
4261
                $v = 0;
4262
            }
4263
            if ($set_visibility == 'v') {
4264
                $v = 1;
4265
            }
4266
4267
            $session_id = api_get_session_id();
4268
            $session_condition = api_get_session_condition($session_id);
4269
4270
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4271
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4272
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4273
4274
            $sql = "SELECT * FROM $tbl_tool
4275
                    WHERE
4276
                        c_id = $course_id AND
4277
                        (link = '$link' OR link = '$oldLink') AND
4278
                        image = 'scormbuilder.gif' AND
4279
                        (
4280
                            link LIKE '$link%' OR
4281
                            link LIKE '$oldLink%'
4282
                        )
4283
                        $session_condition
4284
                    ";
4285
4286
            $result = Database::query($sql);
4287
            $num = Database::num_rows($result);
4288
            if ($set_visibility == 'i' && $num > 0) {
4289
                $sql = "DELETE FROM $tbl_tool
4290
                        WHERE
4291
                            c_id = $course_id AND
4292
                            (link = '$link' OR link = '$oldLink') AND
4293
                            image='scormbuilder.gif'
4294
                            $session_condition";
4295
                Database::query($sql);
4296
            } elseif ($set_visibility == 'v' && $num == 0) {
4297
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4298
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4299
                Database::query($sql);
4300
                $insertId = Database::insert_id();
4301
                if ($insertId) {
4302
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4303
                    Database::query($sql);
4304
                }
4305
            } elseif ($set_visibility == 'v' && $num > 0) {
4306
                $sql = "UPDATE $tbl_tool SET
4307
                            c_id = $course_id,
4308
                            name = '$name',
4309
                            link = '$link',
4310
                            image = 'scormbuilder.gif',
4311
                            visibility = '$v',
4312
                            admin = '0',
4313
                            address = 'pastillegris.gif',
4314
                            added_tool = 0,
4315
                            session_id = $session_id
4316
                        WHERE
4317
                            c_id = ".$course_id." AND
4318
                            (link = '$link' OR link = '$oldLink') AND
4319
                            image='scormbuilder.gif'
4320
                            $session_condition
4321
                        ";
4322
                Database::query($sql);
4323
            } else {
4324
                // Parameter and database incompatible, do nothing, exit.
4325
                return false;
4326
            }
4327
        } else {
4328
            return false;
4329
        }*/
4330
    }
4331
4332
    /**
4333
     * Show or hide the learnpath category on the course homepage.
4334
     *
4335
     * @param int $id
4336
     * @param int $setVisibility
4337
     *
4338
     * @return bool
4339
     */
4340
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4341
    {
4342
        $setVisibility = (int) $setVisibility;
4343
        $addShortcut = false;
4344
        if (1 === $setVisibility) {
4345
            $addShortcut = true;
4346
        }
4347
4348
        $repo = Container::getLpCategoryRepository();
4349
        /** @var CLpCategory $lp */
4350
        $category = $repo->find($id);
4351
4352
        if (null === $category) {
4353
            return false;
4354
        }
4355
4356
        $repoShortcut = Container::getShortcutRepository();
4357
        if ($addShortcut) {
4358
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4359
            $repoShortcut->addShortCut($category, $courseEntity, $courseEntity, api_get_session_entity());
4360
        } else {
4361
            $repoShortcut->removeShortCut($category);
4362
        }
4363
4364
        return true;
4365
    }
4366
4367
    /**
4368
     * Check if the learnpath category is visible for a user.
4369
     *
4370
     * @return bool
4371
     */
4372
    public static function categoryIsVisibleForStudent(
4373
        CLpCategory $category,
4374
        User $user,
4375
        Course $course,
4376
        Session $session = null
4377
    ) {
4378
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4379
4380
        if ($isAllowedToEdit) {
4381
            return true;
4382
        }
4383
4384
        $repo = Container::getLpCategoryRepository();
4385
        $categoryVisibility = $category->isVisible($course, $session);
4386
4387
        /*$categoryVisibility = api_get_item_visibility(
4388
            $courseInfo,
4389
            TOOL_LEARNPATH_CATEGORY,
4390
            $category->getId(),
4391
            $sessionId
4392
        );*/
4393
4394
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4395
            return false;
4396
        }
4397
4398
        $subscriptionSettings = self::getSubscriptionSettings();
4399
4400
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4401
            return true;
4402
        }
4403
4404
        $noUserSubscribed = false;
4405
        $noGroupSubscribed = true;
4406
        $users = $category->getUsers();
4407
        if (empty($users) || !$users->count()) {
4408
            $noUserSubscribed = true;
4409
        } elseif ($category->hasUserAdded($user)) {
4410
            return true;
4411
        }
4412
4413
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4414
        $em = Database::getManager();
4415
4416
        /** @var ItemPropertyRepository $itemRepo */
4417
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4418
4419
        /** @var CourseRepository $courseRepo */
4420
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4421
        $session = null;
4422
        if (!empty($sessionId)) {
4423
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4424
        }
4425
4426
        $course = $courseRepo->find($courseId);
4427
4428
        if (0 != $courseId) {
4429
            // Subscribed groups to a LP
4430
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4431
                    TOOL_LEARNPATH_CATEGORY,
4432
                    $category->getId(),
4433
                    $course,
4434
                    $session
4435
                );
4436
        }
4437
4438
        if (!empty($subscribedGroupsInLp)) {
4439
            $noGroupSubscribed = false;
4440
            if (!empty($groups)) {
4441
                $groups = array_column($groups, 'iid');
4442
                /** @var CItemProperty $item */
4443
                foreach ($subscribedGroupsInLp as $item) {
4444
                    if ($item->getGroup() &&
4445
                        in_array($item->getGroup()->getId(), $groups)
4446
                    ) {
4447
                        return true;
4448
                    }
4449
                }
4450
            }
4451
        }
4452
        $response = $noGroupSubscribed && $noUserSubscribed;
4453
4454
        return $response;
4455
    }
4456
4457
    /**
4458
     * Check if a learnpath category is published as course tool.
4459
     *
4460
     * @param int $courseId
4461
     *
4462
     * @return bool
4463
     */
4464
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4465
    {
4466
        return false;
4467
        $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...
4468
        $em = Database::getManager();
4469
4470
        $tools = $em
4471
            ->createQuery("
4472
                SELECT t FROM ChamiloCourseBundle:CTool t
4473
                WHERE t.course = :course AND
4474
                    t.name = :name AND
4475
                    t.image LIKE 'lp_category.%' AND
4476
                    t.link LIKE :link
4477
            ")
4478
            ->setParameters([
4479
                'course' => $courseId,
4480
                'name' => strip_tags($category->getName()),
4481
                'link' => "$link%",
4482
            ])
4483
            ->getResult();
4484
4485
        /** @var CTool $tool */
4486
        $tool = current($tools);
4487
4488
        return $tool ? $tool->getVisibility() : false;
4489
    }
4490
4491
    /**
4492
     * Restart the whole learnpath. Return the URL of the first element.
4493
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4494
     * To use a similar method  statically, use the create_new_attempt() method.
4495
     *
4496
     * @return bool
4497
     */
4498
    public function restart()
4499
    {
4500
        if ($this->debug > 0) {
4501
            error_log('In learnpath::restart()', 0);
4502
        }
4503
        // TODO
4504
        // Call autosave method to save the current progress.
4505
        //$this->index = 0;
4506
        if (api_is_invitee()) {
4507
            return false;
4508
        }
4509
        $session_id = api_get_session_id();
4510
        $course_id = api_get_course_int_id();
4511
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4512
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4513
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4514
        if ($this->debug > 2) {
4515
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4516
        }
4517
        Database::query($sql);
4518
        $view_id = Database::insert_id();
4519
4520
        if ($view_id) {
4521
            $this->lp_view_id = $view_id;
4522
            $this->attempt = $this->attempt + 1;
4523
        } else {
4524
            $this->error = 'Could not insert into item_view table...';
4525
4526
            return false;
4527
        }
4528
        $this->autocomplete_parents($this->current);
4529
        foreach ($this->items as $index => $dummy) {
4530
            $this->items[$index]->restart();
4531
            $this->items[$index]->set_lp_view($this->lp_view_id);
4532
        }
4533
        $this->first();
4534
4535
        return true;
4536
    }
4537
4538
    /**
4539
     * Saves the current item.
4540
     *
4541
     * @return bool
4542
     */
4543
    public function save_current()
4544
    {
4545
        $debug = $this->debug;
4546
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4547
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4548
        if ($debug) {
4549
            error_log('save_current() saving item '.$this->current, 0);
4550
            error_log(''.print_r($this->items, true), 0);
4551
        }
4552
        if (isset($this->items[$this->current]) &&
4553
            is_object($this->items[$this->current])
4554
        ) {
4555
            if ($debug) {
4556
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4557
            }
4558
4559
            $res = $this->items[$this->current]->save(
4560
                false,
4561
                $this->prerequisites_match($this->current)
4562
            );
4563
            $this->autocomplete_parents($this->current);
4564
            $status = $this->items[$this->current]->get_status();
4565
            $this->update_queue[$this->current] = $status;
4566
4567
            if ($debug) {
4568
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4569
            }
4570
4571
            return $res;
4572
        }
4573
4574
        return false;
4575
    }
4576
4577
    /**
4578
     * Saves the given item.
4579
     *
4580
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4581
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4582
     *
4583
     * @return bool
4584
     */
4585
    public function save_item($item_id = null, $from_outside = true)
4586
    {
4587
        $debug = $this->debug;
4588
        if ($debug) {
4589
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4590
        }
4591
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4592
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4593
        if (empty($item_id)) {
4594
            $item_id = (int) $_REQUEST['id'];
4595
        }
4596
4597
        if (empty($item_id)) {
4598
            $item_id = $this->get_current_item_id();
4599
        }
4600
        if (isset($this->items[$item_id]) &&
4601
            is_object($this->items[$item_id])
4602
        ) {
4603
            if ($debug) {
4604
                error_log('Object exists');
4605
            }
4606
4607
            // Saving the item.
4608
            $res = $this->items[$item_id]->save(
4609
                $from_outside,
4610
                $this->prerequisites_match($item_id)
4611
            );
4612
4613
            if ($debug) {
4614
                error_log('update_queue before:');
4615
                error_log(print_r($this->update_queue, 1));
4616
            }
4617
            $this->autocomplete_parents($item_id);
4618
4619
            $status = $this->items[$item_id]->get_status();
4620
            $this->update_queue[$item_id] = $status;
4621
4622
            if ($debug) {
4623
                error_log('get_status(): '.$status);
4624
                error_log('update_queue after:');
4625
                error_log(print_r($this->update_queue, 1));
4626
            }
4627
4628
            return $res;
4629
        }
4630
4631
        return false;
4632
    }
4633
4634
    /**
4635
     * Saves the last item seen's ID only in case.
4636
     */
4637
    public function save_last()
4638
    {
4639
        $course_id = api_get_course_int_id();
4640
        $debug = $this->debug;
4641
        if ($debug) {
4642
            error_log('In learnpath::save_last()', 0);
4643
        }
4644
        $session_condition = api_get_session_condition(
4645
            api_get_session_id(),
4646
            true,
4647
            false
4648
        );
4649
        $table = Database::get_course_table(TABLE_LP_VIEW);
4650
4651
        $userId = $this->get_user_id();
4652
        if (empty($userId)) {
4653
            $userId = api_get_user_id();
4654
            if ($debug) {
4655
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4656
            }
4657
        }
4658
        if (isset($this->current) && !api_is_invitee()) {
4659
            if ($debug) {
4660
                error_log('Saving current item ('.$this->current.') for later review', 0);
4661
            }
4662
            $sql = "UPDATE $table SET
4663
                        last_item = ".$this->get_current_item_id()."
4664
                    WHERE
4665
                        c_id = $course_id AND
4666
                        lp_id = ".$this->get_id()." AND
4667
                        user_id = ".$userId." ".$session_condition;
4668
4669
            if ($debug) {
4670
                error_log('Saving last item seen : '.$sql, 0);
4671
            }
4672
            Database::query($sql);
4673
        }
4674
4675
        if (!api_is_invitee()) {
4676
            // Save progress.
4677
            [$progress] = $this->get_progress_bar_text('%');
4678
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4679
            $scoreAsProgress = $this->getUseScoreAsProgress();
4680
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4681
                if ($debug) {
4682
                    error_log("Return false: Dont save score: $score");
4683
                    error_log("progress: $progress");
4684
                }
4685
4686
                return false;
4687
            }
4688
4689
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4690
                $storedProgress = self::getProgress(
4691
                    $this->get_id(),
4692
                    $userId,
4693
                    $course_id,
4694
                    $this->get_lp_session_id()
4695
                );
4696
4697
                // Check if the stored progress is higher than the new value
4698
                if ($storedProgress >= $progress) {
4699
                    if ($debug) {
4700
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4701
                    }
4702
4703
                    return false;
4704
                }
4705
            }
4706
            if ($progress >= 0 && $progress <= 100) {
4707
                $progress = (int) $progress;
4708
                $sql = "UPDATE $table SET
4709
                            progress = $progress
4710
                        WHERE
4711
                            c_id = $course_id AND
4712
                            lp_id = ".$this->get_id()." AND
4713
                            user_id = ".$userId." ".$session_condition;
4714
                // Ignore errors as some tables might not have the progress field just yet.
4715
                Database::query($sql);
4716
                $this->progress_db = $progress;
4717
            }
4718
        }
4719
    }
4720
4721
    /**
4722
     * Sets the current item ID (checks if valid and authorized first).
4723
     *
4724
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4725
     */
4726
    public function set_current_item($item_id = null)
4727
    {
4728
        $debug = $this->debug;
4729
        if ($debug) {
4730
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4731
        }
4732
        if (empty($item_id)) {
4733
            if ($debug) {
4734
                error_log('No new current item given, ignore...', 0);
4735
            }
4736
            // Do nothing.
4737
        } else {
4738
            if ($debug) {
4739
                error_log('New current item given is '.$item_id.'...', 0);
4740
            }
4741
            if (is_numeric($item_id)) {
4742
                $item_id = (int) $item_id;
4743
                // TODO: Check in database here.
4744
                $this->last = $this->current;
4745
                $this->current = $item_id;
4746
                // TODO: Update $this->index as well.
4747
                foreach ($this->ordered_items as $index => $item) {
4748
                    if ($item == $this->current) {
4749
                        $this->index = $index;
4750
                        break;
4751
                    }
4752
                }
4753
                if ($debug) {
4754
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4755
                }
4756
            } else {
4757
                if ($debug) {
4758
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4759
                }
4760
            }
4761
        }
4762
    }
4763
4764
    /**
4765
     * Sets the JS lib setting in the database directly.
4766
     * This is the JavaScript library file this lp needs to load on startup.
4767
     *
4768
     * @param string $lib Proximity setting
4769
     *
4770
     * @return bool True on update success. False otherwise.
4771
     */
4772
    public function set_jslib($lib = '')
4773
    {
4774
        $lp = $this->get_id();
4775
4776
        if (0 != $lp) {
4777
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4778
            $lib = Database::escape_string($lib);
4779
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4780
                    WHERE iid = $lp";
4781
            $res = Database::query($sql);
4782
4783
            return $res;
4784
        }
4785
4786
        return false;
4787
    }
4788
4789
    /**
4790
     * Set index specified prefix terms for all items in this path.
4791
     *
4792
     * @param string $terms_string Comma-separated list of terms
4793
     * @param string $prefix       Xapian term prefix
4794
     *
4795
     * @return bool False on error, true otherwise
4796
     */
4797
    public function set_terms_by_prefix($terms_string, $prefix)
4798
    {
4799
        $course_id = api_get_course_int_id();
4800
        if ('true' !== api_get_setting('search_enabled')) {
4801
            return false;
4802
        }
4803
4804
        if (!extension_loaded('xapian')) {
4805
            return false;
4806
        }
4807
4808
        $terms_string = trim($terms_string);
4809
        $terms = explode(',', $terms_string);
4810
        array_walk($terms, 'trim_value');
4811
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4812
4813
        // Don't do anything if no change, verify only at DB, not the search engine.
4814
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
4815
            return false;
4816
        }
4817
4818
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4819
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4820
4821
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4822
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4823
        $lp_id = (int) $_POST['lp_id'];
4824
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4825
        $result = Database::query($sql);
4826
        $di = new ChamiloIndexer();
4827
4828
        while ($lp_item = Database::fetch_array($result)) {
4829
            // Get search_did.
4830
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4831
            $sql = 'SELECT * FROM %s
4832
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4833
                    LIMIT 1';
4834
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4835
4836
            //echo $sql; echo '<br>';
4837
            $res = Database::query($sql);
4838
            if (Database::num_rows($res) > 0) {
4839
                $se_ref = Database::fetch_array($res);
4840
                // Compare terms.
4841
                $doc = $di->get_document($se_ref['search_did']);
4842
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
4843
                $xterms = [];
4844
                foreach ($xapian_terms as $xapian_term) {
4845
                    $xterms[] = substr($xapian_term['name'], 1);
4846
                }
4847
4848
                $dterms = $terms;
4849
                $missing_terms = array_diff($dterms, $xterms);
4850
                $deprecated_terms = array_diff($xterms, $dterms);
4851
4852
                // Save it to search engine.
4853
                foreach ($missing_terms as $term) {
4854
                    $doc->add_term($prefix.$term, 1);
4855
                }
4856
                foreach ($deprecated_terms as $term) {
4857
                    $doc->remove_term($prefix.$term);
4858
                }
4859
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
4860
                $di->getDb()->flush();
4861
            }
4862
        }
4863
4864
        return true;
4865
    }
4866
4867
    /**
4868
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
4869
     *
4870
     * @param int $id DB ID of the item
4871
     */
4872
    public function set_previous_item($id)
4873
    {
4874
        if ($this->debug > 0) {
4875
            error_log('In learnpath::set_previous_item()', 0);
4876
        }
4877
        $this->last = $id;
4878
    }
4879
4880
    /**
4881
     * Sets use_max_score.
4882
     *
4883
     * @param int $use_max_score Optional string giving the new location of this learnpath
4884
     *
4885
     * @return bool True on success / False on error
4886
     */
4887
    public function set_use_max_score($use_max_score = 1)
4888
    {
4889
        $use_max_score = (int) $use_max_score;
4890
        $this->use_max_score = $use_max_score;
4891
        $table = Database::get_course_table(TABLE_LP_MAIN);
4892
        $lp_id = $this->get_id();
4893
        $sql = "UPDATE $table SET
4894
                    use_max_score = '".$this->use_max_score."'
4895
                WHERE iid = $lp_id";
4896
        Database::query($sql);
4897
4898
        return true;
4899
    }
4900
4901
    /**
4902
     * Sets and saves the expired_on date.
4903
     *
4904
     * @return bool Returns true if author's name is not empty
4905
     */
4906
    public function set_modified_on()
4907
    {
4908
        $this->modified_on = api_get_utc_datetime();
4909
        $table = Database::get_course_table(TABLE_LP_MAIN);
4910
        $lp_id = $this->get_id();
4911
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
4912
                WHERE iid = $lp_id";
4913
        Database::query($sql);
4914
4915
        return true;
4916
    }
4917
4918
    /**
4919
     * Sets the object's error message.
4920
     *
4921
     * @param string $error Error message. If empty, reinits the error string
4922
     */
4923
    public function set_error_msg($error = '')
4924
    {
4925
        if ($this->debug > 0) {
4926
            error_log('In learnpath::set_error_msg()', 0);
4927
        }
4928
        if (empty($error)) {
4929
            $this->error = '';
4930
        } else {
4931
            $this->error .= $error;
4932
        }
4933
    }
4934
4935
    /**
4936
     * Launches the current item if not 'sco'
4937
     * (starts timer and make sure there is a record ready in the DB).
4938
     *
4939
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
4940
     *
4941
     * @return bool
4942
     */
4943
    public function start_current_item($allow_new_attempt = false)
4944
    {
4945
        $debug = $this->debug;
4946
        if ($debug) {
4947
            error_log('In learnpath::start_current_item()');
4948
            error_log('current: '.$this->current);
4949
        }
4950
        if (0 != $this->current && isset($this->items[$this->current]) &&
4951
            is_object($this->items[$this->current])
4952
        ) {
4953
            $type = $this->get_type();
4954
            $item_type = $this->items[$this->current]->get_type();
4955
            if ($debug) {
4956
                error_log('item type: '.$item_type);
4957
                error_log('lp type: '.$type);
4958
            }
4959
            if ((2 == $type && 'sco' !== $item_type) ||
4960
                (3 == $type && 'au' !== $item_type) ||
4961
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
4962
            ) {
4963
                $this->items[$this->current]->open($allow_new_attempt);
4964
                $this->autocomplete_parents($this->current);
4965
                $prereq_check = $this->prerequisites_match($this->current);
4966
                if ($debug) {
4967
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
4968
                }
4969
                $this->items[$this->current]->save(false, $prereq_check);
4970
            }
4971
            // If sco, then it is supposed to have been updated by some other call.
4972
            if ('sco' === $item_type) {
4973
                $this->items[$this->current]->restart();
4974
            }
4975
        }
4976
        if ($debug) {
4977
            error_log('lp_view_session_id');
4978
            error_log($this->lp_view_session_id);
4979
            error_log('api session id');
4980
            error_log(api_get_session_id());
4981
            error_log('End of learnpath::start_current_item()');
4982
        }
4983
4984
        return true;
4985
    }
4986
4987
    /**
4988
     * Stops the processing and counters for the old item (as held in $this->last).
4989
     *
4990
     * @return bool True/False
4991
     */
4992
    public function stop_previous_item()
4993
    {
4994
        $debug = $this->debug;
4995
        if ($debug) {
4996
            error_log('In learnpath::stop_previous_item()', 0);
4997
        }
4998
4999
        if (0 != $this->last && $this->last != $this->current &&
5000
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5001
        ) {
5002
            if ($debug) {
5003
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5004
            }
5005
            switch ($this->get_type()) {
5006
                case '3':
5007
                    if ('au' != $this->items[$this->last]->get_type()) {
5008
                        if ($debug) {
5009
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5010
                        }
5011
                        $this->items[$this->last]->close();
5012
                    } else {
5013
                        if ($debug) {
5014
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5015
                        }
5016
                    }
5017
                    break;
5018
                case '2':
5019
                    if ('sco' != $this->items[$this->last]->get_type()) {
5020
                        if ($debug) {
5021
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5022
                        }
5023
                        $this->items[$this->last]->close();
5024
                    } else {
5025
                        if ($debug) {
5026
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5027
                        }
5028
                    }
5029
                    break;
5030
                case '1':
5031
                default:
5032
                    if ($debug) {
5033
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5034
                    }
5035
                    $this->items[$this->last]->close();
5036
                    break;
5037
            }
5038
        } else {
5039
            if ($debug) {
5040
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5041
            }
5042
5043
            return false;
5044
        }
5045
5046
        return true;
5047
    }
5048
5049
    /**
5050
     * Updates the default view mode from fullscreen to embedded and inversely.
5051
     *
5052
     * @return string The current default view mode ('fullscreen' or 'embedded')
5053
     */
5054
    public function update_default_view_mode()
5055
    {
5056
        $table = Database::get_course_table(TABLE_LP_MAIN);
5057
        $sql = "SELECT * FROM $table
5058
                WHERE iid = ".$this->get_id();
5059
        $res = Database::query($sql);
5060
        if (Database::num_rows($res) > 0) {
5061
            $row = Database::fetch_array($res);
5062
            $default_view_mode = $row['default_view_mod'];
5063
            $view_mode = $default_view_mode;
5064
            switch ($default_view_mode) {
5065
                case 'fullscreen': // default with popup
5066
                    $view_mode = 'embedded';
5067
                    break;
5068
                case 'embedded': // default view with left menu
5069
                    $view_mode = 'embedframe';
5070
                    break;
5071
                case 'embedframe': //folded menu
5072
                    $view_mode = 'impress';
5073
                    break;
5074
                case 'impress':
5075
                    $view_mode = 'fullscreen';
5076
                    break;
5077
            }
5078
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5079
                    WHERE iid = ".$this->get_id();
5080
            Database::query($sql);
5081
            $this->mode = $view_mode;
5082
5083
            return $view_mode;
5084
        }
5085
5086
        return -1;
5087
    }
5088
5089
    /**
5090
     * Updates the default behaviour about auto-commiting SCORM updates.
5091
     *
5092
     * @return bool True if auto-commit has been set to 'on', false otherwise
5093
     */
5094
    public function update_default_scorm_commit()
5095
    {
5096
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5097
        $sql = "SELECT * FROM $lp_table
5098
                WHERE iid = ".$this->get_id();
5099
        $res = Database::query($sql);
5100
        if (Database::num_rows($res) > 0) {
5101
            $row = Database::fetch_array($res);
5102
            $force = $row['force_commit'];
5103
            if (1 == $force) {
5104
                $force = 0;
5105
                $force_return = false;
5106
            } elseif (0 == $force) {
5107
                $force = 1;
5108
                $force_return = true;
5109
            }
5110
            $sql = "UPDATE $lp_table SET force_commit = $force
5111
                    WHERE iid = ".$this->get_id();
5112
            Database::query($sql);
5113
            $this->force_commit = $force_return;
5114
5115
            return $force_return;
5116
        }
5117
5118
        return -1;
5119
    }
5120
5121
    /**
5122
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5123
     *
5124
     * @return bool True on success, false on failure
5125
     */
5126
    public function update_display_order()
5127
    {
5128
        return;
5129
        $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...
5130
        $table = Database::get_course_table(TABLE_LP_MAIN);
5131
        $sql = "SELECT * FROM $table
5132
                WHERE c_id = $course_id
5133
                ORDER BY display_order";
5134
        $res = Database::query($sql);
5135
        if (false === $res) {
5136
            return false;
5137
        }
5138
5139
        $num = Database::num_rows($res);
5140
        // First check the order is correct, globally (might be wrong because
5141
        // of versions < 1.8.4).
5142
        if ($num > 0) {
5143
            $i = 1;
5144
            while ($row = Database::fetch_array($res)) {
5145
                if ($row['display_order'] != $i) {
5146
                    // If we find a gap in the order, we need to fix it.
5147
                    $sql = "UPDATE $table SET display_order = $i
5148
                            WHERE iid = ".$row['iid'];
5149
                    Database::query($sql);
5150
                }
5151
                $i++;
5152
            }
5153
        }
5154
5155
        return true;
5156
    }
5157
5158
    /**
5159
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5160
     *
5161
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5162
     */
5163
    public function update_reinit()
5164
    {
5165
        $force = $this->entity->getPreventReinit();
5166
        if (1 == $force) {
5167
            $force = 0;
5168
        } elseif (0 == $force) {
5169
            $force = 1;
5170
        }
5171
5172
        $table = Database::get_course_table(TABLE_LP_MAIN);
5173
        $sql = "UPDATE $table SET prevent_reinit = $force
5174
                WHERE iid = ".$this->get_id();
5175
        Database::query($sql);
5176
        $this->prevent_reinit = $force;
5177
5178
        return $force;
5179
    }
5180
5181
    /**
5182
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5183
     *
5184
     * @return string 'single', 'multi' or 'seriousgame'
5185
     *
5186
     * @author ndiechburg <[email protected]>
5187
     */
5188
    public function get_attempt_mode()
5189
    {
5190
        //Set default value for seriousgame_mode
5191
        if (!isset($this->seriousgame_mode)) {
5192
            $this->seriousgame_mode = 0;
5193
        }
5194
        // Set default value for prevent_reinit
5195
        if (!isset($this->prevent_reinit)) {
5196
            $this->prevent_reinit = 1;
5197
        }
5198
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5199
            return 'seriousgame';
5200
        }
5201
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5202
            return 'single';
5203
        }
5204
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5205
            return 'multiple';
5206
        }
5207
5208
        return 'single';
5209
    }
5210
5211
    /**
5212
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5213
     *
5214
     * @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...
5215
     *
5216
     * @return bool
5217
     *
5218
     * @author ndiechburg <[email protected]>
5219
     */
5220
    public function set_attempt_mode($mode)
5221
    {
5222
        switch ($mode) {
5223
            case 'seriousgame':
5224
                $sg_mode = 1;
5225
                $prevent_reinit = 1;
5226
                break;
5227
            case 'single':
5228
                $sg_mode = 0;
5229
                $prevent_reinit = 1;
5230
                break;
5231
            case 'multiple':
5232
                $sg_mode = 0;
5233
                $prevent_reinit = 0;
5234
                break;
5235
            default:
5236
                $sg_mode = 0;
5237
                $prevent_reinit = 0;
5238
                break;
5239
        }
5240
        $this->prevent_reinit = $prevent_reinit;
5241
        $this->seriousgame_mode = $sg_mode;
5242
        $table = Database::get_course_table(TABLE_LP_MAIN);
5243
        $sql = "UPDATE $table SET
5244
                prevent_reinit = $prevent_reinit ,
5245
                seriousgame_mode = $sg_mode
5246
                WHERE iid = ".$this->get_id();
5247
        $res = Database::query($sql);
5248
        if ($res) {
5249
            return true;
5250
        } else {
5251
            return false;
5252
        }
5253
    }
5254
5255
    /**
5256
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5257
     *
5258
     * @author ndiechburg <[email protected]>
5259
     */
5260
    public function switch_attempt_mode()
5261
    {
5262
        $mode = $this->get_attempt_mode();
5263
        switch ($mode) {
5264
            case 'single':
5265
                $next_mode = 'multiple';
5266
                break;
5267
            case 'multiple':
5268
                $next_mode = 'seriousgame';
5269
                break;
5270
            case 'seriousgame':
5271
            default:
5272
                $next_mode = 'single';
5273
                break;
5274
        }
5275
        $this->set_attempt_mode($next_mode);
5276
    }
5277
5278
    /**
5279
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5280
     * but possibility to do again a completed item.
5281
     *
5282
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5283
     *
5284
     * @author ndiechburg <[email protected]>
5285
     */
5286
    public function set_seriousgame_mode()
5287
    {
5288
        $table = Database::get_course_table(TABLE_LP_MAIN);
5289
        $force = $this->entity->getSeriousgameMode();
5290
        if (1 == $force) {
5291
            $force = 0;
5292
        } elseif (0 == $force) {
5293
            $force = 1;
5294
        }
5295
        $sql = "UPDATE $table SET seriousgame_mode = $force
5296
                WHERE iid = ".$this->get_id();
5297
        Database::query($sql);
5298
        $this->seriousgame_mode = $force;
5299
5300
        return $force;
5301
    }
5302
5303
    /**
5304
     * Updates the "scorm_debug" value that shows or hide the debug window.
5305
     *
5306
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5307
     */
5308
    public function update_scorm_debug()
5309
    {
5310
        $table = Database::get_course_table(TABLE_LP_MAIN);
5311
        $force = $this->entity->getDebug();
5312
        if (1 == $force) {
5313
            $force = 0;
5314
        } elseif (0 == $force) {
5315
            $force = 1;
5316
        }
5317
        $sql = "UPDATE $table SET debug = $force
5318
                WHERE iid = ".$this->get_id();
5319
        Database::query($sql);
5320
        $this->scorm_debug = $force;
5321
5322
        return $force;
5323
    }
5324
5325
    /**
5326
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5327
     *
5328
     * @author Kevin Van Den Haute
5329
     *
5330
     * @param  array
5331
     */
5332
    public function tree_array($array)
5333
    {
5334
        $array = $this->sort_tree_array($array);
5335
        $this->create_tree_array($array);
5336
    }
5337
5338
    /**
5339
     * Creates an array with the elements of the learning path tree in it.
5340
     *
5341
     * @author Kevin Van Den Haute
5342
     *
5343
     * @param array $array
5344
     * @param int   $parent
5345
     * @param int   $depth
5346
     * @param array $tmp
5347
     */
5348
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5349
    {
5350
        if (is_array($array)) {
5351
            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...
5352
                if ($array[$i]['parent_item_id'] == $parent) {
5353
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5354
                        $tmp[] = $array[$i]['parent_item_id'];
5355
                        $depth++;
5356
                    }
5357
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5358
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5359
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5360
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5361
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5362
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5363
                    $this->arrMenu[] = [
5364
                        'id' => $array[$i]['id'],
5365
                        'ref' => $ref,
5366
                        'item_type' => $array[$i]['item_type'],
5367
                        'title' => $array[$i]['title'],
5368
                        'title_raw' => $array[$i]['title_raw'],
5369
                        'path' => $path,
5370
                        'description' => $array[$i]['description'],
5371
                        'parent_item_id' => $array[$i]['parent_item_id'],
5372
                        'previous_item_id' => $array[$i]['previous_item_id'],
5373
                        'next_item_id' => $array[$i]['next_item_id'],
5374
                        'min_score' => $array[$i]['min_score'],
5375
                        'max_score' => $array[$i]['max_score'],
5376
                        'mastery_score' => $array[$i]['mastery_score'],
5377
                        'display_order' => $array[$i]['display_order'],
5378
                        'prerequisite' => $preq,
5379
                        'depth' => $depth,
5380
                        'audio' => $audio,
5381
                        'prerequisite_min_score' => $prerequisiteMinScore,
5382
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5383
                    ];
5384
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5385
                }
5386
            }
5387
        }
5388
    }
5389
5390
    /**
5391
     * Sorts a multi dimensional array by parent id and display order.
5392
     *
5393
     * @author Kevin Van Den Haute
5394
     *
5395
     * @param array $array (array with al the learning path items in it)
5396
     *
5397
     * @return array
5398
     */
5399
    public function sort_tree_array($array)
5400
    {
5401
        foreach ($array as $key => $row) {
5402
            $parent[$key] = $row['parent_item_id'];
5403
            $position[$key] = $row['display_order'];
5404
        }
5405
5406
        if (count($array) > 0) {
5407
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5408
        }
5409
5410
        return $array;
5411
    }
5412
5413
    /**
5414
     * Function that creates a html list of learning path items so that we can add audio files to them.
5415
     *
5416
     * @author Kevin Van Den Haute
5417
     *
5418
     * @return string
5419
     */
5420
    public function overview()
5421
    {
5422
        $return = '';
5423
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5424
5425
        // we need to start a form when we want to update all the mp3 files
5426
        if ('true' == $update_audio) {
5427
            $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">';
5428
        }
5429
        $return .= '<div id="message"></div>';
5430
        if (0 == count($this->items)) {
5431
            $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');
5432
        } else {
5433
            $return_audio = '<table class="table table-hover table-striped data_table">';
5434
            $return_audio .= '<tr>';
5435
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5436
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5437
            $return_audio .= '</tr>';
5438
5439
            if ('true' != $update_audio) {
5440
                $return .= '<div class="col-md-12">';
5441
                $return .= self::return_new_tree($update_audio);
5442
                $return .= '</div>';
5443
                $return .= Display::div(
5444
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5445
                    ['style' => 'float:left; margin-top:15px;width:100%']
5446
                );
5447
            } else {
5448
                $return_audio .= self::return_new_tree($update_audio);
5449
                $return .= $return_audio.'</table>';
5450
            }
5451
5452
            // We need to close the form when we are updating the mp3 files.
5453
            if ('true' == $update_audio) {
5454
                $return .= '<div class="footer-audio">';
5455
                $return .= Display::button(
5456
                    'save_audio',
5457
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5458
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5459
                );
5460
                $return .= '</div>';
5461
            }
5462
        }
5463
5464
        // We need to close the form when we are updating the mp3 files.
5465
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5466
            $return .= '</form>';
5467
        }
5468
5469
        return $return;
5470
    }
5471
5472
    /**
5473
     * @param string $update_audio
5474
     *
5475
     * @return array
5476
     */
5477
    public function processBuildMenuElements($update_audio = 'false')
5478
    {
5479
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5480
        $arrLP = $this->getItemsForForm();
5481
5482
        $this->tree_array($arrLP);
5483
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5484
        unset($this->arrMenu);
5485
        $default_data = null;
5486
        $default_content = null;
5487
        $elements = [];
5488
        $return_audio = null;
5489
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5490
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5491
        $countItems = count($arrLP);
5492
5493
        $upIcon = Display::return_icon(
5494
            'up.png',
5495
            get_lang('Up'),
5496
            [],
5497
            ICON_SIZE_TINY
5498
        );
5499
5500
        $disableUpIcon = Display::return_icon(
5501
            'up_na.png',
5502
            get_lang('Up'),
5503
            [],
5504
            ICON_SIZE_TINY
5505
        );
5506
5507
        $downIcon = Display::return_icon(
5508
            'down.png',
5509
            get_lang('Down'),
5510
            [],
5511
            ICON_SIZE_TINY
5512
        );
5513
5514
        $disableDownIcon = Display::return_icon(
5515
            'down_na.png',
5516
            get_lang('Down'),
5517
            [],
5518
            ICON_SIZE_TINY
5519
        );
5520
5521
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5522
5523
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5524
        $plugin = null;
5525
        if ($pluginCalendar) {
5526
            $plugin = LearningCalendarPlugin::create();
5527
        }
5528
5529
        for ($i = 0; $i < $countItems; $i++) {
5530
            $parent_id = $arrLP[$i]['parent_item_id'];
5531
            $title = $arrLP[$i]['title'];
5532
            $title_cut = $arrLP[$i]['title_raw'];
5533
            if (false === $show) {
5534
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5535
            }
5536
            // Link for the documents
5537
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5538
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5539
                $title_cut = Display::url(
5540
                    $title_cut,
5541
                    $url,
5542
                    [
5543
                        'class' => 'ajax moved',
5544
                        'data-title' => $title,
5545
                        'title' => $title,
5546
                    ]
5547
                );
5548
            }
5549
5550
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5551
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5552
                Session::write('pathItem', $arrLP[$i]['path']);
5553
            }
5554
5555
            $oddClass = 'row_even';
5556
            if (0 == ($i % 2)) {
5557
                $oddClass = 'row_odd';
5558
            }
5559
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5560
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5561
5562
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5563
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5564
            } else {
5565
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5566
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5567
                } else {
5568
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5569
                        $icon = Display::return_icon('certificate.png');
5570
                    } else {
5571
                        $icon = Display::return_icon('folder_document.png');
5572
                    }
5573
                }
5574
            }
5575
5576
            // The audio column.
5577
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5578
            $audio = '';
5579
            if (!$update_audio || 'true' != $update_audio) {
5580
                if (empty($arrLP[$i]['audio'])) {
5581
                    $audio .= '';
5582
                }
5583
            } else {
5584
                $types = self::getChapterTypes();
5585
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5586
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5587
                    if (!empty($arrLP[$i]['audio'])) {
5588
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5589
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
5590
                    }
5591
                }
5592
            }
5593
5594
            $return_audio .= Display::span($icon.' '.$title).
5595
                Display::tag(
5596
                    'td',
5597
                    $audio,
5598
                    ['style' => '']
5599
                );
5600
            $return_audio .= '</td>';
5601
            $move_icon = '';
5602
            $move_item_icon = '';
5603
            $edit_icon = '';
5604
            $delete_icon = '';
5605
            $audio_icon = '';
5606
            $prerequisities_icon = '';
5607
            $forumIcon = '';
5608
            $previewIcon = '';
5609
            $pluginCalendarIcon = '';
5610
            $orderIcons = '';
5611
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5612
5613
            if ($is_allowed_to_edit) {
5614
                if (!$update_audio || 'true' != $update_audio) {
5615
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
5616
                        $move_icon .= '<a class="moved" href="#">';
5617
                        $move_icon .= Display::return_icon(
5618
                            'move_everywhere.png',
5619
                            get_lang('Move'),
5620
                            [],
5621
                            ICON_SIZE_TINY
5622
                        );
5623
                        $move_icon .= '</a>';
5624
                    }
5625
                }
5626
5627
                // No edit for this item types
5628
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5629
                    if ('dir' != $arrLP[$i]['item_type']) {
5630
                        $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">';
5631
                        $edit_icon .= Display::return_icon(
5632
                            'edit.png',
5633
                            get_lang('Edit section description/name'),
5634
                            [],
5635
                            ICON_SIZE_TINY
5636
                        );
5637
                        $edit_icon .= '</a>';
5638
5639
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5640
                            $forumThread = null;
5641
                            if (isset($this->items[$arrLP[$i]['id']])) {
5642
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5643
                                    $this->course_int_id,
5644
                                    $this->lp_session_id
5645
                                );
5646
                            }
5647
                            if ($forumThread) {
5648
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5649
                                        'action' => 'dissociate_forum',
5650
                                        'id' => $arrLP[$i]['id'],
5651
                                        'lp_id' => $this->lp_id,
5652
                                    ]);
5653
                                $forumIcon = Display::url(
5654
                                    Display::return_icon(
5655
                                        'forum.png',
5656
                                        get_lang('Dissociate the forum of this learning path item'),
5657
                                        [],
5658
                                        ICON_SIZE_TINY
5659
                                    ),
5660
                                    $forumIconUrl,
5661
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
5662
                                );
5663
                            } else {
5664
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
5665
                                        'action' => 'create_forum',
5666
                                        'id' => $arrLP[$i]['id'],
5667
                                        'lp_id' => $this->lp_id,
5668
                                    ]);
5669
                                $forumIcon = Display::url(
5670
                                    Display::return_icon(
5671
                                        'forum.png',
5672
                                        get_lang('Associate a forum to this learning path item'),
5673
                                        [],
5674
                                        ICON_SIZE_TINY
5675
                                    ),
5676
                                    $forumIconUrl,
5677
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
5678
                                );
5679
                            }
5680
                        }
5681
                    } else {
5682
                        $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">';
5683
                        $edit_icon .= Display::return_icon(
5684
                            'edit.png',
5685
                            get_lang('Edit section description/name'),
5686
                            [],
5687
                            ICON_SIZE_TINY
5688
                        );
5689
                        $edit_icon .= '</a>';
5690
                    }
5691
                } else {
5692
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
5693
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
5694
                        $edit_icon .= Display::return_icon(
5695
                            'edit.png',
5696
                            get_lang('Edit'),
5697
                            [],
5698
                            ICON_SIZE_TINY
5699
                        );
5700
                        $edit_icon .= '</a>';
5701
                    }
5702
                }
5703
5704
                if ($pluginCalendar) {
5705
                    $pluginLink = $pluginUrl.
5706
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5707
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5708
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
5709
                    if ($itemInfo && 1 == $itemInfo['value']) {
5710
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
5711
                    }
5712
                    $pluginCalendarIcon = Display::url(
5713
                        $iconCalendar,
5714
                        $pluginLink,
5715
                        ['class' => 'btn btn-default']
5716
                    );
5717
                }
5718
5719
                if ('final_item' != $arrLP[$i]['item_type']) {
5720
                    $orderIcons = Display::url(
5721
                        $upIcon,
5722
                        'javascript:void(0)',
5723
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
5724
                    );
5725
                    $orderIcons .= Display::url(
5726
                        $downIcon,
5727
                        'javascript:void(0)',
5728
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
5729
                    );
5730
                }
5731
5732
                $delete_icon .= ' <a
5733
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
5734
                    onclick="return confirmation(\''.addslashes($title).'\');"
5735
                    class="btn btn-default">';
5736
                $delete_icon .= Display::return_icon(
5737
                    'delete.png',
5738
                    get_lang('Delete section'),
5739
                    [],
5740
                    ICON_SIZE_TINY
5741
                );
5742
                $delete_icon .= '</a>';
5743
5744
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5745
                $previewImage = Display::return_icon(
5746
                    'preview_view.png',
5747
                    get_lang('Preview'),
5748
                    [],
5749
                    ICON_SIZE_TINY
5750
                );
5751
5752
                switch ($arrLP[$i]['item_type']) {
5753
                    case TOOL_DOCUMENT:
5754
                    case TOOL_LP_FINAL_ITEM:
5755
                    case TOOL_READOUT_TEXT:
5756
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5757
                        $previewIcon = Display::url(
5758
                            $previewImage,
5759
                            $urlPreviewLink,
5760
                            [
5761
                                'target' => '_blank',
5762
                                'class' => 'btn btn-default',
5763
                                'data-title' => $arrLP[$i]['title'],
5764
                                'title' => $arrLP[$i]['title'],
5765
                            ]
5766
                        );
5767
                        break;
5768
                    case TOOL_THREAD:
5769
                    case TOOL_FORUM:
5770
                    case TOOL_QUIZ:
5771
                    case TOOL_STUDENTPUBLICATION:
5772
                    case TOOL_LINK:
5773
                        $class = 'btn btn-default';
5774
                        $target = '_blank';
5775
                        $link = self::rl_get_resource_link_for_learnpath(
5776
                            $this->course_int_id,
5777
                            $this->lp_id,
5778
                            $arrLP[$i]['id'],
5779
                            0
5780
                        );
5781
                        $previewIcon = Display::url(
5782
                            $previewImage,
5783
                            $link,
5784
                            [
5785
                                'class' => $class,
5786
                                'data-title' => $arrLP[$i]['title'],
5787
                                'title' => $arrLP[$i]['title'],
5788
                                'target' => $target,
5789
                            ]
5790
                        );
5791
                        break;
5792
                    default:
5793
                        $previewIcon = Display::url(
5794
                            $previewImage,
5795
                            $url.'&action=view_item',
5796
                            ['class' => 'btn btn-default', 'target' => '_blank']
5797
                        );
5798
                        break;
5799
                }
5800
5801
                if ('dir' != $arrLP[$i]['item_type']) {
5802
                    $prerequisities_icon = Display::url(
5803
                        Display::return_icon(
5804
                            'accept.png',
5805
                            get_lang('Prerequisites'),
5806
                            [],
5807
                            ICON_SIZE_TINY
5808
                        ),
5809
                        $url.'&action=edit_item_prereq',
5810
                        ['class' => 'btn btn-default']
5811
                    );
5812
                    if ('final_item' != $arrLP[$i]['item_type']) {
5813
                        /*$move_item_icon = Display::url(
5814
                            Display::return_icon(
5815
                                'move.png',
5816
                                get_lang('Move'),
5817
                                [],
5818
                                ICON_SIZE_TINY
5819
                            ),
5820
                            $url.'&action=move_item',
5821
                            ['class' => 'btn btn-default']
5822
                        );*/
5823
                    }
5824
                    $audio_icon = Display::url(
5825
                        Display::return_icon(
5826
                            'audio.png',
5827
                            get_lang('Upload'),
5828
                            [],
5829
                            ICON_SIZE_TINY
5830
                        ),
5831
                        $url.'&action=add_audio',
5832
                        ['class' => 'btn btn-default']
5833
                    );
5834
                }
5835
            }
5836
            if ('true' != $update_audio) {
5837
                $row = $move_icon.' '.$icon.
5838
                    Display::span($title_cut).
5839
                    Display::tag(
5840
                        'div',
5841
                        "<div class=\"btn-group btn-group-xs\">
5842
                                    $previewIcon
5843
                                    $audio
5844
                                    $edit_icon
5845
                                    $pluginCalendarIcon
5846
                                    $forumIcon
5847
                                    $prerequisities_icon
5848
                                    $move_item_icon
5849
                                    $audio_icon
5850
                                    $orderIcons
5851
                                    $delete_icon
5852
                                </div>",
5853
                        ['class' => 'btn-toolbar button_actions']
5854
                    );
5855
            } else {
5856
                $row =
5857
                    Display::span($title.$icon).
5858
                    Display::span($audio, ['class' => 'button_actions']);
5859
            }
5860
5861
            $default_data[$arrLP[$i]['id']] = $row;
5862
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
5863
5864
            if (empty($parent_id)) {
5865
                $elements[$arrLP[$i]['id']]['data'] = $row;
5866
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5867
            } else {
5868
                $parent_arrays = [];
5869
                if ($arrLP[$i]['depth'] > 1) {
5870
                    // Getting list of parents
5871
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
5872
                        foreach ($arrLP as $item) {
5873
                            if ($item['id'] == $parent_id) {
5874
                                if (0 == $item['parent_item_id']) {
5875
                                    $parent_id = $item['id'];
5876
                                    break;
5877
                                } else {
5878
                                    $parent_id = $item['parent_item_id'];
5879
                                    if (empty($parent_arrays)) {
5880
                                        $parent_arrays[] = intval($item['id']);
5881
                                    }
5882
                                    $parent_arrays[] = $parent_id;
5883
                                    break;
5884
                                }
5885
                            }
5886
                        }
5887
                    }
5888
                }
5889
5890
                if (!empty($parent_arrays)) {
5891
                    $parent_arrays = array_reverse($parent_arrays);
5892
                    $val = '$elements';
5893
                    $x = 0;
5894
                    foreach ($parent_arrays as $item) {
5895
                        if ($x != count($parent_arrays) - 1) {
5896
                            $val .= '["'.$item.'"]["children"]';
5897
                        } else {
5898
                            $val .= '["'.$item.'"]["children"]';
5899
                        }
5900
                        $x++;
5901
                    }
5902
                    $val .= "";
5903
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
5904
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
5905
                } else {
5906
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
5907
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
5908
                }
5909
            }
5910
        }
5911
5912
        return [
5913
            'elements' => $elements,
5914
            'default_data' => $default_data,
5915
            'default_content' => $default_content,
5916
            'return_audio' => $return_audio,
5917
        ];
5918
    }
5919
5920
    /**
5921
     * @param string $updateAudio true/false strings
5922
     *
5923
     * @return string
5924
     */
5925
    public function returnLpItemList($updateAudio)
5926
    {
5927
        $result = $this->processBuildMenuElements($updateAudio);
5928
5929
        $html = self::print_recursive(
5930
            $result['elements'],
5931
            $result['default_data'],
5932
            $result['default_content']
5933
        );
5934
5935
        if (!empty($html)) {
5936
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
5937
        }
5938
5939
        return $html;
5940
    }
5941
5942
    /**
5943
     * @param string $update_audio
5944
     * @param bool   $drop_element_here
5945
     *
5946
     * @return string
5947
     */
5948
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
5949
    {
5950
        $result = $this->processBuildMenuElements($update_audio);
5951
5952
        $list = '<ul id="lp_item_list">';
5953
        $tree = $this->print_recursive(
5954
            $result['elements'],
5955
            $result['default_data'],
5956
            $result['default_content']
5957
        );
5958
5959
        if (!empty($tree)) {
5960
            $list .= $tree;
5961
        } else {
5962
            if ($drop_element_here) {
5963
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
5964
            }
5965
        }
5966
        $list .= '</ul>';
5967
5968
        $return = Display::panelCollapse(
5969
            $this->name,
5970
            $list,
5971
            'scorm-list',
5972
            null,
5973
            'scorm-list-accordion',
5974
            'scorm-list-collapse'
5975
        );
5976
5977
        if ('true' === $update_audio) {
5978
            $return = $result['return_audio'];
5979
        }
5980
5981
        return $return;
5982
    }
5983
5984
    /**
5985
     * @param array $elements
5986
     * @param array $default_data
5987
     * @param array $default_content
5988
     *
5989
     * @return string
5990
     */
5991
    public function print_recursive($elements, $default_data, $default_content)
5992
    {
5993
        $return = '';
5994
        foreach ($elements as $key => $item) {
5995
            if (isset($item['load_data']) || empty($item['data'])) {
5996
                $item['data'] = $default_data[$item['load_data']];
5997
                $item['type'] = $default_content[$item['load_data']]['item_type'];
5998
            }
5999
            $sub_list = '';
6000
            if (isset($item['type']) && 'dir' === $item['type']) {
6001
                // empty value
6002
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6003
            }
6004
            if (empty($item['children'])) {
6005
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6006
                $active = null;
6007
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6008
                    $active = 'active';
6009
                }
6010
                $return .= Display::tag(
6011
                    'li',
6012
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6013
                    ['id' => $key, 'class' => 'record li_container']
6014
                );
6015
            } else {
6016
                // Sections
6017
                $data = '';
6018
                if (isset($item['children'])) {
6019
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6020
                }
6021
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6022
                $return .= Display::tag(
6023
                    'li',
6024
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6025
                    ['id' => $key, 'class' => 'record li_container']
6026
                );
6027
            }
6028
        }
6029
6030
        return $return;
6031
    }
6032
6033
    /**
6034
     * This function builds the action menu.
6035
     *
6036
     * @param bool   $returnString           Optional
6037
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6038
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6039
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6040
     * @param string $action
6041
     * @param array  $extraField
6042
     *
6043
     * @return string
6044
     */
6045
    public function build_action_menu(
6046
        $returnString = false,
6047
        $showRequirementButtons = true,
6048
        $isConfigPage = false,
6049
        $allowExpand = true,
6050
        $action = '',
6051
        $extraField = []
6052
    ) {
6053
        $actionsRight = '';
6054
        $lpId = $this->lp_id;
6055
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6056
            $back = Display::url(
6057
                Display::return_icon(
6058
                    'back.png',
6059
                    get_lang('Back to learning paths'),
6060
                    '',
6061
                    ICON_SIZE_MEDIUM
6062
                ),
6063
                'lp_controller.php?'.api_get_cidreq()
6064
            );
6065
        } else {
6066
            $back = Display::url(
6067
                Display::return_icon(
6068
                    'back.png',
6069
                    get_lang('Back'),
6070
                    '',
6071
                    ICON_SIZE_MEDIUM
6072
                ),
6073
                $extraField['backTo']
6074
            );
6075
        }
6076
6077
        /*if ($backToBuild) {
6078
            $back = Display::url(
6079
                Display::return_icon(
6080
                    'back.png',
6081
                    get_lang('GoBack'),
6082
                    '',
6083
                    ICON_SIZE_MEDIUM
6084
                ),
6085
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6086
            );
6087
        }*/
6088
6089
        $actionsLeft = $back;
6090
6091
        $actionsLeft .= Display::url(
6092
            Display::return_icon(
6093
                'preview_view.png',
6094
                get_lang('Preview'),
6095
                '',
6096
                ICON_SIZE_MEDIUM
6097
            ),
6098
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6099
                'action' => 'view',
6100
                'lp_id' => $lpId,
6101
                'isStudentView' => 'true',
6102
            ])
6103
        );
6104
6105
        $actionsLeft .= Display::url(
6106
            Display::return_icon(
6107
                'upload_audio.png',
6108
                get_lang('Add audio'),
6109
                '',
6110
                ICON_SIZE_MEDIUM
6111
            ),
6112
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6113
                'action' => 'admin_view',
6114
                'lp_id' => $lpId,
6115
                'updateaudio' => 'true',
6116
            ])
6117
        );
6118
6119
        $subscriptionSettings = self::getSubscriptionSettings();
6120
6121
        $request = api_request_uri();
6122
        if (false === strpos($request, 'edit')) {
6123
            $actionsLeft .= Display::url(
6124
                Display::return_icon(
6125
                    'settings.png',
6126
                    get_lang('Course settings'),
6127
                    '',
6128
                    ICON_SIZE_MEDIUM
6129
                ),
6130
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6131
                    'action' => 'edit',
6132
                    'lp_id' => $lpId,
6133
                ])
6134
            );
6135
        }
6136
6137
        if ((false === strpos($request, 'build') &&
6138
            false === strpos($request, 'add_item')) ||
6139
            in_array($action, ['add_audio'])
6140
        ) {
6141
            $actionsLeft .= Display::url(
6142
                Display::return_icon(
6143
                    'edit.png',
6144
                    get_lang('Edit'),
6145
                    '',
6146
                    ICON_SIZE_MEDIUM
6147
                ),
6148
                'lp_controller.php?'.http_build_query([
6149
                    'action' => 'build',
6150
                    'lp_id' => $lpId,
6151
                ]).'&'.api_get_cidreq()
6152
            );
6153
        }
6154
6155
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6156
            if (1 == $this->subscribeUsers &&
6157
                $subscriptionSettings['allow_add_users_to_lp']) {
6158
                $actionsLeft .= Display::url(
6159
                    Display::return_icon(
6160
                        'user.png',
6161
                        get_lang('Subscribe users to learning path'),
6162
                        '',
6163
                        ICON_SIZE_MEDIUM
6164
                    ),
6165
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=$lpId&".api_get_cidreq()
6166
                );
6167
            }
6168
        }
6169
6170
        if ($allowExpand) {
6171
            /*$actionsLeft .= Display::url(
6172
                Display::return_icon(
6173
                    'expand.png',
6174
                    get_lang('Expand'),
6175
                    ['id' => 'expand'],
6176
                    ICON_SIZE_MEDIUM
6177
                ).
6178
                Display::return_icon(
6179
                    'contract.png',
6180
                    get_lang('Collapse'),
6181
                    ['id' => 'contract', 'class' => 'hide'],
6182
                    ICON_SIZE_MEDIUM
6183
                ),
6184
                '#',
6185
                ['role' => 'button', 'id' => 'hide_bar_template']
6186
            );*/
6187
        }
6188
6189
        if ($showRequirementButtons) {
6190
            $buttons = [
6191
                [
6192
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6193
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6194
                        'action' => 'set_previous_step_as_prerequisite',
6195
                        'lp_id' => $lpId,
6196
                    ]),
6197
                ],
6198
                [
6199
                    'title' => get_lang('Clear all prerequisites'),
6200
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6201
                        'action' => 'clear_prerequisites',
6202
                        'lp_id' => $lpId,
6203
                    ]),
6204
                ],
6205
            ];
6206
            $actionsRight = Display::groupButtonWithDropDown(
6207
                get_lang('Prerequisites options'),
6208
                $buttons,
6209
                true
6210
            );
6211
        }
6212
6213
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6214
            $actionsLeft .= Display::url(
6215
                Display::return_icon(
6216
                    'add-groups.png',
6217
                    get_lang('Author'),
6218
                    '',
6219
                    ICON_SIZE_MEDIUM
6220
                ),
6221
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6222
                    'action' => 'author_view',
6223
                    'lp_id' => $lpId,
6224
                ])
6225
            );
6226
        }
6227
6228
        $toolbar = Display::toolbarAction(
6229
            'actions-lp-controller',
6230
            [$actionsLeft, $actionsRight]
6231
        );
6232
6233
        if ($returnString) {
6234
            return $toolbar;
6235
        }
6236
6237
        echo $toolbar;
6238
    }
6239
6240
    /**
6241
     * Creates the default learning path folder.
6242
     *
6243
     * @param array $course
6244
     * @param int   $creatorId
6245
     *
6246
     * @return CDocument
6247
     */
6248
    public static function generate_learning_path_folder($course, $creatorId = 0)
6249
    {
6250
        // Creating learning_path folder
6251
        $dir = 'learning_path';
6252
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6253
6254
        return create_unexisting_directory(
6255
            $course,
6256
            $creatorId,
6257
            0,
6258
            null,
6259
            0,
6260
            '',
6261
            $dir,
6262
            get_lang('Learning paths'),
6263
            0
6264
        );
6265
    }
6266
6267
    /**
6268
     * @param array  $course
6269
     * @param string $lp_name
6270
     * @param int    $creatorId
6271
     *
6272
     * @return CDocument
6273
     */
6274
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6275
    {
6276
        $filepath = '';
6277
        $dir = '/learning_path/';
6278
6279
        if (empty($lp_name)) {
6280
            $lp_name = $this->name;
6281
        }
6282
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6283
        $parent = self::generate_learning_path_folder($course, $creatorId);
6284
6285
        // Limits title size
6286
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6287
        $dir = $dir.$title;
6288
6289
        // Creating LP folder
6290
        $documentId = null;
6291
        $folder = null;
6292
        if ($parent) {
6293
            $folder = create_unexisting_directory(
6294
                $course,
6295
                $creatorId,
6296
                0,
6297
                0,
6298
                0,
6299
                $filepath,
6300
                $dir,
6301
                $lp_name,
6302
                '',
6303
                false,
6304
                false,
6305
                $parent
6306
            );
6307
        }
6308
6309
        return $folder;
6310
    }
6311
6312
    /**
6313
     * Create a new document //still needs some finetuning.
6314
     *
6315
     * @param array  $courseInfo
6316
     * @param string $content
6317
     * @param string $title
6318
     * @param string $extension
6319
     * @param int    $parentId
6320
     * @param int    $creatorId  creator id
6321
     *
6322
     * @return int
6323
     */
6324
    public function create_document(
6325
        $courseInfo,
6326
        $content = '',
6327
        $title = '',
6328
        $extension = 'html',
6329
        $parentId = 0,
6330
        $creatorId = 0
6331
    ) {
6332
        if (!empty($courseInfo)) {
6333
            $course_id = $courseInfo['real_id'];
6334
        } else {
6335
            $course_id = api_get_course_int_id();
6336
        }
6337
6338
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6339
        $sessionId = api_get_session_id();
6340
6341
        // Generates folder
6342
        $this->generate_lp_folder($courseInfo);
6343
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6344
        // is already escaped twice when it gets here.
6345
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6346
        if (!empty($title)) {
6347
            $title = api_replace_dangerous_char(stripslashes($title));
6348
        } else {
6349
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6350
        }
6351
6352
        $title = disable_dangerous_file($title);
6353
        $filename = $title;
6354
        $content = !empty($content) ? $content : $_POST['content_lp'];
6355
        $tmp_filename = $filename;
6356
        /*$i = 0;
6357
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6358
            $tmp_filename = $filename.'_'.++$i;
6359
        }*/
6360
        $filename = $tmp_filename.'.'.$extension;
6361
6362
        if ('html' === $extension) {
6363
            $content = stripslashes($content);
6364
            $content = str_replace(
6365
                api_get_path(WEB_COURSE_PATH),
6366
                api_get_path(REL_PATH).'courses/',
6367
                $content
6368
            );
6369
6370
            // Change the path of mp3 to absolute.
6371
            // The first regexp deals with :// urls.
6372
            /*$content = preg_replace(
6373
                "|(flashvars=\"file=)([^:/]+)/|",
6374
                "$1".api_get_path(
6375
                    REL_COURSE_PATH
6376
                ).$courseInfo['path'].'/document/',
6377
                $content
6378
            );*/
6379
            // The second regexp deals with audio/ urls.
6380
            /*$content = preg_replace(
6381
                "|(flashvars=\"file=)([^/]+)/|",
6382
                "$1".api_get_path(
6383
                    REL_COURSE_PATH
6384
                ).$courseInfo['path'].'/document/$2/',
6385
                $content
6386
            );*/
6387
            // For flv player: To prevent edition problem with firefox,
6388
            // we have to use a strange tip (don't blame me please).
6389
            $content = str_replace(
6390
                '</body>',
6391
                '<style type="text/css">body{}</style></body>',
6392
                $content
6393
            );
6394
        }
6395
6396
        //$save_file_path = $dir.$filename;
6397
6398
        $document = DocumentManager::addDocument(
6399
            $courseInfo,
6400
            null,
6401
            'file',
6402
            '',
6403
            $tmp_filename,
6404
            '',
6405
            0, //readonly
6406
            true,
6407
            null,
6408
            $sessionId,
6409
            $creatorId,
6410
            false,
6411
            $content,
6412
            $parentId
6413
        );
6414
6415
        $document_id = $document->getIid();
6416
        if ($document_id) {
6417
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6418
            $new_title = $originalTitle;
6419
6420
            if ($new_comment || $new_title) {
6421
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6422
                $ct = '';
6423
                if ($new_comment) {
6424
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6425
                }
6426
                if ($new_title) {
6427
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6428
                }
6429
6430
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6431
                        WHERE iid = $document_id ";
6432
                Database::query($sql);
6433
            }
6434
        }
6435
6436
        return $document_id;
6437
    }
6438
6439
    /**
6440
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6441
     */
6442
    public function edit_document()
6443
    {
6444
        $repo = Container::getDocumentRepository();
6445
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6446
            $id = (int) $_REQUEST['document_id'];
6447
            /** @var CDocument $document */
6448
            $document = $repo->find($id);
6449
            if ($document->getResourceNode()->hasEditableTextContent()) {
6450
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6451
                /*   $nodeRepo = Container::getDocumentRepository()->getResourceNodeRepository();
6452
                   $nodeRepo->update($document->getResourceNode());
6453
                   var_dump($document->getResourceNode()->getContent());
6454
                   exit;*/
6455
            }
6456
            $document->setTitle($_REQUEST['title']);
6457
            $repo->update($document);
6458
        }
6459
    }
6460
6461
    /**
6462
     * Displays the selected item, with a panel for manipulating the item.
6463
     *
6464
     * @param CLpItem $lpItem
6465
     * @param string  $msg
6466
     * @param bool    $show_actions
6467
     *
6468
     * @return string
6469
     */
6470
    public function display_item($lpItem, $msg = null, $show_actions = true)
6471
    {
6472
        $course_id = api_get_course_int_id();
6473
        $return = '';
6474
6475
        if (null === $lpItem) {
6476
            return '';
6477
        }
6478
        $item_id = $lpItem->getIid();
6479
        $itemType = $lpItem->getItemType();
6480
        $lpId = $lpItem->getLp()->getIid();
6481
        $path = $lpItem->getPath();
6482
6483
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6484
6485
        // Prevents wrong parent selection for document, see Bug#1251.
6486
        if ('dir' !== $itemType) {
6487
            Session::write('parent_item_id', $lpItem->getParentItemId());
6488
        }
6489
6490
        if ($show_actions) {
6491
            $return .= $this->displayItemMenu($lpItem);
6492
        }
6493
        $return .= '<div style="padding:10px;">';
6494
6495
        if ('' != $msg) {
6496
            $return .= $msg;
6497
        }
6498
6499
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6500
6501
        switch ($itemType) {
6502
            case TOOL_THREAD:
6503
                $link = $this->rl_get_resource_link_for_learnpath(
6504
                    $course_id,
6505
                    $lpId,
6506
                    $item_id,
6507
                    0
6508
                );
6509
                $return .= Display::url(
6510
                    get_lang('Go to thread'),
6511
                    $link,
6512
                    ['class' => 'btn btn-primary']
6513
                );
6514
                break;
6515
            case TOOL_FORUM:
6516
                $return .= Display::url(
6517
                    get_lang('Go to the forum'),
6518
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6519
                    ['class' => 'btn btn-primary']
6520
                );
6521
                break;
6522
            case TOOL_QUIZ:
6523
                if (!empty($path)) {
6524
                    $exercise = new Exercise();
6525
                    $exercise->read($path);
6526
                    $return .= $exercise->description.'<br />';
6527
                    $return .= Display::url(
6528
                        get_lang('Go to exercise'),
6529
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6530
                        ['class' => 'btn btn-primary']
6531
                    );
6532
                }
6533
                break;
6534
            case TOOL_LP_FINAL_ITEM:
6535
                $return .= $this->getSavedFinalItem();
6536
                break;
6537
            case TOOL_DOCUMENT:
6538
            case TOOL_READOUT_TEXT:
6539
                $repo = Container::getDocumentRepository();
6540
                /** @var CDocument $document */
6541
                $document = $repo->find($lpItem->getPath());
6542
                $return .= $this->display_document($document, true, true);
6543
                break;
6544
            case TOOL_HOTPOTATOES:
6545
                $return .= $this->display_document($document, false, true);
6546
                break;
6547
        }
6548
        $return .= '</div>';
6549
6550
        return $return;
6551
    }
6552
6553
    /**
6554
     * Shows the needed forms for editing a specific item.
6555
     *
6556
     * @param CLpItem $lpItem
6557
     *
6558
     * @throws Exception
6559
     *
6560
     *
6561
     * @return string
6562
     */
6563
    public function display_edit_item($lpItem, $excludeExtraFields = [])
6564
    {
6565
        $course_id = api_get_course_int_id();
6566
        $return = '';
6567
6568
        if (empty($lpItem)) {
6569
            return '';
6570
        }
6571
        $item_id = $lpItem->getIid();
6572
        $itemType = $lpItem->getItemType();
6573
        $path = $lpItem->getPath();
6574
6575
        switch ($itemType) {
6576
            case 'dir':
6577
            case 'asset':
6578
            case 'sco':
6579
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
6580
                    $return .= $this->displayItemMenu($lpItem);
6581
                    $return .= $this->display_item_form(
6582
                        $lpItem,
6583
                        'edit'
6584
                    );
6585
                } else {
6586
                    $return .= $this->display_item_form(
6587
                        $lpItem,
6588
                        'edit_item'
6589
                    );
6590
                }
6591
                break;
6592
            case TOOL_LP_FINAL_ITEM:
6593
            case TOOL_DOCUMENT:
6594
            case TOOL_READOUT_TEXT:
6595
                $return .= $this->displayItemMenu($lpItem);
6596
                $return .= $this->displayDocumentForm('edit', $lpItem);
6597
                break;
6598
            case TOOL_LINK:
6599
                $link = null;
6600
                if (!empty($path)) {
6601
                    $repo = Container::getLinkRepository();
6602
                    $link = $repo->find($path);
6603
                }
6604
                $return .= $this->displayItemMenu($lpItem);
6605
                $return .= $this->display_link_form('edit', $lpItem, $link);
6606
6607
                break;
6608
            case TOOL_QUIZ:
6609
                if (!empty($path)) {
6610
                    $repo = Container::getQuizRepository();
6611
                    $resource = $repo->find($path);
6612
                }
6613
                $return .= $this->displayItemMenu($lpItem);
6614
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
6615
                break;
6616
            /*case TOOL_HOTPOTATOES:
6617
                $return .= $this->displayItemMenu($lpItem);
6618
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
6619
                break;*/
6620
            case TOOL_STUDENTPUBLICATION:
6621
                if (!empty($path)) {
6622
                    $repo = Container::getStudentPublicationRepository();
6623
                    $resource = $repo->find($path);
6624
                }
6625
                $return .= $this->displayItemMenu($lpItem);
6626
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
6627
                break;
6628
            case TOOL_FORUM:
6629
                if (!empty($path)) {
6630
                    $repo = Container::getForumRepository();
6631
                    $resource = $repo->find($path);
6632
                }
6633
                $return .= $this->displayItemMenu($lpItem);
6634
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
6635
                break;
6636
            case TOOL_THREAD:
6637
                if (!empty($path)) {
6638
                    $repo = Container::getForumPostRepository();
6639
                    $resource = $repo->find($path);
6640
                }
6641
                $return .= $this->displayItemMenu($lpItem);
6642
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
6643
                break;
6644
        }
6645
6646
        return $return;
6647
    }
6648
6649
    /**
6650
     * Function that displays a list with al the resources that
6651
     * could be added to the learning path.
6652
     *
6653
     * @throws Exception
6654
     *
6655
     *
6656
     * @return bool
6657
     */
6658
    public function displayResources()
6659
    {
6660
        // Get all the docs.
6661
        $documents = $this->get_documents(true);
6662
6663
        // Get all the exercises.
6664
        $exercises = $this->get_exercises();
6665
6666
        // Get all the links.
6667
        $links = $this->get_links();
6668
6669
        // Get all the student publications.
6670
        $works = $this->get_student_publications();
6671
6672
        // Get all the forums.
6673
        $forums = $this->get_forums();
6674
6675
        // Get the final item form (see BT#11048) .
6676
        $finish = $this->getFinalItemForm();
6677
6678
        $headers = [
6679
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
6680
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
6681
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
6682
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
6683
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
6684
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
6685
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
6686
        ];
6687
6688
        echo Display::return_message(
6689
            get_lang('Click on the [Learner view] button to see your learning path'),
6690
            'normal'
6691
        );
6692
        $section = $this->displayNewSectionForm();
6693
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
6694
6695
        echo Display::tabs(
6696
            $headers,
6697
            [
6698
                $documents,
6699
                $exercises,
6700
                $links,
6701
                $works,
6702
                $forums,
6703
                $section,
6704
                $finish,
6705
            ],
6706
            'resource_tab',
6707
            [],
6708
            [],
6709
            $selected
6710
        );
6711
6712
        return true;
6713
    }
6714
6715
    /**
6716
     * Returns the extension of a document.
6717
     *
6718
     * @param string $filename
6719
     *
6720
     * @return string Extension (part after the last dot)
6721
     */
6722
    public function get_extension($filename)
6723
    {
6724
        $explode = explode('.', $filename);
6725
6726
        return $explode[count($explode) - 1];
6727
    }
6728
6729
    /**
6730
     * @return string
6731
     */
6732
    public function getCurrentBuildingModeURL()
6733
    {
6734
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
6735
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
6736
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
6737
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
6738
6739
        $currentUrl = api_get_self().'?'.api_get_cidreq().
6740
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
6741
6742
        return $currentUrl;
6743
    }
6744
6745
    /**
6746
     * Displays a document by id.
6747
     *
6748
     * @param CDocument $document
6749
     * @param bool      $show_title
6750
     * @param bool      $iframe
6751
     * @param bool      $edit_link
6752
     *
6753
     * @return string
6754
     */
6755
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
6756
    {
6757
        $return = '';
6758
        if (!$document) {
6759
            return '';
6760
        }
6761
6762
        $repo = Container::getDocumentRepository();
6763
6764
        // TODO: Add a path filter.
6765
        if ($iframe) {
6766
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
6767
            $url = $repo->getResourceFileUrl($document);
6768
6769
            $return .= '<iframe
6770
                id="learnpath_preview_frame"
6771
                frameborder="0"
6772
                height="400"
6773
                width="100%"
6774
                scrolling="auto"
6775
                src="'.$url.'"></iframe>';
6776
        } else {
6777
            $return = $repo->getResourceFileContent($document);
6778
        }
6779
6780
        return $return;
6781
    }
6782
6783
    /**
6784
     * Return HTML form to add/edit a link item.
6785
     *
6786
     * @param string  $action (add/edit)
6787
     * @param CLpItem $lpItem
6788
     * @param CLink   $link
6789
     *
6790
     * @throws Exception
6791
     *
6792
     *
6793
     * @return string HTML form
6794
     */
6795
    public function display_link_form($action, $lpItem, $link)
6796
    {
6797
        $item_url = '';
6798
        if ($link) {
6799
            $item_url = stripslashes($link->getUrl());
6800
        }
6801
        $form = new FormValidator(
6802
            'edit_link',
6803
            'POST',
6804
            $this->getCurrentBuildingModeURL()
6805
        );
6806
6807
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6808
6809
        $urlAttributes = ['class' => 'learnpath_item_form'];
6810
        $urlAttributes['disabled'] = 'disabled';
6811
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
6812
        $form->setDefault('url', $item_url);
6813
6814
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6815
6816
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6817
    }
6818
6819
    /**
6820
     * Return HTML form to add/edit a quiz.
6821
     *
6822
     * @param string  $action   Action (add/edit)
6823
     * @param CLpItem $lpItem   Item ID if already exists
6824
     * @param CQuiz   $exercise Extra information (quiz ID if integer)
6825
     *
6826
     * @throws Exception
6827
     *
6828
     * @return string HTML form
6829
     */
6830
    public function display_quiz_form($action, $lpItem, $exercise)
6831
    {
6832
        $form = new FormValidator(
6833
            'quiz_form',
6834
            'POST',
6835
            $this->getCurrentBuildingModeURL()
6836
        );
6837
6838
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6839
6840
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6841
6842
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6843
    }
6844
6845
    /**
6846
     * Return the form to display the forum edit/add option.
6847
     *
6848
     * @param CLpItem $lpItem
6849
     *
6850
     * @throws Exception
6851
     *
6852
     * @return string HTML form
6853
     */
6854
    public function display_forum_form($action, $lpItem, $resource)
6855
    {
6856
        $form = new FormValidator(
6857
            'forum_form',
6858
            'POST',
6859
            $this->getCurrentBuildingModeURL()
6860
        );
6861
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6862
6863
        if ('add' === $action) {
6864
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
6865
        } else {
6866
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
6867
        }
6868
6869
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
6870
    }
6871
6872
    /**
6873
     * Return HTML form to add/edit forum threads.
6874
     *
6875
     * @param string  $action
6876
     * @param CLpItem $lpItem
6877
     * @param string  $resource
6878
     *
6879
     * @throws Exception
6880
     *
6881
     * @return string HTML form
6882
     */
6883
    public function display_thread_form($action, $lpItem, $resource)
6884
    {
6885
        $form = new FormValidator(
6886
            'thread_form',
6887
            'POST',
6888
            $this->getCurrentBuildingModeURL()
6889
        );
6890
6891
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6892
6893
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6894
6895
        return $form->returnForm();
6896
    }
6897
6898
    /**
6899
     * Return the HTML form to display an item (generally a dir item).
6900
     *
6901
     * @param CLpItem $lpItem
6902
     * @param string  $action
6903
     *
6904
     * @throws Exception
6905
     *
6906
     *
6907
     * @return string HTML form
6908
     */
6909
    public function display_item_form(
6910
        $lpItem,
6911
        $action = 'add_item'
6912
    ) {
6913
        $item_type = $lpItem->getItemType();
6914
6915
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6916
6917
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6918
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6919
6920
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6921
6922
        return $form->returnForm();
6923
    }
6924
6925
    /**
6926
     * Return HTML form to add/edit a student publication (work).
6927
     *
6928
     * @param string              $action
6929
     * @param CStudentPublication $resource
6930
     *
6931
     * @throws Exception
6932
     *
6933
     * @return string HTML form
6934
     */
6935
    public function display_student_publication_form($action, CLpItem $lpItem, $resource)
6936
    {
6937
        $form = new FormValidator('frm_student_publication', 'post', '#');
6938
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
6939
6940
        $form->addButtonSave(get_lang('Save'), 'submit_button');
6941
6942
        $return = '<div class="sectioncomment">';
6943
        $return .= $form->returnForm();
6944
        $return .= '</div>';
6945
6946
        return $return;
6947
    }
6948
6949
    public function displayNewSectionForm()
6950
    {
6951
        $action = 'add_item';
6952
        $item_type = 'dir';
6953
6954
        $lpItem = new CLpItem();
6955
        $lpItem->setTitle('dir');
6956
        $lpItem->setItemType('dir');
6957
6958
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
6959
6960
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
6961
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
6962
6963
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
6964
        $form->addElement('hidden', 'type', 'dir');
6965
6966
        return $form->returnForm();
6967
    }
6968
6969
    /**
6970
     * Returns the form to update or create a document.
6971
     *
6972
     * @param string  $action (add/edit)
6973
     * @param CLpItem $lpItem
6974
     *
6975
     *
6976
     * @throws Exception
6977
     *
6978
     * @return string HTML form
6979
     */
6980
    public function displayDocumentForm($action = 'add', $lpItem = null)
6981
    {
6982
        $courseInfo = api_get_course_info();
6983
6984
        $form = new FormValidator(
6985
            'form',
6986
            'POST',
6987
            $this->getCurrentBuildingModeURL(),
6988
            '',
6989
            ['enctype' => 'multipart/form-data']
6990
        );
6991
6992
        $data = $this->generate_lp_folder($courseInfo);
6993
6994
        if (null !== $lpItem) {
6995
            LearnPathItemForm::setForm($form, $action, $this, $lpItem);
6996
        }
6997
6998
        switch ($action) {
6999
            case 'add':
7000
                $folders = DocumentManager::get_all_document_folders(
7001
                    $courseInfo,
7002
                    0,
7003
                    true
7004
                );
7005
                DocumentManager::build_directory_selector(
7006
                    $folders,
7007
                    '',
7008
                    [],
7009
                    true,
7010
                    $form,
7011
                    'directory_parent_id'
7012
                );
7013
7014
                if ($data) {
7015
                    $defaults['directory_parent_id'] = $data->getIid();
7016
                }
7017
7018
                break;
7019
        }
7020
7021
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7022
7023
        return $form->returnForm();
7024
    }
7025
7026
    /**
7027
     * @param array  $courseInfo
7028
     * @param string $content
7029
     * @param string $title
7030
     * @param int    $parentId
7031
     *
7032
     * @throws \Doctrine\ORM\ORMException
7033
     * @throws \Doctrine\ORM\OptimisticLockException
7034
     * @throws \Doctrine\ORM\TransactionRequiredException
7035
     *
7036
     * @return int
7037
     */
7038
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7039
    {
7040
        $creatorId = api_get_user_id();
7041
        $sessionId = api_get_session_id();
7042
7043
        // Generates folder
7044
        $result = $this->generate_lp_folder($courseInfo);
7045
        $dir = $result['dir'];
7046
7047
        if (empty($parentId) || '/' == $parentId) {
7048
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7049
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7050
7051
            if ('/' === $parentId) {
7052
                $dir = '/';
7053
            }
7054
7055
            // Please, do not modify this dirname formatting.
7056
            if (strstr($dir, '..')) {
7057
                $dir = '/';
7058
            }
7059
7060
            if (!empty($dir[0]) && '.' == $dir[0]) {
7061
                $dir = substr($dir, 1);
7062
            }
7063
            if (!empty($dir[0]) && '/' != $dir[0]) {
7064
                $dir = '/'.$dir;
7065
            }
7066
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7067
                $dir .= '/';
7068
            }
7069
        } else {
7070
            $parentInfo = DocumentManager::get_document_data_by_id(
7071
                $parentId,
7072
                $courseInfo['code']
7073
            );
7074
            if (!empty($parentInfo)) {
7075
                $dir = $parentInfo['path'].'/';
7076
            }
7077
        }
7078
7079
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7080
7081
        if (!is_dir($filepath)) {
7082
            $dir = '/';
7083
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7084
        }
7085
7086
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7087
7088
        if (!empty($title)) {
7089
            $title = api_replace_dangerous_char(stripslashes($title));
7090
        } else {
7091
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7092
        }
7093
7094
        $title = disable_dangerous_file($title);
7095
        $filename = $title;
7096
        $content = !empty($content) ? $content : $_POST['content_lp'];
7097
        $tmpFileName = $filename;
7098
7099
        $i = 0;
7100
        while (file_exists($filepath.$tmpFileName.'.html')) {
7101
            $tmpFileName = $filename.'_'.++$i;
7102
        }
7103
7104
        $filename = $tmpFileName.'.html';
7105
        $content = stripslashes($content);
7106
7107
        if (file_exists($filepath.$filename)) {
7108
            return 0;
7109
        }
7110
7111
        $putContent = file_put_contents($filepath.$filename, $content);
7112
7113
        if (false === $putContent) {
7114
            return 0;
7115
        }
7116
7117
        $fileSize = filesize($filepath.$filename);
7118
        $saveFilePath = $dir.$filename;
7119
7120
        $document = DocumentManager::addDocument(
7121
            $courseInfo,
7122
            $saveFilePath,
7123
            'file',
7124
            $fileSize,
7125
            $tmpFileName,
7126
            '',
7127
            0, //readonly
7128
            true,
7129
            null,
7130
            $sessionId,
7131
            $creatorId
7132
        );
7133
7134
        $documentId = $document->getId();
7135
7136
        if (!$document) {
7137
            return 0;
7138
        }
7139
7140
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7141
        $newTitle = $originalTitle;
7142
7143
        if ($newComment || $newTitle) {
7144
            $em = Database::getManager();
7145
7146
            if ($newComment) {
7147
                $document->setComment($newComment);
7148
            }
7149
7150
            if ($newTitle) {
7151
                $document->setTitle($newTitle);
7152
            }
7153
7154
            $em->persist($document);
7155
            $em->flush();
7156
        }
7157
7158
        return $documentId;
7159
    }
7160
7161
    /**
7162
     * Displays the menu for manipulating a step.
7163
     *
7164
     * @return string
7165
     */
7166
    public function displayItemMenu(CLpItem $lpItem)
7167
    {
7168
        $item_id = $lpItem->getIid();
7169
        $audio = $lpItem->getAudio();
7170
        $itemType = $lpItem->getItemType();
7171
        $path = $lpItem->getPath();
7172
7173
        $return = '<div class="actions">';
7174
        $audio_player = null;
7175
        // We display an audio player if needed.
7176
        if (!empty($audio)) {
7177
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7178
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7179
                .'<audio src="'.$webAudioPath.'" controls>'
7180
                .'</div><br>';*/
7181
        }
7182
7183
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7184
7185
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7186
            $return .= Display::url(
7187
                Display::return_icon(
7188
                    'edit.png',
7189
                    get_lang('Edit'),
7190
                    [],
7191
                    ICON_SIZE_SMALL
7192
                ),
7193
                $url.'&action=edit_item&path_item='.$path
7194
            );
7195
7196
            /*$return .= Display::url(
7197
                Display::return_icon(
7198
                    'move.png',
7199
                    get_lang('Move'),
7200
                    [],
7201
                    ICON_SIZE_SMALL
7202
                ),
7203
                $url.'&action=move_item'
7204
            );*/
7205
        }
7206
7207
        // Commented for now as prerequisites cannot be added to chapters.
7208
        if ('dir' !== $itemType) {
7209
            $return .= Display::url(
7210
                Display::return_icon(
7211
                    'accept.png',
7212
                    get_lang('Prerequisites'),
7213
                    [],
7214
                    ICON_SIZE_SMALL
7215
                ),
7216
                $url.'&action=edit_item_prereq'
7217
            );
7218
        }
7219
        $return .= Display::url(
7220
            Display::return_icon(
7221
                'delete.png',
7222
                get_lang('Delete'),
7223
                [],
7224
                ICON_SIZE_SMALL
7225
            ),
7226
            $url.'&action=delete_item'
7227
        );
7228
7229
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7230
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7231
            if (empty($documentData)) {
7232
                // Try with iid
7233
                $table = Database::get_course_table(TABLE_DOCUMENT);
7234
                $sql = "SELECT path FROM $table
7235
                        WHERE
7236
                              c_id = ".api_get_course_int_id()." AND
7237
                              iid = ".$path." AND
7238
                              path NOT LIKE '%_DELETED_%'";
7239
                $result = Database::query($sql);
7240
                $documentData = Database::fetch_array($result);
7241
                if ($documentData) {
7242
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7243
                }
7244
            }
7245
            if (isset($documentData['absolute_path_from_document'])) {
7246
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7247
            }
7248
        }*/
7249
7250
        $return .= '</div>';
7251
7252
        if (!empty($audio_player)) {
7253
            $return .= $audio_player;
7254
        }
7255
7256
        return $return;
7257
    }
7258
7259
    /**
7260
     * Creates the javascript needed for filling up the checkboxes without page reload.
7261
     *
7262
     * @return string
7263
     */
7264
    public function get_js_dropdown_array()
7265
    {
7266
        $course_id = api_get_course_int_id();
7267
        $return = 'var child_name = new Array();'."\n";
7268
        $return .= 'var child_value = new Array();'."\n\n";
7269
        $return .= 'child_name[0] = new Array();'."\n";
7270
        $return .= 'child_value[0] = new Array();'."\n\n";
7271
7272
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7273
        $sql = "SELECT * FROM ".$tbl_lp_item."
7274
                WHERE
7275
                    lp_id = ".$this->lp_id." AND
7276
                    parent_item_id = 0
7277
                ORDER BY display_order ASC";
7278
        Database::query($sql);
7279
        $i = 0;
7280
7281
        $list = $this->getItemsForForm(true);
7282
7283
        foreach ($list as $row_zero) {
7284
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7285
                if (TOOL_QUIZ == $row_zero['item_type']) {
7286
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7287
                }
7288
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7289
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7290
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7291
            }
7292
        }
7293
7294
        $return .= "\n";
7295
        $sql = "SELECT * FROM $tbl_lp_item
7296
                WHERE lp_id = ".$this->lp_id;
7297
        $res = Database::query($sql);
7298
        while ($row = Database::fetch_array($res)) {
7299
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7300
                           WHERE
7301
                                parent_item_id = ".$row['iid']."
7302
                           ORDER BY display_order ASC";
7303
            $res_parent = Database::query($sql_parent);
7304
            $i = 0;
7305
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7306
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7307
7308
            while ($row_parent = Database::fetch_array($res_parent)) {
7309
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7310
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7311
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7312
            }
7313
            $return .= "\n";
7314
        }
7315
7316
        $return .= "
7317
            function load_cbo(id) {
7318
                if (!id) {
7319
                    return false;
7320
                }
7321
7322
                var cbo = document.getElementById('previous');
7323
                for(var i = cbo.length - 1; i > 0; i--) {
7324
                    cbo.options[i] = null;
7325
                }
7326
7327
                var k=0;
7328
                for(var i = 1; i <= child_name[id].length; i++){
7329
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7330
                    option.style.paddingLeft = '40px';
7331
                    cbo.options[i] = option;
7332
                    k = i;
7333
                }
7334
7335
                cbo.options[k].selected = true;
7336
                //$('#previous').selectpicker('refresh');
7337
            }";
7338
7339
        return $return;
7340
    }
7341
7342
    /**
7343
     * Display the form to allow moving an item.
7344
     *
7345
     * @param CLpItem $lpItem
7346
     *
7347
     * @throws Exception
7348
     *
7349
     *
7350
     * @return string HTML form
7351
     */
7352
    public function display_move_item($lpItem)
7353
    {
7354
        $return = '';
7355
        $path = $lpItem->getPath();
7356
7357
        if ($lpItem) {
7358
            $itemType = $lpItem->getItemType();
7359
            switch ($itemType) {
7360
                case 'dir':
7361
                case 'asset':
7362
                    $return .= $this->displayItemMenu($lpItem);
7363
                    $return .= $this->display_item_form(
7364
                        $lpItem,
7365
                        get_lang('Move the current section'),
7366
                        'move',
7367
                        $row
7368
                    );
7369
                    break;
7370
                case TOOL_DOCUMENT:
7371
                    $return .= $this->displayItemMenu($lpItem);
7372
                    $return .= $this->displayDocumentForm('move', $lpItem);
7373
                    break;
7374
                case TOOL_LINK:
7375
                    $link = null;
7376
                    if (!empty($path)) {
7377
                        $repo = Container::getLinkRepository();
7378
                        $link = $repo->find($path);
7379
                    }
7380
                    $return .= $this->displayItemMenu($lpItem);
7381
                    $return .= $this->display_link_form('move', $lpItem, $link);
7382
                    break;
7383
                case TOOL_HOTPOTATOES:
7384
                    $return .= $this->displayItemMenu($lpItem);
7385
                    $return .= $this->display_link_form('move', $lpItem, $row);
7386
                    break;
7387
                case TOOL_QUIZ:
7388
                    $return .= $this->displayItemMenu($lpItem);
7389
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7390
                    break;
7391
                case TOOL_STUDENTPUBLICATION:
7392
                    $return .= $this->displayItemMenu($lpItem);
7393
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7394
                    break;
7395
                case TOOL_FORUM:
7396
                    $return .= $this->displayItemMenu($lpItem);
7397
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7398
                    break;
7399
                case TOOL_THREAD:
7400
                    $return .= $this->displayItemMenu($lpItem);
7401
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7402
                    break;
7403
            }
7404
        }
7405
7406
        return $return;
7407
    }
7408
7409
    /**
7410
     * Return HTML form to allow prerequisites selection.
7411
     *
7412
     * @todo use FormValidator
7413
     *
7414
     * @return string HTML form
7415
     */
7416
    public function display_item_prerequisites_form(CLpItem $lpItem)
7417
    {
7418
        $course_id = api_get_course_int_id();
7419
        $prerequisiteId = $lpItem->getPrerequisite();
7420
        $itemId = $lpItem->getIid();
7421
7422
        $return = '<legend>';
7423
        $return .= get_lang('Add/edit prerequisites');
7424
        $return .= '</legend>';
7425
        $return .= '<form method="POST">';
7426
        $return .= '<div class="table-responsive">';
7427
        $return .= '<table class="table table-hover">';
7428
        $return .= '<thead>';
7429
        $return .= '<tr>';
7430
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7431
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7432
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7433
        $return .= '</tr>';
7434
        $return .= '</thead>';
7435
7436
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7437
        $return .= '<tbody>';
7438
        $return .= '<tr>';
7439
        $return .= '<td colspan="3">';
7440
        $return .= '<div class="radio learnpath"><label for="idnone">';
7441
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7442
        $return .= get_lang('none').'</label>';
7443
        $return .= '</div>';
7444
        $return .= '</tr>';
7445
7446
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7447
        $sql = "SELECT * FROM $tbl_lp_item
7448
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7449
        $result = Database::query($sql);
7450
7451
        $selectedMinScore = [];
7452
        $selectedMaxScore = [];
7453
        $masteryScore = [];
7454
        while ($row = Database::fetch_array($result)) {
7455
            if ($row['iid'] == $itemId) {
7456
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7457
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7458
            }
7459
            $masteryScore[$row['iid']] = $row['mastery_score'];
7460
        }
7461
7462
        $arrLP = $this->getItemsForForm();
7463
        $this->tree_array($arrLP);
7464
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7465
        unset($this->arrMenu);
7466
7467
        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...
7468
            $item = $arrLP[$i];
7469
7470
            if ($item['id'] == $itemId) {
7471
                break;
7472
            }
7473
7474
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7475
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7476
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7477
7478
            $return .= '<tr>';
7479
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7480
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7481
            $return .= '<label for="id'.$item['id'].'">';
7482
7483
            $checked = '';
7484
            if (null !== $prerequisiteId) {
7485
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
7486
            }
7487
7488
            $disabled = 'dir' === $item['item_type'] ? ' disabled="disabled" ' : '';
7489
7490
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
7491
7492
            $icon_name = str_replace(' ', '', $item['item_type']);
7493
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7494
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7495
            } else {
7496
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7497
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7498
                } else {
7499
                    $return .= Display::return_icon('folder_document.png');
7500
                }
7501
            }
7502
7503
            $return .= $item['title'].'</label>';
7504
            $return .= '</div>';
7505
            $return .= '</td>';
7506
7507
            if (TOOL_QUIZ == $item['item_type']) {
7508
                // lets update max_score Tests information depending of the Tests Advanced properties
7509
                $lpItemObj = new LpItem($course_id, $item['id']);
7510
                $exercise = new Exercise($course_id);
7511
                $exercise->read($lpItemObj->path);
7512
                $lpItemObj->max_score = $exercise->get_max_score();
7513
                $lpItemObj->update();
7514
                $item['max_score'] = $lpItemObj->max_score;
7515
7516
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7517
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7518
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7519
                }
7520
7521
                $return .= '<td>';
7522
                $return .= '<input
7523
                    class="form-control"
7524
                    size="4" maxlength="3"
7525
                    name="min_'.$item['id'].'"
7526
                    type="number"
7527
                    min="0"
7528
                    step="any"
7529
                    max="'.$item['max_score'].'"
7530
                    value="'.$selectedMinScoreValue.'"
7531
                />';
7532
                $return .= '</td>';
7533
                $return .= '<td>';
7534
                $return .= '<input
7535
                    class="form-control"
7536
                    size="4"
7537
                    maxlength="3"
7538
                    name="max_'.$item['id'].'"
7539
                    type="number"
7540
                    min="0"
7541
                    step="any"
7542
                    max="'.$item['max_score'].'"
7543
                    value="'.$selectedMaxScoreValue.'"
7544
                />';
7545
                $return .= '</td>';
7546
            }
7547
7548
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7549
                $return .= '<td>';
7550
                $return .= '<input
7551
                    size="4"
7552
                    maxlength="3"
7553
                    name="min_'.$item['id'].'"
7554
                    type="number"
7555
                    min="0"
7556
                    step="any"
7557
                    max="'.$item['max_score'].'"
7558
                    value="'.$selectedMinScoreValue.'"
7559
                />';
7560
                $return .= '</td>';
7561
                $return .= '<td>';
7562
                $return .= '<input
7563
                    size="4"
7564
                    maxlength="3"
7565
                    name="max_'.$item['id'].'"
7566
                    type="number"
7567
                    min="0"
7568
                    step="any"
7569
                    max="'.$item['max_score'].'"
7570
                    value="'.$selectedMaxScoreValue.'"
7571
                />';
7572
                $return .= '</td>';
7573
            }
7574
            $return .= '</tr>';
7575
        }
7576
        $return .= '<tr>';
7577
        $return .= '</tr>';
7578
        $return .= '</tbody>';
7579
        $return .= '</table>';
7580
        $return .= '</div>';
7581
        $return .= '<div class="form-group">';
7582
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
7583
            get_lang('Save prerequisites settings').'</button>';
7584
        $return .= '</form>';
7585
7586
        return $return;
7587
    }
7588
7589
    /**
7590
     * Return HTML list to allow prerequisites selection for lp.
7591
     *
7592
     * @return string HTML form
7593
     */
7594
    public function display_lp_prerequisites_list()
7595
    {
7596
        $lp_id = $this->lp_id;
7597
        $prerequisiteId = $this->entity->getPrerequisite();
7598
7599
        $repo = Container::getLpRepository();
7600
        $qb = $repo->findAllByCourse(api_get_course_entity(), api_get_session_entity());
7601
        /** @var CLp[] $lps */
7602
        $lps = $qb->getQuery()->getResult();
7603
7604
        //$session_id = api_get_session_id();
7605
        /*$session_condition = api_get_session_condition($session_id, true, true);
7606
        $sql = "SELECT * FROM $tbl_lp
7607
                WHERE c_id = $course_id $session_condition
7608
                ORDER BY display_order ";
7609
        $rs = Database::query($sql);*/
7610
        $return = '';
7611
        $return .= '<select name="prerequisites" class="form-control">';
7612
        $return .= '<option value="0">'.get_lang('none').'</option>';
7613
7614
        foreach ($lps as $lp) {
7615
            $myLpId = $lp->getIid();
7616
            if ($myLpId == $lp_id) {
7617
                continue;
7618
            }
7619
            $return .= '<option
7620
                value="'.$myLpId.'" '.(($myLpId == $prerequisiteId) ? ' selected ' : '').'>'.
7621
                $lp->getName().
7622
                '</option>';
7623
        }
7624
7625
        $return .= '</select>';
7626
7627
        return $return;
7628
    }
7629
7630
    /**
7631
     * Creates a list with all the documents in it.
7632
     *
7633
     * @param bool $showInvisibleFiles
7634
     *
7635
     * @throws Exception
7636
     *
7637
     *
7638
     * @return string
7639
     */
7640
    public function get_documents($showInvisibleFiles = false)
7641
    {
7642
        $course_info = api_get_course_info();
7643
        $sessionId = api_get_session_id();
7644
        $documentTree = DocumentManager::get_document_preview(
7645
            $course_info,
7646
            $this->lp_id,
7647
            null,
7648
            $sessionId,
7649
            true,
7650
            null,
7651
            null,
7652
            $showInvisibleFiles,
7653
            true
7654
        );
7655
7656
        $form = new FormValidator(
7657
            'form_upload',
7658
            'POST',
7659
            $this->getCurrentBuildingModeURL(),
7660
            '',
7661
            ['enctype' => 'multipart/form-data']
7662
        );
7663
7664
        $folders = DocumentManager::get_all_document_folders(
7665
            api_get_course_info(),
7666
            0,
7667
            true
7668
        );
7669
7670
        $folder = $this->generate_lp_folder(api_get_course_info());
7671
7672
        DocumentManager::build_directory_selector(
7673
            $folders,
7674
            $folder->getIid(),
7675
            [],
7676
            true,
7677
            $form,
7678
            'directory_parent_id'
7679
        );
7680
7681
        $group = [
7682
            $form->createElement(
7683
                'radio',
7684
                'if_exists',
7685
                get_lang('If file exists:'),
7686
                get_lang('Do nothing'),
7687
                'nothing'
7688
            ),
7689
            $form->createElement(
7690
                'radio',
7691
                'if_exists',
7692
                null,
7693
                get_lang('Overwrite the existing file'),
7694
                'overwrite'
7695
            ),
7696
            $form->createElement(
7697
                'radio',
7698
                'if_exists',
7699
                null,
7700
                get_lang('Rename the uploaded file if it exists'),
7701
                'rename'
7702
            ),
7703
        ];
7704
        $form->addGroup($group, null, get_lang('If file exists:'));
7705
7706
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
7707
        $defaultFileExistsOption = 'rename';
7708
        if (!empty($fileExistsOption)) {
7709
            $defaultFileExistsOption = $fileExistsOption;
7710
        }
7711
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
7712
7713
        // Check box options
7714
        $form->addElement(
7715
            'checkbox',
7716
            'unzip',
7717
            get_lang('Options'),
7718
            get_lang('Uncompress zip')
7719
        );
7720
7721
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
7722
        $form->addMultipleUpload($url);
7723
7724
        $lpItem = new CLpItem();
7725
        $lpItem->setTitle('');
7726
        $lpItem->setItemType(TOOL_DOCUMENT);
7727
        $new = $this->displayDocumentForm('add', $lpItem);
7728
7729
        /*$lpItem = new CLpItem();
7730
        $lpItem->setItemType(TOOL_READOUT_TEXT);
7731
        $frmReadOutText = $this->displayDocumentForm('add');*/
7732
7733
        $headers = [
7734
            get_lang('Files'),
7735
            get_lang('Create a new document'),
7736
            //get_lang('Create read-out text'),
7737
            get_lang('Upload'),
7738
        ];
7739
7740
        return Display::tabs(
7741
            $headers,
7742
            [$documentTree, $new, $form->returnForm()],
7743
            'subtab'
7744
        );
7745
    }
7746
7747
    /**
7748
     * Creates a list with all the exercises (quiz) in it.
7749
     *
7750
     * @return string
7751
     */
7752
    public function get_exercises()
7753
    {
7754
        $course_id = api_get_course_int_id();
7755
        $session_id = api_get_session_id();
7756
        $userInfo = api_get_user_info();
7757
7758
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7759
        $condition_session = api_get_session_condition($session_id, true, true);
7760
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
7761
7762
        //$activeCondition = ' active <> -1 ';
7763
        $active = 2;
7764
        if ($setting) {
7765
            $active = 1;
7766
            //$activeCondition = ' active = 1 ';
7767
        }
7768
7769
        $categoryCondition = '';
7770
7771
        $keyword = $_REQUEST['keyword'] ?? null;
7772
        $categoryId = $_REQUEST['category_id'] ?? null;
7773
        /*if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
7774
            $categoryCondition = " AND exercise_category_id = $categoryId ";
7775
        }
7776
7777
        $keywordCondition = '';
7778
7779
        if (!empty($keyword)) {
7780
            $keyword = Database::escape_string($keyword);
7781
            $keywordCondition = " AND title LIKE '%$keyword%' ";
7782
        }
7783
        */
7784
        $course = api_get_course_entity($course_id);
7785
        $session = api_get_session_entity($session_id);
7786
7787
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, $keyword, $active, false, $categoryId);
7788
        /** @var CQuiz[] $exercises */
7789
        $exercises = $qb->getQuery()->getResult();
7790
7791
        /*$sql_quiz = "SELECT * FROM $tbl_quiz
7792
                     WHERE
7793
                            c_id = $course_id AND
7794
                            $activeCondition
7795
                            $condition_session
7796
                            $categoryCondition
7797
                            $keywordCondition
7798
                     ORDER BY title ASC";
7799
        $res_quiz = Database::query($sql_quiz);*/
7800
7801
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
7802
7803
        // Create a search-box
7804
        $form = new FormValidator('search_simple', 'get', $currentUrl);
7805
        $form->addHidden('action', 'add_item');
7806
        $form->addHidden('type', 'step');
7807
        $form->addHidden('lp_id', $this->lp_id);
7808
        $form->addHidden('lp_build_selected', '2');
7809
7810
        $form->addCourseHiddenParams();
7811
        $form->addText(
7812
            'keyword',
7813
            get_lang('Search'),
7814
            false,
7815
            [
7816
                'aria-label' => get_lang('Search'),
7817
            ]
7818
        );
7819
7820
        if (api_get_configuration_value('allow_exercise_categories')) {
7821
            $manager = new ExerciseCategoryManager();
7822
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
7823
            if (!empty($options)) {
7824
                $form->addSelect(
7825
                    'category_id',
7826
                    get_lang('Category'),
7827
                    $options,
7828
                    ['placeholder' => get_lang('Please select an option')]
7829
                );
7830
            }
7831
        }
7832
7833
        $form->addButtonSearch(get_lang('Search'));
7834
        $return = $form->returnForm();
7835
7836
        $return .= '<ul class="lp_resource">';
7837
        $return .= '<li class="lp_resource_element">';
7838
        $return .= Display::return_icon('new_exercice.png');
7839
        $return .= '<a
7840
            href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
7841
            get_lang('New test').'</a>';
7842
        $return .= '</li>';
7843
7844
        $previewIcon = Display::return_icon(
7845
            'preview_view.png',
7846
            get_lang('Preview')
7847
        );
7848
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
7849
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7850
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
7851
        foreach ($exercises as $exercise) {
7852
            $exerciseId = $exercise->getIid();
7853
            $title = strip_tags(api_html_entity_decode($exercise->getTitle()));
7854
            $visibility = $exercise->isVisible($course, $session);
7855
7856
            $link = Display::url(
7857
                $previewIcon,
7858
                $exerciseUrl.'&exerciseId='.$exerciseId,
7859
                ['target' => '_blank']
7860
            );
7861
            $return .= '<li class="lp_resource_element" title="'.$title.'">';
7862
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
7863
            $return .= $quizIcon;
7864
            $sessionStar = '';
7865
            /*$sessionStar = api_get_session_image(
7866
                $row_quiz['session_id'],
7867
                $userInfo['status']
7868
            );*/
7869
            $return .= Display::url(
7870
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
7871
                api_get_self().'?'.
7872
                    api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$exerciseId.'&lp_id='.$this->lp_id,
7873
                [
7874
                    'class' => false === $visibility ? 'moved text-muted ' : 'moved link_with_id',
7875
                    'data_type' => 'quiz',
7876
                    'data_id' => $exerciseId,
7877
                ]
7878
            );
7879
            $return .= '</li>';
7880
        }
7881
7882
        $return .= '</ul>';
7883
7884
        return $return;
7885
    }
7886
7887
    /**
7888
     * Creates a list with all the links in it.
7889
     *
7890
     * @return string
7891
     */
7892
    public function get_links()
7893
    {
7894
        $sessionId = api_get_session_id();
7895
        $repo = Container::getLinkRepository();
7896
7897
        $course = api_get_course_entity();
7898
        $session = api_get_session_entity($sessionId);
7899
        $qb = $repo->getResourcesByCourse($course, $session);
7900
        /** @var CLink[] $links */
7901
        $links = $qb->getQuery()->getResult();
7902
7903
        $selfUrl = api_get_self();
7904
        $courseIdReq = api_get_cidreq();
7905
        $userInfo = api_get_user_info();
7906
7907
        $moveEverywhereIcon = Display::return_icon(
7908
            'move_everywhere.png',
7909
            get_lang('Move'),
7910
            [],
7911
            ICON_SIZE_TINY
7912
        );
7913
7914
        /*$condition_session = api_get_session_condition(
7915
            $session_id,
7916
            true,
7917
            true,
7918
            'link.session_id'
7919
        );
7920
7921
        $sql = "SELECT
7922
                    link.id as link_id,
7923
                    link.title as link_title,
7924
                    link.session_id as link_session_id,
7925
                    link.category_id as category_id,
7926
                    link_category.category_title as category_title
7927
                FROM $tbl_link as link
7928
                LEFT JOIN $linkCategoryTable as link_category
7929
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
7930
                WHERE link.c_id = $course_id $condition_session
7931
                ORDER BY link_category.category_title ASC, link.title ASC";
7932
        $result = Database::query($sql);*/
7933
        $categorizedLinks = [];
7934
        $categories = [];
7935
7936
        foreach ($links as $link) {
7937
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
7938
7939
            if (empty($categoryId)) {
7940
                $categories[0] = get_lang('Uncategorized');
7941
            } else {
7942
                $category = $link->getCategory();
7943
                $categories[$categoryId] = $category->getCategoryTitle();
7944
            }
7945
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
7946
        }
7947
7948
        $linksHtmlCode =
7949
            '<script>
7950
            function toggle_tool(tool, id) {
7951
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
7952
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
7953
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
7954
                } else {
7955
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
7956
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
7957
                }
7958
            }
7959
        </script>
7960
7961
        <ul class="lp_resource">
7962
            <li class="lp_resource_element">
7963
                '.Display::return_icon('linksnew.gif').'
7964
                <a
7965
                href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'"
7966
                title="'.get_lang('Add a link').'">'.
7967
                get_lang('Add a link').'
7968
                </a>
7969
            </li>';
7970
        $linkIcon = Display::return_icon('links.png', '', [], ICON_SIZE_TINY);
7971
        foreach ($categorizedLinks as $categoryId => $links) {
7972
            $linkNodes = null;
7973
            /** @var CLink $link */
7974
            foreach ($links as $key => $link) {
7975
                $title = $link->getTitle();
7976
7977
                $linkUrl = Display::url(
7978
                    Display::return_icon('preview_view.png', get_lang('Preview')),
7979
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
7980
                    ['target' => '_blank']
7981
                );
7982
7983
                if ($link->isVisible($course, $session)) {
7984
                    //$sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
7985
                    $sessionStar = '';
7986
                    $url = $selfUrl.'?'.$courseIdReq.'&action=add_item&type='.TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id;
7987
                    $link = Display::url(
7988
                        Security::remove_XSS($title).$sessionStar.$linkUrl,
7989
                        $url,
7990
                        [
7991
                            'class' => 'moved link_with_id',
7992
                            'data_id' => $key,
7993
                            'data_type' => TOOL_LINK,
7994
                            'title' => $title,
7995
                        ]
7996
                    );
7997
                    $linkNodes .=
7998
                        '<li class="lp_resource_element">
7999
                         <a class="moved" href="#">'.
8000
                            $moveEverywhereIcon.
8001
                        '</a>
8002
                        '.$linkIcon.$link.'
8003
                        </li>';
8004
                }
8005
            }
8006
            $linksHtmlCode .=
8007
                '<li>
8008
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8009
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8010
                    align="absbottom" />
8011
                </a>
8012
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8013
            </li>
8014
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8015
        }
8016
        $linksHtmlCode .= '</ul>';
8017
8018
        return $linksHtmlCode;
8019
    }
8020
8021
    /**
8022
     * Creates a list with all the student publications in it.
8023
     *
8024
     * @return string
8025
     */
8026
    public function get_student_publications()
8027
    {
8028
        $return = '<ul class="lp_resource">';
8029
        $return .= '<li class="lp_resource_element">';
8030
        /*
8031
        $return .= Display::return_icon('works_new.gif');
8032
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8033
            get_lang('Add the Assignments tool to the course').'</a>';
8034
        $return .= '</li>';*/
8035
8036
        $works = getWorkListTeacher(0, 100, null, null, null);
8037
        if (!empty($works)) {
8038
            $icon = Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8039
            foreach ($works as $work) {
8040
                $link = Display::url(
8041
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8042
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8043
                    ['target' => '_blank']
8044
                );
8045
8046
                $return .= '<li class="lp_resource_element">';
8047
                $return .= '<a class="moved" href="#">';
8048
                $return .= Display::return_icon(
8049
                    'move_everywhere.png',
8050
                    get_lang('Move'),
8051
                    [],
8052
                    ICON_SIZE_TINY
8053
                );
8054
                $return .= '</a> ';
8055
8056
                $return .= $icon;
8057
                $return .= Display::url(
8058
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link,
8059
                    api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id,
8060
                    [
8061
                        'class' => 'moved link_with_id',
8062
                        'data_id' => $work['iid'],
8063
                        'data_type' => TOOL_STUDENTPUBLICATION,
8064
                        'title' => Security::remove_XSS(cut(strip_tags($work['title']), 80)),
8065
                    ]
8066
                );
8067
8068
                $return .= '</li>';
8069
            }
8070
        }
8071
8072
        $return .= '</ul>';
8073
8074
        return $return;
8075
    }
8076
8077
    /**
8078
     * Creates a list with all the forums in it.
8079
     *
8080
     * @return string
8081
     */
8082
    public function get_forums()
8083
    {
8084
        $forumCategories = get_forum_categories();
8085
        $forumsInNoCategory = get_forums_in_category(0);
8086
        if (!empty($forumsInNoCategory)) {
8087
            $forumCategories = array_merge(
8088
                $forumCategories,
8089
                [
8090
                    [
8091
                        'cat_id' => 0,
8092
                        'session_id' => 0,
8093
                        'visibility' => 1,
8094
                        'cat_comment' => null,
8095
                    ],
8096
                ]
8097
            );
8098
        }
8099
8100
        $a_forums = [];
8101
        $courseEntity = api_get_course_entity(api_get_course_int_id());
8102
        $sessionEntity = api_get_session_entity(api_get_session_id());
8103
8104
        foreach ($forumCategories as $forumCategory) {
8105
            // The forums in this category.
8106
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8107
            if (!empty($forumsInCategory)) {
8108
                foreach ($forumsInCategory as $forum) {
8109
                    if ($forum->isVisible($courseEntity, $sessionEntity)) {
8110
                        $a_forums[] = $forum;
8111
                    }
8112
                }
8113
            }
8114
        }
8115
8116
        $return = '<ul class="lp_resource">';
8117
8118
        // First add link
8119
        $return .= '<li class="lp_resource_element">';
8120
        $return .= Display::return_icon('new_forum.png');
8121
        $return .= Display::url(
8122
            get_lang('Create a new forum'),
8123
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8124
                'action' => 'add',
8125
                'content' => 'forum',
8126
                'lp_id' => $this->lp_id,
8127
            ]),
8128
            ['title' => get_lang('Create a new forum')]
8129
        );
8130
        $return .= '</li>';
8131
8132
        $return .= '<script>
8133
            function toggle_forum(forum_id) {
8134
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8135
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8136
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8137
                } else {
8138
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8139
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8140
                }
8141
            }
8142
        </script>';
8143
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8144
        foreach ($a_forums as $forum) {
8145
            $forumId = $forum->getIid();
8146
            $title = Security::remove_XSS($forum->getForumTitle());
8147
            $link = Display::url(
8148
                Display::return_icon('preview_view.png', get_lang('Preview')),
8149
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8150
                ['target' => '_blank']
8151
            );
8152
8153
            $return .= '<li class="lp_resource_element">';
8154
            $return .= '<a class="moved" href="#">';
8155
            $return .= $moveIcon;
8156
            $return .= ' </a>';
8157
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8158
8159
            $moveLink = Display::url(
8160
                $title.' '.$link,
8161
                api_get_self().'?'.
8162
                api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id,
8163
                [
8164
                    'class' => 'moved link_with_id',
8165
                    'data_id' => $forumId,
8166
                    'data_type' => TOOL_FORUM,
8167
                    'title' => $title,
8168
                    'style' => 'vertical-align:middle',
8169
                ]
8170
            );
8171
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8172
                            <img
8173
                                src="'.Display::returnIconPath('add.png').'"
8174
                                id="forum_'.$forumId.'_opener" align="absbottom"
8175
                             />
8176
                        </a>
8177
                        '.$moveLink;
8178
            $return .= '</li>';
8179
8180
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8181
            $threads = get_threads($forumId);
8182
            if (is_array($threads)) {
8183
                foreach ($threads as $thread) {
8184
                    $threadId = $thread->getIid();
8185
                    $link = Display::url(
8186
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8187
                        api_get_path(WEB_CODE_PATH).
8188
                        'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8189
                        ['target' => '_blank']
8190
                    );
8191
8192
                    $return .= '<li class="lp_resource_element">';
8193
                    $return .= '&nbsp;<a class="moved" href="#">';
8194
                    $return .= $moveIcon;
8195
                    $return .= ' </a>';
8196
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8197
                    $return .= '<a
8198
                        class="moved link_with_id"
8199
                        data_id="'.$thread->getIid().'"
8200
                        data_type="'.TOOL_THREAD.'"
8201
                        title="'.$thread->getThreadTitle().'"
8202
                        href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'"
8203
                        >'.
8204
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8205
                    $return .= '</li>';
8206
                }
8207
            }
8208
            $return .= '</div>';
8209
        }
8210
        $return .= '</ul>';
8211
8212
        return $return;
8213
    }
8214
8215
    /**
8216
     * // TODO: The output encoding should be equal to the system encoding.
8217
     *
8218
     * Exports the learning path as a SCORM package. This is the main function that
8219
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8220
     * whole thing and returns the zip.
8221
     *
8222
     * This method needs to be called in PHP5, as it will fail with non-adequate
8223
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8224
     * you need to call it on a learnpath object.
8225
     *
8226
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8227
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8228
     * path has been modified, it should use the generic method here below.
8229
     *
8230
     * @return string Returns the zip package string, or null if error
8231
     */
8232
    public function scormExport()
8233
    {
8234
        api_set_more_memory_and_time_limits();
8235
8236
        $_course = api_get_course_info();
8237
        $course_id = $_course['real_id'];
8238
        // Create the zip handler (this will remain available throughout the method).
8239
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8240
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8241
        $temp_dir_short = uniqid('scorm_export', true);
8242
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8243
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8244
        $zip_folder = new PclZip($temp_zip_file);
8245
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8246
        $root_path = $main_path = api_get_path(SYS_PATH);
8247
        $files_cleanup = [];
8248
8249
        // Place to temporarily stash the zip file.
8250
        // create the temp dir if it doesn't exist
8251
        // or do a cleanup before creating the zip file.
8252
        if (!is_dir($temp_zip_dir)) {
8253
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8254
        } else {
8255
            // Cleanup: Check the temp dir for old files and delete them.
8256
            $handle = opendir($temp_zip_dir);
8257
            while (false !== ($file = readdir($handle))) {
8258
                if ('.' != $file && '..' != $file) {
8259
                    unlink("$temp_zip_dir/$file");
8260
                }
8261
            }
8262
            closedir($handle);
8263
        }
8264
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8265
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8266
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8267
        ) {
8268
            // Remove the possible . at the end of the path.
8269
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8270
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8271
            mkdir(
8272
                $dest_path_to_scorm_folder,
8273
                api_get_permissions_for_new_directories(),
8274
                true
8275
            );
8276
            copyr(
8277
                $current_course_path.'/scorm/'.$this->path,
8278
                $dest_path_to_scorm_folder,
8279
                ['imsmanifest'],
8280
                $zip_files
8281
            );
8282
        }
8283
8284
        // Build a dummy imsmanifest structure.
8285
        // Do not add to the zip yet (we still need it).
8286
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8287
        // Aggregation Model official document, section "2.3 Content Packaging".
8288
        // We are going to build a UTF-8 encoded manifest.
8289
        // Later we will recode it to the desired (and supported) encoding.
8290
        $xmldoc = new DOMDocument('1.0');
8291
        $root = $xmldoc->createElement('manifest');
8292
        $root->setAttribute('identifier', 'SingleCourseManifest');
8293
        $root->setAttribute('version', '1.1');
8294
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8295
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8296
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8297
        $root->setAttribute(
8298
            'xsi:schemaLocation',
8299
            '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'
8300
        );
8301
        // Build mandatory sub-root container elements.
8302
        $metadata = $xmldoc->createElement('metadata');
8303
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8304
        $metadata->appendChild($md_schema);
8305
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8306
        $metadata->appendChild($md_schemaversion);
8307
        $root->appendChild($metadata);
8308
8309
        $organizations = $xmldoc->createElement('organizations');
8310
        $resources = $xmldoc->createElement('resources');
8311
8312
        // Build the only organization we will use in building our learnpaths.
8313
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8314
        $organization = $xmldoc->createElement('organization');
8315
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8316
        // To set the title of the SCORM entity (=organization), we take the name given
8317
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8318
        // learning path charset) as it is the encoding that defines how it is stored
8319
        // in the database. Then we convert it to HTML entities again as the "&" character
8320
        // alone is not authorized in XML (must be &amp;).
8321
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8322
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8323
        $organization->appendChild($org_title);
8324
        $folder_name = 'document';
8325
8326
        // Removes the learning_path/scorm_folder path when exporting see #4841
8327
        $path_to_remove = '';
8328
        $path_to_replace = '';
8329
        $result = $this->generate_lp_folder($_course);
8330
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8331
            $path_to_remove = 'document'.$result['dir'];
8332
            $path_to_replace = $folder_name.'/';
8333
        }
8334
8335
        // Fixes chamilo scorm exports
8336
        if ('chamilo_scorm_export' === $this->ref) {
8337
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8338
        }
8339
8340
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8341
        $link_updates = [];
8342
        $links_to_create = [];
8343
        foreach ($this->ordered_items as $index => $itemId) {
8344
            /** @var learnpathItem $item */
8345
            $item = $this->items[$itemId];
8346
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8347
                // Get included documents from this item.
8348
                if ('sco' === $item->type) {
8349
                    $inc_docs = $item->get_resources_from_source(
8350
                        null,
8351
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8352
                    );
8353
                } else {
8354
                    $inc_docs = $item->get_resources_from_source();
8355
                }
8356
8357
                // Give a child element <item> to the <organization> element.
8358
                $my_item_id = $item->get_id();
8359
                $my_item = $xmldoc->createElement('item');
8360
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8361
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8362
                $my_item->setAttribute('isvisible', 'true');
8363
                // Give a child element <title> to the <item> element.
8364
                $my_title = $xmldoc->createElement(
8365
                    'title',
8366
                    htmlspecialchars(
8367
                        api_utf8_encode($item->get_title()),
8368
                        ENT_QUOTES,
8369
                        'UTF-8'
8370
                    )
8371
                );
8372
                $my_item->appendChild($my_title);
8373
                // Give a child element <adlcp:prerequisites> to the <item> element.
8374
                $my_prereqs = $xmldoc->createElement(
8375
                    'adlcp:prerequisites',
8376
                    $this->get_scorm_prereq_string($my_item_id)
8377
                );
8378
                $my_prereqs->setAttribute('type', 'aicc_script');
8379
                $my_item->appendChild($my_prereqs);
8380
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8381
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8382
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8383
                //$xmldoc->createElement('adlcp:timelimitaction','');
8384
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8385
                //$xmldoc->createElement('adlcp:datafromlms','');
8386
                // Give a child element <adlcp:masteryscore> to the <item> element.
8387
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8388
                $my_item->appendChild($my_masteryscore);
8389
8390
                // Attach this item to the organization element or hits parent if there is one.
8391
                if (!empty($item->parent) && 0 != $item->parent) {
8392
                    $children = $organization->childNodes;
8393
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8394
                    if (is_object($possible_parent)) {
8395
                        $possible_parent->appendChild($my_item);
8396
                    } else {
8397
                        if ($this->debug > 0) {
8398
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8399
                        }
8400
                    }
8401
                } else {
8402
                    if ($this->debug > 0) {
8403
                        error_log('No parent');
8404
                    }
8405
                    $organization->appendChild($my_item);
8406
                }
8407
8408
                // Get the path of the file(s) from the course directory root.
8409
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8410
                $my_xml_file_path = $my_file_path;
8411
                if (!empty($path_to_remove)) {
8412
                    // From docs
8413
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8414
8415
                    // From quiz
8416
                    if ('chamilo_scorm_export' === $this->ref) {
8417
                        $path_to_remove = 'scorm/'.$this->path.'/';
8418
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8419
                    }
8420
                }
8421
8422
                $my_sub_dir = dirname($my_file_path);
8423
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8424
                $my_xml_sub_dir = $my_sub_dir;
8425
                // Give a <resource> child to the <resources> element
8426
                $my_resource = $xmldoc->createElement('resource');
8427
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8428
                $my_resource->setAttribute('type', 'webcontent');
8429
                $my_resource->setAttribute('href', $my_xml_file_path);
8430
                // adlcp:scormtype can be either 'sco' or 'asset'.
8431
                if ('sco' === $item->type) {
8432
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8433
                } else {
8434
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8435
                }
8436
                // xml:base is the base directory to find the files declared in this resource.
8437
                $my_resource->setAttribute('xml:base', '');
8438
                // Give a <file> child to the <resource> element.
8439
                $my_file = $xmldoc->createElement('file');
8440
                $my_file->setAttribute('href', $my_xml_file_path);
8441
                $my_resource->appendChild($my_file);
8442
8443
                // Dependency to other files - not yet supported.
8444
                $i = 1;
8445
                if ($inc_docs) {
8446
                    foreach ($inc_docs as $doc_info) {
8447
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8448
                            continue;
8449
                        }
8450
                        $my_dep = $xmldoc->createElement('resource');
8451
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8452
                        $my_dep->setAttribute('identifier', $res_id);
8453
                        $my_dep->setAttribute('type', 'webcontent');
8454
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8455
                        $my_dep_file = $xmldoc->createElement('file');
8456
                        // Check type of URL.
8457
                        if ('remote' == $doc_info[1]) {
8458
                            // Remote file. Save url as is.
8459
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8460
                            $my_dep->setAttribute('xml:base', '');
8461
                        } elseif ('local' === $doc_info[1]) {
8462
                            switch ($doc_info[2]) {
8463
                                case 'url':
8464
                                    // Local URL - save path as url for now, don't zip file.
8465
                                    $abs_path = api_get_path(SYS_PATH).
8466
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8467
                                    $current_dir = dirname($abs_path);
8468
                                    $current_dir = str_replace('\\', '/', $current_dir);
8469
                                    $file_path = realpath($abs_path);
8470
                                    $file_path = str_replace('\\', '/', $file_path);
8471
                                    $my_dep_file->setAttribute('href', $file_path);
8472
                                    $my_dep->setAttribute('xml:base', '');
8473
                                    if (false !== strstr($file_path, $main_path)) {
8474
                                        // The calculated real path is really inside Chamilo's root path.
8475
                                        // Reduce file path to what's under the DocumentRoot.
8476
                                        $replace = $file_path;
8477
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8478
                                        $destinationFile = $file_path;
8479
8480
                                        if (false !== strstr($file_path, 'upload/users')) {
8481
                                            $pos = strpos($file_path, 'my_files/');
8482
                                            if (false !== $pos) {
8483
                                                $onlyDirectory = str_replace(
8484
                                                    'upload/users/',
8485
                                                    '',
8486
                                                    substr($file_path, $pos, strlen($file_path))
8487
                                                );
8488
                                            }
8489
                                            $replace = $onlyDirectory;
8490
                                            $destinationFile = $replace;
8491
                                        }
8492
                                        $zip_files_abs[] = $file_path;
8493
                                        $link_updates[$my_file_path][] = [
8494
                                            'orig' => $doc_info[0],
8495
                                            'dest' => $destinationFile,
8496
                                            'replace' => $replace,
8497
                                        ];
8498
                                        $my_dep_file->setAttribute('href', $file_path);
8499
                                        $my_dep->setAttribute('xml:base', '');
8500
                                    } elseif (empty($file_path)) {
8501
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8502
                                        $file_path = str_replace('//', '/', $file_path);
8503
                                        if (file_exists($file_path)) {
8504
                                            // We get the relative path.
8505
                                            $file_path = substr($file_path, strlen($current_dir));
8506
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8507
                                            $link_updates[$my_file_path][] = [
8508
                                                'orig' => $doc_info[0],
8509
                                                'dest' => $file_path,
8510
                                            ];
8511
                                            $my_dep_file->setAttribute('href', $file_path);
8512
                                            $my_dep->setAttribute('xml:base', '');
8513
                                        }
8514
                                    }
8515
                                    break;
8516
                                case 'abs':
8517
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8518
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8519
                                    $my_dep->setAttribute('xml:base', '');
8520
8521
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8522
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8523
                                    $abs_img_path_without_subdir = $doc_info[0];
8524
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8525
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8526
                                    if (0 === $pos) {
8527
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8528
                                    }
8529
8530
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8531
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8532
8533
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8534
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8535
                                    // Check if the current document is in that path.
8536
                                    if (false !== strstr($file_path, $cur_path)) {
8537
                                        $destinationFile = substr($file_path, strlen($cur_path));
8538
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8539
8540
                                        $fileToTest = $cur_path.$my_file_path;
8541
                                        if (!empty($path_to_remove)) {
8542
                                            $fileToTest = str_replace(
8543
                                                $path_to_remove.'/',
8544
                                                $path_to_replace,
8545
                                                $cur_path.$my_file_path
8546
                                            );
8547
                                        }
8548
8549
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8550
8551
                                        // Put the current document in the zip (this array is the array
8552
                                        // that will manage documents already in the course folder - relative).
8553
                                        $zip_files[] = $filePathNoCoursePart;
8554
                                        // Update the links to the current document in the
8555
                                        // containing document (make them relative).
8556
                                        $link_updates[$my_file_path][] = [
8557
                                            'orig' => $doc_info[0],
8558
                                            'dest' => $destinationFile,
8559
                                            'replace' => $relative_path,
8560
                                        ];
8561
8562
                                        $my_dep_file->setAttribute('href', $file_path);
8563
                                        $my_dep->setAttribute('xml:base', '');
8564
                                    } elseif (false !== strstr($file_path, $main_path)) {
8565
                                        // The calculated real path is really inside Chamilo's root path.
8566
                                        // Reduce file path to what's under the DocumentRoot.
8567
                                        $file_path = substr($file_path, strlen($root_path));
8568
                                        $zip_files_abs[] = $file_path;
8569
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8570
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8571
                                        $my_dep->setAttribute('xml:base', '');
8572
                                    } elseif (empty($file_path)) {
8573
                                        // Probably this is an image inside "/main" directory
8574
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8575
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8576
8577
                                        if (file_exists($file_path)) {
8578
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8579
                                                // We get the relative path.
8580
                                                $pos = strpos($file_path, 'main/default_course_document/');
8581
                                                if (false !== $pos) {
8582
                                                    $onlyDirectory = str_replace(
8583
                                                        'main/default_course_document/',
8584
                                                        '',
8585
                                                        substr($file_path, $pos, strlen($file_path))
8586
                                                    );
8587
                                                }
8588
8589
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8590
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8591
                                                $zip_files_abs[] = $fileAbs;
8592
                                                $link_updates[$my_file_path][] = [
8593
                                                    'orig' => $doc_info[0],
8594
                                                    'dest' => $destinationFile,
8595
                                                ];
8596
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8597
                                                $my_dep->setAttribute('xml:base', '');
8598
                                            }
8599
                                        }
8600
                                    }
8601
                                    break;
8602
                                case 'rel':
8603
                                    // Path relative to the current document.
8604
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8605
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8606
                                        // Relative path going up.
8607
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8608
                                        $current_dir = str_replace('\\', '/', $current_dir);
8609
                                        $file_path = realpath($current_dir.$doc_info[0]);
8610
                                        $file_path = str_replace('\\', '/', $file_path);
8611
                                        if (false !== strstr($file_path, $main_path)) {
8612
                                            // The calculated real path is really inside Chamilo's root path.
8613
                                            // Reduce file path to what's under the DocumentRoot.
8614
                                            $file_path = substr($file_path, strlen($root_path));
8615
                                            $zip_files_abs[] = $file_path;
8616
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8617
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8618
                                            $my_dep->setAttribute('xml:base', '');
8619
                                        }
8620
                                    } else {
8621
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8622
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
8623
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8624
                                    }
8625
                                    break;
8626
                                default:
8627
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8628
                                    $my_dep->setAttribute('xml:base', '');
8629
                                    break;
8630
                            }
8631
                        }
8632
                        $my_dep->appendChild($my_dep_file);
8633
                        $resources->appendChild($my_dep);
8634
                        $dependency = $xmldoc->createElement('dependency');
8635
                        $dependency->setAttribute('identifierref', $res_id);
8636
                        $my_resource->appendChild($dependency);
8637
                        $i++;
8638
                    }
8639
                }
8640
                $resources->appendChild($my_resource);
8641
                $zip_files[] = $my_file_path;
8642
            } else {
8643
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
8644
                switch ($item->type) {
8645
                    case TOOL_LINK:
8646
                        $my_item = $xmldoc->createElement('item');
8647
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8648
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8649
                        $my_item->setAttribute('isvisible', 'true');
8650
                        // Give a child element <title> to the <item> element.
8651
                        $my_title = $xmldoc->createElement(
8652
                            'title',
8653
                            htmlspecialchars(
8654
                                api_utf8_encode($item->get_title()),
8655
                                ENT_QUOTES,
8656
                                'UTF-8'
8657
                            )
8658
                        );
8659
                        $my_item->appendChild($my_title);
8660
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8661
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8662
                        $my_prereqs->setAttribute('type', 'aicc_script');
8663
                        $my_item->appendChild($my_prereqs);
8664
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8665
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
8666
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8667
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
8668
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8669
                        //$xmldoc->createElement('adlcp:datafromlms', '');
8670
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8671
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8672
                        $my_item->appendChild($my_masteryscore);
8673
8674
                        // Attach this item to the organization element or its parent if there is one.
8675
                        if (!empty($item->parent) && 0 != $item->parent) {
8676
                            $children = $organization->childNodes;
8677
                            for ($i = 0; $i < $children->length; $i++) {
8678
                                $item_temp = $children->item($i);
8679
                                if ('item' == $item_temp->nodeName) {
8680
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
8681
                                        $item_temp->appendChild($my_item);
8682
                                    }
8683
                                }
8684
                            }
8685
                        } else {
8686
                            $organization->appendChild($my_item);
8687
                        }
8688
8689
                        $my_file_path = 'link_'.$item->get_id().'.html';
8690
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
8691
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
8692
                        $rs = Database::query($sql);
8693
                        if ($link = Database::fetch_array($rs)) {
8694
                            $url = $link['url'];
8695
                            $title = stripslashes($link['title']);
8696
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
8697
                            $my_xml_file_path = $my_file_path;
8698
                            $my_sub_dir = dirname($my_file_path);
8699
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8700
                            $my_xml_sub_dir = $my_sub_dir;
8701
                            // Give a <resource> child to the <resources> element.
8702
                            $my_resource = $xmldoc->createElement('resource');
8703
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8704
                            $my_resource->setAttribute('type', 'webcontent');
8705
                            $my_resource->setAttribute('href', $my_xml_file_path);
8706
                            // adlcp:scormtype can be either 'sco' or 'asset'.
8707
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
8708
                            // xml:base is the base directory to find the files declared in this resource.
8709
                            $my_resource->setAttribute('xml:base', '');
8710
                            // give a <file> child to the <resource> element.
8711
                            $my_file = $xmldoc->createElement('file');
8712
                            $my_file->setAttribute('href', $my_xml_file_path);
8713
                            $my_resource->appendChild($my_file);
8714
                            $resources->appendChild($my_resource);
8715
                        }
8716
                        break;
8717
                    case TOOL_QUIZ:
8718
                        $exe_id = $item->path;
8719
                        // Should be using ref when everything will be cleaned up in this regard.
8720
                        $exe = new Exercise();
8721
                        $exe->read($exe_id);
8722
                        $my_item = $xmldoc->createElement('item');
8723
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
8724
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
8725
                        $my_item->setAttribute('isvisible', 'true');
8726
                        // Give a child element <title> to the <item> element.
8727
                        $my_title = $xmldoc->createElement(
8728
                            'title',
8729
                            htmlspecialchars(
8730
                                api_utf8_encode($item->get_title()),
8731
                                ENT_QUOTES,
8732
                                'UTF-8'
8733
                            )
8734
                        );
8735
                        $my_item->appendChild($my_title);
8736
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
8737
                        $my_item->appendChild($my_max_score);
8738
                        // Give a child element <adlcp:prerequisites> to the <item> element.
8739
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
8740
                        $my_prereqs->setAttribute('type', 'aicc_script');
8741
                        $my_item->appendChild($my_prereqs);
8742
                        // Give a child element <adlcp:masteryscore> to the <item> element.
8743
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8744
                        $my_item->appendChild($my_masteryscore);
8745
8746
                        // Attach this item to the organization element or hits parent if there is one.
8747
                        if (!empty($item->parent) && 0 != $item->parent) {
8748
                            $children = $organization->childNodes;
8749
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8750
                            if ($possible_parent) {
8751
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
8752
                                    $possible_parent->appendChild($my_item);
8753
                                }
8754
                            }
8755
                        } else {
8756
                            $organization->appendChild($my_item);
8757
                        }
8758
8759
                        // Get the path of the file(s) from the course directory root
8760
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8761
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
8762
                        // Write the contents of the exported exercise into a (big) html file
8763
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
8764
                        $scormExercise = new ScormExercise($exe, true);
8765
                        $contents = $scormExercise->export();
8766
8767
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
8768
                        $res = file_put_contents($tmp_file_path, $contents);
8769
                        if (false === $res) {
8770
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
8771
                        }
8772
                        $files_cleanup[] = $tmp_file_path;
8773
                        $my_xml_file_path = $my_file_path;
8774
                        $my_sub_dir = dirname($my_file_path);
8775
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8776
                        $my_xml_sub_dir = $my_sub_dir;
8777
                        // Give a <resource> child to the <resources> element.
8778
                        $my_resource = $xmldoc->createElement('resource');
8779
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8780
                        $my_resource->setAttribute('type', 'webcontent');
8781
                        $my_resource->setAttribute('href', $my_xml_file_path);
8782
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8783
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
8784
                        // xml:base is the base directory to find the files declared in this resource.
8785
                        $my_resource->setAttribute('xml:base', '');
8786
                        // Give a <file> child to the <resource> element.
8787
                        $my_file = $xmldoc->createElement('file');
8788
                        $my_file->setAttribute('href', $my_xml_file_path);
8789
                        $my_resource->appendChild($my_file);
8790
8791
                        // Get included docs.
8792
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
8793
8794
                        // Dependency to other files - not yet supported.
8795
                        $i = 1;
8796
                        foreach ($inc_docs as $doc_info) {
8797
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
8798
                                continue;
8799
                            }
8800
                            $my_dep = $xmldoc->createElement('resource');
8801
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8802
                            $my_dep->setAttribute('identifier', $res_id);
8803
                            $my_dep->setAttribute('type', 'webcontent');
8804
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
8805
                            $my_dep_file = $xmldoc->createElement('file');
8806
                            // Check type of URL.
8807
                            if ('remote' == $doc_info[1]) {
8808
                                // Remote file. Save url as is.
8809
                                $my_dep_file->setAttribute('href', $doc_info[0]);
8810
                                $my_dep->setAttribute('xml:base', '');
8811
                            } elseif ('local' == $doc_info[1]) {
8812
                                switch ($doc_info[2]) {
8813
                                    case 'url': // Local URL - save path as url for now, don't zip file.
8814
                                        // Save file but as local file (retrieve from URL).
8815
                                        $abs_path = api_get_path(SYS_PATH).
8816
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8817
                                        $current_dir = dirname($abs_path);
8818
                                        $current_dir = str_replace('\\', '/', $current_dir);
8819
                                        $file_path = realpath($abs_path);
8820
                                        $file_path = str_replace('\\', '/', $file_path);
8821
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8822
                                        $my_dep->setAttribute('xml:base', '');
8823
                                        if (false !== strstr($file_path, $main_path)) {
8824
                                            // The calculated real path is really inside the chamilo root path.
8825
                                            // Reduce file path to what's under the DocumentRoot.
8826
                                            $file_path = substr($file_path, strlen($root_path));
8827
                                            $zip_files_abs[] = $file_path;
8828
                                            $link_updates[$my_file_path][] = [
8829
                                                'orig' => $doc_info[0],
8830
                                                'dest' => 'document/'.$file_path,
8831
                                            ];
8832
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8833
                                            $my_dep->setAttribute('xml:base', '');
8834
                                        } elseif (empty($file_path)) {
8835
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8836
                                            $file_path = str_replace('//', '/', $file_path);
8837
                                            if (file_exists($file_path)) {
8838
                                                $file_path = substr($file_path, strlen($current_dir));
8839
                                                // We get the relative path.
8840
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8841
                                                $link_updates[$my_file_path][] = [
8842
                                                    'orig' => $doc_info[0],
8843
                                                    'dest' => 'document/'.$file_path,
8844
                                                ];
8845
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8846
                                                $my_dep->setAttribute('xml:base', '');
8847
                                            }
8848
                                        }
8849
                                        break;
8850
                                    case 'abs':
8851
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8852
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8853
                                        $current_dir = str_replace('\\', '/', $current_dir);
8854
                                        $file_path = realpath($doc_info[0]);
8855
                                        $file_path = str_replace('\\', '/', $file_path);
8856
                                        $my_dep_file->setAttribute('href', $file_path);
8857
                                        $my_dep->setAttribute('xml:base', '');
8858
8859
                                        if (false !== strstr($file_path, $main_path)) {
8860
                                            // The calculated real path is really inside the chamilo root path.
8861
                                            // Reduce file path to what's under the DocumentRoot.
8862
                                            $file_path = substr($file_path, strlen($root_path));
8863
                                            $zip_files_abs[] = $file_path;
8864
                                            $link_updates[$my_file_path][] = [
8865
                                                'orig' => $doc_info[0],
8866
                                                'dest' => $file_path,
8867
                                            ];
8868
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
8869
                                            $my_dep->setAttribute('xml:base', '');
8870
                                        } elseif (empty($file_path)) {
8871
                                            $docSysPartPath = str_replace(
8872
                                                api_get_path(REL_COURSE_PATH),
8873
                                                '',
8874
                                                $doc_info[0]
8875
                                            );
8876
8877
                                            $docSysPartPathNoCourseCode = str_replace(
8878
                                                $_course['directory'].'/',
8879
                                                '',
8880
                                                $docSysPartPath
8881
                                            );
8882
8883
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
8884
                                            if (file_exists($docSysPath)) {
8885
                                                $file_path = $docSysPartPathNoCourseCode;
8886
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
8887
                                                $link_updates[$my_file_path][] = [
8888
                                                    'orig' => $doc_info[0],
8889
                                                    'dest' => $file_path,
8890
                                                ];
8891
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8892
                                                $my_dep->setAttribute('xml:base', '');
8893
                                            }
8894
                                        }
8895
                                        break;
8896
                                    case 'rel':
8897
                                        // Path relative to the current document. Save xml:base as current document's
8898
                                        // directory and save file in zip as subdir.file_path
8899
                                        if ('..' === substr($doc_info[0], 0, 2)) {
8900
                                            // Relative path going up.
8901
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8902
                                            $current_dir = str_replace('\\', '/', $current_dir);
8903
                                            $file_path = realpath($current_dir.$doc_info[0]);
8904
                                            $file_path = str_replace('\\', '/', $file_path);
8905
                                            if (false !== strstr($file_path, $main_path)) {
8906
                                                // The calculated real path is really inside Chamilo's root path.
8907
                                                // Reduce file path to what's under the DocumentRoot.
8908
8909
                                                $file_path = substr($file_path, strlen($root_path));
8910
                                                $file_path_dest = $file_path;
8911
8912
                                                // File path is courses/CHAMILO/document/....
8913
                                                $info_file_path = explode('/', $file_path);
8914
                                                if ('courses' == $info_file_path[0]) {
8915
                                                    // Add character "/" in file path.
8916
                                                    $file_path_dest = 'document/'.$file_path;
8917
                                                }
8918
                                                $zip_files_abs[] = $file_path;
8919
8920
                                                $link_updates[$my_file_path][] = [
8921
                                                    'orig' => $doc_info[0],
8922
                                                    'dest' => $file_path_dest,
8923
                                                ];
8924
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8925
                                                $my_dep->setAttribute('xml:base', '');
8926
                                            }
8927
                                        } else {
8928
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
8929
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
8930
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
8931
                                        }
8932
                                        break;
8933
                                    default:
8934
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
8935
                                        $my_dep->setAttribute('xml:base', '');
8936
                                        break;
8937
                                }
8938
                            }
8939
                            $my_dep->appendChild($my_dep_file);
8940
                            $resources->appendChild($my_dep);
8941
                            $dependency = $xmldoc->createElement('dependency');
8942
                            $dependency->setAttribute('identifierref', $res_id);
8943
                            $my_resource->appendChild($dependency);
8944
                            $i++;
8945
                        }
8946
                        $resources->appendChild($my_resource);
8947
                        $zip_files[] = $my_file_path;
8948
                        break;
8949
                    default:
8950
                        // Get the path of the file(s) from the course directory root
8951
                        $my_file_path = 'non_exportable.html';
8952
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
8953
                        $my_xml_file_path = $my_file_path;
8954
                        $my_sub_dir = dirname($my_file_path);
8955
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8956
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
8957
                        $my_xml_sub_dir = $my_sub_dir;
8958
                        // Give a <resource> child to the <resources> element.
8959
                        $my_resource = $xmldoc->createElement('resource');
8960
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8961
                        $my_resource->setAttribute('type', 'webcontent');
8962
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
8963
                        // adlcp:scormtype can be either 'sco' or 'asset'.
8964
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
8965
                        // xml:base is the base directory to find the files declared in this resource.
8966
                        $my_resource->setAttribute('xml:base', '');
8967
                        // Give a <file> child to the <resource> element.
8968
                        $my_file = $xmldoc->createElement('file');
8969
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
8970
                        $my_resource->appendChild($my_file);
8971
                        $resources->appendChild($my_resource);
8972
                        break;
8973
                }
8974
            }
8975
        }
8976
        $organizations->appendChild($organization);
8977
        $root->appendChild($organizations);
8978
        $root->appendChild($resources);
8979
        $xmldoc->appendChild($root);
8980
8981
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
8982
8983
        // then add the file to the zip, then destroy the file (this is done automatically).
8984
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
8985
        foreach ($zip_files as $file_path) {
8986
            if (empty($file_path)) {
8987
                continue;
8988
            }
8989
8990
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
8991
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
8992
8993
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
8994
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
8995
            }
8996
8997
            $this->create_path($dest_file);
8998
            @copy($filePath, $dest_file);
8999
9000
            // Check if the file needs a link update.
9001
            if (in_array($file_path, array_keys($link_updates))) {
9002
                $string = file_get_contents($dest_file);
9003
                unlink($dest_file);
9004
                foreach ($link_updates[$file_path] as $old_new) {
9005
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9006
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9007
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9008
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9009
                    if ('flv' === substr($old_new['dest'], -3) &&
9010
                        'main/' === substr($old_new['dest'], 0, 5)
9011
                    ) {
9012
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9013
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9014
                        'video/' === substr($old_new['dest'], 0, 6)
9015
                    ) {
9016
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9017
                    }
9018
9019
                    // Fix to avoid problems with default_course_document
9020
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9021
                        $newDestination = $old_new['dest'];
9022
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9023
                            $newDestination = $old_new['replace'];
9024
                        }
9025
                    } else {
9026
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9027
                    }
9028
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9029
9030
                    // Add files inside the HTMLs
9031
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9032
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9033
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9034
                        copy(
9035
                            $sys_course_path.$new_path,
9036
                            $destinationFile
9037
                        );
9038
                    }
9039
                }
9040
                file_put_contents($dest_file, $string);
9041
            }
9042
9043
            if (file_exists($filePath) && $copyAll) {
9044
                $extension = $this->get_extension($filePath);
9045
                if (in_array($extension, ['html', 'html'])) {
9046
                    $containerOrigin = dirname($filePath);
9047
                    $containerDestination = dirname($dest_file);
9048
9049
                    $finder = new Finder();
9050
                    $finder->files()->in($containerOrigin)
9051
                        ->notName('*_DELETED_*')
9052
                        ->exclude('share_folder')
9053
                        ->exclude('chat_files')
9054
                        ->exclude('certificates')
9055
                    ;
9056
9057
                    if (is_dir($containerOrigin) &&
9058
                        is_dir($containerDestination)
9059
                    ) {
9060
                        $fs = new Filesystem();
9061
                        $fs->mirror(
9062
                            $containerOrigin,
9063
                            $containerDestination,
9064
                            $finder
9065
                        );
9066
                    }
9067
                }
9068
            }
9069
        }
9070
9071
        foreach ($zip_files_abs as $file_path) {
9072
            if (empty($file_path)) {
9073
                continue;
9074
            }
9075
9076
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9077
                continue;
9078
            }
9079
9080
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9081
            if (false !== strstr($file_path, 'upload/users')) {
9082
                $pos = strpos($file_path, 'my_files/');
9083
                if (false !== $pos) {
9084
                    $onlyDirectory = str_replace(
9085
                        'upload/users/',
9086
                        '',
9087
                        substr($file_path, $pos, strlen($file_path))
9088
                    );
9089
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9090
                }
9091
            }
9092
9093
            if (false !== strstr($file_path, 'default_course_document/')) {
9094
                $replace = str_replace('/main', '', $file_path);
9095
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9096
            }
9097
9098
            if (empty($dest_file)) {
9099
                continue;
9100
            }
9101
9102
            $this->create_path($dest_file);
9103
            copy($main_path.$file_path, $dest_file);
9104
            // Check if the file needs a link update.
9105
            if (in_array($file_path, array_keys($link_updates))) {
9106
                $string = file_get_contents($dest_file);
9107
                unlink($dest_file);
9108
                foreach ($link_updates[$file_path] as $old_new) {
9109
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9110
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9111
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9112
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9113
                    if ('flv' == substr($old_new['dest'], -3) &&
9114
                        'main/' == substr($old_new['dest'], 0, 5)
9115
                    ) {
9116
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9117
                    }
9118
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9119
                }
9120
                file_put_contents($dest_file, $string);
9121
            }
9122
        }
9123
9124
        if (is_array($links_to_create)) {
9125
            foreach ($links_to_create as $file => $link) {
9126
                $content = '<!DOCTYPE html><head>
9127
                            <meta charset="'.api_get_language_isocode().'" />
9128
                            <title>'.$link['title'].'</title>
9129
                            </head>
9130
                            <body dir="'.api_get_text_direction().'">
9131
                            <div style="text-align:center">
9132
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9133
                            </body>
9134
                            </html>';
9135
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9136
            }
9137
        }
9138
9139
        // Add non exportable message explanation.
9140
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9141
        $file_content = '<!DOCTYPE html><head>
9142
                        <meta charset="'.api_get_language_isocode().'" />
9143
                        <title>'.$lang_not_exportable.'</title>
9144
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9145
                        </head>
9146
                        <body dir="'.api_get_text_direction().'">';
9147
        $file_content .=
9148
            <<<EOD
9149
                    <style>
9150
            .error-message {
9151
                font-family: arial, verdana, helvetica, sans-serif;
9152
                border-width: 1px;
9153
                border-style: solid;
9154
                left: 50%;
9155
                margin: 10px auto;
9156
                min-height: 30px;
9157
                padding: 5px;
9158
                right: 50%;
9159
                width: 500px;
9160
                background-color: #FFD1D1;
9161
                border-color: #FF0000;
9162
                color: #000;
9163
            }
9164
        </style>
9165
    <body>
9166
        <div class="error-message">
9167
            $lang_not_exportable
9168
        </div>
9169
    </body>
9170
</html>
9171
EOD;
9172
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9173
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9174
        }
9175
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9176
9177
        // Add the extra files that go along with a SCORM package.
9178
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9179
9180
        $fs = new Filesystem();
9181
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9182
9183
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9184
        $manifest = @$xmldoc->saveXML();
9185
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9186
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9187
        $zip_folder->add(
9188
            $archivePath.'/'.$temp_dir_short,
9189
            PCLZIP_OPT_REMOVE_PATH,
9190
            $archivePath.'/'.$temp_dir_short.'/'
9191
        );
9192
9193
        // Clean possible temporary files.
9194
        foreach ($files_cleanup as $file) {
9195
            $res = unlink($file);
9196
            if (false === $res) {
9197
                error_log(
9198
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9199
                    0
9200
                );
9201
            }
9202
        }
9203
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9204
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9205
    }
9206
9207
    /**
9208
     * @param int $lp_id
9209
     *
9210
     * @return bool
9211
     */
9212
    public function scorm_export_to_pdf($lp_id)
9213
    {
9214
        $lp_id = (int) $lp_id;
9215
        $files_to_export = [];
9216
9217
        $sessionId = api_get_session_id();
9218
        $course_data = api_get_course_info($this->cc);
9219
9220
        $lp = Container::getLpRepository()->find($lp_id);
9221
        if (!empty($course_data)) {
9222
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9223
            $list = self::get_flat_ordered_items_list($lp);
9224
            if (!empty($list)) {
9225
                foreach ($list as $item_id) {
9226
                    $item = $this->items[$item_id];
9227
                    switch ($item->type) {
9228
                        case 'document':
9229
                            // Getting documents from a LP with chamilo documents
9230
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9231
                            // Try loading document from the base course.
9232
                            if (empty($file_data) && !empty($sessionId)) {
9233
                                $file_data = DocumentManager::get_document_data_by_id(
9234
                                    $item->path,
9235
                                    $this->cc,
9236
                                    false,
9237
                                    0
9238
                                );
9239
                            }
9240
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9241
                            if (file_exists($file_path)) {
9242
                                $files_to_export[] = [
9243
                                    'title' => $item->get_title(),
9244
                                    'path' => $file_path,
9245
                                ];
9246
                            }
9247
                            break;
9248
                        case 'asset': //commes from a scorm package generated by chamilo
9249
                        case 'sco':
9250
                            $file_path = $scorm_path.'/'.$item->path;
9251
                            if (file_exists($file_path)) {
9252
                                $files_to_export[] = [
9253
                                    'title' => $item->get_title(),
9254
                                    'path' => $file_path,
9255
                                ];
9256
                            }
9257
                            break;
9258
                        case 'dir':
9259
                            $files_to_export[] = [
9260
                                'title' => $item->get_title(),
9261
                                'path' => null,
9262
                            ];
9263
                            break;
9264
                    }
9265
                }
9266
            }
9267
9268
            $pdf = new PDF();
9269
            $result = $pdf->html_to_pdf(
9270
                $files_to_export,
9271
                $this->name,
9272
                $this->cc,
9273
                true,
9274
                true,
9275
                true,
9276
                $this->get_name()
9277
            );
9278
9279
            return $result;
9280
        }
9281
9282
        return false;
9283
    }
9284
9285
    /**
9286
     * Temp function to be moved in main_api or the best place around for this.
9287
     * Creates a file path if it doesn't exist.
9288
     *
9289
     * @param string $path
9290
     */
9291
    public function create_path($path)
9292
    {
9293
        $path_bits = explode('/', dirname($path));
9294
9295
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9296
        $path_built = IS_WINDOWS_OS ? '' : '/';
9297
        foreach ($path_bits as $bit) {
9298
            if (!empty($bit)) {
9299
                $new_path = $path_built.$bit;
9300
                if (is_dir($new_path)) {
9301
                    $path_built = $new_path.'/';
9302
                } else {
9303
                    mkdir($new_path, api_get_permissions_for_new_directories());
9304
                    $path_built = $new_path.'/';
9305
                }
9306
            }
9307
        }
9308
    }
9309
9310
    /**
9311
     * @param int    $lp_id
9312
     * @param string $status
9313
     */
9314
    public function set_autolaunch($lp_id, $status)
9315
    {
9316
        $course_id = api_get_course_int_id();
9317
        $lp_id = (int) $lp_id;
9318
        $status = (int) $status;
9319
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9320
9321
        // Setting everything to autolaunch = 0
9322
        $attributes['autolaunch'] = 0;
9323
        $where = [
9324
            'session_id = ? AND c_id = ? ' => [
9325
                api_get_session_id(),
9326
                $course_id,
9327
            ],
9328
        ];
9329
        Database::update($lp_table, $attributes, $where);
9330
        if (1 == $status) {
9331
            //Setting my lp_id to autolaunch = 1
9332
            $attributes['autolaunch'] = 1;
9333
            $where = [
9334
                'iid = ? AND session_id = ? AND c_id = ?' => [
9335
                    $lp_id,
9336
                    api_get_session_id(),
9337
                    $course_id,
9338
                ],
9339
            ];
9340
            Database::update($lp_table, $attributes, $where);
9341
        }
9342
    }
9343
9344
    /**
9345
     * Gets previous_item_id for the next element of the lp_item table.
9346
     *
9347
     * @author Isaac flores paz
9348
     *
9349
     * @return int Previous item ID
9350
     */
9351
    public function select_previous_item_id()
9352
    {
9353
        $course_id = api_get_course_int_id();
9354
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9355
9356
        // Get the max order of the items
9357
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9358
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9359
        $rs_max_order = Database::query($sql);
9360
        $row_max_order = Database::fetch_object($rs_max_order);
9361
        $max_order = $row_max_order->display_order;
9362
        // Get the previous item ID
9363
        $sql = "SELECT iid as previous FROM $table_lp_item
9364
                WHERE
9365
                    c_id = $course_id AND
9366
                    lp_id = ".$this->lp_id." AND
9367
                    display_order = '$max_order' ";
9368
        $rs_max = Database::query($sql);
9369
        $row_max = Database::fetch_object($rs_max);
9370
9371
        // Return the previous item ID
9372
        return $row_max->previous;
9373
    }
9374
9375
    /**
9376
     * Copies an LP.
9377
     */
9378
    public function copy()
9379
    {
9380
        // Course builder
9381
        $cb = new CourseBuilder();
9382
9383
        //Setting tools that will be copied
9384
        $cb->set_tools_to_build(['learnpaths']);
9385
9386
        //Setting elements that will be copied
9387
        $cb->set_tools_specific_id_list(
9388
            ['learnpaths' => [$this->lp_id]]
9389
        );
9390
9391
        $course = $cb->build();
9392
9393
        //Course restorer
9394
        $course_restorer = new CourseRestorer($course);
9395
        $course_restorer->set_add_text_in_items(true);
9396
        $course_restorer->set_tool_copy_settings(
9397
            ['learnpaths' => ['reset_dates' => true]]
9398
        );
9399
        $course_restorer->restore(
9400
            api_get_course_id(),
9401
            api_get_session_id(),
9402
            false,
9403
            false
9404
        );
9405
    }
9406
9407
    /**
9408
     * Verify document size.
9409
     *
9410
     * @param string $s
9411
     *
9412
     * @return bool
9413
     */
9414
    public static function verify_document_size($s)
9415
    {
9416
        $post_max = ini_get('post_max_size');
9417
        if ('M' == substr($post_max, -1, 1)) {
9418
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9419
        } elseif ('G' == substr($post_max, -1, 1)) {
9420
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9421
        }
9422
        $upl_max = ini_get('upload_max_filesize');
9423
        if ('M' == substr($upl_max, -1, 1)) {
9424
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9425
        } elseif ('G' == substr($upl_max, -1, 1)) {
9426
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9427
        }
9428
9429
        $repo = Container::getDocumentRepository();
9430
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9431
9432
        $course_max_space = DocumentManager::get_course_quota();
9433
        $total_size = filesize($s) + $documents_total_space;
9434
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9435
            return true;
9436
        }
9437
9438
        return false;
9439
    }
9440
9441
    /**
9442
     * Clear LP prerequisites.
9443
     */
9444
    public function clearPrerequisites()
9445
    {
9446
        $course_id = $this->get_course_int_id();
9447
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9448
        $lp_id = $this->get_id();
9449
        // Cleaning prerequisites
9450
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9451
                WHERE lp_id = $lp_id";
9452
        Database::query($sql);
9453
9454
        // Cleaning mastery score for exercises
9455
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9456
                WHERE lp_id = $lp_id AND item_type = 'quiz'";
9457
        Database::query($sql);
9458
    }
9459
9460
    public function set_previous_step_as_prerequisite_for_all_items()
9461
    {
9462
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9463
        $course_id = $this->get_course_int_id();
9464
        $lp_id = $this->get_id();
9465
9466
        if (!empty($this->items)) {
9467
            $previous_item_id = null;
9468
            $previous_item_max = 0;
9469
            $previous_item_type = null;
9470
            $last_item_not_dir = null;
9471
            $last_item_not_dir_type = null;
9472
            $last_item_not_dir_max = null;
9473
9474
            foreach ($this->ordered_items as $itemId) {
9475
                $item = $this->getItem($itemId);
9476
                // if there was a previous item... (otherwise jump to set it)
9477
                if (!empty($previous_item_id)) {
9478
                    $current_item_id = $item->get_id(); //save current id
9479
                    if ('dir' != $item->get_type()) {
9480
                        // Current item is not a folder, so it qualifies to get a prerequisites
9481
                        if ('quiz' == $last_item_not_dir_type) {
9482
                            // if previous is quiz, mark its max score as default score to be achieved
9483
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9484
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9485
                            Database::query($sql);
9486
                        }
9487
                        // now simply update the prerequisite to set it to the last non-chapter item
9488
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9489
                                WHERE lp_id = $lp_id AND iid = $current_item_id";
9490
                        Database::query($sql);
9491
                        // record item as 'non-chapter' reference
9492
                        $last_item_not_dir = $item->get_id();
9493
                        $last_item_not_dir_type = $item->get_type();
9494
                        $last_item_not_dir_max = $item->get_max();
9495
                    }
9496
                } else {
9497
                    if ('dir' != $item->get_type()) {
9498
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9499
                        $last_item_not_dir = $item->get_id();
9500
                        $last_item_not_dir_type = $item->get_type();
9501
                        $last_item_not_dir_max = $item->get_max();
9502
                    }
9503
                }
9504
                // Saving the item as "previous item" for the next loop
9505
                $previous_item_id = $item->get_id();
9506
                $previous_item_max = $item->get_max();
9507
                $previous_item_type = $item->get_type();
9508
            }
9509
        }
9510
    }
9511
9512
    /**
9513
     * @param array $params
9514
     *
9515
     * @return int
9516
     */
9517
    public static function createCategory($params)
9518
    {
9519
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9520
9521
        $item = new CLpCategory();
9522
        $item
9523
            ->setName($params['name'])
9524
            ->setParent($courseEntity)
9525
            ->addCourseLink($courseEntity, api_get_session_entity())
9526
        ;
9527
9528
        $repo = Container::getLpCategoryRepository();
9529
        $repo->create($item);
9530
9531
        return $item->getIid();
9532
    }
9533
9534
    /**
9535
     * @param array $params
9536
     */
9537
    public static function updateCategory($params)
9538
    {
9539
        $em = Database::getManager();
9540
        /** @var CLpCategory $item */
9541
        $item = $em->find(CLpCategory::class, $params['id']);
9542
        if ($item) {
9543
            $item->setName($params['name']);
9544
            $em->persist($item);
9545
            $em->flush();
9546
        }
9547
    }
9548
9549
    /**
9550
     * @param int $id
9551
     */
9552
    public static function moveUpCategory($id)
9553
    {
9554
        $id = (int) $id;
9555
        $em = Database::getManager();
9556
        /** @var CLpCategory $item */
9557
        $item = $em->find(CLpCategory::class, $id);
9558
        if ($item) {
9559
            $position = $item->getPosition() - 1;
9560
            $item->setPosition($position);
9561
            $em->persist($item);
9562
            $em->flush();
9563
        }
9564
    }
9565
9566
    /**
9567
     * @param int $id
9568
     */
9569
    public static function moveDownCategory($id)
9570
    {
9571
        $id = (int) $id;
9572
        $em = Database::getManager();
9573
        /** @var CLpCategory $item */
9574
        $item = $em->find(CLpCategory::class, $id);
9575
        if ($item) {
9576
            $position = $item->getPosition() + 1;
9577
            $item->setPosition($position);
9578
            $em->persist($item);
9579
            $em->flush();
9580
        }
9581
    }
9582
9583
    /**
9584
     * @param int $courseId
9585
     *
9586
     * @return int|mixed
9587
     */
9588
    public static function getCountCategories($courseId)
9589
    {
9590
        if (empty($courseId)) {
9591
            return 0;
9592
        }
9593
        $repo = Container::getLpCategoryRepository();
9594
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9595
        $qb->addSelect('count(resource)');
9596
9597
        return $qb->getSingleScalarResult();
9598
    }
9599
9600
    /**
9601
     * @param int $courseId
9602
     *
9603
     * @return CLpCategory[]
9604
     */
9605
    public static function getCategories($courseId)
9606
    {
9607
        $em = Database::getManager();
9608
9609
        // Using doctrine extensions
9610
        $repo = Container::getLpCategoryRepository();
9611
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9612
9613
        return $qb->getQuery()->getResult();
9614
9615
        //return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
9616
    }
9617
9618
    public static function getCategorySessionId($id)
9619
    {
9620
        if (false === api_get_configuration_value('allow_session_lp_category')) {
9621
            return 0;
9622
        }
9623
9624
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
9625
        $id = (int) $id;
9626
9627
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
9628
        $result = Database::query($sql);
9629
        $result = Database::fetch_array($result, 'ASSOC');
9630
9631
        if ($result) {
9632
            return (int) $result['session_id'];
9633
        }
9634
9635
        return 0;
9636
    }
9637
9638
    /**
9639
     * @param int $id
9640
     */
9641
    public static function deleteCategory($id): bool
9642
    {
9643
        $repo = Container::getLpCategoryRepository();
9644
        /** @var CLpCategory $category */
9645
        $category = $repo->find($id);
9646
        if ($category) {
9647
            $em = Database::getManager();
9648
            $lps = $category->getLps();
9649
9650
            foreach ($lps as $lp) {
9651
                $lp->setCategory(null);
9652
                $em->persist($lp);
9653
            }
9654
9655
            // Removing category.
9656
            $em->remove($category);
9657
            $em->flush();
9658
9659
            return true;
9660
        }
9661
9662
        return false;
9663
    }
9664
9665
    /**
9666
     * @param int  $courseId
9667
     * @param bool $addSelectOption
9668
     *
9669
     * @return mixed
9670
     */
9671
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
9672
    {
9673
        $repo = Container::getLpCategoryRepository();
9674
        $items = $repo->getResourcesByCourse(api_get_course_entity($courseId));
9675
        $cats = [];
9676
        if ($addSelectOption) {
9677
            $cats = [get_lang('Select a category')];
9678
        }
9679
9680
        if (!empty($items)) {
9681
            foreach ($items as $cat) {
9682
                $cats[$cat->getIid()] = $cat->getName();
9683
            }
9684
        }
9685
9686
        return $cats;
9687
    }
9688
9689
    /**
9690
     * @param string $courseCode
9691
     * @param int    $lpId
9692
     * @param int    $user_id
9693
     *
9694
     * @return learnpath
9695
     */
9696
    public static function getLpFromSession($courseCode, $lpId, $user_id)
9697
    {
9698
        $debug = 0;
9699
        $learnPath = null;
9700
        $lpObject = Session::read('lpobject');
9701
9702
        $repo = Container::getLpRepository();
9703
        $lp = $repo->find($lpId);
9704
        if (null !== $lpObject) {
9705
            /** @var learnpath $learnPath */
9706
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
9707
            $learnPath->entity = $lp;
9708
            if ($debug) {
9709
                error_log('getLpFromSession: unserialize');
9710
                error_log('------getLpFromSession------');
9711
                error_log('------unserialize------');
9712
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9713
                error_log("api_get_sessionid: ".api_get_session_id());
9714
            }
9715
        }
9716
9717
        if (!is_object($learnPath)) {
9718
            $learnPath = new learnpath($lp, api_get_course_info($courseCode), $user_id);
9719
            if ($debug) {
9720
                error_log('------getLpFromSession------');
9721
                error_log('getLpFromSession: create new learnpath');
9722
                error_log("create new LP with $courseCode - $lpId - $user_id");
9723
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
9724
                error_log("api_get_sessionid: ".api_get_session_id());
9725
            }
9726
        }
9727
9728
        return $learnPath;
9729
    }
9730
9731
    /**
9732
     * @param int $itemId
9733
     *
9734
     * @return learnpathItem|false
9735
     */
9736
    public function getItem($itemId)
9737
    {
9738
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
9739
            return $this->items[$itemId];
9740
        }
9741
9742
        return false;
9743
    }
9744
9745
    /**
9746
     * @return int
9747
     */
9748
    public function getCurrentAttempt()
9749
    {
9750
        $attempt = $this->getItem($this->get_current_item_id());
9751
        if ($attempt) {
9752
            return $attempt->get_attempt_id();
9753
        }
9754
9755
        return 0;
9756
    }
9757
9758
    /**
9759
     * @return int
9760
     */
9761
    public function getCategoryId()
9762
    {
9763
        return (int) $this->categoryId;
9764
    }
9765
9766
    /**
9767
     * Get whether this is a learning path with the possibility to subscribe
9768
     * users or not.
9769
     *
9770
     * @return int
9771
     */
9772
    public function getSubscribeUsers()
9773
    {
9774
        return $this->subscribeUsers;
9775
    }
9776
9777
    /**
9778
     * Calculate the count of stars for a user in this LP
9779
     * This calculation is based on the following rules:
9780
     * - the student gets one star when he gets to 50% of the learning path
9781
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
9782
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
9783
     * - the student gets the final star when the score for the *last* test is >= 80%.
9784
     *
9785
     * @param int $sessionId Optional. The session ID
9786
     *
9787
     * @return int The count of stars
9788
     */
9789
    public function getCalculateStars($sessionId = 0)
9790
    {
9791
        $stars = 0;
9792
        $progress = self::getProgress(
9793
            $this->lp_id,
9794
            $this->user_id,
9795
            $this->course_int_id,
9796
            $sessionId
9797
        );
9798
9799
        if ($progress >= 50) {
9800
            $stars++;
9801
        }
9802
9803
        // Calculate stars chapters evaluation
9804
        $exercisesItems = $this->getExercisesItems();
9805
9806
        if (!empty($exercisesItems)) {
9807
            $totalResult = 0;
9808
9809
            foreach ($exercisesItems as $exerciseItem) {
9810
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9811
                    $this->user_id,
9812
                    $exerciseItem->path,
9813
                    $this->course_int_id,
9814
                    $sessionId,
9815
                    $this->lp_id,
9816
                    $exerciseItem->db_id
9817
                );
9818
9819
                $exerciseResultInfo = end($exerciseResultInfo);
9820
9821
                if (!$exerciseResultInfo) {
9822
                    continue;
9823
                }
9824
9825
                if (!empty($exerciseResultInfo['max_score'])) {
9826
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
9827
                } else {
9828
                    $exerciseResult = 0;
9829
                }
9830
                $totalResult += $exerciseResult;
9831
            }
9832
9833
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
9834
9835
            if ($totalExerciseAverage >= 50) {
9836
                $stars++;
9837
            }
9838
9839
            if ($totalExerciseAverage >= 80) {
9840
                $stars++;
9841
            }
9842
        }
9843
9844
        // Calculate star for final evaluation
9845
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9846
9847
        if (!empty($finalEvaluationItem)) {
9848
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9849
                $this->user_id,
9850
                $finalEvaluationItem->path,
9851
                $this->course_int_id,
9852
                $sessionId,
9853
                $this->lp_id,
9854
                $finalEvaluationItem->db_id
9855
            );
9856
9857
            $evaluationResultInfo = end($evaluationResultInfo);
9858
9859
            if ($evaluationResultInfo) {
9860
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
9861
9862
                if ($evaluationResult >= 80) {
9863
                    $stars++;
9864
                }
9865
            }
9866
        }
9867
9868
        return $stars;
9869
    }
9870
9871
    /**
9872
     * Get the items of exercise type.
9873
     *
9874
     * @return array The items. Otherwise return false
9875
     */
9876
    public function getExercisesItems()
9877
    {
9878
        $exercises = [];
9879
        foreach ($this->items as $item) {
9880
            if ('quiz' != $item->type) {
9881
                continue;
9882
            }
9883
            $exercises[] = $item;
9884
        }
9885
9886
        array_pop($exercises);
9887
9888
        return $exercises;
9889
    }
9890
9891
    /**
9892
     * Get the item of exercise type (evaluation type).
9893
     *
9894
     * @return array The final evaluation. Otherwise return false
9895
     */
9896
    public function getFinalEvaluationItem()
9897
    {
9898
        $exercises = [];
9899
        foreach ($this->items as $item) {
9900
            if (TOOL_QUIZ !== $item->type) {
9901
                continue;
9902
            }
9903
9904
            $exercises[] = $item;
9905
        }
9906
9907
        return array_pop($exercises);
9908
    }
9909
9910
    /**
9911
     * Calculate the total points achieved for the current user in this learning path.
9912
     *
9913
     * @param int $sessionId Optional. The session Id
9914
     *
9915
     * @return int
9916
     */
9917
    public function getCalculateScore($sessionId = 0)
9918
    {
9919
        // Calculate stars chapters evaluation
9920
        $exercisesItems = $this->getExercisesItems();
9921
        $finalEvaluationItem = $this->getFinalEvaluationItem();
9922
        $totalExercisesResult = 0;
9923
        $totalEvaluationResult = 0;
9924
9925
        if (false !== $exercisesItems) {
9926
            foreach ($exercisesItems as $exerciseItem) {
9927
                $exerciseResultInfo = Event::getExerciseResultsByUser(
9928
                    $this->user_id,
9929
                    $exerciseItem->path,
9930
                    $this->course_int_id,
9931
                    $sessionId,
9932
                    $this->lp_id,
9933
                    $exerciseItem->db_id
9934
                );
9935
9936
                $exerciseResultInfo = end($exerciseResultInfo);
9937
9938
                if (!$exerciseResultInfo) {
9939
                    continue;
9940
                }
9941
9942
                $totalExercisesResult += $exerciseResultInfo['score'];
9943
            }
9944
        }
9945
9946
        if (!empty($finalEvaluationItem)) {
9947
            $evaluationResultInfo = Event::getExerciseResultsByUser(
9948
                $this->user_id,
9949
                $finalEvaluationItem->path,
9950
                $this->course_int_id,
9951
                $sessionId,
9952
                $this->lp_id,
9953
                $finalEvaluationItem->db_id
9954
            );
9955
9956
            $evaluationResultInfo = end($evaluationResultInfo);
9957
9958
            if ($evaluationResultInfo) {
9959
                $totalEvaluationResult += $evaluationResultInfo['score'];
9960
            }
9961
        }
9962
9963
        return $totalExercisesResult + $totalEvaluationResult;
9964
    }
9965
9966
    /**
9967
     * Check if URL is not allowed to be show in a iframe.
9968
     *
9969
     * @param string $src
9970
     *
9971
     * @return string
9972
     */
9973
    public function fixBlockedLinks($src)
9974
    {
9975
        $urlInfo = parse_url($src);
9976
9977
        $platformProtocol = 'https';
9978
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
9979
            $platformProtocol = 'http';
9980
        }
9981
9982
        $protocolFixApplied = false;
9983
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
9984
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
9985
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
9986
9987
        if ($platformProtocol != $scheme) {
9988
            Session::write('x_frame_source', $src);
9989
            $src = 'blank.php?error=x_frames_options';
9990
            $protocolFixApplied = true;
9991
        }
9992
9993
        if (false == $protocolFixApplied) {
9994
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
9995
                // Check X-Frame-Options
9996
                $ch = curl_init();
9997
                $options = [
9998
                    CURLOPT_URL => $src,
9999
                    CURLOPT_RETURNTRANSFER => true,
10000
                    CURLOPT_HEADER => true,
10001
                    CURLOPT_FOLLOWLOCATION => true,
10002
                    CURLOPT_ENCODING => "",
10003
                    CURLOPT_AUTOREFERER => true,
10004
                    CURLOPT_CONNECTTIMEOUT => 120,
10005
                    CURLOPT_TIMEOUT => 120,
10006
                    CURLOPT_MAXREDIRS => 10,
10007
                ];
10008
10009
                $proxySettings = api_get_configuration_value('proxy_settings');
10010
                if (!empty($proxySettings) &&
10011
                    isset($proxySettings['curl_setopt_array'])
10012
                ) {
10013
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10014
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10015
                }
10016
10017
                curl_setopt_array($ch, $options);
10018
                $response = curl_exec($ch);
10019
                $httpCode = curl_getinfo($ch);
10020
                $headers = substr($response, 0, $httpCode['header_size']);
10021
10022
                $error = false;
10023
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10024
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10025
                ) {
10026
                    $error = true;
10027
                }
10028
10029
                if ($error) {
10030
                    Session::write('x_frame_source', $src);
10031
                    $src = 'blank.php?error=x_frames_options';
10032
                }
10033
            }
10034
        }
10035
10036
        return $src;
10037
    }
10038
10039
    /**
10040
     * Check if this LP has a created forum in the basis course.
10041
     *
10042
     * @deprecated
10043
     *
10044
     * @return bool
10045
     */
10046
    public function lpHasForum()
10047
    {
10048
        $forumTable = Database::get_course_table(TABLE_FORUM);
10049
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10050
10051
        $fakeFrom = "
10052
            $forumTable f
10053
            INNER JOIN $itemProperty ip
10054
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10055
        ";
10056
10057
        $resultData = Database::select(
10058
            'COUNT(f.iid) AS qty',
10059
            $fakeFrom,
10060
            [
10061
                'where' => [
10062
                    'ip.visibility != ? AND ' => 2,
10063
                    'ip.tool = ? AND ' => TOOL_FORUM,
10064
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10065
                    'f.lp_id = ?' => intval($this->lp_id),
10066
                ],
10067
            ],
10068
            'first'
10069
        );
10070
10071
        return $resultData['qty'] > 0;
10072
    }
10073
10074
    /**
10075
     * Get the forum for this learning path.
10076
     *
10077
     * @param int $sessionId
10078
     *
10079
     * @return array
10080
     */
10081
    public function getForum($sessionId = 0)
10082
    {
10083
        $repo = Container::getForumRepository();
10084
10085
        $course = api_get_course_entity();
10086
        $session = api_get_session_entity($sessionId);
10087
        $qb = $repo->getResourcesByCourse($course, $session);
10088
10089
        return $qb->getQuery()->getResult();
10090
    }
10091
10092
    /**
10093
     * Create a forum for this learning path.
10094
     *
10095
     * @return int The forum ID if was created. Otherwise return false
10096
     */
10097
    public function createForum(CForumCategory $forumCategory)
10098
    {
10099
        return store_forum(
10100
            [
10101
                'lp_id' => $this->lp_id,
10102
                'forum_title' => $this->name,
10103
                'forum_comment' => null,
10104
                'forum_category' => $forumCategory->getIid(),
10105
                'students_can_edit_group' => ['students_can_edit' => 0],
10106
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10107
                'default_view_type_group' => ['default_view_type' => 'flat'],
10108
                'group_forum' => 0,
10109
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10110
            ],
10111
            [],
10112
            true
10113
        );
10114
    }
10115
10116
    /**
10117
     * Get the LP Final Item form.
10118
     *
10119
     * @throws Exception
10120
     *
10121
     *
10122
     * @return string
10123
     */
10124
    public function getFinalItemForm()
10125
    {
10126
        $finalItem = $this->getFinalItem();
10127
        $title = '';
10128
10129
        if ($finalItem) {
10130
            $title = $finalItem->get_title();
10131
            $buttonText = get_lang('Save');
10132
            $content = $this->getSavedFinalItem();
10133
        } else {
10134
            $buttonText = get_lang('Add this document to the course');
10135
            $content = $this->getFinalItemTemplate();
10136
        }
10137
10138
        $editorConfig = [
10139
            'ToolbarSet' => 'LearningPathDocuments',
10140
            'Width' => '100%',
10141
            'Height' => '500',
10142
            'FullPage' => true,
10143
//            'CreateDocumentDir' => $relative_prefix,
10144
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10145
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10146
        ];
10147
10148
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10149
            'type' => 'document',
10150
            'lp_id' => $this->lp_id,
10151
        ]);
10152
10153
        $form = new FormValidator('final_item', 'POST', $url);
10154
        $form->addText('title', get_lang('Title'));
10155
        $form->addButtonSave($buttonText);
10156
        $form->addHtml(
10157
            Display::return_message(
10158
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10159
                'normal',
10160
                false
10161
            )
10162
        );
10163
10164
        $renderer = $form->defaultRenderer();
10165
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10166
10167
        $form->addHtmlEditor(
10168
            'content_lp_certificate',
10169
            null,
10170
            true,
10171
            false,
10172
            $editorConfig,
10173
            true
10174
        );
10175
        $form->addHidden('action', 'add_final_item');
10176
        $form->addHidden('path', Session::read('pathItem'));
10177
        $form->addHidden('previous', $this->get_last());
10178
        $form->setDefaults(
10179
            ['title' => $title, 'content_lp_certificate' => $content]
10180
        );
10181
10182
        if ($form->validate()) {
10183
            $values = $form->exportValues();
10184
            $lastItemId = $this->getLastInFirstLevel();
10185
10186
            if (!$finalItem) {
10187
                $documentId = $this->create_document(
10188
                    $this->course_info,
10189
                    $values['content_lp_certificate'],
10190
                    $values['title']
10191
                );
10192
                $this->add_item(
10193
                    0,
10194
                    $lastItemId,
10195
                    'final_item',
10196
                    $documentId,
10197
                    $values['title'],
10198
                    ''
10199
                );
10200
10201
                Display::addFlash(
10202
                    Display::return_message(get_lang('Added'))
10203
                );
10204
            } else {
10205
                $this->edit_document($this->course_info);
10206
            }
10207
        }
10208
10209
        return $form->returnForm();
10210
    }
10211
10212
    /**
10213
     * Check if the current lp item is first, both, last or none from lp list.
10214
     *
10215
     * @param int $currentItemId
10216
     *
10217
     * @return string
10218
     */
10219
    public function isFirstOrLastItem($currentItemId)
10220
    {
10221
        $lpItemId = [];
10222
        $typeListNotToVerify = self::getChapterTypes();
10223
10224
        // Using get_toc() function instead $this->items because returns the correct order of the items
10225
        foreach ($this->get_toc() as $item) {
10226
            if (!in_array($item['type'], $typeListNotToVerify)) {
10227
                $lpItemId[] = $item['id'];
10228
            }
10229
        }
10230
10231
        $lastLpItemIndex = count($lpItemId) - 1;
10232
        $position = array_search($currentItemId, $lpItemId);
10233
10234
        switch ($position) {
10235
            case 0:
10236
                if (!$lastLpItemIndex) {
10237
                    $answer = 'both';
10238
                    break;
10239
                }
10240
10241
                $answer = 'first';
10242
                break;
10243
            case $lastLpItemIndex:
10244
                $answer = 'last';
10245
                break;
10246
            default:
10247
                $answer = 'none';
10248
        }
10249
10250
        return $answer;
10251
    }
10252
10253
    /**
10254
     * Get whether this is a learning path with the accumulated SCORM time or not.
10255
     *
10256
     * @return int
10257
     */
10258
    public function getAccumulateScormTime()
10259
    {
10260
        return $this->accumulateScormTime;
10261
    }
10262
10263
    /**
10264
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10265
     * the new learning path tool.
10266
     *
10267
     * The function is a big switch on tool type.
10268
     * In each case, we query the corresponding table for information and build the link
10269
     * with that information.
10270
     *
10271
     * @author Yannick Warnier <[email protected]> - rebranding based on
10272
     * previous work (display_addedresource_link_in_learnpath())
10273
     *
10274
     * @param int $course_id      Course code
10275
     * @param int $learningPathId The learning path ID (in lp table)
10276
     * @param int $id_in_path     the unique index in the items table
10277
     * @param int $lpViewId
10278
     *
10279
     * @return string
10280
     */
10281
    public static function rl_get_resource_link_for_learnpath(
10282
        $course_id,
10283
        $learningPathId,
10284
        $id_in_path,
10285
        $lpViewId
10286
    ) {
10287
        $session_id = api_get_session_id();
10288
10289
        $learningPathId = (int) $learningPathId;
10290
        $id_in_path = (int) $id_in_path;
10291
        $lpViewId = (int) $lpViewId;
10292
10293
        $em = Database::getManager();
10294
        $lpItemRepo = $em->getRepository(CLpItem::class);
10295
10296
        /** @var CLpItem $rowItem */
10297
        $rowItem = $lpItemRepo->findOneBy([
10298
            'lp' => $learningPathId,
10299
            'iid' => $id_in_path,
10300
        ]);
10301
10302
        if (!$rowItem) {
10303
            // Try one more time with "id"
10304
            /** @var CLpItem $rowItem */
10305
            $rowItem = $lpItemRepo->findOneBy([
10306
                'lp' => $learningPathId,
10307
                'id' => $id_in_path,
10308
            ]);
10309
10310
            if (!$rowItem) {
10311
                return -1;
10312
            }
10313
        }
10314
10315
        $type = $rowItem->getItemType();
10316
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10317
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10318
        $link = '';
10319
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&sid='.$session_id;
10320
10321
        switch ($type) {
10322
            case 'dir':
10323
                return $main_dir_path.'lp/blank.php';
10324
            case TOOL_CALENDAR_EVENT:
10325
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10326
            case TOOL_ANNOUNCEMENT:
10327
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10328
            case TOOL_LINK:
10329
                $linkInfo = Link::getLinkInfo($id);
10330
                if (isset($linkInfo['url'])) {
10331
                    return $linkInfo['url'];
10332
                }
10333
10334
                return '';
10335
            case TOOL_QUIZ:
10336
                if (empty($id)) {
10337
                    return '';
10338
                }
10339
10340
                // Get the lp_item_view with the highest view_count.
10341
                $learnpathItemViewResult = $em
10342
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10343
                    ->findBy(
10344
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getIid(), 'lpViewId' => $lpViewId],
10345
                        ['viewCount' => 'DESC'],
10346
                        1
10347
                    );
10348
                /** @var CLpItemView $learnpathItemViewData */
10349
                $learnpathItemViewData = current($learnpathItemViewResult);
10350
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getIid() : 0;
10351
10352
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10353
                    .http_build_query([
10354
                        'lp_init' => 1,
10355
                        'learnpath_item_view_id' => $learnpathItemViewId,
10356
                        'learnpath_id' => $learningPathId,
10357
                        'learnpath_item_id' => $id_in_path,
10358
                        'exerciseId' => $id,
10359
                    ]);
10360
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10361
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10362
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10363
                $myrow = Database::fetch_array($result);
10364
                $path = $myrow['path'];
10365
10366
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10367
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10368
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10369
            case TOOL_FORUM:
10370
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10371
            case TOOL_THREAD:
10372
                // forum post
10373
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10374
                if (empty($id)) {
10375
                    return '';
10376
                }
10377
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND iid=$id";
10378
                $result = Database::query($sql);
10379
                $myrow = Database::fetch_array($result);
10380
10381
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10382
                    .$extraParams;
10383
            case TOOL_POST:
10384
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10385
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10386
                $myrow = Database::fetch_array($result);
10387
10388
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10389
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10390
            case TOOL_READOUT_TEXT:
10391
                return api_get_path(WEB_CODE_PATH).
10392
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10393
            case TOOL_DOCUMENT:
10394
                $repo = Container::getDocumentRepository();
10395
                $document = $repo->find($rowItem->getPath());
10396
                $params = [
10397
                    'cid' => $course_id,
10398
                    'sid' => $session_id,
10399
                ];
10400
                $file = $repo->getResourceFileUrl($document, $params, UrlGeneratorInterface::ABSOLUTE_URL);
10401
10402
                return $file;
10403
10404
                $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...
10405
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10406
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10407
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10408
10409
                $openmethod = 2;
10410
                $officedoc = false;
10411
                Session::write('openmethod', $openmethod);
10412
                Session::write('officedoc', $officedoc);
10413
10414
                if ($showDirectUrl) {
10415
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10416
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10417
                        if (Link::isPdfLink($file)) {
10418
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10419
10420
                            return $pdfUrl;
10421
                        }
10422
                    }
10423
10424
                    return $file;
10425
                }
10426
10427
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10428
            case TOOL_LP_FINAL_ITEM:
10429
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10430
                    .$extraParams;
10431
            case 'assignments':
10432
                return $main_dir_path.'work/work.php?'.$extraParams;
10433
            case TOOL_DROPBOX:
10434
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10435
            case 'introduction_text': //DEPRECATED
10436
                return '';
10437
            case TOOL_COURSE_DESCRIPTION:
10438
                return $main_dir_path.'course_description?'.$extraParams;
10439
            case TOOL_GROUP:
10440
                return $main_dir_path.'group/group.php?'.$extraParams;
10441
            case TOOL_USER:
10442
                return $main_dir_path.'user/user.php?'.$extraParams;
10443
            case TOOL_STUDENTPUBLICATION:
10444
                if (!empty($rowItem->getPath())) {
10445
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
10446
                }
10447
10448
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
10449
        }
10450
10451
        return $link;
10452
    }
10453
10454
    /**
10455
     * Gets the name of a resource (generally used in learnpath when no name is provided).
10456
     *
10457
     * @author Yannick Warnier <[email protected]>
10458
     *
10459
     * @param string $course_code    Course code
10460
     * @param int    $learningPathId
10461
     * @param int    $id_in_path     The resource ID
10462
     *
10463
     * @return string
10464
     */
10465
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
10466
    {
10467
        $_course = api_get_course_info($course_code);
10468
        if (empty($_course)) {
10469
            return '';
10470
        }
10471
        $course_id = $_course['real_id'];
10472
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10473
        $learningPathId = (int) $learningPathId;
10474
        $id_in_path = (int) $id_in_path;
10475
10476
        $sql = "SELECT item_type, title, ref
10477
                FROM $tbl_lp_item
10478
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
10479
        $res_item = Database::query($sql);
10480
10481
        if (Database::num_rows($res_item) < 1) {
10482
            return '';
10483
        }
10484
        $row_item = Database::fetch_array($res_item);
10485
        $type = strtolower($row_item['item_type']);
10486
        $id = $row_item['ref'];
10487
        $output = '';
10488
10489
        switch ($type) {
10490
            case TOOL_CALENDAR_EVENT:
10491
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
10492
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
10493
                $myrow = Database::fetch_array($result);
10494
                $output = $myrow['title'];
10495
                break;
10496
            case TOOL_ANNOUNCEMENT:
10497
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
10498
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
10499
                $myrow = Database::fetch_array($result);
10500
                $output = $myrow['title'];
10501
                break;
10502
            case TOOL_LINK:
10503
                // Doesn't take $target into account.
10504
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
10505
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
10506
                $myrow = Database::fetch_array($result);
10507
                $output = $myrow['title'];
10508
                break;
10509
            case TOOL_QUIZ:
10510
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
10511
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
10512
                $myrow = Database::fetch_array($result);
10513
                $output = $myrow['title'];
10514
                break;
10515
            case TOOL_FORUM:
10516
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
10517
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
10518
                $myrow = Database::fetch_array($result);
10519
                $output = $myrow['forum_name'];
10520
                break;
10521
            case TOOL_THREAD:
10522
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10523
                // Grabbing the title of the post.
10524
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
10525
                $result_title = Database::query($sql_title);
10526
                $myrow_title = Database::fetch_array($result_title);
10527
                $output = $myrow_title['post_title'];
10528
                break;
10529
            case TOOL_POST:
10530
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10531
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
10532
                $result = Database::query($sql);
10533
                $post = Database::fetch_array($result);
10534
                $output = $post['post_title'];
10535
                break;
10536
            case 'dir':
10537
            case TOOL_DOCUMENT:
10538
                $title = $row_item['title'];
10539
                $output = '-';
10540
                if (!empty($title)) {
10541
                    $output = $title;
10542
                }
10543
                break;
10544
            case 'hotpotatoes':
10545
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10546
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
10547
                $myrow = Database::fetch_array($result);
10548
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
10549
                $last = count($pathname) - 1; // Making a correct name for the link.
10550
                $filename = $pathname[$last]; // Making a correct name for the link.
10551
                $myrow['path'] = rawurlencode($myrow['path']);
10552
                $output = $filename;
10553
                break;
10554
        }
10555
10556
        return stripslashes($output);
10557
    }
10558
10559
    /**
10560
     * Get the parent names for the current item.
10561
     *
10562
     * @param int $newItemId Optional. The item ID
10563
     *
10564
     * @return array
10565
     */
10566
    public function getCurrentItemParentNames($newItemId = 0)
10567
    {
10568
        $newItemId = $newItemId ?: $this->get_current_item_id();
10569
        $return = [];
10570
        $item = $this->getItem($newItemId);
10571
        $parent = $this->getItem($item->get_parent());
10572
10573
        while ($parent) {
10574
            $return[] = $parent->get_title();
10575
            $parent = $this->getItem($parent->get_parent());
10576
        }
10577
10578
        return array_reverse($return);
10579
    }
10580
10581
    /**
10582
     * Reads and process "lp_subscription_settings" setting.
10583
     *
10584
     * @return array
10585
     */
10586
    public static function getSubscriptionSettings()
10587
    {
10588
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
10589
        if (empty($subscriptionSettings)) {
10590
            // By default allow both settings
10591
            $subscriptionSettings = [
10592
                'allow_add_users_to_lp' => true,
10593
                'allow_add_users_to_lp_category' => true,
10594
            ];
10595
        } else {
10596
            $subscriptionSettings = $subscriptionSettings['options'];
10597
        }
10598
10599
        return $subscriptionSettings;
10600
    }
10601
10602
    /**
10603
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
10604
     */
10605
    public function exportToCourseBuildFormat()
10606
    {
10607
        if (!api_is_allowed_to_edit()) {
10608
            return false;
10609
        }
10610
10611
        $courseBuilder = new CourseBuilder();
10612
        $itemList = [];
10613
        /** @var learnpathItem $item */
10614
        foreach ($this->items as $item) {
10615
            $itemList[$item->get_type()][] = $item->get_path();
10616
        }
10617
10618
        if (empty($itemList)) {
10619
            return false;
10620
        }
10621
10622
        if (isset($itemList['document'])) {
10623
            // Get parents
10624
            foreach ($itemList['document'] as $documentId) {
10625
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
10626
                if (!empty($documentInfo['parents'])) {
10627
                    foreach ($documentInfo['parents'] as $parentInfo) {
10628
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
10629
                            continue;
10630
                        }
10631
                        $itemList['document'][] = $parentInfo['iid'];
10632
                    }
10633
                }
10634
            }
10635
10636
            $courseInfo = api_get_course_info();
10637
            foreach ($itemList['document'] as $documentId) {
10638
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
10639
                $items = DocumentManager::get_resources_from_source_html(
10640
                    $documentInfo['absolute_path'],
10641
                    true,
10642
                    TOOL_DOCUMENT
10643
                );
10644
10645
                if (!empty($items)) {
10646
                    foreach ($items as $item) {
10647
                        // Get information about source url
10648
                        $url = $item[0]; // url
10649
                        $scope = $item[1]; // scope (local, remote)
10650
                        $type = $item[2]; // type (rel, abs, url)
10651
10652
                        $origParseUrl = parse_url($url);
10653
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
10654
10655
                        if ('local' === $scope) {
10656
                            if ('abs' === $type || 'rel' === $type) {
10657
                                $documentFile = strstr($realOrigPath, 'document');
10658
                                if (false !== strpos($realOrigPath, $documentFile)) {
10659
                                    $documentFile = str_replace('document', '', $documentFile);
10660
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
10661
                                    // Document found! Add it to the list
10662
                                    if ($itemDocumentId) {
10663
                                        $itemList['document'][] = $itemDocumentId;
10664
                                    }
10665
                                }
10666
                            }
10667
                        }
10668
                    }
10669
                }
10670
            }
10671
10672
            $courseBuilder->build_documents(
10673
                api_get_session_id(),
10674
                $this->get_course_int_id(),
10675
                true,
10676
                $itemList['document']
10677
            );
10678
        }
10679
10680
        if (isset($itemList['quiz'])) {
10681
            $courseBuilder->build_quizzes(
10682
                api_get_session_id(),
10683
                $this->get_course_int_id(),
10684
                true,
10685
                $itemList['quiz']
10686
            );
10687
        }
10688
10689
        /*if (!empty($itemList['thread'])) {
10690
            $postList = [];
10691
            foreach ($itemList['thread'] as $postId) {
10692
                $post = get_post_information($postId);
10693
                if ($post) {
10694
                    if (!isset($itemList['forum'])) {
10695
                        $itemList['forum'] = [];
10696
                    }
10697
                    $itemList['forum'][] = $post['forum_id'];
10698
                    $postList[] = $postId;
10699
                }
10700
            }
10701
10702
            if (!empty($postList)) {
10703
                $courseBuilder->build_forum_posts(
10704
                    $this->get_course_int_id(),
10705
                    null,
10706
                    null,
10707
                    $postList
10708
                );
10709
            }
10710
        }*/
10711
10712
        if (!empty($itemList['thread'])) {
10713
            $threadList = [];
10714
            $em = Database::getManager();
10715
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
10716
            foreach ($itemList['thread'] as $threadId) {
10717
                /** @var CForumThread $thread */
10718
                $thread = $repo->find($threadId);
10719
                if ($thread) {
10720
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
10721
                    $threadList[] = $thread->getIid();
10722
                }
10723
            }
10724
10725
            if (!empty($threadList)) {
10726
                $courseBuilder->build_forum_topics(
10727
                    api_get_session_id(),
10728
                    $this->get_course_int_id(),
10729
                    null,
10730
                    $threadList
10731
                );
10732
            }
10733
        }
10734
10735
        $forumCategoryList = [];
10736
        if (isset($itemList['forum'])) {
10737
            foreach ($itemList['forum'] as $forumId) {
10738
                $forumInfo = get_forums($forumId);
10739
                $forumCategoryList[] = $forumInfo['forum_category'];
10740
            }
10741
        }
10742
10743
        if (!empty($forumCategoryList)) {
10744
            $courseBuilder->build_forum_category(
10745
                api_get_session_id(),
10746
                $this->get_course_int_id(),
10747
                true,
10748
                $forumCategoryList
10749
            );
10750
        }
10751
10752
        if (!empty($itemList['forum'])) {
10753
            $courseBuilder->build_forums(
10754
                api_get_session_id(),
10755
                $this->get_course_int_id(),
10756
                true,
10757
                $itemList['forum']
10758
            );
10759
        }
10760
10761
        if (isset($itemList['link'])) {
10762
            $courseBuilder->build_links(
10763
                api_get_session_id(),
10764
                $this->get_course_int_id(),
10765
                true,
10766
                $itemList['link']
10767
            );
10768
        }
10769
10770
        if (!empty($itemList['student_publication'])) {
10771
            $courseBuilder->build_works(
10772
                api_get_session_id(),
10773
                $this->get_course_int_id(),
10774
                true,
10775
                $itemList['student_publication']
10776
            );
10777
        }
10778
10779
        $courseBuilder->build_learnpaths(
10780
            api_get_session_id(),
10781
            $this->get_course_int_id(),
10782
            true,
10783
            [$this->get_id()],
10784
            false
10785
        );
10786
10787
        $courseBuilder->restoreDocumentsFromList();
10788
10789
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
10790
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
10791
        $result = DocumentManager::file_send_for_download(
10792
            $zipPath,
10793
            true,
10794
            $this->get_name().'.zip'
10795
        );
10796
10797
        if ($result) {
10798
            api_not_allowed();
10799
        }
10800
10801
        return true;
10802
    }
10803
10804
    /**
10805
     * Get whether this is a learning path with the accumulated work time or not.
10806
     *
10807
     * @return int
10808
     */
10809
    public function getAccumulateWorkTime()
10810
    {
10811
        return (int) $this->accumulateWorkTime;
10812
    }
10813
10814
    /**
10815
     * Get whether this is a learning path with the accumulated work time or not.
10816
     *
10817
     * @return int
10818
     */
10819
    public function getAccumulateWorkTimeTotalCourse()
10820
    {
10821
        $table = Database::get_course_table(TABLE_LP_MAIN);
10822
        $sql = "SELECT SUM(accumulate_work_time) AS total
10823
                FROM $table
10824
                WHERE c_id = ".$this->course_int_id;
10825
        $result = Database::query($sql);
10826
        $row = Database::fetch_array($result);
10827
10828
        return (int) $row['total'];
10829
    }
10830
10831
    /**
10832
     * @param int $lpId
10833
     * @param int $courseId
10834
     *
10835
     * @return mixed
10836
     */
10837
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
10838
    {
10839
        $lpId = (int) $lpId;
10840
        $table = Database::get_course_table(TABLE_LP_MAIN);
10841
        $sql = "SELECT accumulate_work_time
10842
                FROM $table
10843
                WHERE iid = $lpId";
10844
        $result = Database::query($sql);
10845
        $row = Database::fetch_array($result);
10846
10847
        return $row['accumulate_work_time'];
10848
    }
10849
10850
    /**
10851
     * @param int $courseId
10852
     *
10853
     * @return int
10854
     */
10855
    public static function getAccumulateWorkTimeTotal($courseId)
10856
    {
10857
        $table = Database::get_course_table(TABLE_LP_MAIN);
10858
        $courseId = (int) $courseId;
10859
        $sql = "SELECT SUM(accumulate_work_time) AS total
10860
                FROM $table
10861
                WHERE c_id = $courseId";
10862
        $result = Database::query($sql);
10863
        $row = Database::fetch_array($result);
10864
10865
        return (int) $row['total'];
10866
    }
10867
10868
    /**
10869
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
10870
     * and put the images in.
10871
     *
10872
     * @return array
10873
     */
10874
    public static function getIconSelect()
10875
    {
10876
        $theme = api_get_visual_theme();
10877
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
10878
        $icons = ['' => get_lang('Please select an option')];
10879
10880
        if (is_dir($path)) {
10881
            $finder = new Finder();
10882
            $finder->files()->in($path);
10883
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
10884
            /** @var SplFileInfo $file */
10885
            foreach ($finder as $file) {
10886
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
10887
                    $icons[$file->getFilename()] = $file->getFilename();
10888
                }
10889
            }
10890
        }
10891
10892
        return $icons;
10893
    }
10894
10895
    /**
10896
     * @param int $lpId
10897
     *
10898
     * @return string
10899
     */
10900
    public static function getSelectedIcon($lpId)
10901
    {
10902
        $extraFieldValue = new ExtraFieldValue('lp');
10903
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
10904
        $icon = '';
10905
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
10906
            $icon = $lpIcon['value'];
10907
        }
10908
10909
        return $icon;
10910
    }
10911
10912
    /**
10913
     * @param int $lpId
10914
     *
10915
     * @return string
10916
     */
10917
    public static function getSelectedIconHtml($lpId)
10918
    {
10919
        $icon = self::getSelectedIcon($lpId);
10920
10921
        if (empty($icon)) {
10922
            return '';
10923
        }
10924
10925
        $theme = api_get_visual_theme();
10926
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
10927
10928
        return Display::img($path);
10929
    }
10930
10931
    /**
10932
     * @param string $value
10933
     *
10934
     * @return string
10935
     */
10936
    public function cleanItemTitle($value)
10937
    {
10938
        $value = Security::remove_XSS(strip_tags($value));
10939
10940
        return $value;
10941
    }
10942
10943
    public function setItemTitle(FormValidator $form)
10944
    {
10945
        if (api_get_configuration_value('save_titles_as_html')) {
10946
            $form->addHtmlEditor(
10947
                'title',
10948
                get_lang('Title'),
10949
                true,
10950
                false,
10951
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
10952
            );
10953
        } else {
10954
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
10955
            $form->applyFilter('title', 'trim');
10956
            $form->applyFilter('title', 'html_filter');
10957
        }
10958
    }
10959
10960
    /**
10961
     * @return array
10962
     */
10963
    public function getItemsForForm($addParentCondition = false)
10964
    {
10965
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10966
10967
        $sql = "SELECT * FROM $tbl_lp_item
10968
                WHERE lp_id = ".$this->lp_id;
10969
10970
        if ($addParentCondition) {
10971
            $sql .= ' AND parent_item_id IS NULL ';
10972
        }
10973
        $sql .= ' ORDER BY display_order ASC';
10974
10975
        $result = Database::query($sql);
10976
        $arrLP = [];
10977
        while ($row = Database::fetch_array($result)) {
10978
            $arrLP[] = [
10979
                'iid' => $row['iid'],
10980
                'id' => $row['iid'],
10981
                'item_type' => $row['item_type'],
10982
                'title' => $this->cleanItemTitle($row['title']),
10983
                'title_raw' => $row['title'],
10984
                'path' => $row['path'],
10985
                'description' => Security::remove_XSS($row['description']),
10986
                'parent_item_id' => $row['parent_item_id'],
10987
                'previous_item_id' => $row['previous_item_id'],
10988
                'next_item_id' => $row['next_item_id'],
10989
                'display_order' => $row['display_order'],
10990
                'max_score' => $row['max_score'],
10991
                'min_score' => $row['min_score'],
10992
                'mastery_score' => $row['mastery_score'],
10993
                'prerequisite' => $row['prerequisite'],
10994
                'max_time_allowed' => $row['max_time_allowed'],
10995
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10996
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10997
            ];
10998
        }
10999
11000
        return $arrLP;
11001
    }
11002
11003
    /**
11004
     * Gets whether this SCORM learning path has been marked to use the score
11005
     * as progress. Takes into account whether the learnpath matches (SCORM
11006
     * content + less than 2 items).
11007
     *
11008
     * @return bool True if the score should be used as progress, false otherwise
11009
     */
11010
    public function getUseScoreAsProgress()
11011
    {
11012
        // If not a SCORM, we don't care about the setting
11013
        if (2 != $this->get_type()) {
11014
            return false;
11015
        }
11016
        // If more than one step in the SCORM, we don't care about the setting
11017
        if ($this->get_total_items_count() > 1) {
11018
            return false;
11019
        }
11020
        $extraFieldValue = new ExtraFieldValue('lp');
11021
        $doUseScore = false;
11022
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable(
11023
            $this->get_id(),
11024
            'use_score_as_progress'
11025
        );
11026
        if (!empty($useScore) && isset($useScore['value'])) {
11027
            $doUseScore = $useScore['value'];
11028
        }
11029
11030
        return $doUseScore;
11031
    }
11032
11033
    /**
11034
     * Get the user identifier (user_id or username
11035
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
11036
     *
11037
     * @return string User ID or username, depending on configuration setting
11038
     */
11039
    public static function getUserIdentifierForExternalServices()
11040
    {
11041
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
11042
            return api_get_user_info(api_get_user_id())['username'];
11043
        } elseif (null != api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')) {
11044
            $extraFieldValue = new ExtraFieldValue('user');
11045
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(
11046
                api_get_user_id(),
11047
                api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id')
11048
            );
11049
11050
            return $extrafield['value'];
11051
        } else {
11052
            return api_get_user_id();
11053
        }
11054
    }
11055
11056
    /**
11057
     * Save the new order for learning path items.
11058
     *
11059
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
11060
     *
11061
     * @param array $orderList A associative array with item ID as key and parent ID as value.
11062
     * @param int   $courseId
11063
     */
11064
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
11065
    {
11066
        $itemList = new LpItemOrderList();
11067
11068
        foreach ($orderList as $id => $parentId) {
11069
            $item = new LpOrderItem($id, $parentId);
11070
            $itemList->add($item);
11071
        }
11072
11073
        $parents = $itemList->getListOfParents();
11074
11075
        foreach ($parents as $parentId) {
11076
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
11077
            $previous_item_id = 0;
11078
            for ($i = 0; $i < count($sameParentLpItemList->list); $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...
11079
                $item_id = $sameParentLpItemList->list[$i]->id;
11080
                // display_order
11081
                $display_order = $i + 1;
11082
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
11083
                // previous_item_id
11084
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
11085
                $previous_item_id = $item_id;
11086
                // next_item_id
11087
                $next_item_id = 0;
11088
                if ($i < count($sameParentLpItemList->list) - 1) {
11089
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
11090
                }
11091
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
11092
            }
11093
        }
11094
11095
        $table = Database::get_course_table(TABLE_LP_ITEM);
11096
11097
        foreach ($itemList->list as $item) {
11098
            $params = [];
11099
            $params['display_order'] = $item->display_order;
11100
            $params['previous_item_id'] = $item->previous_item_id;
11101
            $params['next_item_id'] = $item->next_item_id;
11102
            $params['parent_item_id'] = empty($item->parent_item_id) ? null : $item->parent_item_id;
11103
11104
            Database::update(
11105
                $table,
11106
                $params,
11107
                [
11108
                    'iid = ? ' => [(int) $item->id],
11109
                ]
11110
            );
11111
        }
11112
    }
11113
11114
    /**
11115
     * Get the depth level of LP item.
11116
     *
11117
     * @param array $items
11118
     * @param int   $currentItemId
11119
     *
11120
     * @return int
11121
     */
11122
    private static function get_level_for_item($items, $currentItemId)
11123
    {
11124
        $parentItemId = 0;
11125
        if (isset($items[$currentItemId])) {
11126
            $parentItemId = $items[$currentItemId]->parent;
11127
        }
11128
11129
        if (0 == $parentItemId) {
11130
            return 0;
11131
        }
11132
11133
        return self::get_level_for_item($items, $parentItemId) + 1;
11134
    }
11135
11136
    /**
11137
     * Generate the link for a learnpath category as course tool.
11138
     *
11139
     * @param int $categoryId
11140
     *
11141
     * @return string
11142
     */
11143
    private static function getCategoryLinkForTool($categoryId)
11144
    {
11145
        $categoryId = (int) $categoryId;
11146
        return 'lp/lp_controller.php?'.api_get_cidreq().'&'
11147
            .http_build_query(
11148
                [
11149
                    'action' => 'view_category',
11150
                    'id' => $categoryId,
11151
                ]
11152
            );
11153
    }
11154
11155
    /**
11156
     * Return the scorm item type object with spaces replaced with _
11157
     * The return result is use to build a css classname like scorm_type_$return.
11158
     *
11159
     * @param $in_type
11160
     *
11161
     * @return mixed
11162
     */
11163
    private static function format_scorm_type_item($in_type)
11164
    {
11165
        return str_replace(' ', '_', $in_type);
11166
    }
11167
11168
    /**
11169
     * Check and obtain the lp final item if exist.
11170
     *
11171
     * @return learnpathItem
11172
     */
11173
    private function getFinalItem()
11174
    {
11175
        if (empty($this->items)) {
11176
            return null;
11177
        }
11178
11179
        foreach ($this->items as $item) {
11180
            if ('final_item' !== $item->type) {
11181
                continue;
11182
            }
11183
11184
            return $item;
11185
        }
11186
    }
11187
11188
    /**
11189
     * Get the LP Final Item Template.
11190
     *
11191
     * @return string
11192
     */
11193
    private function getFinalItemTemplate()
11194
    {
11195
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11196
    }
11197
11198
    /**
11199
     * Get the LP Final Item Url.
11200
     *
11201
     * @return string
11202
     */
11203
    private function getSavedFinalItem()
11204
    {
11205
        $finalItem = $this->getFinalItem();
11206
11207
        $repo = Container::getDocumentRepository();
11208
        /** @var CDocument $document */
11209
        $document = $repo->find($finalItem->path);
11210
11211
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11212
            return $repo->getResourceFileContent($document);
11213
        }
11214
11215
        return '';
11216
    }
11217
}
11218