Passed
Push — master ( 784f8c...891151 )
by Julito
11:16
created

learnpath::set_seriousgame_mode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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