Passed
Push — master ( 7f030f...a70d3c )
by Julito
13:07
created

learnpath::tree_array()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Resource\ResourceLink;
6
use Chamilo\CoreBundle\Framework\Container;
7
use Chamilo\CoreBundle\Repository\CourseRepository;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
9
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
10
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
11
use Chamilo\CourseBundle\Entity\CDocument;
12
use Chamilo\CourseBundle\Entity\CLink;
13
use Chamilo\CourseBundle\Entity\CLp;
14
use Chamilo\CourseBundle\Entity\CLpCategory;
15
use Chamilo\CourseBundle\Entity\CLpItem;
16
use Chamilo\CourseBundle\Entity\CLpItemView;
17
use Chamilo\CourseBundle\Entity\CQuiz;
18
use Chamilo\CourseBundle\Entity\CShortcut;
19
use Chamilo\CourseBundle\Entity\CStudentPublication;
20
use Chamilo\CourseBundle\Entity\CTool;
21
use Chamilo\UserBundle\Entity\User;
22
use ChamiloSession as Session;
23
use Gedmo\Sortable\Entity\Repository\SortableRepository;
24
use Symfony\Component\Filesystem\Filesystem;
25
use Symfony\Component\Finder\Finder;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
28
/**
29
 * Class learnpath
30
 * This class defines the parent attributes and methods for Chamilo learnpaths
31
 * and SCORM learnpaths. It is used by the scorm class.
32
 *
33
 * @todo decouple class
34
 * *
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
43
    public $attempt = 0; // The number for the current ID view.
44
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
45
    public $current; // Id of the current item the user is viewing.
46
    public $current_score; // The score of the current item.
47
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
48
    public $current_time_stop; // The time the user closed this resource.
49
    public $default_status = 'not attempted';
50
    public $encoding = 'UTF-8';
51
    public $error = '';
52
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
53
    public $index; // The index of the active learnpath_item in $ordered_items array.
54
    public $items = [];
55
    public $last; // item_id of last item viewed in the learning path.
56
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
57
    public $license; // Which license this course has been given - not used yet on 20060522.
58
    public $lp_id; // DB iid for this learnpath.
59
    public $lp_view_id; // DB ID for lp_view
60
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
61
    public $message = '';
62
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
63
    public $name; // Learnpath name (they generally have one).
64
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
65
    public $path = ''; // Path inside the scorm directory (if scorm).
66
    public $theme; // The current theme of the learning path.
67
    public $preview_image; // The current image of the learning path.
68
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
69
    public $accumulateWorkTime; // The min time of learnpath
70
71
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
72
    public $prevent_reinit = 1;
73
74
    // Describes the mode of progress bar display.
75
    public $seriousgame_mode = 0;
76
    public $progress_bar_mode = '%';
77
78
    // Percentage progress as saved in the db.
79
    public $progress_db = 0;
80
    public $proximity; // Wether the content is distant or local or unknown.
81
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
82
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
83
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
84
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
85
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
86
    public $user_id; //ID of the user that is viewing/using the course
87
    public $update_queue = [];
88
    public $scorm_debug = 0;
89
    public $arrMenu = []; // Array for the menu items.
90
    public $debug = 0; // Logging level.
91
    public $lp_session_id = 0;
92
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
93
    public $prerequisite = 0;
94
    public $use_max_score = 1; // 1 or 0
95
    public $subscribeUsers = 0; // Subscribe users or not
96
    public $created_on = '';
97
    public $modified_on = '';
98
    public $publicated_on = '';
99
    public $expired_on = '';
100
    public $ref = null;
101
    public $course_int_id;
102
    public $course_info = [];
103
    public $categoryId;
104
    public $entity;
105
106
    /**
107
     * Constructor.
108
     * Needs a database handler, a course code and a learnpath id from the database.
109
     * Also builds the list of items into $this->items.
110
     *
111
     * @param string $course  Course code
112
     * @param int    $lp_id   c_lp.iid
113
     * @param int    $user_id
114
     */
115
    public function __construct($course, $lp_id, $user_id)
116
    {
117
        $debug = $this->debug;
118
        $this->encoding = api_get_system_encoding();
119
        if (empty($course)) {
120
            $course = api_get_course_id();
121
        }
122
        $course_info = api_get_course_info($course);
123
        if (!empty($course_info)) {
124
            $this->cc = $course_info['code'];
125
            $this->course_info = $course_info;
126
            $course_id = $course_info['real_id'];
127
        } else {
128
            $this->error = 'Course code does not exist in database.';
129
        }
130
131
        $lp_id = (int) $lp_id;
132
        $course_id = (int) $course_id;
133
        $this->set_course_int_id($course_id);
134
        // Check learnpath ID.
135
        if (empty($lp_id) || empty($course_id)) {
136
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
137
        } else {
138
            $repo = Container::getLpRepository();
139
            /** @var CLp $entity */
140
            $entity = $repo->find($lp_id);
141
            if ($entity) {
0 ignored issues
show
introduced by
$entity is of type Chamilo\CourseBundle\Entity\CLp, thus it always evaluated to true.
Loading history...
142
                $this->entity = $entity;
143
                $this->lp_id = $lp_id;
144
                $this->type = $entity->getLpType();
145
                $this->name = stripslashes($entity->getName());
146
                $this->proximity = $entity->getContentLocal();
147
                $this->theme = $entity->getTheme();
148
                $this->maker = $entity->getContentLocal();
149
                $this->prevent_reinit = $entity->getPreventReinit();
150
                $this->seriousgame_mode = $entity->getSeriousgameMode();
151
                $this->license = $entity->getContentLicense();
152
                $this->scorm_debug = $entity->getDebug();
153
                $this->js_lib = $entity->getJsLib();
154
                $this->path = $entity->getPath();
155
                $this->preview_image = $entity->getPreviewImage();
156
                $this->author = $entity->getAuthor();
157
                $this->hide_toc_frame = $entity->getHideTocFrame();
158
                $this->lp_session_id = $entity->getSessionId();
159
                $this->use_max_score = $entity->getUseMaxScore();
160
                $this->subscribeUsers = $entity->getSubscribeUsers();
161
                $this->created_on = $entity->getCreatedOn()->format('Y-m-d H:i:s');
162
                $this->modified_on = $entity->getModifiedOn()->format('Y-m-d H:i:s');
163
                $this->ref = $entity->getRef();
164
                $this->categoryId = $entity->getCategoryId();
165
                $this->accumulateScormTime = $entity->getAccumulateWorkTime();
166
167
                if (!empty($entity->getPublicatedOn())) {
168
                    $this->publicated_on = $entity->getPublicatedOn()->format('Y-m-d H:i:s');
169
                }
170
171
                if (!empty($entity->getExpiredOn())) {
172
                    $this->expired_on = $entity->getExpiredOn()->format('Y-m-d H:i:s');
173
                }
174
                if (2 == $this->type) {
175
                    if (1 == $entity->getForceCommit()) {
176
                        $this->force_commit = true;
177
                    }
178
                }
179
                $this->mode = $entity->getDefaultViewMod();
180
181
                // Check user ID.
182
                if (empty($user_id)) {
183
                    $this->error = 'User ID is empty';
184
                } else {
185
                    $userInfo = api_get_user_info($user_id);
186
                    if (!empty($userInfo)) {
187
                        $this->user_id = $userInfo['user_id'];
188
                    } else {
189
                        $this->error = 'User ID does not exist in database #'.$user_id;
190
                    }
191
                }
192
193
                // End of variables checking.
194
                $session_id = api_get_session_id();
195
                //  Get the session condition for learning paths of the base + session.
196
                $session = api_get_session_condition($session_id);
197
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
198
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
199
200
                // Selecting by view_count descending allows to get the highest view_count first.
201
                $sql = "SELECT * FROM $lp_table
202
                        WHERE
203
                            c_id = $course_id AND
204
                            lp_id = $lp_id AND
205
                            user_id = $user_id
206
                            $session
207
                        ORDER BY view_count DESC";
208
                $res = Database::query($sql);
209
210
                if (Database::num_rows($res) > 0) {
211
                    $row = Database::fetch_array($res);
212
                    $this->attempt = $row['view_count'];
213
                    $this->lp_view_id = $row['id'];
214
                    $this->last_item_seen = $row['last_item'];
215
                    $this->progress_db = $row['progress'];
216
                    $this->lp_view_session_id = $row['session_id'];
217
                } elseif (!api_is_invitee()) {
218
                    $this->attempt = 1;
219
                    $params = [
220
                        'c_id' => $course_id,
221
                        'lp_id' => $lp_id,
222
                        'user_id' => $user_id,
223
                        'view_count' => 1,
224
                        'session_id' => $session_id,
225
                        'last_item' => 0,
226
                    ];
227
                    $this->last_item_seen = 0;
228
                    $this->lp_view_session_id = $session_id;
229
                    $this->lp_view_id = Database::insert($lp_table, $params);
230
                    if (!empty($this->lp_view_id)) {
231
                        $sql = "UPDATE $lp_table SET id = iid
232
                                WHERE iid = ".$this->lp_view_id;
233
                        Database::query($sql);
234
                    }
235
                }
236
237
                // Initialise items.
238
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
239
                $sql = "SELECT * FROM $lp_item_table
240
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
241
                        ORDER BY parent_item_id, display_order";
242
                $res = Database::query($sql);
243
244
                $lp_item_id_list = [];
245
                while ($row = Database::fetch_array($res)) {
246
                    $lp_item_id_list[] = $row['iid'];
247
                    switch ($this->type) {
248
                        case 3: //aicc
249
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
250
                            if (is_object($oItem)) {
251
                                $my_item_id = $oItem->get_id();
252
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
253
                                $oItem->set_prevent_reinit($this->prevent_reinit);
254
                                // Don't use reference here as the next loop will make the pointed object change.
255
                                $this->items[$my_item_id] = $oItem;
256
                                $this->refs_list[$oItem->ref] = $my_item_id;
257
                            }
258
                            break;
259
                        case 2:
260
                            $oItem = new scormItem('db', $row['iid'], $course_id);
261
                            if (is_object($oItem)) {
262
                                $my_item_id = $oItem->get_id();
263
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
264
                                $oItem->set_prevent_reinit($this->prevent_reinit);
265
                                // Don't use reference here as the next loop will make the pointed object change.
266
                                $this->items[$my_item_id] = $oItem;
267
                                $this->refs_list[$oItem->ref] = $my_item_id;
268
                            }
269
                            break;
270
                        case 1:
271
                        default:
272
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
273
                            if (is_object($oItem)) {
274
                                $my_item_id = $oItem->get_id();
275
                                // Moved down to when we are sure the item_view exists.
276
                                //$oItem->set_lp_view($this->lp_view_id);
277
                                $oItem->set_prevent_reinit($this->prevent_reinit);
278
                                // Don't use reference here as the next loop will make the pointed object change.
279
                                $this->items[$my_item_id] = $oItem;
280
                                $this->refs_list[$my_item_id] = $my_item_id;
281
                            }
282
                            break;
283
                    }
284
285
                    // Setting the object level with variable $this->items[$i][parent]
286
                    foreach ($this->items as $itemLPObject) {
287
                        $level = self::get_level_for_item(
288
                            $this->items,
289
                            $itemLPObject->db_id
290
                        );
291
                        $itemLPObject->level = $level;
292
                    }
293
294
                    // Setting the view in the item object.
295
                    if (is_object($this->items[$row['iid']])) {
296
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
297
                        if (TOOL_HOTPOTATOES == $this->items[$row['iid']]->get_type()) {
298
                            $this->items[$row['iid']]->current_start_time = 0;
299
                            $this->items[$row['iid']]->current_stop_time = 0;
300
                        }
301
                    }
302
                }
303
304
                if (!empty($lp_item_id_list)) {
305
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
306
                    if (!empty($lp_item_id_list_to_string)) {
307
                        // Get last viewing vars.
308
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
309
                        // This query should only return one or zero result.
310
                        $sql = "SELECT lp_item_id, status
311
                                FROM $itemViewTable
312
                                WHERE
313
                                    c_id = $course_id AND
314
                                    lp_view_id = ".$this->get_view_id()." AND
315
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
316
                                ORDER BY view_count DESC ";
317
                        $status_list = [];
318
                        $res = Database::query($sql);
319
                        while ($row = Database:: fetch_array($res)) {
320
                            $status_list[$row['lp_item_id']] = $row['status'];
321
                        }
322
323
                        foreach ($lp_item_id_list as $item_id) {
324
                            if (isset($status_list[$item_id])) {
325
                                $status = $status_list[$item_id];
326
                                if (is_object($this->items[$item_id])) {
327
                                    $this->items[$item_id]->set_status($status);
328
                                    if (empty($status)) {
329
                                        $this->items[$item_id]->set_status(
330
                                            $this->default_status
331
                                        );
332
                                    }
333
                                }
334
                            } else {
335
                                if (!api_is_invitee()) {
336
                                    if (is_object($this->items[$item_id])) {
337
                                        $this->items[$item_id]->set_status(
338
                                            $this->default_status
339
                                        );
340
                                    }
341
342
                                    if (!empty($this->lp_view_id)) {
343
                                        // Add that row to the lp_item_view table so that
344
                                        // we have something to show in the stats page.
345
                                        $params = [
346
                                            'c_id' => $course_id,
347
                                            'lp_item_id' => $item_id,
348
                                            'lp_view_id' => $this->lp_view_id,
349
                                            'view_count' => 1,
350
                                            'status' => 'not attempted',
351
                                            'start_time' => time(),
352
                                            'total_time' => 0,
353
                                            'score' => 0,
354
                                        ];
355
                                        $insertId = Database::insert($itemViewTable, $params);
356
357
                                        if ($insertId) {
358
                                            $sql = "UPDATE $itemViewTable SET id = iid
359
                                                    WHERE iid = $insertId";
360
                                            Database::query($sql);
361
                                        }
362
363
                                        $this->items[$item_id]->set_lp_view(
364
                                            $this->lp_view_id,
365
                                            $course_id
366
                                        );
367
                                    }
368
                                }
369
                            }
370
                        }
371
                    }
372
                }
373
374
                $this->ordered_items = self::get_flat_ordered_items_list(
375
                    $this->get_id(),
376
                    0,
377
                    $course_id
378
                );
379
                $this->max_ordered_items = 0;
380
                foreach ($this->ordered_items as $index => $dummy) {
381
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
382
                        $this->max_ordered_items = $index;
383
                    }
384
                }
385
                // TODO: Define the current item better.
386
                $this->first();
387
                if ($debug) {
388
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
389
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
390
                }
391
            } else {
392
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
393
            }
394
        }
395
    }
396
397
    public function getEntity(): CLp
398
    {
399
        return $this->entity;
400
    }
401
402
    /**
403
     * @return string
404
     */
405
    public function getCourseCode()
406
    {
407
        return $this->cc;
408
    }
409
410
    /**
411
     * @return int
412
     */
413
    public function get_course_int_id()
414
    {
415
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
416
    }
417
418
    /**
419
     * @param $course_id
420
     *
421
     * @return int
422
     */
423
    public function set_course_int_id($course_id)
424
    {
425
        return $this->course_int_id = (int) $course_id;
426
    }
427
428
    /**
429
     * Function rewritten based on old_add_item() from Yannick Warnier.
430
     * Due the fact that users can decide where the item should come, I had to overlook this function and
431
     * I found it better to rewrite it. Old function is still available.
432
     * Added also the possibility to add a description.
433
     *
434
     * @param int    $parent
435
     * @param int    $previous
436
     * @param string $type
437
     * @param int    $id               resource ID (ref)
438
     * @param string $title
439
     * @param string $description
440
     * @param int    $prerequisites
441
     * @param int    $max_time_allowed
442
     * @param int    $userId
443
     *
444
     * @return int
445
     */
446
    public function add_item(
447
        $parent,
448
        $previous,
449
        $type = 'dir',
450
        $id,
451
        $title,
452
        $description,
453
        $prerequisites = 0,
454
        $max_time_allowed = 0,
455
        $userId = 0
456
    ) {
457
        $course_id = $this->course_info['real_id'];
458
        if (empty($course_id)) {
459
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
460
            $this->course_info = api_get_course_info($this->cc);
461
            $course_id = $this->course_info['real_id'];
462
        }
463
        $userId = empty($userId) ? api_get_user_id() : $userId;
464
        $sessionId = api_get_session_id();
465
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
466
        $_course = $this->course_info;
467
        $parent = (int) $parent;
468
        $previous = (int) $previous;
469
        $id = (int) $id;
470
        $max_time_allowed = htmlentities($max_time_allowed);
471
        if (empty($max_time_allowed)) {
472
            $max_time_allowed = 0;
473
        }
474
        $sql = "SELECT COUNT(iid) AS num
475
                FROM $tbl_lp_item
476
                WHERE
477
                    c_id = $course_id AND
478
                    lp_id = ".$this->get_id()." AND
479
                    parent_item_id = $parent ";
480
481
        $res_count = Database::query($sql);
482
        $row = Database::fetch_array($res_count);
483
        $num = $row['num'];
484
485
        $tmp_previous = 0;
486
        $display_order = 0;
487
        $next = 0;
488
        if ($num > 0) {
489
            if (empty($previous)) {
490
                $sql = "SELECT iid, next_item_id, display_order
491
                        FROM $tbl_lp_item
492
                        WHERE
493
                            c_id = $course_id AND
494
                            lp_id = ".$this->get_id()." AND
495
                            parent_item_id = $parent AND
496
                            previous_item_id = 0 OR
497
                            previous_item_id = $parent";
498
                $result = Database::query($sql);
499
                $row = Database::fetch_array($result);
500
                if ($row) {
501
                    $next = $row['iid'];
502
                }
503
            } else {
504
                $previous = (int) $previous;
505
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
506
						FROM $tbl_lp_item
507
                        WHERE
508
                            c_id = $course_id AND
509
                            lp_id = ".$this->get_id()." AND
510
                            id = $previous";
511
                $result = Database::query($sql);
512
                $row = Database::fetch_array($result);
513
                if ($row) {
514
                    $tmp_previous = $row['iid'];
515
                    $next = $row['next_item_id'];
516
                    $display_order = $row['display_order'];
517
                }
518
            }
519
        }
520
521
        $id = (int) $id;
522
        $typeCleaned = Database::escape_string($type);
523
        $max_score = 100;
524
        if ('quiz' === $type) {
525
            $sql = 'SELECT SUM(ponderation)
526
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
527
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
528
                    ON
529
                        quiz_question.id = quiz_rel_question.question_id AND
530
                        quiz_question.c_id = quiz_rel_question.c_id
531
                    WHERE
532
                        quiz_rel_question.exercice_id = '.$id." AND
533
                        quiz_question.c_id = $course_id AND
534
                        quiz_rel_question.c_id = $course_id ";
535
            $rsQuiz = Database::query($sql);
536
            $max_score = Database::result($rsQuiz, 0, 0);
537
538
            // Disabling the exercise if we add it inside a LP
539
            $exercise = new Exercise($course_id);
540
            $exercise->read($id);
541
            $exercise->disable();
542
            $exercise->save();
543
        }
544
545
        $params = [
546
            'c_id' => $course_id,
547
            'lp_id' => $this->get_id(),
548
            'item_type' => $typeCleaned,
549
            'ref' => '',
550
            'title' => $title,
551
            'description' => $description,
552
            'path' => $id,
553
            'max_score' => $max_score,
554
            'parent_item_id' => $parent,
555
            'previous_item_id' => $previous,
556
            'next_item_id' => (int) $next,
557
            'display_order' => $display_order + 1,
558
            'prerequisite' => $prerequisites,
559
            'max_time_allowed' => $max_time_allowed,
560
            'min_score' => 0,
561
            'launch_data' => '',
562
        ];
563
564
        if (0 != $prerequisites) {
565
            $params['prerequisite'] = $prerequisites;
566
        }
567
568
        $new_item_id = Database::insert($tbl_lp_item, $params);
569
        if ($new_item_id) {
570
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
571
            Database::query($sql);
572
573
            if (!empty($next)) {
574
                $sql = "UPDATE $tbl_lp_item
575
                        SET previous_item_id = $new_item_id
576
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
577
                Database::query($sql);
578
            }
579
580
            // Update the item that should be before the new item.
581
            if (!empty($tmp_previous)) {
582
                $sql = "UPDATE $tbl_lp_item
583
                        SET next_item_id = $new_item_id
584
                        WHERE c_id = $course_id AND id = $tmp_previous";
585
                Database::query($sql);
586
            }
587
588
            // Update all the items after the new item.
589
            $sql = "UPDATE $tbl_lp_item
590
                        SET display_order = display_order + 1
591
                    WHERE
592
                        c_id = $course_id AND
593
                        lp_id = ".$this->get_id()." AND
594
                        iid <> $new_item_id AND
595
                        parent_item_id = $parent AND
596
                        display_order > $display_order";
597
            Database::query($sql);
598
599
            // Update the item that should come after the new item.
600
            $sql = "UPDATE $tbl_lp_item
601
                    SET ref = $new_item_id
602
                    WHERE c_id = $course_id AND iid = $new_item_id";
603
            Database::query($sql);
604
605
            $sql = "UPDATE $tbl_lp_item
606
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
607
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
608
            Database::query($sql);
609
610
            // Upload audio.
611
            if (!empty($_FILES['mp3']['name'])) {
612
                // Create the audio folder if it does not exist yet.
613
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
614
                if (!is_dir($filepath.'audio')) {
615
                    mkdir(
616
                        $filepath.'audio',
617
                        api_get_permissions_for_new_directories()
618
                    );
619
                    $audio_id = DocumentManager::addDocument(
620
                        $_course,
621
                        '/audio',
622
                        'folder',
623
                        0,
624
                        'audio',
625
                        '',
626
                        0,
627
                        true,
628
                        null,
629
                        $sessionId,
630
                        $userId
631
                    );
632
                }
633
634
                $file_path = handle_uploaded_document(
635
                    $_course,
636
                    $_FILES['mp3'],
637
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
638
                    '/audio',
639
                    $userId,
640
                    '',
641
                    '',
642
                    '',
643
                    '',
644
                    false
645
                );
646
647
                // Getting the filename only.
648
                $file_components = explode('/', $file_path);
649
                $file = $file_components[count($file_components) - 1];
650
651
                // Store the mp3 file in the lp_item table.
652
                $sql = "UPDATE $tbl_lp_item SET
653
                          audio = '".Database::escape_string($file)."'
654
                        WHERE iid = '".intval($new_item_id)."'";
655
                Database::query($sql);
656
            }
657
        }
658
659
        return $new_item_id;
660
    }
661
662
    /**
663
     * Static admin function allowing addition of a learnpath to a course.
664
     *
665
     * @param string $courseCode
666
     * @param string $name
667
     * @param string $description
668
     * @param string $learnpath
669
     * @param string $origin
670
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
671
     * @param string $publicated_on
672
     * @param string $expired_on
673
     * @param int    $categoryId
674
     * @param int    $userId
675
     *
676
     * @return int The new learnpath ID on success, 0 on failure
677
     */
678
    public static function add_lp(
679
        $courseCode,
680
        $name,
681
        $description = '',
682
        $learnpath = 'guess',
683
        $origin = 'zip',
684
        $zipname = '',
685
        $publicated_on = '',
686
        $expired_on = '',
687
        $categoryId = 0,
688
        $userId = 0
689
    ) {
690
        global $charset;
691
692
        if (!empty($courseCode)) {
693
            $courseInfo = api_get_course_info($courseCode);
694
            $course_id = $courseInfo['real_id'];
695
        } else {
696
            $course_id = api_get_course_int_id();
697
            $courseInfo = api_get_course_info();
698
        }
699
700
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
701
        // Check course code exists.
702
        // Check lp_name doesn't exist, otherwise append something.
703
        $i = 0;
704
        $categoryId = (int) $categoryId;
705
        // Session id.
706
        $session_id = api_get_session_id();
707
        $userId = empty($userId) ? api_get_user_id() : $userId;
708
709
        if (empty($publicated_on)) {
710
            $publicated_on = null;
711
        } else {
712
            $publicated_on = api_get_utc_datetime($publicated_on, true, true);
713
        }
714
715
        if (empty($expired_on)) {
716
            $expired_on = null;
717
        } else {
718
            $expired_on = api_get_utc_datetime($expired_on, true, true);
719
        }
720
721
        $check_name = "SELECT * FROM $tbl_lp
722
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
723
        $res_name = Database::query($check_name);
724
725
        while (Database::num_rows($res_name)) {
726
            // There is already one such name, update the current one a bit.
727
            $i++;
728
            $name = $name.' - '.$i;
729
            $check_name = "SELECT * FROM $tbl_lp
730
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
731
            $res_name = Database::query($check_name);
732
        }
733
        // New name does not exist yet; keep it.
734
        // Escape description.
735
        // Kevin: added htmlentities().
736
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
737
        $type = 1;
738
        switch ($learnpath) {
739
            case 'guess':
740
            case 'aicc':
741
                break;
742
            case 'dokeos':
743
            case 'chamilo':
744
                $type = 1;
745
                break;
746
        }
747
748
        $id = null;
749
        switch ($origin) {
750
            case 'zip':
751
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
752
                break;
753
            case 'manual':
754
            default:
755
                $get_max = "SELECT MAX(display_order)
756
                            FROM $tbl_lp WHERE c_id = $course_id";
757
                $res_max = Database::query($get_max);
758
                if (Database::num_rows($res_max) < 1) {
759
                    $dsp = 1;
760
                } else {
761
                    $row = Database::fetch_array($res_max);
762
                    $dsp = $row[0] + 1;
763
                }
764
765
                $lp = new CLp();
766
                $lp
767
                    ->setCId($course_id)
768
                    ->setLpType($type)
769
                    ->setName($name)
770
                    ->setDescription($description)
771
                    ->setDisplayOrder($dsp)
772
                    ->setSessionId($session_id)
773
                    ->setCategoryId($categoryId)
774
                    ->setPublicatedOn($publicated_on)
775
                    ->setExpiredOn($expired_on)
776
                ;
777
778
                $repo = Container::getLpRepository();
779
                $em = $repo->getEntityManager();
780
                $em->persist($lp);
781
                $courseEntity = api_get_course_entity($courseInfo['real_id']);
782
783
                $repo->addResourceToCourse(
784
                    $lp,
785
                    ResourceLink::VISIBILITY_PUBLISHED,
786
                    api_get_user_entity(api_get_user_id()),
787
                    $courseEntity,
788
                    api_get_session_entity(),
789
                    api_get_group_entity()
790
                );
791
792
                $em->flush();
793
                if ($lp->getIid()) {
794
                    $id = $lp->getIid();
795
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
796
                    Database::query($sql);
797
                }
798
799
                // Insert into item_property.
800
                /*api_item_property_update(
801
                    $courseInfo,
802
                    TOOL_LEARNPATH,
803
                    $id,
804
                    'LearnpathAdded',
805
                    $userId
806
                );
807
                api_set_default_visibility(
808
                    $id,
809
                    TOOL_LEARNPATH,
810
                    0,
811
                    $courseInfo,
812
                    $session_id,
813
                    $userId
814
                );*/
815
816
                break;
817
        }
818
819
        return $id;
820
    }
821
822
    /**
823
     * Auto completes the parents of an item in case it's been completed or passed.
824
     *
825
     * @param int $item Optional ID of the item from which to look for parents
826
     */
827
    public function autocomplete_parents($item)
828
    {
829
        $debug = $this->debug;
830
831
        if (empty($item)) {
832
            $item = $this->current;
833
        }
834
835
        $currentItem = $this->getItem($item);
836
        if ($currentItem) {
837
            $parent_id = $currentItem->get_parent();
838
            $parent = $this->getItem($parent_id);
839
            if ($parent) {
840
                // if $item points to an object and there is a parent.
841
                if ($debug) {
842
                    error_log(
843
                        'Autocompleting parent of item '.$item.' '.
844
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
845
                        0
846
                    );
847
                }
848
849
                // New experiment including failed and browsed in completed status.
850
                //$current_status = $currentItem->get_status();
851
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
852
                // Fixes chapter auto complete
853
                if (true) {
854
                    // If the current item is completed or passes or succeeded.
855
                    $updateParentStatus = true;
856
                    if ($debug) {
857
                        error_log('Status of current item is alright');
858
                    }
859
860
                    foreach ($parent->get_children() as $childItemId) {
861
                        $childItem = $this->getItem($childItemId);
862
863
                        // If children was not set try to get the info
864
                        if (empty($childItem->db_item_view_id)) {
865
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
866
                        }
867
868
                        // Check all his brothers (parent's children) for completion status.
869
                        if ($childItemId != $item) {
870
                            if ($debug) {
871
                                error_log(
872
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
873
                                    0
874
                                );
875
                            }
876
                            // Trying completing parents of failed and browsed items as well.
877
                            if ($childItem->status_is(
878
                                [
879
                                    'completed',
880
                                    'passed',
881
                                    'succeeded',
882
                                    'browsed',
883
                                    'failed',
884
                                ]
885
                            )
886
                            ) {
887
                                // Keep completion status to true.
888
                                continue;
889
                            } else {
890
                                if ($debug > 2) {
891
                                    error_log(
892
                                        '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,
893
                                        0
894
                                    );
895
                                }
896
                                $updateParentStatus = false;
897
                                break;
898
                            }
899
                        }
900
                    }
901
902
                    if ($updateParentStatus) {
903
                        // If all the children were completed:
904
                        $parent->set_status('completed');
905
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
906
                        // Force the status to "completed"
907
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
908
                        $this->update_queue[$parent->get_id()] = 'completed';
909
                        if ($debug) {
910
                            error_log(
911
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
912
                                print_r($this->update_queue, 1),
913
                                0
914
                            );
915
                        }
916
                        // Recursive call.
917
                        $this->autocomplete_parents($parent->get_id());
918
                    }
919
                }
920
            } else {
921
                if ($debug) {
922
                    error_log("Parent #$parent_id does not exists");
923
                }
924
            }
925
        } else {
926
            if ($debug) {
927
                error_log("#$item is an item that doesn't have parents");
928
            }
929
        }
930
    }
931
932
    /**
933
     * Closes the current resource.
934
     *
935
     * Stops the timer
936
     * Saves into the database if required
937
     * Clears the current resource data from this object
938
     *
939
     * @return bool True on success, false on failure
940
     */
941
    public function close()
942
    {
943
        if (empty($this->lp_id)) {
944
            $this->error = 'Trying to close this learnpath but no ID is set';
945
946
            return false;
947
        }
948
        $this->current_time_stop = time();
949
        $this->ordered_items = [];
950
        $this->index = 0;
951
        unset($this->lp_id);
952
        //unset other stuff
953
        return true;
954
    }
955
956
    /**
957
     * Static admin function allowing removal of a learnpath.
958
     *
959
     * @param array  $courseInfo
960
     * @param int    $id         Learnpath ID
961
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
962
     *
963
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
964
     */
965
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
966
    {
967
        $course_id = api_get_course_int_id();
968
        if (!empty($courseInfo)) {
969
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
970
        }
971
972
        // TODO: Implement a way of getting this to work when the current object is not set.
973
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
974
        // If an ID is specifically given and the current LP is not the same, prevent delete.
975
        if (!empty($id) && ($id != $this->lp_id)) {
976
            return false;
977
        }
978
979
        $lp = Database::get_course_table(TABLE_LP_MAIN);
980
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
981
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
982
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
983
984
        // Delete lp item id.
985
        foreach ($this->items as $lpItemId => $dummy) {
986
            $sql = "DELETE FROM $lp_item_view
987
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
988
            Database::query($sql);
989
        }
990
991
        // Proposed by Christophe (nickname: clefevre)
992
        $sql = "DELETE FROM $lp_item
993
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
994
        Database::query($sql);
995
996
        $sql = "DELETE FROM $lp_view
997
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
998
        Database::query($sql);
999
1000
        //self::toggle_publish($this->lp_id, 'i');
1001
1002
        if (2 == $this->type || 3 == $this->type) {
1003
            // This is a scorm learning path, delete the files as well.
1004
            $sql = "SELECT path FROM $lp
1005
                    WHERE iid = ".$this->lp_id;
1006
            $res = Database::query($sql);
1007
            if (Database::num_rows($res) > 0) {
1008
                $row = Database::fetch_array($res);
1009
                $path = $row['path'];
1010
                $sql = "SELECT id FROM $lp
1011
                        WHERE
1012
                            c_id = $course_id AND
1013
                            path = '$path' AND
1014
                            iid != ".$this->lp_id;
1015
                $res = Database::query($sql);
1016
                if (Database::num_rows($res) > 0) {
1017
                    // Another learning path uses this directory, so don't delete it.
1018
                    if ($this->debug > 2) {
1019
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1020
                    }
1021
                } else {
1022
                    // No other LP uses that directory, delete it.
1023
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1024
                    // The absolute system path for this course.
1025
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1026
                    if ('remove' == $delete && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1027
                        if ($this->debug > 2) {
1028
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1029
                        }
1030
                        // Proposed by Christophe (clefevre).
1031
                        if (0 == strcmp(substr($path, -2), "/.")) {
1032
                            $path = substr($path, 0, -1); // Remove "." at the end.
1033
                        }
1034
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1035
                        rmdirr($course_scorm_dir.$path);
1036
                    }
1037
                }
1038
            }
1039
        }
1040
1041
        /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1042
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1043
        // Delete tools
1044
        $sql = "DELETE FROM $tbl_tool
1045
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1046
        Database::query($sql);*/
1047
1048
        /*$sql = "DELETE FROM $lp
1049
                WHERE iid = ".$this->lp_id;
1050
        Database::query($sql);*/
1051
        $repo = Container::getLpRepository();
1052
        $lp = $repo->find($this->lp_id);
1053
        $repo->getEntityManager()->remove($lp);
1054
        $repo->getEntityManager()->flush();
1055
1056
        // Updates the display order of all lps.
1057
        $this->update_display_order();
1058
1059
        /*api_item_property_update(
1060
            api_get_course_info(),
1061
            TOOL_LEARNPATH,
1062
            $this->lp_id,
1063
            'delete',
1064
            api_get_user_id()
1065
        );*/
1066
1067
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1068
            api_get_course_id(),
1069
            4,
1070
            $id,
1071
            api_get_session_id()
1072
        );
1073
1074
        if (false !== $link_info) {
1075
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1076
        }
1077
1078
        if ('true' == api_get_setting('search_enabled')) {
1079
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1080
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1081
        }
1082
    }
1083
1084
    /**
1085
     * Removes all the children of one item - dangerous!
1086
     *
1087
     * @param int $id Element ID of which children have to be removed
1088
     *
1089
     * @return int Total number of children removed
1090
     */
1091
    public function delete_children_items($id)
1092
    {
1093
        $course_id = $this->course_info['real_id'];
1094
1095
        $num = 0;
1096
        $id = (int) $id;
1097
        if (empty($id) || empty($course_id)) {
1098
            return false;
1099
        }
1100
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1101
        $sql = "SELECT * FROM $lp_item
1102
                WHERE c_id = $course_id AND parent_item_id = $id";
1103
        $res = Database::query($sql);
1104
        while ($row = Database::fetch_array($res)) {
1105
            $num += $this->delete_children_items($row['iid']);
1106
            $sql = "DELETE FROM $lp_item
1107
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1108
            Database::query($sql);
1109
            $num++;
1110
        }
1111
1112
        return $num;
1113
    }
1114
1115
    /**
1116
     * Removes an item from the current learnpath.
1117
     *
1118
     * @param int $id Elem ID (0 if first)
1119
     *
1120
     * @return int Number of elements moved
1121
     *
1122
     * @todo implement resource removal
1123
     */
1124
    public function delete_item($id)
1125
    {
1126
        $course_id = api_get_course_int_id();
1127
        $id = (int) $id;
1128
        // TODO: Implement the resource removal.
1129
        if (empty($id) || empty($course_id)) {
1130
            return false;
1131
        }
1132
        // First select item to get previous, next, and display order.
1133
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1134
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1135
        $res_sel = Database::query($sql_sel);
1136
        if (Database::num_rows($res_sel) < 1) {
1137
            return false;
1138
        }
1139
        $row = Database::fetch_array($res_sel);
1140
        $previous = $row['previous_item_id'];
1141
        $next = $row['next_item_id'];
1142
        $display = $row['display_order'];
1143
        $parent = $row['parent_item_id'];
1144
        $lp = $row['lp_id'];
1145
        // Delete children items.
1146
        $this->delete_children_items($id);
1147
        // Now delete the item.
1148
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1149
        Database::query($sql_del);
1150
        // Now update surrounding items.
1151
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1152
                    WHERE iid = $previous";
1153
        Database::query($sql_upd);
1154
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1155
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1156
        Database::query($sql_upd);
1157
        // Now update all following items with new display order.
1158
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1159
                    WHERE
1160
                        c_id = $course_id AND
1161
                        lp_id = $lp AND
1162
                        parent_item_id = $parent AND
1163
                        display_order > $display";
1164
        Database::query($sql_all);
1165
1166
        //Removing prerequisites since the item will not longer exist
1167
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1168
                    WHERE c_id = $course_id AND prerequisite = '$id'";
1169
        Database::query($sql_all);
1170
1171
        $sql = "UPDATE $lp_item
1172
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1173
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1174
        Database::query($sql);
1175
1176
        // Remove from search engine if enabled.
1177
        if ('true' === api_get_setting('search_enabled')) {
1178
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1179
            $sql = 'SELECT * FROM %s
1180
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1181
                    LIMIT 1';
1182
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1183
            $res = Database::query($sql);
1184
            if (Database::num_rows($res) > 0) {
1185
                $row2 = Database::fetch_array($res);
1186
                $di = new ChamiloIndexer();
1187
                $di->remove_document($row2['search_did']);
1188
            }
1189
            $sql = 'DELETE FROM %s
1190
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1191
                    LIMIT 1';
1192
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1193
            Database::query($sql);
1194
        }
1195
    }
1196
1197
    /**
1198
     * Updates an item's content in place.
1199
     *
1200
     * @param int    $id               Element ID
1201
     * @param int    $parent           Parent item ID
1202
     * @param int    $previous         Previous item ID
1203
     * @param string $title            Item title
1204
     * @param string $description      Item description
1205
     * @param string $prerequisites    Prerequisites (optional)
1206
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1207
     * @param int    $max_time_allowed
1208
     * @param string $url
1209
     *
1210
     * @return bool True on success, false on error
1211
     */
1212
    public function edit_item(
1213
        $id,
1214
        $parent,
1215
        $previous,
1216
        $title,
1217
        $description,
1218
        $prerequisites = '0',
1219
        $audio = [],
1220
        $max_time_allowed = 0,
1221
        $url = ''
1222
    ) {
1223
        $course_id = api_get_course_int_id();
1224
        $_course = api_get_course_info();
1225
        $id = (int) $id;
1226
1227
        if (empty($max_time_allowed)) {
1228
            $max_time_allowed = 0;
1229
        }
1230
1231
        if (empty($id) || empty($_course)) {
1232
            return false;
1233
        }
1234
1235
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1236
        $sql = "SELECT * FROM $tbl_lp_item
1237
                WHERE iid = $id";
1238
        $res_select = Database::query($sql);
1239
        $row_select = Database::fetch_array($res_select);
1240
        $audio_update_sql = '';
1241
        if (is_array($audio) && !empty($audio['tmp_name']) && 0 === $audio['error']) {
1242
            // Create the audio folder if it does not exist yet.
1243
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1244
            if (!is_dir($filepath.'audio')) {
1245
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1246
                $audio_id = DocumentManager::addDocument(
1247
                    $_course,
1248
                    '/audio',
1249
                    'folder',
1250
                    0,
1251
                    'audio'
1252
                );
1253
            }
1254
1255
            // Upload file in documents.
1256
            $pi = pathinfo($audio['name']);
1257
            if ('mp3' === $pi['extension']) {
1258
                $c_det = api_get_course_info($this->cc);
1259
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1260
                $path = handle_uploaded_document(
1261
                    $c_det,
1262
                    $audio,
1263
                    $bp,
1264
                    '/audio',
1265
                    api_get_user_id(),
1266
                    0,
1267
                    null,
1268
                    0,
1269
                    'rename',
1270
                    false,
1271
                    0
1272
                );
1273
                $path = substr($path, 7);
1274
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1275
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1276
            }
1277
        }
1278
1279
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1280
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1281
1282
        // TODO: htmlspecialchars to be checked for encoding related problems.
1283
        if ($same_parent && $same_previous) {
1284
            // Only update title and description.
1285
            $sql = "UPDATE $tbl_lp_item
1286
                    SET title = '".Database::escape_string($title)."',
1287
                        prerequisite = '".$prerequisites."',
1288
                        description = '".Database::escape_string($description)."'
1289
                        ".$audio_update_sql.",
1290
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1291
                    WHERE iid = $id";
1292
            Database::query($sql);
1293
        } else {
1294
            $old_parent = $row_select['parent_item_id'];
1295
            $old_previous = $row_select['previous_item_id'];
1296
            $old_next = $row_select['next_item_id'];
1297
            $old_order = $row_select['display_order'];
1298
            $old_prerequisite = $row_select['prerequisite'];
1299
            $old_max_time_allowed = $row_select['max_time_allowed'];
1300
1301
            /* BEGIN -- virtually remove the current item id */
1302
            /* for the next and previous item it is like the current item doesn't exist anymore */
1303
            if (0 != $old_previous) {
1304
                // Next
1305
                $sql = "UPDATE $tbl_lp_item
1306
                        SET next_item_id = $old_next
1307
                        WHERE iid = $old_previous";
1308
                Database::query($sql);
1309
            }
1310
1311
            if (!empty($old_next)) {
1312
                // Previous
1313
                $sql = "UPDATE $tbl_lp_item
1314
                        SET previous_item_id = $old_previous
1315
                        WHERE iid = $old_next";
1316
                Database::query($sql);
1317
            }
1318
1319
            // display_order - 1 for every item with a display_order
1320
            // bigger then the display_order of the current item.
1321
            $sql = "UPDATE $tbl_lp_item
1322
                    SET display_order = display_order - 1
1323
                    WHERE
1324
                        c_id = $course_id AND
1325
                        display_order > $old_order AND
1326
                        lp_id = ".$this->lp_id." AND
1327
                        parent_item_id = $old_parent";
1328
            Database::query($sql);
1329
            /* END -- virtually remove the current item id */
1330
1331
            /* BEGIN -- update the current item id to his new location */
1332
            if (0 == $previous) {
1333
                // Select the data of the item that should come after the current item.
1334
                $sql = "SELECT id, display_order
1335
                        FROM $tbl_lp_item
1336
                        WHERE
1337
                            c_id = $course_id AND
1338
                            lp_id = ".$this->lp_id." AND
1339
                            parent_item_id = $parent AND
1340
                            previous_item_id = $previous";
1341
                $res_select_old = Database::query($sql);
1342
                $row_select_old = Database::fetch_array($res_select_old);
1343
1344
                // If the new parent didn't have children before.
1345
                if (0 == Database::num_rows($res_select_old)) {
1346
                    $new_next = 0;
1347
                    $new_order = 1;
1348
                } else {
1349
                    $new_next = $row_select_old['id'];
1350
                    $new_order = $row_select_old['display_order'];
1351
                }
1352
            } else {
1353
                // Select the data of the item that should come before the current item.
1354
                $sql = "SELECT next_item_id, display_order
1355
                        FROM $tbl_lp_item
1356
                        WHERE iid = $previous";
1357
                $res_select_old = Database::query($sql);
1358
                $row_select_old = Database::fetch_array($res_select_old);
1359
                $new_next = $row_select_old['next_item_id'];
1360
                $new_order = $row_select_old['display_order'] + 1;
1361
            }
1362
1363
            // TODO: htmlspecialchars to be checked for encoding related problems.
1364
            // Update the current item with the new data.
1365
            $sql = "UPDATE $tbl_lp_item
1366
                    SET
1367
                        title = '".Database::escape_string($title)."',
1368
                        description = '".Database::escape_string($description)."',
1369
                        parent_item_id = $parent,
1370
                        previous_item_id = $previous,
1371
                        next_item_id = $new_next,
1372
                        display_order = $new_order
1373
                        $audio_update_sql
1374
                    WHERE iid = $id";
1375
            Database::query($sql);
1376
1377
            if (0 != $previous) {
1378
                // Update the previous item's next_item_id.
1379
                $sql = "UPDATE $tbl_lp_item
1380
                        SET next_item_id = $id
1381
                        WHERE iid = $previous";
1382
                Database::query($sql);
1383
            }
1384
1385
            if (!empty($new_next)) {
1386
                // Update the next item's previous_item_id.
1387
                $sql = "UPDATE $tbl_lp_item
1388
                        SET previous_item_id = $id
1389
                        WHERE iid = $new_next";
1390
                Database::query($sql);
1391
            }
1392
1393
            if ($old_prerequisite != $prerequisites) {
1394
                $sql = "UPDATE $tbl_lp_item
1395
                        SET prerequisite = '$prerequisites'
1396
                        WHERE iid = $id";
1397
                Database::query($sql);
1398
            }
1399
1400
            if ($old_max_time_allowed != $max_time_allowed) {
1401
                // update max time allowed
1402
                $sql = "UPDATE $tbl_lp_item
1403
                        SET max_time_allowed = $max_time_allowed
1404
                        WHERE iid = $id";
1405
                Database::query($sql);
1406
            }
1407
1408
            // Update all the items with the same or a bigger display_order than the current item.
1409
            $sql = "UPDATE $tbl_lp_item
1410
                    SET display_order = display_order + 1
1411
                    WHERE
1412
                       c_id = $course_id AND
1413
                       lp_id = ".$this->get_id()." AND
1414
                       iid <> $id AND
1415
                       parent_item_id = $parent AND
1416
                       display_order >= $new_order";
1417
            Database::query($sql);
1418
        }
1419
1420
        if ('link' == $row_select['item_type']) {
1421
            $link = new Link();
1422
            $linkId = $row_select['path'];
1423
            $link->updateLink($linkId, $url);
1424
        }
1425
    }
1426
1427
    /**
1428
     * Updates an item's prereq in place.
1429
     *
1430
     * @param int    $id              Element ID
1431
     * @param string $prerequisite_id Prerequisite Element ID
1432
     * @param int    $minScore        Prerequisite min score
1433
     * @param int    $maxScore        Prerequisite max score
1434
     *
1435
     * @return bool True on success, false on error
1436
     */
1437
    public function edit_item_prereq(
1438
        $id,
1439
        $prerequisite_id,
1440
        $minScore = 0,
1441
        $maxScore = 100
1442
    ) {
1443
        $id = (int) $id;
1444
        $prerequisite_id = (int) $prerequisite_id;
1445
1446
        if (empty($id)) {
1447
            return false;
1448
        }
1449
1450
        if (empty($minScore) || $minScore < 0) {
1451
            $minScore = 0;
1452
        }
1453
1454
        if (empty($maxScore) || $maxScore < 0) {
1455
            $maxScore = 100;
1456
        }
1457
1458
        $minScore = floatval($minScore);
1459
        $maxScore = floatval($maxScore);
1460
1461
        if (empty($prerequisite_id)) {
1462
            $prerequisite_id = 'NULL';
1463
            $minScore = 0;
1464
            $maxScore = 100;
1465
        }
1466
1467
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1468
        $sql = " UPDATE $tbl_lp_item
1469
                 SET
1470
                    prerequisite = $prerequisite_id ,
1471
                    prerequisite_min_score = $minScore ,
1472
                    prerequisite_max_score = $maxScore
1473
                 WHERE iid = $id";
1474
1475
        Database::query($sql);
1476
1477
        return true;
1478
    }
1479
1480
    /**
1481
     * Get the specific prefix index terms of this learning path.
1482
     *
1483
     * @param string $prefix
1484
     *
1485
     * @return array Array of terms
1486
     */
1487
    public function get_common_index_terms_by_prefix($prefix)
1488
    {
1489
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1490
        $terms = get_specific_field_values_list_by_prefix(
1491
            $prefix,
1492
            $this->cc,
1493
            TOOL_LEARNPATH,
1494
            $this->lp_id
1495
        );
1496
        $prefix_terms = [];
1497
        if (!empty($terms)) {
1498
            foreach ($terms as $term) {
1499
                $prefix_terms[] = $term['value'];
1500
            }
1501
        }
1502
1503
        return $prefix_terms;
1504
    }
1505
1506
    /**
1507
     * Gets the number of items currently completed.
1508
     *
1509
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1510
     *
1511
     * @return int The number of items currently completed
1512
     */
1513
    public function get_complete_items_count($failedStatusException = false)
1514
    {
1515
        $i = 0;
1516
        $completedStatusList = [
1517
            'completed',
1518
            'passed',
1519
            'succeeded',
1520
            'browsed',
1521
        ];
1522
1523
        if (!$failedStatusException) {
1524
            $completedStatusList[] = 'failed';
1525
        }
1526
1527
        foreach ($this->items as $id => $dummy) {
1528
            // Trying failed and browsed considered "progressed" as well.
1529
            if ($this->items[$id]->status_is($completedStatusList) &&
1530
                'dir' != $this->items[$id]->get_type()
1531
            ) {
1532
                $i++;
1533
            }
1534
        }
1535
1536
        return $i;
1537
    }
1538
1539
    /**
1540
     * Gets the current item ID.
1541
     *
1542
     * @return int The current learnpath item id
1543
     */
1544
    public function get_current_item_id()
1545
    {
1546
        $current = 0;
1547
        if (!empty($this->current)) {
1548
            $current = (int) $this->current;
1549
        }
1550
1551
        return $current;
1552
    }
1553
1554
    /**
1555
     * Force to get the first learnpath item id.
1556
     *
1557
     * @return int The current learnpath item id
1558
     */
1559
    public function get_first_item_id()
1560
    {
1561
        $current = 0;
1562
        if (is_array($this->ordered_items)) {
1563
            $current = $this->ordered_items[0];
1564
        }
1565
1566
        return $current;
1567
    }
1568
1569
    /**
1570
     * Gets the total number of items available for viewing in this SCORM.
1571
     *
1572
     * @return int The total number of items
1573
     */
1574
    public function get_total_items_count()
1575
    {
1576
        return count($this->items);
1577
    }
1578
1579
    /**
1580
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1581
     *
1582
     * @return int The total no-chapters number of items
1583
     */
1584
    public function getTotalItemsCountWithoutDirs()
1585
    {
1586
        $total = 0;
1587
        $typeListNotToCount = self::getChapterTypes();
1588
        foreach ($this->items as $temp2) {
1589
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1590
                $total++;
1591
            }
1592
        }
1593
1594
        return $total;
1595
    }
1596
1597
    /**
1598
     *  Sets the first element URL.
1599
     */
1600
    public function first()
1601
    {
1602
        if ($this->debug > 0) {
1603
            error_log('In learnpath::first()', 0);
1604
            error_log('$this->last_item_seen '.$this->last_item_seen);
1605
        }
1606
1607
        // Test if the last_item_seen exists and is not a dir.
1608
        if (0 == count($this->ordered_items)) {
1609
            $this->index = 0;
1610
        }
1611
1612
        if (!empty($this->last_item_seen) &&
1613
            !empty($this->items[$this->last_item_seen]) &&
1614
            'dir' != $this->items[$this->last_item_seen]->get_type()
1615
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1616
            //&& !$this->items[$this->last_item_seen]->is_done()
1617
        ) {
1618
            if ($this->debug > 2) {
1619
                error_log(
1620
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1621
                    $this->items[$this->last_item_seen]->get_type()
1622
                );
1623
            }
1624
            $index = -1;
1625
            foreach ($this->ordered_items as $myindex => $item_id) {
1626
                if ($item_id == $this->last_item_seen) {
1627
                    $index = $myindex;
1628
                    break;
1629
                }
1630
            }
1631
            if (-1 == $index) {
1632
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1633
                if ($this->debug > 2) {
1634
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1635
                }
1636
1637
                return false;
1638
            } else {
1639
                $this->last = $this->last_item_seen;
1640
                $this->current = $this->last_item_seen;
1641
                $this->index = $index;
1642
            }
1643
        } else {
1644
            if ($this->debug > 2) {
1645
                error_log('In learnpath::first() - No last item seen', 0);
1646
            }
1647
            $index = 0;
1648
            // Loop through all ordered items and stop at the first item that is
1649
            // not a directory *and* that has not been completed yet.
1650
            while (!empty($this->ordered_items[$index]) &&
1651
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1652
                (
1653
                    'dir' == $this->items[$this->ordered_items[$index]]->get_type() ||
1654
                    true === $this->items[$this->ordered_items[$index]]->is_done()
1655
                ) && $index < $this->max_ordered_items) {
1656
                $index++;
1657
            }
1658
1659
            $this->last = $this->current;
1660
            // current is
1661
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1662
            $this->index = $index;
1663
            if ($this->debug > 2) {
1664
                error_log('$index '.$index);
1665
                error_log('In learnpath::first() - No last item seen');
1666
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1667
            }
1668
        }
1669
        if ($this->debug > 2) {
1670
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1671
        }
1672
    }
1673
1674
    /**
1675
     * Gets the js library from the database.
1676
     *
1677
     * @return string The name of the javascript library to be used
1678
     */
1679
    public function get_js_lib()
1680
    {
1681
        $lib = '';
1682
        if (!empty($this->js_lib)) {
1683
            $lib = $this->js_lib;
1684
        }
1685
1686
        return $lib;
1687
    }
1688
1689
    /**
1690
     * Gets the learnpath database ID.
1691
     *
1692
     * @return int Learnpath ID in the lp table
1693
     */
1694
    public function get_id()
1695
    {
1696
        if (!empty($this->lp_id)) {
1697
            return (int) $this->lp_id;
1698
        }
1699
1700
        return 0;
1701
    }
1702
1703
    /**
1704
     * Gets the last element URL.
1705
     *
1706
     * @return string URL to load into the viewer
1707
     */
1708
    public function get_last()
1709
    {
1710
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1711
        if (count($this->ordered_items) > 0) {
1712
            $this->index = count($this->ordered_items) - 1;
1713
1714
            return $this->ordered_items[$this->index];
1715
        }
1716
1717
        return false;
1718
    }
1719
1720
    /**
1721
     * Get the last element in the first level.
1722
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1723
     *
1724
     * @return mixed
1725
     */
1726
    public function getLastInFirstLevel()
1727
    {
1728
        try {
1729
            $lastId = Database::getManager()
1730
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1731
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1732
                ->setMaxResults(1)
1733
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1734
                ->getSingleScalarResult();
1735
1736
            return $lastId;
1737
        } catch (Exception $exception) {
1738
            return 0;
1739
        }
1740
    }
1741
1742
    /**
1743
     * Gets the navigation bar for the learnpath display screen.
1744
     *
1745
     * @param string $barId
1746
     *
1747
     * @return string The HTML string to use as a navigation bar
1748
     */
1749
    public function get_navigation_bar($barId = '')
1750
    {
1751
        if (empty($barId)) {
1752
            $barId = 'control-top';
1753
        }
1754
        $lpId = $this->lp_id;
1755
        $mycurrentitemid = $this->get_current_item_id();
1756
1757
        $reportingText = get_lang('Reporting');
1758
        $previousText = get_lang('Previous');
1759
        $nextText = get_lang('Next');
1760
        $fullScreenText = get_lang('Back to normal screen');
1761
1762
        $settings = api_get_configuration_value('lp_view_settings');
1763
        $display = isset($settings['display']) ? $settings['display'] : false;
1764
        $reportingIcon = '
1765
            <a class="icon-toolbar"
1766
                id="stats_link"
1767
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1768
                onclick="window.parent.API.save_asset(); return true;"
1769
                target="content_name" title="'.$reportingText.'">
1770
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1771
            </a>';
1772
1773
        if (!empty($display)) {
1774
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1775
            if (false === $showReporting) {
1776
                $reportingIcon = '';
1777
            }
1778
        }
1779
1780
        $hideArrows = false;
1781
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1782
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1783
        }
1784
1785
        $previousIcon = '';
1786
        $nextIcon = '';
1787
        if (false === $hideArrows) {
1788
            $previousIcon = '
1789
                <a class="icon-toolbar" id="scorm-previous" href="#"
1790
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1791
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1792
                </a>';
1793
1794
            $nextIcon = '
1795
                <a class="icon-toolbar" id="scorm-next" href="#"
1796
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1797
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1798
                </a>';
1799
        }
1800
1801
        if ('fullscreen' === $this->mode) {
1802
            $navbar = '
1803
                  <span id="'.$barId.'" class="buttons">
1804
                    '.$reportingIcon.'
1805
                    '.$previousIcon.'
1806
                    '.$nextIcon.'
1807
                    <a class="icon-toolbar" id="view-embedded"
1808
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1809
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1810
                    </a>
1811
                  </span>';
1812
        } else {
1813
            $navbar = '
1814
                 <span id="'.$barId.'" class="buttons text-right">
1815
                    '.$reportingIcon.'
1816
                    '.$previousIcon.'
1817
                    '.$nextIcon.'
1818
                </span>';
1819
        }
1820
1821
        return $navbar;
1822
    }
1823
1824
    /**
1825
     * Gets the next resource in queue (url).
1826
     *
1827
     * @return string URL to load into the viewer
1828
     */
1829
    public function get_next_index()
1830
    {
1831
        // TODO
1832
        $index = $this->index;
1833
        $index++;
1834
        while (
1835
            !empty($this->ordered_items[$index]) && ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) &&
1836
            $index < $this->max_ordered_items
1837
        ) {
1838
            $index++;
1839
            if ($index == $this->max_ordered_items) {
1840
                if ('dir' == $this->items[$this->ordered_items[$index]]->get_type()) {
1841
                    return $this->index;
1842
                }
1843
1844
                return $index;
1845
            }
1846
        }
1847
        if (empty($this->ordered_items[$index])) {
1848
            return $this->index;
1849
        }
1850
1851
        return $index;
1852
    }
1853
1854
    /**
1855
     * Gets item_id for the next element.
1856
     *
1857
     * @return int Next item (DB) ID
1858
     */
1859
    public function get_next_item_id()
1860
    {
1861
        $new_index = $this->get_next_index();
1862
        if (!empty($new_index)) {
1863
            if (isset($this->ordered_items[$new_index])) {
1864
                return $this->ordered_items[$new_index];
1865
            }
1866
        }
1867
1868
        return 0;
1869
    }
1870
1871
    /**
1872
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1873
     *
1874
     * Generally, the package provided is in the form of a zip file, so the function
1875
     * has been written to test a zip file. If not a zip, the function will return the
1876
     * default return value: ''
1877
     *
1878
     * @param string $file_path the path to the file
1879
     * @param string $file_name the original name of the file
1880
     *
1881
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1882
     */
1883
    public static function get_package_type($file_path, $file_name)
1884
    {
1885
        // Get name of the zip file without the extension.
1886
        $file_info = pathinfo($file_name);
1887
        $extension = $file_info['extension']; // Extension only.
1888
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1889
                'dll',
1890
                'exe',
1891
            ])) {
1892
            return 'oogie';
1893
        }
1894
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1895
                'dll',
1896
                'exe',
1897
            ])) {
1898
            return 'woogie';
1899
        }
1900
1901
        $zipFile = new PclZip($file_path);
1902
        // Check the zip content (real size and file extension).
1903
        $zipContentArray = $zipFile->listContent();
1904
        $package_type = '';
1905
        $manifest = '';
1906
        $aicc_match_crs = 0;
1907
        $aicc_match_au = 0;
1908
        $aicc_match_des = 0;
1909
        $aicc_match_cst = 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) && count($zipContentArray) > 0) {
1913
            foreach ($zipContentArray as $thisContent) {
1914
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1915
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1916
                } elseif (false !== stristr($thisContent['filename'], 'imsmanifest.xml')) {
1917
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1918
                    $package_type = 'scorm';
1919
                    break; // Exit the foreach loop.
1920
                } elseif (
1921
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1922
                    in_array(
1923
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1924
                        ['crs', 'au', 'des', 'cst']
1925
                    )
1926
                ) {
1927
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1928
                    switch ($ext) {
1929
                        case 'crs':
1930
                            $aicc_match_crs = 1;
1931
                            break;
1932
                        case 'au':
1933
                            $aicc_match_au = 1;
1934
                            break;
1935
                        case 'des':
1936
                            $aicc_match_des = 1;
1937
                            break;
1938
                        case 'cst':
1939
                            $aicc_match_cst = 1;
1940
                            break;
1941
                        default:
1942
                            break;
1943
                    }
1944
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1945
                } else {
1946
                    $package_type = '';
1947
                }
1948
            }
1949
        }
1950
1951
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1952
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1953
            $package_type = 'aicc';
1954
        }
1955
1956
        // Try with chamilo course builder
1957
        if (empty($package_type)) {
1958
            $package_type = 'chamilo';
1959
        }
1960
1961
        return $package_type;
1962
    }
1963
1964
    /**
1965
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1966
     *
1967
     * @return string URL to load into the viewer
1968
     */
1969
    public function get_previous_index()
1970
    {
1971
        $index = $this->index;
1972
        if (isset($this->ordered_items[$index - 1])) {
1973
            $index--;
1974
            while (isset($this->ordered_items[$index]) &&
1975
                ('dir' == $this->items[$this->ordered_items[$index]]->get_type())
1976
            ) {
1977
                $index--;
1978
                if ($index < 0) {
1979
                    return $this->index;
1980
                }
1981
            }
1982
        }
1983
1984
        return $index;
1985
    }
1986
1987
    /**
1988
     * Gets item_id for the next element.
1989
     *
1990
     * @return int Previous item (DB) ID
1991
     */
1992
    public function get_previous_item_id()
1993
    {
1994
        $index = $this->get_previous_index();
1995
1996
        return $this->ordered_items[$index];
1997
    }
1998
1999
    /**
2000
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2001
     *
2002
     * @param int    $lpItemId
2003
     * @param string $autostart
2004
     *
2005
     * @return string The mediaplayer HTML
2006
     */
2007
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2008
    {
2009
        $course_id = api_get_course_int_id();
2010
        $_course = api_get_course_info();
2011
        if (empty($_course)) {
2012
            return '';
2013
        }
2014
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2015
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2016
        $lpItemId = (int) $lpItemId;
2017
2018
        /** @var learnpathItem $item */
2019
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2020
        $itemViewId = 0;
2021
        if ($item) {
2022
            $itemViewId = (int) $item->db_item_view_id;
2023
        }
2024
2025
        // Getting all the information about the item.
2026
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2027
                FROM $tbl_lp_item as lpi
2028
                INNER JOIN $tbl_lp_item_view as lp_view
2029
                ON (lpi.iid = lp_view.lp_item_id)
2030
                WHERE
2031
                    lp_view.iid = $itemViewId AND
2032
                    lpi.iid = $lpItemId AND
2033
                    lp_view.c_id = $course_id";
2034
        $result = Database::query($sql);
2035
        $row = Database::fetch_assoc($result);
2036
        $output = '';
2037
2038
        if (!empty($row['audio'])) {
2039
            $list = $_SESSION['oLP']->get_toc();
2040
2041
            switch ($row['item_type']) {
2042
                case 'quiz':
2043
                    $type_quiz = false;
2044
                    foreach ($list as $toc) {
2045
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2046
                            $type_quiz = true;
2047
                        }
2048
                    }
2049
2050
                    if ($type_quiz) {
2051
                        if (1 == $_SESSION['oLP']->prevent_reinit) {
2052
                            $autostart_audio = 'completed' === $row['status'] ? 'false' : 'true';
2053
                        } else {
2054
                            $autostart_audio = $autostart;
2055
                        }
2056
                    }
2057
                    break;
2058
                case TOOL_READOUT_TEXT:;
2059
                    $autostart_audio = 'false';
2060
                    break;
2061
                default:
2062
                    $autostart_audio = 'true';
2063
            }
2064
2065
            $courseInfo = api_get_course_info();
2066
            $audio = $row['audio'];
2067
2068
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2069
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2070
2071
            if (!file_exists($file)) {
2072
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2073
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2074
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2075
            }
2076
2077
            $player = Display::getMediaPlayer(
2078
                $file,
2079
                [
2080
                    'id' => 'lp_audio_media_player',
2081
                    'url' => $url,
2082
                    'autoplay' => $autostart_audio,
2083
                    'width' => '100%',
2084
                ]
2085
            );
2086
2087
            // The mp3 player.
2088
            $output = '<div id="container">';
2089
            $output .= $player;
2090
            $output .= '</div>';
2091
        }
2092
2093
        return $output;
2094
    }
2095
2096
    /**
2097
     * @param int   $studentId
2098
     * @param int   $prerequisite
2099
     * @param array $courseInfo
2100
     * @param int   $sessionId
2101
     *
2102
     * @return bool
2103
     */
2104
    public static function isBlockedByPrerequisite(
2105
        $studentId,
2106
        $prerequisite,
2107
        $courseInfo,
2108
        $sessionId
2109
    ) {
2110
        if (empty($courseInfo)) {
2111
            return false;
2112
        }
2113
2114
        $courseId = $courseInfo['real_id'];
2115
2116
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2117
        if ($allow) {
2118
            if (api_is_allowed_to_edit() ||
2119
                api_is_platform_admin(true) ||
2120
                api_is_drh() ||
2121
                api_is_coach($sessionId, $courseId, false)
2122
            ) {
2123
                return false;
2124
            }
2125
        }
2126
2127
        $isBlocked = false;
2128
        if (!empty($prerequisite)) {
2129
            $progress = self::getProgress(
2130
                $prerequisite,
2131
                $studentId,
2132
                $courseId,
2133
                $sessionId
2134
            );
2135
            if ($progress < 100) {
2136
                $isBlocked = true;
2137
            }
2138
2139
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2140
                // Block if it does not exceed minimum time
2141
                // Minimum time (in minutes) to pass the learning path
2142
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2143
2144
                if ($accumulateWorkTime > 0) {
2145
                    // Total time in course (sum of times in learning paths from course)
2146
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2147
2148
                    // Connect with the plugin_licences_course_session table
2149
                    // which indicates what percentage of the time applies
2150
                    // Minimum connection percentage
2151
                    $perc = 100;
2152
                    // Time from the course
2153
                    $tc = $accumulateWorkTimeTotal;
2154
2155
                    // Percentage of the learning paths
2156
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2157
                    // Minimum time for each learning path
2158
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2159
2160
                    // Spent time (in seconds) so far in the learning path
2161
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2162
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2163
2164
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2165
                        $isBlocked = true;
2166
                    }
2167
                }
2168
            }
2169
        }
2170
2171
        return $isBlocked;
2172
    }
2173
2174
    /**
2175
     * Checks if the learning path is visible for student after the progress
2176
     * of its prerequisite is completed, considering the time availability and
2177
     * the LP visibility.
2178
     *
2179
     * @param int   $lp_id
2180
     * @param int   $student_id
2181
     * @param array $courseInfo
2182
     * @param int   $sessionId
2183
     *
2184
     * @return bool
2185
     */
2186
    public static function is_lp_visible_for_student(
2187
        CLp $lp,
2188
        $student_id,
2189
        $courseInfo = [],
2190
        $sessionId = 0
2191
    ) {
2192
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2193
        $sessionId = (int) $sessionId;
2194
2195
        if (empty($courseInfo)) {
2196
            return false;
2197
        }
2198
2199
        if (empty($sessionId)) {
2200
            $sessionId = api_get_session_id();
2201
        }
2202
2203
        $courseId = $courseInfo['real_id'];
2204
2205
        /*$itemInfo = api_get_item_property_info(
2206
            $courseId,
2207
            TOOL_LEARNPATH,
2208
            $lp_id,
2209
            $sessionId
2210
        );*/
2211
2212
        $visibility = $lp->isVisible($courseInfo['entity'], api_get_session_entity($sessionId));
2213
        // If the item was deleted.
2214
        if (false === $visibility) {
2215
            return false;
2216
        }
2217
2218
        $lp_id = $lp->getIid();
2219
        // @todo remove this query and load the row info as a parameter
2220
        $table = Database::get_course_table(TABLE_LP_MAIN);
2221
        // Get current prerequisite
2222
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2223
                FROM $table
2224
                WHERE iid = $lp_id";
2225
        $rs = Database::query($sql);
2226
        $now = time();
2227
        if (Database::num_rows($rs) > 0) {
2228
            $row = Database::fetch_array($rs, 'ASSOC');
2229
2230
            if (!empty($row['category_id'])) {
2231
                $em = Database::getManager();
2232
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2233
                if (false === self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id))) {
2234
                    return false;
2235
                }
2236
            }
2237
2238
            $prerequisite = $row['prerequisite'];
2239
            $is_visible = true;
2240
2241
            $isBlocked = self::isBlockedByPrerequisite(
2242
                $student_id,
2243
                $prerequisite,
2244
                $courseInfo,
2245
                $sessionId
2246
            );
2247
2248
            if ($isBlocked) {
2249
                $is_visible = false;
2250
            }
2251
2252
            // Also check the time availability of the LP
2253
            if ($is_visible) {
2254
                // Adding visibility restrictions
2255
                if (!empty($row['publicated_on'])) {
2256
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2257
                        $is_visible = false;
2258
                    }
2259
                }
2260
                // Blocking empty start times see BT#2800
2261
                global $_custom;
2262
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2263
                    $_custom['lps_hidden_when_no_start_date']
2264
                ) {
2265
                    if (empty($row['publicated_on'])) {
2266
                        $is_visible = false;
2267
                    }
2268
                }
2269
2270
                if (!empty($row['expired_on'])) {
2271
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2272
                        $is_visible = false;
2273
                    }
2274
                }
2275
            }
2276
2277
            if ($is_visible) {
2278
                $subscriptionSettings = self::getSubscriptionSettings();
2279
2280
                // Check if the subscription users/group to a LP is ON
2281
                if (isset($row['subscribe_users']) && 1 == $row['subscribe_users'] &&
2282
                    true === $subscriptionSettings['allow_add_users_to_lp']
2283
                ) {
2284
                    // Try group
2285
                    $is_visible = false;
2286
                    // Checking only the user visibility
2287
                    $userVisibility = api_get_item_visibility(
2288
                        $courseInfo,
2289
                        'learnpath',
2290
                        $row['id'],
2291
                        $sessionId,
2292
                        $student_id,
2293
                        'LearnpathSubscription'
2294
                    );
2295
2296
                    if (1 == $userVisibility) {
2297
                        $is_visible = true;
2298
                    } else {
2299
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2300
                        if (!empty($userGroups)) {
2301
                            foreach ($userGroups as $groupInfo) {
2302
                                $groupId = $groupInfo['iid'];
2303
                                $userVisibility = api_get_item_visibility(
2304
                                    $courseInfo,
2305
                                    'learnpath',
2306
                                    $row['id'],
2307
                                    $sessionId,
2308
                                    null,
2309
                                    'LearnpathSubscription',
2310
                                    $groupId
2311
                                );
2312
2313
                                if (1 == $userVisibility) {
2314
                                    $is_visible = true;
2315
                                    break;
2316
                                }
2317
                            }
2318
                        }
2319
                    }
2320
                }
2321
            }
2322
2323
            return $is_visible;
2324
        }
2325
2326
        return false;
2327
    }
2328
2329
    /**
2330
     * @param int $lpId
2331
     * @param int $userId
2332
     * @param int $courseId
2333
     * @param int $sessionId
2334
     *
2335
     * @return int
2336
     */
2337
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2338
    {
2339
        $lpId = (int) $lpId;
2340
        $userId = (int) $userId;
2341
        $courseId = (int) $courseId;
2342
        $sessionId = (int) $sessionId;
2343
2344
        $sessionCondition = api_get_session_condition($sessionId);
2345
        $table = Database::get_course_table(TABLE_LP_VIEW);
2346
        $sql = "SELECT progress FROM $table
2347
                WHERE
2348
                    c_id = $courseId AND
2349
                    lp_id = $lpId AND
2350
                    user_id = $userId $sessionCondition ";
2351
        $res = Database::query($sql);
2352
2353
        $progress = 0;
2354
        if (Database::num_rows($res) > 0) {
2355
            $row = Database::fetch_array($res);
2356
            $progress = (int) $row['progress'];
2357
        }
2358
2359
        return $progress;
2360
    }
2361
2362
    /**
2363
     * @param array $lpList
2364
     * @param int   $userId
2365
     * @param int   $courseId
2366
     * @param int   $sessionId
2367
     *
2368
     * @return array
2369
     */
2370
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2371
    {
2372
        $lpList = array_map('intval', $lpList);
2373
        if (empty($lpList)) {
2374
            return [];
2375
        }
2376
2377
        $lpList = implode("','", $lpList);
2378
2379
        $userId = (int) $userId;
2380
        $courseId = (int) $courseId;
2381
        $sessionId = (int) $sessionId;
2382
2383
        $sessionCondition = api_get_session_condition($sessionId);
2384
        $table = Database::get_course_table(TABLE_LP_VIEW);
2385
        $sql = "SELECT lp_id, progress FROM $table
2386
                WHERE
2387
                    c_id = $courseId AND
2388
                    lp_id IN ('".$lpList."') AND
2389
                    user_id = $userId $sessionCondition ";
2390
        $res = Database::query($sql);
2391
2392
        if (Database::num_rows($res) > 0) {
2393
            $list = [];
2394
            while ($row = Database::fetch_array($res)) {
2395
                $list[$row['lp_id']] = $row['progress'];
2396
            }
2397
2398
            return $list;
2399
        }
2400
2401
        return [];
2402
    }
2403
2404
    /**
2405
     * Displays a progress bar
2406
     * completed so far.
2407
     *
2408
     * @param int    $percentage Progress value to display
2409
     * @param string $text_add   Text to display near the progress value
2410
     *
2411
     * @return string HTML string containing the progress bar
2412
     */
2413
    public static function get_progress_bar($percentage = -1, $text_add = '')
2414
    {
2415
        $text = $percentage.$text_add;
2416
        $output = '<div class="progress">
2417
            <div id="progress_bar_value"
2418
                class="progress-bar progress-bar-success" role="progressbar"
2419
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2420
            '.$text.'
2421
            </div>
2422
        </div>';
2423
2424
        return $output;
2425
    }
2426
2427
    /**
2428
     * @param string $mode can be '%' or 'abs'
2429
     *                     otherwise this value will be used $this->progress_bar_mode
2430
     *
2431
     * @return string
2432
     */
2433
    public function getProgressBar($mode = null)
2434
    {
2435
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2436
2437
        return self::get_progress_bar($percentage, $text_add);
2438
    }
2439
2440
    /**
2441
     * Gets the progress bar info to display inside the progress bar.
2442
     * Also used by scorm_api.php.
2443
     *
2444
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2445
     *                     we display a number of completed elements per total elements
2446
     * @param int    $add  Additional steps to fake as completed
2447
     *
2448
     * @return array Percentage or number and symbol (% or /xx)
2449
     */
2450
    public function get_progress_bar_text($mode = '', $add = 0)
2451
    {
2452
        if (empty($mode)) {
2453
            $mode = $this->progress_bar_mode;
2454
        }
2455
        $total_items = $this->getTotalItemsCountWithoutDirs();
2456
        $completeItems = $this->get_complete_items_count();
2457
        if (0 != $add) {
2458
            $completeItems += $add;
2459
        }
2460
        $text = '';
2461
        if ($completeItems > $total_items) {
2462
            $completeItems = $total_items;
2463
        }
2464
        $percentage = 0;
2465
        if ('%' == $mode) {
2466
            if ($total_items > 0) {
2467
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2468
            }
2469
            $percentage = number_format($percentage, 0);
2470
            $text = '%';
2471
        } elseif ('abs' === $mode) {
2472
            $percentage = $completeItems;
2473
            $text = '/'.$total_items;
2474
        }
2475
2476
        return [
2477
            $percentage,
2478
            $text,
2479
        ];
2480
    }
2481
2482
    /**
2483
     * Gets the progress bar mode.
2484
     *
2485
     * @return string The progress bar mode attribute
2486
     */
2487
    public function get_progress_bar_mode()
2488
    {
2489
        if (!empty($this->progress_bar_mode)) {
2490
            return $this->progress_bar_mode;
2491
        }
2492
2493
        return '%';
2494
    }
2495
2496
    /**
2497
     * Gets the learnpath theme (remote or local).
2498
     *
2499
     * @return string Learnpath theme
2500
     */
2501
    public function get_theme()
2502
    {
2503
        if (!empty($this->theme)) {
2504
            return $this->theme;
2505
        }
2506
2507
        return '';
2508
    }
2509
2510
    /**
2511
     * Gets the learnpath session id.
2512
     *
2513
     * @return int
2514
     */
2515
    public function get_lp_session_id()
2516
    {
2517
        if (!empty($this->lp_session_id)) {
2518
            return (int) $this->lp_session_id;
2519
        }
2520
2521
        return 0;
2522
    }
2523
2524
    /**
2525
     * Gets the learnpath image.
2526
     *
2527
     * @return string Web URL of the LP image
2528
     */
2529
    public function get_preview_image()
2530
    {
2531
        if (!empty($this->preview_image)) {
2532
            return $this->preview_image;
2533
        }
2534
2535
        return '';
2536
    }
2537
2538
    /**
2539
     * @param string $size
2540
     * @param string $path_type
2541
     *
2542
     * @return bool|string
2543
     */
2544
    public function get_preview_image_path($size = null, $path_type = 'web')
2545
    {
2546
        $preview_image = $this->get_preview_image();
2547
        if (isset($preview_image) && !empty($preview_image)) {
2548
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2549
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2550
2551
            if (isset($size)) {
2552
                $info = pathinfo($preview_image);
2553
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2554
2555
                if (file_exists($image_sys_path.$image_custom_size)) {
2556
                    if ('web' == $path_type) {
2557
                        return $image_path.$image_custom_size;
2558
                    } else {
2559
                        return $image_sys_path.$image_custom_size;
2560
                    }
2561
                }
2562
            } else {
2563
                if ('web' == $path_type) {
2564
                    return $image_path.$preview_image;
2565
                } else {
2566
                    return $image_sys_path.$preview_image;
2567
                }
2568
            }
2569
        }
2570
2571
        return false;
2572
    }
2573
2574
    /**
2575
     * Gets the learnpath author.
2576
     *
2577
     * @return string LP's author
2578
     */
2579
    public function get_author()
2580
    {
2581
        if (!empty($this->author)) {
2582
            return $this->author;
2583
        }
2584
2585
        return '';
2586
    }
2587
2588
    /**
2589
     * Gets hide table of contents.
2590
     *
2591
     * @return int
2592
     */
2593
    public function getHideTableOfContents()
2594
    {
2595
        return (int) $this->hide_toc_frame;
2596
    }
2597
2598
    /**
2599
     * Generate a new prerequisites string for a given item. If this item was a sco and
2600
     * its prerequisites were strings (instead of IDs), then transform those strings into
2601
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2602
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2603
     * same rule as the scormExport() method.
2604
     *
2605
     * @param int $item_id Item ID
2606
     *
2607
     * @return string Prerequisites string ready for the export as SCORM
2608
     */
2609
    public function get_scorm_prereq_string($item_id)
2610
    {
2611
        if ($this->debug > 0) {
2612
            error_log('In learnpath::get_scorm_prereq_string()');
2613
        }
2614
        if (!is_object($this->items[$item_id])) {
2615
            return false;
2616
        }
2617
        /** @var learnpathItem $oItem */
2618
        $oItem = $this->items[$item_id];
2619
        $prereq = $oItem->get_prereq_string();
2620
2621
        if (empty($prereq)) {
2622
            return '';
2623
        }
2624
        if (preg_match('/^\d+$/', $prereq) &&
2625
            isset($this->items[$prereq]) &&
2626
            is_object($this->items[$prereq])
2627
        ) {
2628
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2629
            // then simply return it (with the ITEM_ prefix).
2630
            //return 'ITEM_' . $prereq;
2631
            return $this->items[$prereq]->ref;
2632
        } else {
2633
            if (isset($this->refs_list[$prereq])) {
2634
                // It's a simple string item from which the ID can be found in the refs list,
2635
                // so we can transform it directly to an ID for export.
2636
                return $this->items[$this->refs_list[$prereq]]->ref;
2637
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2638
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2639
            } else {
2640
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2641
                // and replace them, one by one, by the internal IDs (chamilo db)
2642
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2643
                // by a space as well.
2644
                $find = [
2645
                    '&',
2646
                    '|',
2647
                    '~',
2648
                    '=',
2649
                    '<>',
2650
                    '{',
2651
                    '}',
2652
                    '*',
2653
                    '(',
2654
                    ')',
2655
                ];
2656
                $replace = [
2657
                    ' ',
2658
                    ' ',
2659
                    ' ',
2660
                    ' ',
2661
                    ' ',
2662
                    ' ',
2663
                    ' ',
2664
                    ' ',
2665
                    ' ',
2666
                    ' ',
2667
                ];
2668
                $prereq_mod = str_replace($find, $replace, $prereq);
2669
                $ids = explode(' ', $prereq_mod);
2670
                foreach ($ids as $id) {
2671
                    $id = trim($id);
2672
                    if (isset($this->refs_list[$id])) {
2673
                        $prereq = preg_replace(
2674
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2675
                            'ITEM_'.$this->refs_list[$id],
2676
                            $prereq
2677
                        );
2678
                    }
2679
                }
2680
2681
                return $prereq;
2682
            }
2683
        }
2684
    }
2685
2686
    /**
2687
     * Returns the XML DOM document's node.
2688
     *
2689
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2690
     * @param string   $id       The identifier to look for
2691
     *
2692
     * @return mixed The reference to the element found with that identifier. False if not found
2693
     */
2694
    public function get_scorm_xml_node(&$children, $id)
2695
    {
2696
        for ($i = 0; $i < $children->length; $i++) {
2697
            $item_temp = $children->item($i);
2698
            if ('item' == $item_temp->nodeName) {
2699
                if ($item_temp->getAttribute('identifier') == $id) {
2700
                    return $item_temp;
2701
                }
2702
            }
2703
            $subchildren = $item_temp->childNodes;
2704
            if ($subchildren && $subchildren->length > 0) {
2705
                $val = $this->get_scorm_xml_node($subchildren, $id);
2706
                if (is_object($val)) {
2707
                    return $val;
2708
                }
2709
            }
2710
        }
2711
2712
        return false;
2713
    }
2714
2715
    /**
2716
     * Gets the status list for all LP's items.
2717
     *
2718
     * @return array Array of [index] => [item ID => current status]
2719
     */
2720
    public function get_items_status_list()
2721
    {
2722
        $list = [];
2723
        foreach ($this->ordered_items as $item_id) {
2724
            $list[] = [
2725
                $item_id => $this->items[$item_id]->get_status(),
2726
            ];
2727
        }
2728
2729
        return $list;
2730
    }
2731
2732
    /**
2733
     * Return the number of interactions for the given learnpath Item View ID.
2734
     * This method can be used as static.
2735
     *
2736
     * @param int $lp_iv_id  Item View ID
2737
     * @param int $course_id course id
2738
     *
2739
     * @return int
2740
     */
2741
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2742
    {
2743
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2744
        $lp_iv_id = (int) $lp_iv_id;
2745
        $course_id = (int) $course_id;
2746
2747
        $sql = "SELECT count(*) FROM $table
2748
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2749
        $res = Database::query($sql);
2750
        $num = 0;
2751
        if (Database::num_rows($res)) {
2752
            $row = Database::fetch_array($res);
2753
            $num = $row[0];
2754
        }
2755
2756
        return $num;
2757
    }
2758
2759
    /**
2760
     * Return the interactions as an array for the given lp_iv_id.
2761
     * This method can be used as static.
2762
     *
2763
     * @param int $lp_iv_id Learnpath Item View ID
2764
     *
2765
     * @return array
2766
     *
2767
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2768
     */
2769
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2770
    {
2771
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2772
        $list = [];
2773
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2774
        $lp_iv_id = (int) $lp_iv_id;
2775
2776
        if (empty($lp_iv_id) || empty($course_id)) {
2777
            return [];
2778
        }
2779
2780
        $sql = "SELECT * FROM $table
2781
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2782
                ORDER BY order_id ASC";
2783
        $res = Database::query($sql);
2784
        $num = Database::num_rows($res);
2785
        if ($num > 0) {
2786
            $list[] = [
2787
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2788
                'id' => api_htmlentities(get_lang('Interaction ID'), ENT_QUOTES),
2789
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2790
                'time' => api_htmlentities(get_lang('Time (finished at...)'), ENT_QUOTES),
2791
                'correct_responses' => api_htmlentities(get_lang('Correct answers'), ENT_QUOTES),
2792
                'student_response' => api_htmlentities(get_lang('Learner answers'), ENT_QUOTES),
2793
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2794
                'latency' => api_htmlentities(get_lang('Time spent'), ENT_QUOTES),
2795
                'student_response_formatted' => '',
2796
            ];
2797
            while ($row = Database::fetch_array($res)) {
2798
                $studentResponseFormatted = urldecode($row['student_response']);
2799
                $content_student_response = explode('__|', $studentResponseFormatted);
2800
                if (count($content_student_response) > 0) {
2801
                    if (count($content_student_response) >= 3) {
2802
                        // Pop the element off the end of array.
2803
                        array_pop($content_student_response);
2804
                    }
2805
                    $studentResponseFormatted = implode(',', $content_student_response);
2806
                }
2807
2808
                $list[] = [
2809
                    'order_id' => $row['order_id'] + 1,
2810
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2811
                    'type' => $row['interaction_type'],
2812
                    'time' => $row['completion_time'],
2813
                    'correct_responses' => '', // Hide correct responses from students.
2814
                    'student_response' => $row['student_response'],
2815
                    'result' => $row['result'],
2816
                    'latency' => $row['latency'],
2817
                    'student_response_formatted' => $studentResponseFormatted,
2818
                ];
2819
            }
2820
        }
2821
2822
        return $list;
2823
    }
2824
2825
    /**
2826
     * Return the number of objectives for the given learnpath Item View ID.
2827
     * This method can be used as static.
2828
     *
2829
     * @param int $lp_iv_id  Item View ID
2830
     * @param int $course_id Course ID
2831
     *
2832
     * @return int Number of objectives
2833
     */
2834
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2835
    {
2836
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2837
        $course_id = (int) $course_id;
2838
        $lp_iv_id = (int) $lp_iv_id;
2839
        $sql = "SELECT count(*) FROM $table
2840
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2841
        //@todo seems that this always returns 0
2842
        $res = Database::query($sql);
2843
        $num = 0;
2844
        if (Database::num_rows($res)) {
2845
            $row = Database::fetch_array($res);
2846
            $num = $row[0];
2847
        }
2848
2849
        return $num;
2850
    }
2851
2852
    /**
2853
     * Return the objectives as an array for the given lp_iv_id.
2854
     * This method can be used as static.
2855
     *
2856
     * @param int $lpItemViewId Learnpath Item View ID
2857
     * @param int $course_id
2858
     *
2859
     * @return array
2860
     *
2861
     * @todo    Translate labels
2862
     */
2863
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2864
    {
2865
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2866
        $lpItemViewId = (int) $lpItemViewId;
2867
2868
        if (empty($course_id) || empty($lpItemViewId)) {
2869
            return [];
2870
        }
2871
2872
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2873
        $sql = "SELECT * FROM $table
2874
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2875
                ORDER BY order_id ASC";
2876
        $res = Database::query($sql);
2877
        $num = Database::num_rows($res);
2878
        $list = [];
2879
        if ($num > 0) {
2880
            $list[] = [
2881
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2882
                'objective_id' => api_htmlentities(get_lang('Objective ID'), ENT_QUOTES),
2883
                'score_raw' => api_htmlentities(get_lang('Objective raw score'), ENT_QUOTES),
2884
                'score_max' => api_htmlentities(get_lang('Objective max score'), ENT_QUOTES),
2885
                'score_min' => api_htmlentities(get_lang('Objective min score'), ENT_QUOTES),
2886
                'status' => api_htmlentities(get_lang('Objective status'), ENT_QUOTES),
2887
            ];
2888
            while ($row = Database::fetch_array($res)) {
2889
                $list[] = [
2890
                    'order_id' => $row['order_id'] + 1,
2891
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2892
                    'score_raw' => $row['score_raw'],
2893
                    'score_max' => $row['score_max'],
2894
                    'score_min' => $row['score_min'],
2895
                    'status' => $row['status'],
2896
                ];
2897
            }
2898
        }
2899
2900
        return $list;
2901
    }
2902
2903
    /**
2904
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2905
     * used by get_html_toc() to be ready to display.
2906
     *
2907
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2908
     */
2909
    public function get_toc()
2910
    {
2911
        $toc = [];
2912
        foreach ($this->ordered_items as $item_id) {
2913
            // TODO: Change this link generation and use new function instead.
2914
            $toc[] = [
2915
                'id' => $item_id,
2916
                'title' => $this->items[$item_id]->get_title(),
2917
                'status' => $this->items[$item_id]->get_status(),
2918
                'level' => $this->items[$item_id]->get_level(),
2919
                'type' => $this->items[$item_id]->get_type(),
2920
                'description' => $this->items[$item_id]->get_description(),
2921
                'path' => $this->items[$item_id]->get_path(),
2922
                'parent' => $this->items[$item_id]->get_parent(),
2923
            ];
2924
        }
2925
2926
        return $toc;
2927
    }
2928
2929
    /**
2930
     * Generate and return the table of contents for this learnpath. The JS
2931
     * table returned is used inside of scorm_api.php.
2932
     *
2933
     * @param string $varname
2934
     *
2935
     * @return string A JS array variable construction
2936
     */
2937
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2938
    {
2939
        $toc = $varname.' = new Array();';
2940
        foreach ($this->ordered_items as $item_id) {
2941
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2942
        }
2943
2944
        return $toc;
2945
    }
2946
2947
    /**
2948
     * Gets the learning path type.
2949
     *
2950
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2951
     *
2952
     * @return mixed Type ID or name, depending on the parameter
2953
     */
2954
    public function get_type($get_name = false)
2955
    {
2956
        $res = false;
2957
        if (!empty($this->type) && (!$get_name)) {
2958
            $res = $this->type;
2959
        }
2960
2961
        return $res;
2962
    }
2963
2964
    /**
2965
     * Gets the learning path type as static method.
2966
     *
2967
     * @param int $lp_id
2968
     *
2969
     * @return mixed Type ID or name, depending on the parameter
2970
     */
2971
    public static function get_type_static($lp_id = 0)
2972
    {
2973
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2974
        $lp_id = (int) $lp_id;
2975
        $sql = "SELECT lp_type FROM $tbl_lp
2976
                WHERE iid = $lp_id";
2977
        $res = Database::query($sql);
2978
        if (false === $res) {
2979
            return null;
2980
        }
2981
        if (Database::num_rows($res) <= 0) {
2982
            return null;
2983
        }
2984
        $row = Database::fetch_array($res);
2985
2986
        return $row['lp_type'];
2987
    }
2988
2989
    /**
2990
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2991
     * This method can be used as abstract and is recursive.
2992
     *
2993
     * @param int $lp        Learnpath ID
2994
     * @param int $parent    Parent ID of the items to look for
2995
     * @param int $course_id
2996
     *
2997
     * @return array Ordered list of item IDs (empty array on error)
2998
     */
2999
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3000
    {
3001
        if (empty($course_id)) {
3002
            $course_id = api_get_course_int_id();
3003
        } else {
3004
            $course_id = (int) $course_id;
3005
        }
3006
        $list = [];
3007
3008
        if (empty($lp)) {
3009
            return $list;
3010
        }
3011
3012
        $lp = (int) $lp;
3013
        $parent = (int) $parent;
3014
3015
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3016
        $sql = "SELECT iid FROM $tbl_lp_item
3017
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3018
                ORDER BY display_order";
3019
3020
        $res = Database::query($sql);
3021
        while ($row = Database::fetch_array($res)) {
3022
            $sublist = self::get_flat_ordered_items_list(
3023
                $lp,
3024
                $row['iid'],
3025
                $course_id
3026
            );
3027
            $list[] = $row['iid'];
3028
            foreach ($sublist as $item) {
3029
                $list[] = $item;
3030
            }
3031
        }
3032
3033
        return $list;
3034
    }
3035
3036
    /**
3037
     * @return array
3038
     */
3039
    public static function getChapterTypes()
3040
    {
3041
        return [
3042
            'dir',
3043
        ];
3044
    }
3045
3046
    /**
3047
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3048
     *
3049
     * @param $tree
3050
     *
3051
     * @return array HTML TOC ready to display
3052
     */
3053
    public function getParentToc($tree)
3054
    {
3055
        if (empty($tree)) {
3056
            $tree = $this->get_toc();
3057
        }
3058
        $dirTypes = self::getChapterTypes();
3059
        $myCurrentId = $this->get_current_item_id();
3060
        $listParent = [];
3061
        $listChildren = [];
3062
        $listNotParent = [];
3063
        $list = [];
3064
        foreach ($tree as $subtree) {
3065
            if (in_array($subtree['type'], $dirTypes)) {
3066
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3067
                $subtree['children'] = $listChildren;
3068
                if (!empty($subtree['children'])) {
3069
                    foreach ($subtree['children'] as $subItem) {
3070
                        if ($subItem['id'] == $this->current) {
3071
                            $subtree['parent_current'] = 'in';
3072
                            $subtree['current'] = 'on';
3073
                        }
3074
                    }
3075
                }
3076
                $listParent[] = $subtree;
3077
            }
3078
            if (!in_array($subtree['type'], $dirTypes) && null == $subtree['parent']) {
3079
                $classStatus = [
3080
                    'not attempted' => 'scorm_not_attempted',
3081
                    'incomplete' => 'scorm_not_attempted',
3082
                    'failed' => 'scorm_failed',
3083
                    'completed' => 'scorm_completed',
3084
                    'passed' => 'scorm_completed',
3085
                    'succeeded' => 'scorm_completed',
3086
                    'browsed' => 'scorm_completed',
3087
                ];
3088
3089
                if (isset($classStatus[$subtree['status']])) {
3090
                    $cssStatus = $classStatus[$subtree['status']];
3091
                }
3092
3093
                $title = Security::remove_XSS($subtree['title']);
3094
                unset($subtree['title']);
3095
3096
                if (empty($title)) {
3097
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3098
                }
3099
                $classStyle = null;
3100
                if ($subtree['id'] == $this->current) {
3101
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3102
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3103
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3104
                }
3105
                $subtree['title'] = $title;
3106
                $subtree['class'] = $classStyle.' '.$cssStatus;
3107
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3108
                $subtree['current_id'] = $myCurrentId;
3109
                $listNotParent[] = $subtree;
3110
            }
3111
        }
3112
3113
        $list['are_parents'] = $listParent;
3114
        $list['not_parents'] = $listNotParent;
3115
3116
        return $list;
3117
    }
3118
3119
    /**
3120
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3121
     *
3122
     * @param array $tree
3123
     * @param int   $id
3124
     * @param bool  $parent
3125
     *
3126
     * @return array HTML TOC ready to display
3127
     */
3128
    public function getChildrenToc($tree, $id, $parent = true)
3129
    {
3130
        if (empty($tree)) {
3131
            $tree = $this->get_toc();
3132
        }
3133
3134
        $dirTypes = self::getChapterTypes();
3135
        $currentItemId = $this->get_current_item_id();
3136
        $list = [];
3137
        $classStatus = [
3138
            'not attempted' => 'scorm_not_attempted',
3139
            'incomplete' => 'scorm_not_attempted',
3140
            'failed' => 'scorm_failed',
3141
            'completed' => 'scorm_completed',
3142
            'passed' => 'scorm_completed',
3143
            'succeeded' => 'scorm_completed',
3144
            'browsed' => 'scorm_completed',
3145
        ];
3146
3147
        foreach ($tree as $subtree) {
3148
            $subtree['tree'] = null;
3149
3150
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3151
                if ($subtree['id'] == $this->current) {
3152
                    $subtree['current'] = 'active';
3153
                } else {
3154
                    $subtree['current'] = null;
3155
                }
3156
                if (isset($classStatus[$subtree['status']])) {
3157
                    $cssStatus = $classStatus[$subtree['status']];
3158
                }
3159
3160
                $title = Security::remove_XSS($subtree['title']);
3161
                unset($subtree['title']);
3162
                if (empty($title)) {
3163
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3164
                }
3165
3166
                $classStyle = null;
3167
                if ($subtree['id'] == $this->current) {
3168
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3169
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3170
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3171
                }
3172
3173
                if (in_array($subtree['type'], $dirTypes)) {
3174
                    $subtree['title'] = stripslashes($title);
3175
                } else {
3176
                    $subtree['title'] = $title;
3177
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3178
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3179
                    $subtree['current_id'] = $currentItemId;
3180
                }
3181
                $list[] = $subtree;
3182
            }
3183
        }
3184
3185
        return $list;
3186
    }
3187
3188
    /**
3189
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3190
     *
3191
     * @param array $toc_list
3192
     *
3193
     * @return array HTML TOC ready to display
3194
     */
3195
    public function getListArrayToc($toc_list = [])
3196
    {
3197
        if (empty($toc_list)) {
3198
            $toc_list = $this->get_toc();
3199
        }
3200
        // Temporary variables.
3201
        $currentItemId = $this->get_current_item_id();
3202
        $list = [];
3203
        $arrayList = [];
3204
        $classStatus = [
3205
            'not attempted' => 'scorm_not_attempted',
3206
            'incomplete' => 'scorm_not_attempted',
3207
            'failed' => 'scorm_failed',
3208
            'completed' => 'scorm_completed',
3209
            'passed' => 'scorm_completed',
3210
            'succeeded' => 'scorm_completed',
3211
            'browsed' => 'scorm_completed',
3212
        ];
3213
3214
        foreach ($toc_list as $item) {
3215
            $list['id'] = $item['id'];
3216
            $list['status'] = $item['status'];
3217
            $cssStatus = null;
3218
3219
            if (isset($classStatus[$item['status']])) {
3220
                $cssStatus = $classStatus[$item['status']];
3221
            }
3222
3223
            $classStyle = ' ';
3224
            $dirTypes = self::getChapterTypes();
3225
3226
            if (in_array($item['type'], $dirTypes)) {
3227
                $classStyle = 'scorm_item_section ';
3228
            }
3229
            if ($item['id'] == $this->current) {
3230
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3231
            } elseif (!in_array($item['type'], $dirTypes)) {
3232
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3233
            }
3234
            $title = $item['title'];
3235
            if (empty($title)) {
3236
                $title = self::rl_get_resource_name(
3237
                    api_get_course_id(),
3238
                    $this->get_id(),
3239
                    $item['id']
3240
                );
3241
            }
3242
            $title = Security::remove_XSS($item['title']);
3243
3244
            if (empty($item['description'])) {
3245
                $list['description'] = $title;
3246
            } else {
3247
                $list['description'] = $item['description'];
3248
            }
3249
3250
            $list['class'] = $classStyle.' '.$cssStatus;
3251
            $list['level'] = $item['level'];
3252
            $list['type'] = $item['type'];
3253
3254
            if (in_array($item['type'], $dirTypes)) {
3255
                $list['css_level'] = 'level_'.$item['level'];
3256
            } else {
3257
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3258
            }
3259
3260
            if (in_array($item['type'], $dirTypes)) {
3261
                $list['title'] = stripslashes($title);
3262
            } else {
3263
                $list['title'] = stripslashes($title);
3264
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3265
                $list['current_id'] = $currentItemId;
3266
            }
3267
            $arrayList[] = $list;
3268
        }
3269
3270
        return $arrayList;
3271
    }
3272
3273
    /**
3274
     * Returns an HTML-formatted string ready to display with teacher buttons
3275
     * in LP view menu.
3276
     *
3277
     * @return string HTML TOC ready to display
3278
     */
3279
    public function get_teacher_toc_buttons()
3280
    {
3281
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3282
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3283
        $html = '';
3284
        if ($isAllow && false == $hideIcons) {
3285
            if ($this->get_lp_session_id() == api_get_session_id()) {
3286
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3287
                $html .= '<div class="btn-group">';
3288
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3289
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3290
                $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'>".
3291
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3292
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3293
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3294
                $html .= '</div>';
3295
                $html .= '</div>';
3296
            }
3297
        }
3298
3299
        return $html;
3300
    }
3301
3302
    /**
3303
     * Gets the learnpath maker name - generally the editor's name.
3304
     *
3305
     * @return string Learnpath maker name
3306
     */
3307
    public function get_maker()
3308
    {
3309
        if (!empty($this->maker)) {
3310
            return $this->maker;
3311
        }
3312
3313
        return '';
3314
    }
3315
3316
    /**
3317
     * Gets the learnpath name/title.
3318
     *
3319
     * @return string Learnpath name/title
3320
     */
3321
    public function get_name()
3322
    {
3323
        if (!empty($this->name)) {
3324
            return $this->name;
3325
        }
3326
3327
        return 'N/A';
3328
    }
3329
3330
    /**
3331
     * @return string
3332
     */
3333
    public function getNameNoTags()
3334
    {
3335
        return strip_tags($this->get_name());
3336
    }
3337
3338
    /**
3339
     * Gets a link to the resource from the present location, depending on item ID.
3340
     *
3341
     * @param string $type         Type of link expected
3342
     * @param int    $item_id      Learnpath item ID
3343
     * @param bool   $provided_toc
3344
     *
3345
     * @return string $provided_toc Link to the lp_item resource
3346
     */
3347
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3348
    {
3349
        $course_id = $this->get_course_int_id();
3350
        $item_id = (int) $item_id;
3351
3352
        if (empty($item_id)) {
3353
            $item_id = $this->get_current_item_id();
3354
3355
            if (empty($item_id)) {
3356
                //still empty, this means there was no item_id given and we are not in an object context or
3357
                //the object property is empty, return empty link
3358
                $this->first();
3359
3360
                return '';
3361
            }
3362
        }
3363
3364
        $file = '';
3365
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3366
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3367
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3368
3369
        $sql = "SELECT
3370
                    l.lp_type as ltype,
3371
                    l.path as lpath,
3372
                    li.item_type as litype,
3373
                    li.path as lipath,
3374
                    li.parameters as liparams
3375
        		FROM $lp_table l
3376
                INNER JOIN $lp_item_table li
3377
                ON (li.lp_id = l.iid)
3378
        		WHERE
3379
        		    li.iid = $item_id
3380
        		";
3381
        $res = Database::query($sql);
3382
        if (Database::num_rows($res) > 0) {
3383
            $row = Database::fetch_array($res);
3384
            $lp_type = $row['ltype'];
3385
            $lp_path = $row['lpath'];
3386
            $lp_item_type = $row['litype'];
3387
            $lp_item_path = $row['lipath'];
3388
            $lp_item_params = $row['liparams'];
3389
3390
            if (empty($lp_item_params) && false !== strpos($lp_item_path, '?')) {
3391
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3392
            }
3393
            //$sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3394
            if ('http' === $type) {
3395
                //web path
3396
                //$course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3397
            } else {
3398
                //$course_path = $sys_course_path; //system path
3399
            }
3400
3401
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3402
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3403
            if (in_array(
3404
                $lp_item_type,
3405
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3406
            )
3407
            ) {
3408
                $lp_type = 1;
3409
            }
3410
3411
            // Now go through the specific cases to get the end of the path
3412
            // @todo Use constants instead of int values.
3413
3414
            if ($this->debug > 2) {
3415
                error_log('In learnpath::get_link() '.__LINE__.' - $lp_item_type: '.$lp_item_type, 0);
3416
                error_log('In learnpath::get_link() '.__LINE__.' - $lp_type: '.$lp_type, 0);
3417
            }
3418
3419
            switch ($lp_type) {
3420
                case 1:
3421
                    $file = self::rl_get_resource_link_for_learnpath(
3422
                        $course_id,
3423
                        $this->get_id(),
3424
                        $item_id,
3425
                        $this->get_view_id()
3426
                    );
3427
                    switch ($lp_item_type) {
3428
                        case 'document':
3429
                            // Shows a button to download the file instead of just downloading the file directly.
3430
                            $documentPathInfo = pathinfo($file);
3431
                            if (isset($documentPathInfo['extension'])) {
3432
                                $parsed = parse_url($documentPathInfo['extension']);
3433
                                if (isset($parsed['path'])) {
3434
                                    $extension = $parsed['path'];
3435
                                    $extensionsToDownload = [
3436
                                        'zip',
3437
                                        'ppt',
3438
                                        'pptx',
3439
                                        'ods',
3440
                                        'xlsx',
3441
                                        'xls',
3442
                                        'csv',
3443
                                        'doc',
3444
                                        'docx',
3445
                                        'dot',
3446
                                    ];
3447
3448
                                    if (in_array($extension, $extensionsToDownload)) {
3449
                                        $file = api_get_path(WEB_CODE_PATH).
3450
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3451
                                    }
3452
                                }
3453
                            }
3454
                            break;
3455
                        case 'dir':
3456
                            $file = 'lp_content.php?type=dir';
3457
                            break;
3458
                        case 'link':
3459
                            if (Link::is_youtube_link($file)) {
3460
                                $src = Link::get_youtube_video_id($file);
3461
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3462
                            } elseif (Link::isVimeoLink($file)) {
3463
                                $src = Link::getVimeoLinkId($file);
3464
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3465
                            } else {
3466
                                // If the current site is HTTPS and the link is
3467
                                // HTTP, browsers will refuse opening the link
3468
                                $urlId = api_get_current_access_url_id();
3469
                                $url = api_get_access_url($urlId, false);
3470
                                $protocol = substr($url['url'], 0, 5);
3471
                                if ('https' === $protocol) {
3472
                                    $linkProtocol = substr($file, 0, 5);
3473
                                    if ('http:' === $linkProtocol) {
3474
                                        //this is the special intervention case
3475
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3476
                                    }
3477
                                }
3478
                            }
3479
                            break;
3480
                        case 'quiz':
3481
                            // Check how much attempts of a exercise exits in lp
3482
                            $lp_item_id = $this->get_current_item_id();
3483
                            $lp_view_id = $this->get_view_id();
3484
3485
                            $prevent_reinit = null;
3486
                            if (isset($this->items[$this->current])) {
3487
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3488
                            }
3489
3490
                            if (empty($provided_toc)) {
3491
                                $list = $this->get_toc();
3492
                            } else {
3493
                                $list = $provided_toc;
3494
                            }
3495
3496
                            $type_quiz = false;
3497
                            foreach ($list as $toc) {
3498
                                if ($toc['id'] == $lp_item_id && 'quiz' == $toc['type']) {
3499
                                    $type_quiz = true;
3500
                                }
3501
                            }
3502
3503
                            if ($type_quiz) {
3504
                                $lp_item_id = (int) $lp_item_id;
3505
                                $lp_view_id = (int) $lp_view_id;
3506
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3507
                                        WHERE
3508
                                            c_id = $course_id AND
3509
                                            lp_item_id='".$lp_item_id."' AND
3510
                                            lp_view_id ='".$lp_view_id."' AND
3511
                                            status='completed'";
3512
                                $result = Database::query($sql);
3513
                                $row_count = Database:: fetch_row($result);
3514
                                $count_item_view = (int) $row_count[0];
3515
                                $not_multiple_attempt = 0;
3516
                                if (1 === $prevent_reinit && $count_item_view > 0) {
3517
                                    $not_multiple_attempt = 1;
3518
                                }
3519
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3520
                            }
3521
                            break;
3522
                    }
3523
3524
                    $tmp_array = explode('/', $file);
3525
                    $document_name = $tmp_array[count($tmp_array) - 1];
3526
                    if (strpos($document_name, '_DELETED_')) {
3527
                        $file = 'blank.php?error=document_deleted';
3528
                    }
3529
                    break;
3530
                case 2:
3531
                    if ('dir' !== $lp_item_type) {
3532
                        // Quite complex here:
3533
                        // We want to make sure 'http://' (and similar) links can
3534
                        // be loaded as is (withouth the Chamilo path in front) but
3535
                        // some contents use this form: resource.htm?resource=http://blablabla
3536
                        // which means we have to find a protocol at the path's start, otherwise
3537
                        // it should not be considered as an external URL.
3538
                        // if ($this->prerequisites_match($item_id)) {
3539
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3540
                            if ($this->debug > 2) {
3541
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3542
                            }
3543
                            // Distant url, return as is.
3544
                            $file = $lp_item_path;
3545
                        } else {
3546
                            if ($this->debug > 2) {
3547
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3548
                            }
3549
                            // Prevent getting untranslatable urls.
3550
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3551
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3552
                            // Prepare the path.
3553
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3554
                            // TODO: Fix this for urls with protocol header.
3555
                            $file = str_replace('//', '/', $file);
3556
                            $file = str_replace(':/', '://', $file);
3557
                            if ('/' == substr($lp_path, -1)) {
3558
                                $lp_path = substr($lp_path, 0, -1);
3559
                            }
3560
3561
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3562
                                // if file not found.
3563
                                $decoded = html_entity_decode($lp_item_path);
3564
                                list($decoded) = explode('?', $decoded);
3565
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3566
                                    $file = self::rl_get_resource_link_for_learnpath(
3567
                                        $course_id,
3568
                                        $this->get_id(),
3569
                                        $item_id,
3570
                                        $this->get_view_id()
3571
                                    );
3572
                                    if (empty($file)) {
3573
                                        $file = 'blank.php?error=document_not_found';
3574
                                    } else {
3575
                                        $tmp_array = explode('/', $file);
3576
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3577
                                        if (strpos($document_name, '_DELETED_')) {
3578
                                            $file = 'blank.php?error=document_deleted';
3579
                                        } else {
3580
                                            $file = 'blank.php?error=document_not_found';
3581
                                        }
3582
                                    }
3583
                                } else {
3584
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3585
                                }
3586
                            }
3587
                        }
3588
3589
                        // We want to use parameters if they were defined in the imsmanifest
3590
                        if (false === strpos($file, 'blank.php')) {
3591
                            $lp_item_params = ltrim($lp_item_params, '?');
3592
                            $file .= (false === strstr($file, '?') ? '?' : '').$lp_item_params;
3593
                        }
3594
                    } else {
3595
                        $file = 'lp_content.php?type=dir';
3596
                    }
3597
                    break;
3598
                case 3:
3599
                    // Formatting AICC HACP append URL.
3600
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3601
                    if (!empty($lp_item_params)) {
3602
                        $aicc_append .= $lp_item_params.'&';
3603
                    }
3604
                    if ('dir' !== $lp_item_type) {
3605
                        // Quite complex here:
3606
                        // We want to make sure 'http://' (and similar) links can
3607
                        // be loaded as is (withouth the Chamilo path in front) but
3608
                        // some contents use this form: resource.htm?resource=http://blablabla
3609
                        // which means we have to find a protocol at the path's start, otherwise
3610
                        // it should not be considered as an external URL.
3611
                        if (0 != preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path)) {
3612
                            if ($this->debug > 2) {
3613
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3614
                            }
3615
                            // Distant url, return as is.
3616
                            $file = $lp_item_path;
3617
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3618
                            /*
3619
                            if (stristr($file,'<servername>') !== false) {
3620
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3621
                            }
3622
                            */
3623
                            if (false !== stripos($file, '<servername>')) {
3624
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3625
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3626
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3627
                            }
3628
3629
                            $file .= $aicc_append;
3630
                        } else {
3631
                            if ($this->debug > 2) {
3632
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3633
                            }
3634
                            // Prevent getting untranslatable urls.
3635
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3636
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3637
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3638
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3639
                            // TODO: Fix this for urls with protocol header.
3640
                            $file = str_replace('//', '/', $file);
3641
                            $file = str_replace(':/', '://', $file);
3642
                            $file .= $aicc_append;
3643
                        }
3644
                    } else {
3645
                        $file = 'lp_content.php?type=dir';
3646
                    }
3647
                    break;
3648
                case 4:
3649
                    break;
3650
                default:
3651
                    break;
3652
            }
3653
            // Replace &amp; by & because &amp; will break URL with params
3654
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3655
        }
3656
        if ($this->debug > 2) {
3657
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3658
        }
3659
3660
        return $file;
3661
    }
3662
3663
    /**
3664
     * Gets the latest usable view or generate a new one.
3665
     *
3666
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3667
     *
3668
     * @return int DB lp_view id
3669
     */
3670
    public function get_view($attempt_num = 0)
3671
    {
3672
        $search = '';
3673
        // Use $attempt_num to enable multi-views management (disabled so far).
3674
        if (0 != $attempt_num && intval(strval($attempt_num)) == $attempt_num) {
3675
            $search = 'AND view_count = '.$attempt_num;
3676
        }
3677
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3678
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3679
3680
        $course_id = api_get_course_int_id();
3681
        $sessionId = api_get_session_id();
3682
3683
        $sql = "SELECT iid, view_count FROM $lp_view_table
3684
        		WHERE
3685
        		    c_id = $course_id AND
3686
        		    lp_id = ".$this->get_id()." AND
3687
        		    user_id = ".$this->get_user_id()." AND
3688
        		    session_id = $sessionId
3689
        		    $search
3690
                ORDER BY view_count DESC";
3691
        $res = Database::query($sql);
3692
        if (Database::num_rows($res) > 0) {
3693
            $row = Database::fetch_array($res);
3694
            $this->lp_view_id = $row['iid'];
3695
        } elseif (!api_is_invitee()) {
3696
            // There is no database record, create one.
3697
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3698
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3699
            Database::query($sql);
3700
            $id = Database::insert_id();
3701
            $this->lp_view_id = $id;
3702
3703
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3704
            Database::query($sql);
3705
        }
3706
3707
        return $this->lp_view_id;
3708
    }
3709
3710
    /**
3711
     * Gets the current view id.
3712
     *
3713
     * @return int View ID (from lp_view)
3714
     */
3715
    public function get_view_id()
3716
    {
3717
        if (!empty($this->lp_view_id)) {
3718
            return (int) $this->lp_view_id;
3719
        }
3720
3721
        return 0;
3722
    }
3723
3724
    /**
3725
     * Gets the update queue.
3726
     *
3727
     * @return array Array containing IDs of items to be updated by JavaScript
3728
     */
3729
    public function get_update_queue()
3730
    {
3731
        return $this->update_queue;
3732
    }
3733
3734
    /**
3735
     * Gets the user ID.
3736
     *
3737
     * @return int User ID
3738
     */
3739
    public function get_user_id()
3740
    {
3741
        if (!empty($this->user_id)) {
3742
            return (int) $this->user_id;
3743
        }
3744
3745
        return false;
3746
    }
3747
3748
    /**
3749
     * Checks if any of the items has an audio element attached.
3750
     *
3751
     * @return bool True or false
3752
     */
3753
    public function has_audio()
3754
    {
3755
        $has = false;
3756
        foreach ($this->items as $i => $item) {
3757
            if (!empty($this->items[$i]->audio)) {
3758
                $has = true;
3759
                break;
3760
            }
3761
        }
3762
3763
        return $has;
3764
    }
3765
3766
    /**
3767
     * Moves an item up and down at its level.
3768
     *
3769
     * @param int    $id        Item to move up and down
3770
     * @param string $direction Direction 'up' or 'down'
3771
     *
3772
     * @return bool|int
3773
     */
3774
    public function move_item($id, $direction)
3775
    {
3776
        $course_id = api_get_course_int_id();
3777
        if (empty($id) || empty($direction)) {
3778
            return false;
3779
        }
3780
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3781
        $sql_sel = "SELECT *
3782
                    FROM $tbl_lp_item
3783
                    WHERE
3784
                        iid = $id
3785
                    ";
3786
        $res_sel = Database::query($sql_sel);
3787
        // Check if elem exists.
3788
        if (Database::num_rows($res_sel) < 1) {
3789
            return false;
3790
        }
3791
        // Gather data.
3792
        $row = Database::fetch_array($res_sel);
3793
        $previous = $row['previous_item_id'];
3794
        $next = $row['next_item_id'];
3795
        $display = $row['display_order'];
3796
        $parent = $row['parent_item_id'];
3797
        $lp = $row['lp_id'];
3798
        // Update the item (switch with previous/next one).
3799
        switch ($direction) {
3800
            case 'up':
3801
                if ($display > 1) {
3802
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3803
                                 WHERE iid = $previous";
3804
                    $res_sel2 = Database::query($sql_sel2);
3805
                    if (Database::num_rows($res_sel2) < 1) {
3806
                        $previous_previous = 0;
3807
                    }
3808
                    // Gather data.
3809
                    $row2 = Database::fetch_array($res_sel2);
3810
                    $previous_previous = $row2['previous_item_id'];
3811
                    // Update previous_previous item (switch "next" with current).
3812
                    if (0 != $previous_previous) {
3813
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3814
                                        next_item_id = $id
3815
                                    WHERE iid = $previous_previous";
3816
                        Database::query($sql_upd2);
3817
                    }
3818
                    // Update previous item (switch with current).
3819
                    if (0 != $previous) {
3820
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3821
                                    next_item_id = $next,
3822
                                    previous_item_id = $id,
3823
                                    display_order = display_order +1
3824
                                    WHERE iid = $previous";
3825
                        Database::query($sql_upd2);
3826
                    }
3827
3828
                    // Update current item (switch with previous).
3829
                    if (0 != $id) {
3830
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3831
                                        next_item_id = $previous,
3832
                                        previous_item_id = $previous_previous,
3833
                                        display_order = display_order-1
3834
                                    WHERE c_id = ".$course_id." AND id = $id";
3835
                        Database::query($sql_upd2);
3836
                    }
3837
                    // Update next item (new previous item).
3838
                    if (!empty($next)) {
3839
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3840
                                     WHERE iid = $next";
3841
                        Database::query($sql_upd2);
3842
                    }
3843
                    $display = $display - 1;
3844
                }
3845
                break;
3846
            case 'down':
3847
                if (0 != $next) {
3848
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3849
                                 WHERE iid = $next";
3850
                    $res_sel2 = Database::query($sql_sel2);
3851
                    if (Database::num_rows($res_sel2) < 1) {
3852
                        $next_next = 0;
3853
                    }
3854
                    // Gather data.
3855
                    $row2 = Database::fetch_array($res_sel2);
3856
                    $next_next = $row2['next_item_id'];
3857
                    // Update previous item (switch with current).
3858
                    if (0 != $previous) {
3859
                        $sql_upd2 = "UPDATE $tbl_lp_item
3860
                                     SET next_item_id = $next
3861
                                     WHERE iid = $previous";
3862
                        Database::query($sql_upd2);
3863
                    }
3864
                    // Update current item (switch with previous).
3865
                    if (0 != $id) {
3866
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3867
                                     previous_item_id = $next,
3868
                                     next_item_id = $next_next,
3869
                                     display_order = display_order + 1
3870
                                     WHERE iid = $id";
3871
                        Database::query($sql_upd2);
3872
                    }
3873
3874
                    // Update next item (new previous item).
3875
                    if (0 != $next) {
3876
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3877
                                     previous_item_id = $previous,
3878
                                     next_item_id = $id,
3879
                                     display_order = display_order-1
3880
                                     WHERE iid = $next";
3881
                        Database::query($sql_upd2);
3882
                    }
3883
3884
                    // Update next_next item (switch "previous" with current).
3885
                    if (0 != $next_next) {
3886
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3887
                                     previous_item_id = $id
3888
                                     WHERE iid = $next_next";
3889
                        Database::query($sql_upd2);
3890
                    }
3891
                    $display = $display + 1;
3892
                }
3893
                break;
3894
            default:
3895
                return false;
3896
        }
3897
3898
        return $display;
3899
    }
3900
3901
    /**
3902
     * Move a LP up (display_order).
3903
     *
3904
     * @param int $lp_id      Learnpath ID
3905
     * @param int $categoryId Category ID
3906
     *
3907
     * @return bool
3908
     */
3909
    public static function move_up($lp_id, $categoryId = 0)
3910
    {
3911
        $courseId = api_get_course_int_id();
3912
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3913
3914
        $categoryCondition = '';
3915
        if (!empty($categoryId)) {
3916
            $categoryId = (int) $categoryId;
3917
            $categoryCondition = " AND category_id = $categoryId";
3918
        }
3919
        $sql = "SELECT * FROM $lp_table
3920
                WHERE c_id = $courseId
3921
                $categoryCondition
3922
                ORDER BY display_order";
3923
        $res = Database::query($sql);
3924
        if (false === $res) {
3925
            return false;
3926
        }
3927
3928
        $lps = [];
3929
        $lp_order = [];
3930
        $num = Database::num_rows($res);
3931
        // First check the order is correct, globally (might be wrong because
3932
        // of versions < 1.8.4)
3933
        if ($num > 0) {
3934
            $i = 1;
3935
            while ($row = Database::fetch_array($res)) {
3936
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3937
                    $sql = "UPDATE $lp_table SET display_order = $i
3938
                            WHERE iid = ".$row['iid'];
3939
                    Database::query($sql);
3940
                }
3941
                $row['display_order'] = $i;
3942
                $lps[$row['iid']] = $row;
3943
                $lp_order[$i] = $row['iid'];
3944
                $i++;
3945
            }
3946
        }
3947
        if ($num > 1) { // If there's only one element, no need to sort.
3948
            $order = $lps[$lp_id]['display_order'];
3949
            if ($order > 1) { // If it's the first element, no need to move up.
3950
                $sql = "UPDATE $lp_table SET display_order = $order
3951
                        WHERE iid = ".$lp_order[$order - 1];
3952
                Database::query($sql);
3953
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3954
                        WHERE iid = $lp_id";
3955
                Database::query($sql);
3956
            }
3957
        }
3958
3959
        return true;
3960
    }
3961
3962
    /**
3963
     * Move a learnpath down (display_order).
3964
     *
3965
     * @param int $lp_id      Learnpath ID
3966
     * @param int $categoryId Category ID
3967
     *
3968
     * @return bool
3969
     */
3970
    public static function move_down($lp_id, $categoryId = 0)
3971
    {
3972
        $courseId = api_get_course_int_id();
3973
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3974
3975
        $categoryCondition = '';
3976
        if (!empty($categoryId)) {
3977
            $categoryId = (int) $categoryId;
3978
            $categoryCondition = " AND category_id = $categoryId";
3979
        }
3980
3981
        $sql = "SELECT * FROM $lp_table
3982
                WHERE c_id = $courseId
3983
                $categoryCondition
3984
                ORDER BY display_order";
3985
        $res = Database::query($sql);
3986
        if (false === $res) {
3987
            return false;
3988
        }
3989
        $lps = [];
3990
        $lp_order = [];
3991
        $num = Database::num_rows($res);
3992
        $max = 0;
3993
        // First check the order is correct, globally (might be wrong because
3994
        // of versions < 1.8.4).
3995
        if ($num > 0) {
3996
            $i = 1;
3997
            while ($row = Database::fetch_array($res)) {
3998
                $max = $i;
3999
                if ($row['display_order'] != $i) {
4000
                    // If we find a gap in the order, we need to fix it.
4001
                    $sql = "UPDATE $lp_table SET display_order = $i
4002
                              WHERE iid = ".$row['iid'];
4003
                    Database::query($sql);
4004
                }
4005
                $row['display_order'] = $i;
4006
                $lps[$row['iid']] = $row;
4007
                $lp_order[$i] = $row['iid'];
4008
                $i++;
4009
            }
4010
        }
4011
        if ($num > 1) { // If there's only one element, no need to sort.
4012
            $order = $lps[$lp_id]['display_order'];
4013
            if ($order < $max) { // If it's the first element, no need to move up.
4014
                $sql = "UPDATE $lp_table SET display_order = $order
4015
                        WHERE iid = ".$lp_order[$order + 1];
4016
                Database::query($sql);
4017
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4018
                        WHERE iid = $lp_id";
4019
                Database::query($sql);
4020
            }
4021
        }
4022
4023
        return true;
4024
    }
4025
4026
    /**
4027
     * Updates learnpath attributes to point to the next element
4028
     * The last part is similar to set_current_item but processing the other way around.
4029
     */
4030
    public function next()
4031
    {
4032
        if ($this->debug > 0) {
4033
            error_log('In learnpath::next()', 0);
4034
        }
4035
        $this->last = $this->get_current_item_id();
4036
        $this->items[$this->last]->save(
4037
            false,
4038
            $this->prerequisites_match($this->last)
4039
        );
4040
        $this->autocomplete_parents($this->last);
4041
        $new_index = $this->get_next_index();
4042
        if ($this->debug > 2) {
4043
            error_log('New index: '.$new_index, 0);
4044
        }
4045
        $this->index = $new_index;
4046
        if ($this->debug > 2) {
4047
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4048
        }
4049
        $this->current = $this->ordered_items[$new_index];
4050
        if ($this->debug > 2) {
4051
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4052
        }
4053
    }
4054
4055
    /**
4056
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4057
     * class, this might be redefined to allow several behaviours depending on the document type.
4058
     *
4059
     * @param int $id Resource ID
4060
     */
4061
    public function open($id)
4062
    {
4063
        // TODO:
4064
        // set the current resource attribute to this resource
4065
        // switch on element type (redefine in child class?)
4066
        // set status for this item to "opened"
4067
        // start timer
4068
        // initialise score
4069
        $this->index = 0; //or = the last item seen (see $this->last)
4070
    }
4071
4072
    /**
4073
     * Check that all prerequisites are fulfilled. Returns true and an
4074
     * empty string on success, returns false
4075
     * and the prerequisite string on error.
4076
     * This function is based on the rules for aicc_script language as
4077
     * described in the SCORM 1.2 CAM documentation page 108.
4078
     *
4079
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4080
     *
4081
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4082
     *              string otherwise
4083
     */
4084
    public function prerequisites_match($itemId = null)
4085
    {
4086
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4087
        if ($allow) {
4088
            if (api_is_allowed_to_edit() ||
4089
                api_is_platform_admin(true) ||
4090
                api_is_drh() ||
4091
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4092
            ) {
4093
                return true;
4094
            }
4095
        }
4096
4097
        $debug = $this->debug;
4098
        if ($debug > 0) {
4099
            error_log('In learnpath::prerequisites_match()');
4100
        }
4101
4102
        if (empty($itemId)) {
4103
            $itemId = $this->current;
4104
        }
4105
4106
        $currentItem = $this->getItem($itemId);
4107
4108
        if ($currentItem) {
4109
            if (2 == $this->type) {
4110
                // Getting prereq from scorm
4111
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4112
            } else {
4113
                $prereq_string = $currentItem->get_prereq_string();
4114
            }
4115
4116
            if (empty($prereq_string)) {
4117
                if ($debug > 0) {
4118
                    error_log('Found prereq_string is empty return true');
4119
                }
4120
4121
                return true;
4122
            }
4123
4124
            // Clean spaces.
4125
            $prereq_string = str_replace(' ', '', $prereq_string);
4126
            if ($debug > 0) {
4127
                error_log('Found prereq_string: '.$prereq_string, 0);
4128
            }
4129
4130
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4131
            $result = $currentItem->parse_prereq(
4132
                $prereq_string,
4133
                $this->items,
4134
                $this->refs_list,
4135
                $this->get_user_id()
4136
            );
4137
4138
            if (false === $result) {
4139
                $this->set_error_msg($currentItem->prereq_alert);
4140
            }
4141
        } else {
4142
            $result = true;
4143
            if ($debug > 1) {
4144
                error_log('$this->items['.$itemId.'] was not an object', 0);
4145
            }
4146
        }
4147
4148
        if ($debug > 1) {
4149
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4150
        }
4151
4152
        return $result;
4153
    }
4154
4155
    /**
4156
     * Updates learnpath attributes to point to the previous element
4157
     * The last part is similar to set_current_item but processing the other way around.
4158
     */
4159
    public function previous()
4160
    {
4161
        $this->last = $this->get_current_item_id();
4162
        $this->items[$this->last]->save(
4163
            false,
4164
            $this->prerequisites_match($this->last)
4165
        );
4166
        $this->autocomplete_parents($this->last);
4167
        $new_index = $this->get_previous_index();
4168
        $this->index = $new_index;
4169
        $this->current = $this->ordered_items[$new_index];
4170
    }
4171
4172
    /**
4173
     * Publishes a learnpath. This basically means show or hide the learnpath
4174
     * to normal users.
4175
     * Can be used as abstract.
4176
     *
4177
     * @param int $id          Learnpath ID
4178
     * @param int $visibility New visibility
4179
     *
4180
     * @return bool
4181
     */
4182
    public static function toggleVisibility($id, $visibility = 1)
4183
    {
4184
        $repo = Container::getLpRepository();
4185
        $lp = $repo->find($id);
4186
4187
        if (!$lp) {
4188
            return false;
4189
        }
4190
4191
        $visibility = (int) $visibility;
4192
4193
        if (1 === $visibility) {
4194
            $repo->setVisibilityPublished($lp);
4195
        } else {
4196
            $repo->setVisibilityDraft($lp);
4197
        }
4198
4199
        return true;
4200
4201
        /*$action = 'visible';
4202
        if (1 != $set_visibility) {
4203
            $action = 'invisible';
4204
            self::toggle_publish($lp_id, 'i');
4205
        }
4206
4207
        return api_item_property_update(
4208
            api_get_course_info(),
4209
            TOOL_LEARNPATH,
4210
            $lp_id,
4211
            $action,
4212
            api_get_user_id()
4213
        );*/
4214
    }
4215
4216
    /**
4217
     * Publishes a learnpath category.
4218
     * This basically means show or hide the learnpath category to normal users.
4219
     *
4220
     * @param int $id
4221
     * @param int $visibility
4222
     *
4223
     * @return bool
4224
     */
4225
    public static function toggleCategoryVisibility($id, $visibility = 1)
4226
    {
4227
        $repo = Container::getLpCategoryRepository();
4228
        $resource = $repo->find($id);
4229
4230
        if (!$resource) {
4231
            return false;
4232
        }
4233
4234
        $visibility = (int) $visibility;
4235
4236
        if (1 === $visibility) {
4237
            $repo->setVisibilityPublished($resource);
4238
        } else {
4239
            $repo->setVisibilityDraft($resource);
4240
            self::toggleCategoryPublish($id, 0);
4241
        }
4242
4243
        return false;
4244
        /*
4245
        $action = 'visible';
4246
        if (1 != $visibility) {
4247
            self::toggleCategoryPublish($id, 0);
4248
            $action = 'invisible';
4249
        }
4250
4251
        return api_item_property_update(
4252
            api_get_course_info(),
4253
            TOOL_LEARNPATH_CATEGORY,
4254
            $id,
4255
            $action,
4256
            api_get_user_id()
4257
        );*/
4258
    }
4259
4260
    /**
4261
     * Publishes a learnpath. This basically means show or hide the learnpath
4262
     * on the course homepage
4263
     * Can be used as abstract.
4264
     *
4265
     * @param int    $id          Learnpath id
4266
     * @param string $setVisibility New visibility (v/i - visible/invisible)
4267
     *
4268
     * @return bool
4269
     */
4270
    public static function togglePublish($id, $setVisibility = 'v')
4271
    {
4272
        $addShortcut = false;
4273
        if ('v' === $setVisibility) {
4274
            $addShortcut = true;
4275
        }
4276
        $repo = Container::getLpRepository();
4277
        /** @var CLp $lp */
4278
        $lp = $repo->find($id);
4279
        $repoShortcut = Container::getShortcutRepository();
4280
        if ($addShortcut) {
4281
            $shortcut = new CShortcut();
4282
            $shortcut->setName($lp->getName());
4283
            $shortcut->setShortCutNode($lp->getResourceNode());
4284
4285
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4286
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4287
            $repoShortcut->getEntityManager()->flush();
4288
        } else {
4289
            $shortcut = $repoShortcut->getShortcutFromResource($lp);
4290
            if (null !== $shortcut) {
4291
                $repoShortcut->getEntityManager()->remove($shortcut);
4292
                $repoShortcut->getEntityManager()->flush();
4293
            }
4294
        }
4295
4296
        return true;
4297
        /*
4298
        $course_id = api_get_course_int_id();
4299
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4300
        $lp_id = (int) $lp_id;
4301
        $sql = "SELECT * FROM $tbl_lp
4302
                WHERE iid = $lp_id";
4303
        $result = Database::query($sql);
4304
4305
        if (Database::num_rows($result)) {
4306
            $row = Database::fetch_array($result);
4307
            $name = Database::escape_string($row['name']);
4308
            if ($set_visibility == 'i') {
4309
                $v = 0;
4310
            }
4311
            if ($set_visibility == 'v') {
4312
                $v = 1;
4313
            }
4314
4315
            $session_id = api_get_session_id();
4316
            $session_condition = api_get_session_condition($session_id);
4317
4318
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4319
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4320
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4321
4322
            $sql = "SELECT * FROM $tbl_tool
4323
                    WHERE
4324
                        c_id = $course_id AND
4325
                        (link = '$link' OR link = '$oldLink') AND
4326
                        image = 'scormbuilder.gif' AND
4327
                        (
4328
                            link LIKE '$link%' OR
4329
                            link LIKE '$oldLink%'
4330
                        )
4331
                        $session_condition
4332
                    ";
4333
4334
            $result = Database::query($sql);
4335
            $num = Database::num_rows($result);
4336
            if ($set_visibility == 'i' && $num > 0) {
4337
                $sql = "DELETE FROM $tbl_tool
4338
                        WHERE
4339
                            c_id = $course_id AND
4340
                            (link = '$link' OR link = '$oldLink') AND
4341
                            image='scormbuilder.gif'
4342
                            $session_condition";
4343
                Database::query($sql);
4344
            } elseif ($set_visibility == 'v' && $num == 0) {
4345
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4346
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4347
                Database::query($sql);
4348
                $insertId = Database::insert_id();
4349
                if ($insertId) {
4350
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4351
                    Database::query($sql);
4352
                }
4353
            } elseif ($set_visibility == 'v' && $num > 0) {
4354
                $sql = "UPDATE $tbl_tool SET
4355
                            c_id = $course_id,
4356
                            name = '$name',
4357
                            link = '$link',
4358
                            image = 'scormbuilder.gif',
4359
                            visibility = '$v',
4360
                            admin = '0',
4361
                            address = 'pastillegris.gif',
4362
                            added_tool = 0,
4363
                            session_id = $session_id
4364
                        WHERE
4365
                            c_id = ".$course_id." AND
4366
                            (link = '$link' OR link = '$oldLink') AND
4367
                            image='scormbuilder.gif'
4368
                            $session_condition
4369
                        ";
4370
                Database::query($sql);
4371
            } else {
4372
                // Parameter and database incompatible, do nothing, exit.
4373
                return false;
4374
            }
4375
        } else {
4376
            return false;
4377
        }*/
4378
    }
4379
4380
    /**
4381
     * Publishes a learnpath.
4382
     * Show or hide the learnpath category on the course homepage.
4383
     *
4384
     * @param int $id
4385
     * @param int $setVisibility
4386
     *
4387
     * @return bool
4388
     */
4389
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4390
    {
4391
        $setVisibility = (int) $setVisibility;
4392
        $sessionId = api_get_session_id();
4393
        $addShortcut = false;
4394
        if (1 === $setVisibility) {
4395
            $addShortcut = true;
4396
        }
4397
4398
        $repo = Container::getLpCategoryRepository();
4399
        /** @var CLpCategory $lp */
4400
        $category = $repo->find($id);
4401
        $repoShortcut = Container::getShortcutRepository();
4402
        if ($addShortcut) {
4403
            $shortcut = new CShortcut();
4404
            $shortcut->setName($category->getName());
4405
            $shortcut->setShortCutNode($category->getResourceNode());
4406
4407
            $courseEntity = api_get_course_entity(api_get_course_int_id());
4408
            $repoShortcut->addResourceNode($shortcut, api_get_user_entity(api_get_user_id()), $courseEntity);
4409
            $repoShortcut->getEntityManager()->flush();
4410
        } else {
4411
            $shortcut = $repoShortcut->getShortcutFromResource($category);
4412
            if (null !== $shortcut) {
4413
                $repoShortcut->getEntityManager()->remove($shortcut);
4414
                $repoShortcut->getEntityManager()->flush();
4415
            }
4416
        }
4417
4418
        return true;
4419
4420
        $em = Database::getManager();
0 ignored issues
show
Unused Code introduced by
$em = Database::getManager() 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...
4421
4422
        /** @var CLpCategory $category */
4423
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4424
4425
        if (!$category) {
4426
            return false;
4427
        }
4428
4429
        if (empty($courseId)) {
4430
            return false;
4431
        }
4432
4433
        $link = self::getCategoryLinkForTool($id);
4434
4435
        /** @var CTool $tool */
4436
        $tool = $em->createQuery("
4437
                SELECT t FROM ChamiloCourseBundle:CTool t
4438
                WHERE
4439
                    t.course = :course AND
4440
                    t.link = :link1 AND
4441
                    t.image LIKE 'lp_category.%' AND
4442
                    t.link LIKE :link2
4443
                    $sessionCondition
4444
            ")
4445
            ->setParameters([
4446
                'course' => $courseId,
4447
                'link1' => $link,
4448
                'link2' => "$link%",
4449
            ])
4450
            ->getOneOrNullResult();
4451
4452
        if (0 == $setVisibility && $tool) {
4453
            $em->remove($tool);
4454
            $em->flush();
4455
4456
            return true;
4457
        }
4458
4459
        if (1 == $setVisibility && !$tool) {
4460
            $tool = new CTool();
4461
            $tool
4462
                ->setCategory('authoring')
4463
                ->setCourse(api_get_course_entity($courseId))
4464
                ->setName(strip_tags($category->getName()))
4465
                ->setLink($link)
4466
                ->setImage('lp_category.png')
4467
                ->setVisibility(1)
4468
                ->setAdmin(0)
4469
                ->setAddress('pastillegris.gif')
4470
                ->setAddedTool(0)
4471
                ->setSessionId($sessionId)
4472
                ->setTarget('_self');
4473
4474
            $em->persist($tool);
4475
            $em->flush();
4476
4477
            $tool->setId($tool->getIid());
4478
4479
            $em->persist($tool);
4480
            $em->flush();
4481
4482
            return true;
4483
        }
4484
4485
        if (1 == $setVisibility && $tool) {
4486
            $tool
4487
                ->setName(strip_tags($category->getName()))
4488
                ->setVisibility(1);
4489
4490
            $em->persist($tool);
4491
            $em->flush();
4492
4493
            return true;
4494
        }
4495
4496
        return false;
4497
    }
4498
4499
    /**
4500
     * Check if the learnpath category is visible for a user.
4501
     *
4502
     * @param int
4503
     * @param int
4504
     *
4505
     * @return bool
4506
     */
4507
    public static function categoryIsVisibleForStudent(
4508
        CLpCategory $category,
4509
        User $user,
4510
        $courseId = 0,
4511
        $sessionId = 0
4512
    ) {
4513
        if (empty($category)) {
4514
            return false;
4515
        }
4516
4517
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4518
4519
        if ($isAllowedToEdit) {
4520
            return true;
4521
        }
4522
4523
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4524
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4525
4526
        $courseInfo = api_get_course_info_by_id($courseId);
4527
4528
        $categoryVisibility = api_get_item_visibility(
4529
            $courseInfo,
4530
            TOOL_LEARNPATH_CATEGORY,
4531
            $category->getId(),
4532
            $sessionId
4533
        );
4534
4535
        if (1 !== $categoryVisibility && -1 != $categoryVisibility) {
4536
            return false;
4537
        }
4538
4539
        $subscriptionSettings = self::getSubscriptionSettings();
4540
4541
        if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
4542
            return true;
4543
        }
4544
4545
        $users = $category->getUsers();
4546
4547
        if (empty($users) || !$users->count()) {
4548
            return true;
4549
        }
4550
4551
        if ($category->hasUserAdded($user)) {
4552
            return true;
4553
        }
4554
4555
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4556
        if (!empty($groups)) {
4557
            $em = Database::getManager();
4558
4559
            /** @var ItemPropertyRepository $itemRepo */
4560
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4561
4562
            /** @var CourseRepository $courseRepo */
4563
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4564
            $session = null;
4565
            if (!empty($sessionId)) {
4566
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4567
            }
4568
4569
            if (0 != $courseId) {
4570
                $course = $courseRepo->find($courseId);
4571
4572
                // Subscribed groups to a LP
4573
                $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4574
                    TOOL_LEARNPATH_CATEGORY,
4575
                    $category->getId(),
4576
                    $course,
4577
                    $session
4578
                );
4579
            }
4580
4581
            if (!empty($subscribedGroupsInLp)) {
4582
                $groups = array_column($groups, 'iid');
4583
                /** @var CItemProperty $item */
4584
                foreach ($subscribedGroupsInLp as $item) {
4585
                    if ($item->getGroup() &&
4586
                        in_array($item->getGroup()->getId(), $groups)
4587
                    ) {
4588
                        return true;
4589
                    }
4590
                }
4591
            }
4592
        }
4593
4594
        return false;
4595
    }
4596
4597
    /**
4598
     * Check if a learnpath category is published as course tool.
4599
     *
4600
     * @param int $courseId
4601
     *
4602
     * @return bool
4603
     */
4604
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4605
    {
4606
        return false;
4607
        $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...
4608
        $em = Database::getManager();
4609
4610
        $tools = $em
4611
            ->createQuery("
4612
                SELECT t FROM ChamiloCourseBundle:CTool t
4613
                WHERE t.course = :course AND
4614
                    t.name = :name AND
4615
                    t.image LIKE 'lp_category.%' AND
4616
                    t.link LIKE :link
4617
            ")
4618
            ->setParameters([
4619
                'course' => $courseId,
4620
                'name' => strip_tags($category->getName()),
4621
                'link' => "$link%",
4622
            ])
4623
            ->getResult();
4624
4625
        /** @var CTool $tool */
4626
        $tool = current($tools);
4627
4628
        return $tool ? $tool->getVisibility() : false;
4629
    }
4630
4631
    /**
4632
     * Restart the whole learnpath. Return the URL of the first element.
4633
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4634
     * To use a similar method  statically, use the create_new_attempt() method.
4635
     *
4636
     * @return bool
4637
     */
4638
    public function restart()
4639
    {
4640
        if ($this->debug > 0) {
4641
            error_log('In learnpath::restart()', 0);
4642
        }
4643
        // TODO
4644
        // Call autosave method to save the current progress.
4645
        //$this->index = 0;
4646
        if (api_is_invitee()) {
4647
            return false;
4648
        }
4649
        $session_id = api_get_session_id();
4650
        $course_id = api_get_course_int_id();
4651
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4652
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4653
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4654
        if ($this->debug > 2) {
4655
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4656
        }
4657
        Database::query($sql);
4658
        $view_id = Database::insert_id();
4659
4660
        if ($view_id) {
4661
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4662
            Database::query($sql);
4663
            $this->lp_view_id = $view_id;
4664
            $this->attempt = $this->attempt + 1;
4665
        } else {
4666
            $this->error = 'Could not insert into item_view table...';
4667
4668
            return false;
4669
        }
4670
        $this->autocomplete_parents($this->current);
4671
        foreach ($this->items as $index => $dummy) {
4672
            $this->items[$index]->restart();
4673
            $this->items[$index]->set_lp_view($this->lp_view_id);
4674
        }
4675
        $this->first();
4676
4677
        return true;
4678
    }
4679
4680
    /**
4681
     * Saves the current item.
4682
     *
4683
     * @return bool
4684
     */
4685
    public function save_current()
4686
    {
4687
        $debug = $this->debug;
4688
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4689
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4690
        if ($debug) {
4691
            error_log('save_current() saving item '.$this->current, 0);
4692
            error_log(''.print_r($this->items, true), 0);
4693
        }
4694
        if (isset($this->items[$this->current]) &&
4695
            is_object($this->items[$this->current])
4696
        ) {
4697
            if ($debug) {
4698
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4699
            }
4700
4701
            $res = $this->items[$this->current]->save(
4702
                false,
4703
                $this->prerequisites_match($this->current)
4704
            );
4705
            $this->autocomplete_parents($this->current);
4706
            $status = $this->items[$this->current]->get_status();
4707
            $this->update_queue[$this->current] = $status;
4708
4709
            if ($debug) {
4710
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4711
            }
4712
4713
            return $res;
4714
        }
4715
4716
        return false;
4717
    }
4718
4719
    /**
4720
     * Saves the given item.
4721
     *
4722
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4723
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4724
     *
4725
     * @return bool
4726
     */
4727
    public function save_item($item_id = null, $from_outside = true)
4728
    {
4729
        $debug = $this->debug;
4730
        if ($debug) {
4731
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4732
        }
4733
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4734
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4735
        if (empty($item_id)) {
4736
            $item_id = (int) $_REQUEST['id'];
4737
        }
4738
4739
        if (empty($item_id)) {
4740
            $item_id = $this->get_current_item_id();
4741
        }
4742
        if (isset($this->items[$item_id]) &&
4743
            is_object($this->items[$item_id])
4744
        ) {
4745
            if ($debug) {
4746
                error_log('Object exists');
4747
            }
4748
4749
            // Saving the item.
4750
            $res = $this->items[$item_id]->save(
4751
                $from_outside,
4752
                $this->prerequisites_match($item_id)
4753
            );
4754
4755
            if ($debug) {
4756
                error_log('update_queue before:');
4757
                error_log(print_r($this->update_queue, 1));
4758
            }
4759
            $this->autocomplete_parents($item_id);
4760
4761
            $status = $this->items[$item_id]->get_status();
4762
            $this->update_queue[$item_id] = $status;
4763
4764
            if ($debug) {
4765
                error_log('get_status(): '.$status);
4766
                error_log('update_queue after:');
4767
                error_log(print_r($this->update_queue, 1));
4768
            }
4769
4770
            return $res;
4771
        }
4772
4773
        return false;
4774
    }
4775
4776
    /**
4777
     * Saves the last item seen's ID only in case.
4778
     */
4779
    public function save_last()
4780
    {
4781
        $course_id = api_get_course_int_id();
4782
        $debug = $this->debug;
4783
        if ($debug) {
4784
            error_log('In learnpath::save_last()', 0);
4785
        }
4786
        $session_condition = api_get_session_condition(
4787
            api_get_session_id(),
4788
            true,
4789
            false
4790
        );
4791
        $table = Database::get_course_table(TABLE_LP_VIEW);
4792
4793
        if (isset($this->current) && !api_is_invitee()) {
4794
            if ($debug) {
4795
                error_log('Saving current item ('.$this->current.') for later review', 0);
4796
            }
4797
            $sql = "UPDATE $table SET
4798
                        last_item = ".$this->get_current_item_id()."
4799
                    WHERE
4800
                        c_id = $course_id AND
4801
                        lp_id = ".$this->get_id()." AND
4802
                        user_id = ".$this->get_user_id()." ".$session_condition;
4803
4804
            if ($debug) {
4805
                error_log('Saving last item seen : '.$sql, 0);
4806
            }
4807
            Database::query($sql);
4808
        }
4809
4810
        if (!api_is_invitee()) {
4811
            // Save progress.
4812
            list($progress) = $this->get_progress_bar_text('%');
4813
            if ($progress >= 0 && $progress <= 100) {
4814
                $progress = (int) $progress;
4815
                $sql = "UPDATE $table SET
4816
                            progress = $progress
4817
                        WHERE
4818
                            c_id = $course_id AND
4819
                            lp_id = ".$this->get_id()." AND
4820
                            user_id = ".$this->get_user_id()." ".$session_condition;
4821
                // Ignore errors as some tables might not have the progress field just yet.
4822
                Database::query($sql);
4823
                $this->progress_db = $progress;
4824
            }
4825
        }
4826
    }
4827
4828
    /**
4829
     * Sets the current item ID (checks if valid and authorized first).
4830
     *
4831
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4832
     */
4833
    public function set_current_item($item_id = null)
4834
    {
4835
        $debug = $this->debug;
4836
        if ($debug) {
4837
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4838
        }
4839
        if (empty($item_id)) {
4840
            if ($debug) {
4841
                error_log('No new current item given, ignore...', 0);
4842
            }
4843
            // Do nothing.
4844
        } else {
4845
            if ($debug) {
4846
                error_log('New current item given is '.$item_id.'...', 0);
4847
            }
4848
            if (is_numeric($item_id)) {
4849
                $item_id = (int) $item_id;
4850
                // TODO: Check in database here.
4851
                $this->last = $this->current;
4852
                $this->current = $item_id;
4853
                // TODO: Update $this->index as well.
4854
                foreach ($this->ordered_items as $index => $item) {
4855
                    if ($item == $this->current) {
4856
                        $this->index = $index;
4857
                        break;
4858
                    }
4859
                }
4860
                if ($debug) {
4861
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4862
                }
4863
            } else {
4864
                if ($debug) {
4865
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4866
                }
4867
            }
4868
        }
4869
    }
4870
4871
    /**
4872
     * Sets the encoding.
4873
     *
4874
     * @param string $enc New encoding
4875
     *
4876
     * @return bool
4877
     *
4878
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4879
     */
4880
    public function set_encoding($enc = 'UTF-8')
4881
    {
4882
        $enc = api_refine_encoding_id($enc);
4883
        if (empty($enc)) {
4884
            $enc = api_get_system_encoding();
4885
        }
4886
        if (api_is_encoding_supported($enc)) {
4887
            $lp = $this->get_id();
4888
            if (0 != $lp) {
4889
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4890
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4891
                        WHERE iid = ".$lp;
4892
                $res = Database::query($sql);
4893
4894
                return $res;
4895
            }
4896
        }
4897
4898
        return false;
4899
    }
4900
4901
    /**
4902
     * Sets the JS lib setting in the database directly.
4903
     * This is the JavaScript library file this lp needs to load on startup.
4904
     *
4905
     * @param string $lib Proximity setting
4906
     *
4907
     * @return bool True on update success. False otherwise.
4908
     */
4909
    public function set_jslib($lib = '')
4910
    {
4911
        $lp = $this->get_id();
4912
4913
        if (0 != $lp) {
4914
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4915
            $lib = Database::escape_string($lib);
4916
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4917
                    WHERE iid = $lp";
4918
            $res = Database::query($sql);
4919
4920
            return $res;
4921
        }
4922
4923
        return false;
4924
    }
4925
4926
    /**
4927
     * Sets the name of the LP maker (publisher) (and save).
4928
     *
4929
     * @param string $name Optional string giving the new content_maker of this learnpath
4930
     *
4931
     * @return bool True
4932
     */
4933
    public function set_maker($name = '')
4934
    {
4935
        if (empty($name)) {
4936
            return false;
4937
        }
4938
        $this->maker = $name;
4939
        $table = Database::get_course_table(TABLE_LP_MAIN);
4940
        $lp_id = $this->get_id();
4941
        $sql = "UPDATE $table SET
4942
                content_maker = '".Database::escape_string($this->maker)."'
4943
                WHERE iid = $lp_id";
4944
        Database::query($sql);
4945
4946
        return true;
4947
    }
4948
4949
    /**
4950
     * Sets the name of the current learnpath (and save).
4951
     *
4952
     * @param string $name Optional string giving the new name of this learnpath
4953
     *
4954
     * @return bool True/False
4955
     */
4956
    public function set_name($name = null)
4957
    {
4958
        if (empty($name)) {
4959
            return false;
4960
        }
4961
        $this->name = $name;
4962
4963
        $lp_id = $this->get_id();
4964
4965
        $repo = Container::getLpRepository();
4966
        /** @var CLp $lp */
4967
        $lp = $repo->find($lp_id);
4968
        $lp->setName($name);
4969
        $repo->updateNodeForResource($lp);
4970
4971
        /*
4972
        $course_id = $this->course_info['real_id'];
4973
        $sql = "UPDATE $lp_table SET
4974
            name = '$name'
4975
            WHERE iid = $lp_id";
4976
        $result = Database::query($sql);
4977
        // If the lp is visible on the homepage, change his name there.
4978
        if (Database::affected_rows($result)) {
4979
        $session_id = api_get_session_id();
4980
        $session_condition = api_get_session_condition($session_id);
4981
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4982
        $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4983
        $sql = "UPDATE $tbl_tool SET name = '$name'
4984
        	    WHERE
4985
        	        c_id = $course_id AND
4986
        	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4987
        Database::query($sql);*/
4988
4989
        //return true;
4990
        //}
4991
4992
        return false;
4993
    }
4994
4995
    /**
4996
     * Set index specified prefix terms for all items in this path.
4997
     *
4998
     * @param string $terms_string Comma-separated list of terms
4999
     * @param string $prefix       Xapian term prefix
5000
     *
5001
     * @return bool False on error, true otherwise
5002
     */
5003
    public function set_terms_by_prefix($terms_string, $prefix)
5004
    {
5005
        $course_id = api_get_course_int_id();
5006
        if ('true' !== api_get_setting('search_enabled')) {
5007
            return false;
5008
        }
5009
5010
        if (!extension_loaded('xapian')) {
5011
            return false;
5012
        }
5013
5014
        $terms_string = trim($terms_string);
5015
        $terms = explode(',', $terms_string);
5016
        array_walk($terms, 'trim_value');
5017
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5018
5019
        // Don't do anything if no change, verify only at DB, not the search engine.
5020
        if ((0 == count(array_diff($terms, $stored_terms))) && (0 == count(array_diff($stored_terms, $terms)))) {
5021
            return false;
5022
        }
5023
5024
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5025
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5026
5027
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5028
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5029
        $lp_id = (int) $_POST['lp_id'];
5030
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5031
        $result = Database::query($sql);
5032
        $di = new ChamiloIndexer();
5033
5034
        while ($lp_item = Database::fetch_array($result)) {
5035
            // Get search_did.
5036
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5037
            $sql = 'SELECT * FROM %s
5038
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5039
                    LIMIT 1';
5040
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5041
5042
            //echo $sql; echo '<br>';
5043
            $res = Database::query($sql);
5044
            if (Database::num_rows($res) > 0) {
5045
                $se_ref = Database::fetch_array($res);
5046
                // Compare terms.
5047
                $doc = $di->get_document($se_ref['search_did']);
5048
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5049
                $xterms = [];
5050
                foreach ($xapian_terms as $xapian_term) {
5051
                    $xterms[] = substr($xapian_term['name'], 1);
5052
                }
5053
5054
                $dterms = $terms;
5055
                $missing_terms = array_diff($dterms, $xterms);
5056
                $deprecated_terms = array_diff($xterms, $dterms);
5057
5058
                // Save it to search engine.
5059
                foreach ($missing_terms as $term) {
5060
                    $doc->add_term($prefix.$term, 1);
5061
                }
5062
                foreach ($deprecated_terms as $term) {
5063
                    $doc->remove_term($prefix.$term);
5064
                }
5065
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5066
                $di->getDb()->flush();
5067
            }
5068
        }
5069
5070
        return true;
5071
    }
5072
5073
    /**
5074
     * Sets the theme of the LP (local/remote) (and save).
5075
     *
5076
     * @param string $name Optional string giving the new theme of this learnpath
5077
     *
5078
     * @return bool Returns true if theme name is not empty
5079
     */
5080
    public function set_theme($name = '')
5081
    {
5082
        $this->theme = $name;
5083
        $table = Database::get_course_table(TABLE_LP_MAIN);
5084
        $lp_id = $this->get_id();
5085
        $sql = "UPDATE $table
5086
                SET theme = '".Database::escape_string($this->theme)."'
5087
                WHERE iid = $lp_id";
5088
        Database::query($sql);
5089
5090
        return true;
5091
    }
5092
5093
    /**
5094
     * Sets the image of an LP (and save).
5095
     *
5096
     * @param string $name Optional string giving the new image of this learnpath
5097
     *
5098
     * @return bool Returns true if theme name is not empty
5099
     */
5100
    public function set_preview_image($name = '')
5101
    {
5102
        $this->preview_image = $name;
5103
        $table = Database::get_course_table(TABLE_LP_MAIN);
5104
        $lp_id = $this->get_id();
5105
        $sql = "UPDATE $table SET
5106
                preview_image = '".Database::escape_string($this->preview_image)."'
5107
                WHERE iid = $lp_id";
5108
        Database::query($sql);
5109
5110
        return true;
5111
    }
5112
5113
    /**
5114
     * Sets the author of a LP (and save).
5115
     *
5116
     * @param string $name Optional string giving the new author of this learnpath
5117
     *
5118
     * @return bool Returns true if author's name is not empty
5119
     */
5120
    public function set_author($name = '')
5121
    {
5122
        $this->author = $name;
5123
        $table = Database::get_course_table(TABLE_LP_MAIN);
5124
        $lp_id = $this->get_id();
5125
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5126
                WHERE iid = $lp_id";
5127
        Database::query($sql);
5128
5129
        return true;
5130
    }
5131
5132
    /**
5133
     * Sets the hide_toc_frame parameter of a LP (and save).
5134
     *
5135
     * @param int $hide 1 if frame is hidden 0 then else
5136
     *
5137
     * @return bool Returns true if author's name is not empty
5138
     */
5139
    public function set_hide_toc_frame($hide)
5140
    {
5141
        if (intval($hide) == $hide) {
5142
            $this->hide_toc_frame = $hide;
5143
            $table = Database::get_course_table(TABLE_LP_MAIN);
5144
            $lp_id = $this->get_id();
5145
            $sql = "UPDATE $table SET
5146
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5147
                    WHERE iid = $lp_id";
5148
            Database::query($sql);
5149
5150
            return true;
5151
        }
5152
5153
        return false;
5154
    }
5155
5156
    /**
5157
     * Sets the prerequisite of a LP (and save).
5158
     *
5159
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5160
     *
5161
     * @return bool returns true if prerequisite is not empty
5162
     */
5163
    public function set_prerequisite($prerequisite)
5164
    {
5165
        $this->prerequisite = (int) $prerequisite;
5166
        $table = Database::get_course_table(TABLE_LP_MAIN);
5167
        $lp_id = $this->get_id();
5168
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5169
                WHERE iid = $lp_id";
5170
        Database::query($sql);
5171
5172
        return true;
5173
    }
5174
5175
    /**
5176
     * Sets the location/proximity of the LP (local/remote) (and save).
5177
     *
5178
     * @param string $name Optional string giving the new location of this learnpath
5179
     *
5180
     * @return bool True on success / False on error
5181
     */
5182
    public function set_proximity($name = '')
5183
    {
5184
        if (empty($name)) {
5185
            return false;
5186
        }
5187
5188
        $this->proximity = $name;
5189
        $table = Database::get_course_table(TABLE_LP_MAIN);
5190
        $lp_id = $this->get_id();
5191
        $sql = "UPDATE $table SET
5192
                    content_local = '".Database::escape_string($name)."'
5193
                WHERE iid = $lp_id";
5194
        Database::query($sql);
5195
5196
        return true;
5197
    }
5198
5199
    /**
5200
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5201
     *
5202
     * @param int $id DB ID of the item
5203
     */
5204
    public function set_previous_item($id)
5205
    {
5206
        if ($this->debug > 0) {
5207
            error_log('In learnpath::set_previous_item()', 0);
5208
        }
5209
        $this->last = $id;
5210
    }
5211
5212
    /**
5213
     * Sets use_max_score.
5214
     *
5215
     * @param int $use_max_score Optional string giving the new location of this learnpath
5216
     *
5217
     * @return bool True on success / False on error
5218
     */
5219
    public function set_use_max_score($use_max_score = 1)
5220
    {
5221
        $use_max_score = (int) $use_max_score;
5222
        $this->use_max_score = $use_max_score;
5223
        $table = Database::get_course_table(TABLE_LP_MAIN);
5224
        $lp_id = $this->get_id();
5225
        $sql = "UPDATE $table SET
5226
                    use_max_score = '".$this->use_max_score."'
5227
                WHERE iid = $lp_id";
5228
        Database::query($sql);
5229
5230
        return true;
5231
    }
5232
5233
    /**
5234
     * Sets and saves the expired_on date.
5235
     *
5236
     * @param string $expired_on Optional string giving the new author of this learnpath
5237
     *
5238
     * @throws \Doctrine\ORM\OptimisticLockException
5239
     *
5240
     * @return bool Returns true if author's name is not empty
5241
     */
5242
    public function set_expired_on($expired_on)
5243
    {
5244
        $em = Database::getManager();
5245
        /** @var CLp $lp */
5246
        $lp = $em
5247
            ->getRepository('ChamiloCourseBundle:CLp')
5248
            ->findOneBy(
5249
                [
5250
                    'iid' => $this->get_id(),
5251
                ]
5252
            );
5253
5254
        if (!$lp) {
5255
            return false;
5256
        }
5257
5258
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5259
5260
        $lp->setExpiredOn($this->expired_on);
5261
        $em->persist($lp);
5262
        $em->flush();
5263
5264
        return true;
5265
    }
5266
5267
    /**
5268
     * Sets and saves the publicated_on date.
5269
     *
5270
     * @param string $publicated_on Optional string giving the new author of this learnpath
5271
     *
5272
     * @throws \Doctrine\ORM\OptimisticLockException
5273
     *
5274
     * @return bool Returns true if author's name is not empty
5275
     */
5276
    public function set_publicated_on($publicated_on)
5277
    {
5278
        $em = Database::getManager();
5279
        /** @var CLp $lp */
5280
        $lp = $em
5281
            ->getRepository('ChamiloCourseBundle:CLp')
5282
            ->findOneBy(
5283
                [
5284
                    'iid' => $this->get_id(),
5285
                ]
5286
            );
5287
5288
        if (!$lp) {
5289
            return false;
5290
        }
5291
5292
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5293
        $lp->setPublicatedOn($this->publicated_on);
5294
        $em->persist($lp);
5295
        $em->flush();
5296
5297
        return true;
5298
    }
5299
5300
    /**
5301
     * Sets and saves the expired_on date.
5302
     *
5303
     * @return bool Returns true if author's name is not empty
5304
     */
5305
    public function set_modified_on()
5306
    {
5307
        $this->modified_on = api_get_utc_datetime();
5308
        $table = Database::get_course_table(TABLE_LP_MAIN);
5309
        $lp_id = $this->get_id();
5310
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5311
                WHERE iid = $lp_id";
5312
        Database::query($sql);
5313
5314
        return true;
5315
    }
5316
5317
    /**
5318
     * Sets the object's error message.
5319
     *
5320
     * @param string $error Error message. If empty, reinits the error string
5321
     */
5322
    public function set_error_msg($error = '')
5323
    {
5324
        if ($this->debug > 0) {
5325
            error_log('In learnpath::set_error_msg()', 0);
5326
        }
5327
        if (empty($error)) {
5328
            $this->error = '';
5329
        } else {
5330
            $this->error .= $error;
5331
        }
5332
    }
5333
5334
    /**
5335
     * Launches the current item if not 'sco'
5336
     * (starts timer and make sure there is a record ready in the DB).
5337
     *
5338
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5339
     *
5340
     * @return bool
5341
     */
5342
    public function start_current_item($allow_new_attempt = false)
5343
    {
5344
        $debug = $this->debug;
5345
        if ($debug) {
5346
            error_log('In learnpath::start_current_item()');
5347
            error_log('current: '.$this->current);
5348
        }
5349
        if (0 != $this->current && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5350
            $type = $this->get_type();
5351
            $item_type = $this->items[$this->current]->get_type();
5352
            if ((2 == $type && 'sco' != $item_type) ||
5353
                (3 == $type && 'au' != $item_type) ||
5354
                (1 == $type && TOOL_QUIZ != $item_type && TOOL_HOTPOTATOES != $item_type)
5355
            ) {
5356
                if ($debug) {
5357
                    error_log('item type: '.$item_type);
5358
                    error_log('lp type: '.$type);
5359
                }
5360
                $this->items[$this->current]->open($allow_new_attempt);
5361
                $this->autocomplete_parents($this->current);
5362
                $prereq_check = $this->prerequisites_match($this->current);
5363
                if ($debug) {
5364
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5365
                }
5366
                $this->items[$this->current]->save(false, $prereq_check);
5367
            }
5368
            // If sco, then it is supposed to have been updated by some other call.
5369
            if ('sco' == $item_type) {
5370
                $this->items[$this->current]->restart();
5371
            }
5372
        }
5373
        if ($debug) {
5374
            error_log('lp_view_session_id');
5375
            error_log($this->lp_view_session_id);
5376
            error_log('api session id');
5377
            error_log(api_get_session_id());
5378
            error_log('End of learnpath::start_current_item()');
5379
        }
5380
5381
        return true;
5382
    }
5383
5384
    /**
5385
     * Stops the processing and counters for the old item (as held in $this->last).
5386
     *
5387
     * @return bool True/False
5388
     */
5389
    public function stop_previous_item()
5390
    {
5391
        $debug = $this->debug;
5392
        if ($debug) {
5393
            error_log('In learnpath::stop_previous_item()', 0);
5394
        }
5395
5396
        if (0 != $this->last && $this->last != $this->current &&
5397
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5398
        ) {
5399
            if ($debug) {
5400
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5401
            }
5402
            switch ($this->get_type()) {
5403
                case '3':
5404
                    if ('au' != $this->items[$this->last]->get_type()) {
5405
                        if ($debug) {
5406
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5407
                        }
5408
                        $this->items[$this->last]->close();
5409
                    } else {
5410
                        if ($debug) {
5411
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5412
                        }
5413
                    }
5414
                    break;
5415
                case '2':
5416
                    if ('sco' != $this->items[$this->last]->get_type()) {
5417
                        if ($debug) {
5418
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5419
                        }
5420
                        $this->items[$this->last]->close();
5421
                    } else {
5422
                        if ($debug) {
5423
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5424
                        }
5425
                    }
5426
                    break;
5427
                case '1':
5428
                default:
5429
                    if ($debug) {
5430
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5431
                    }
5432
                    $this->items[$this->last]->close();
5433
                    break;
5434
            }
5435
        } else {
5436
            if ($debug) {
5437
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5438
            }
5439
5440
            return false;
5441
        }
5442
5443
        return true;
5444
    }
5445
5446
    /**
5447
     * Updates the default view mode from fullscreen to embedded and inversely.
5448
     *
5449
     * @return string The current default view mode ('fullscreen' or 'embedded')
5450
     */
5451
    public function update_default_view_mode()
5452
    {
5453
        $table = Database::get_course_table(TABLE_LP_MAIN);
5454
        $sql = "SELECT * FROM $table
5455
                WHERE iid = ".$this->get_id();
5456
        $res = Database::query($sql);
5457
        if (Database::num_rows($res) > 0) {
5458
            $row = Database::fetch_array($res);
5459
            $default_view_mode = $row['default_view_mod'];
5460
            $view_mode = $default_view_mode;
5461
            switch ($default_view_mode) {
5462
                case 'fullscreen': // default with popup
5463
                    $view_mode = 'embedded';
5464
                    break;
5465
                case 'embedded': // default view with left menu
5466
                    $view_mode = 'embedframe';
5467
                    break;
5468
                case 'embedframe': //folded menu
5469
                    $view_mode = 'impress';
5470
                    break;
5471
                case 'impress':
5472
                    $view_mode = 'fullscreen';
5473
                    break;
5474
            }
5475
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5476
                    WHERE iid = ".$this->get_id();
5477
            Database::query($sql);
5478
            $this->mode = $view_mode;
5479
5480
            return $view_mode;
5481
        }
5482
5483
        return -1;
5484
    }
5485
5486
    /**
5487
     * Updates the default behaviour about auto-commiting SCORM updates.
5488
     *
5489
     * @return bool True if auto-commit has been set to 'on', false otherwise
5490
     */
5491
    public function update_default_scorm_commit()
5492
    {
5493
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5494
        $sql = "SELECT * FROM $lp_table
5495
                WHERE iid = ".$this->get_id();
5496
        $res = Database::query($sql);
5497
        if (Database::num_rows($res) > 0) {
5498
            $row = Database::fetch_array($res);
5499
            $force = $row['force_commit'];
5500
            if (1 == $force) {
5501
                $force = 0;
5502
                $force_return = false;
5503
            } elseif (0 == $force) {
5504
                $force = 1;
5505
                $force_return = true;
5506
            }
5507
            $sql = "UPDATE $lp_table SET force_commit = $force
5508
                    WHERE iid = ".$this->get_id();
5509
            Database::query($sql);
5510
            $this->force_commit = $force_return;
5511
5512
            return $force_return;
5513
        }
5514
5515
        return -1;
5516
    }
5517
5518
    /**
5519
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5520
     *
5521
     * @return bool True on success, false on failure
5522
     */
5523
    public function update_display_order()
5524
    {
5525
        $course_id = api_get_course_int_id();
5526
        $table = Database::get_course_table(TABLE_LP_MAIN);
5527
        $sql = "SELECT * FROM $table
5528
                WHERE c_id = $course_id
5529
                ORDER BY display_order";
5530
        $res = Database::query($sql);
5531
        if (false === $res) {
5532
            return false;
5533
        }
5534
5535
        $num = Database::num_rows($res);
5536
        // First check the order is correct, globally (might be wrong because
5537
        // of versions < 1.8.4).
5538
        if ($num > 0) {
5539
            $i = 1;
5540
            while ($row = Database::fetch_array($res)) {
5541
                if ($row['display_order'] != $i) {
5542
                    // If we find a gap in the order, we need to fix it.
5543
                    $sql = "UPDATE $table SET display_order = $i
5544
                            WHERE iid = ".$row['iid'];
5545
                    Database::query($sql);
5546
                }
5547
                $i++;
5548
            }
5549
        }
5550
5551
        return true;
5552
    }
5553
5554
    /**
5555
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5556
     *
5557
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5558
     */
5559
    public function update_reinit()
5560
    {
5561
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5562
        $sql = "SELECT * FROM $lp_table
5563
                WHERE iid = ".$this->get_id();
5564
        $res = Database::query($sql);
5565
        if (Database::num_rows($res) > 0) {
5566
            $row = Database::fetch_array($res);
5567
            $force = $row['prevent_reinit'];
5568
            if (1 == $force) {
5569
                $force = 0;
5570
            } elseif (0 == $force) {
5571
                $force = 1;
5572
            }
5573
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5574
                    WHERE iid = ".$this->get_id();
5575
            Database::query($sql);
5576
            $this->prevent_reinit = $force;
5577
5578
            return $force;
5579
        }
5580
5581
        return -1;
5582
    }
5583
5584
    /**
5585
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5586
     *
5587
     * @return string 'single', 'multi' or 'seriousgame'
5588
     *
5589
     * @author ndiechburg <[email protected]>
5590
     */
5591
    public function get_attempt_mode()
5592
    {
5593
        //Set default value for seriousgame_mode
5594
        if (!isset($this->seriousgame_mode)) {
5595
            $this->seriousgame_mode = 0;
5596
        }
5597
        // Set default value for prevent_reinit
5598
        if (!isset($this->prevent_reinit)) {
5599
            $this->prevent_reinit = 1;
5600
        }
5601
        if (1 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5602
            return 'seriousgame';
5603
        }
5604
        if (0 == $this->seriousgame_mode && 1 == $this->prevent_reinit) {
5605
            return 'single';
5606
        }
5607
        if (0 == $this->seriousgame_mode && 0 == $this->prevent_reinit) {
5608
            return 'multiple';
5609
        }
5610
5611
        return 'single';
5612
    }
5613
5614
    /**
5615
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5616
     *
5617
     * @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...
5618
     *
5619
     * @return bool
5620
     *
5621
     * @author ndiechburg <[email protected]>
5622
     */
5623
    public function set_attempt_mode($mode)
5624
    {
5625
        switch ($mode) {
5626
            case 'seriousgame':
5627
                $sg_mode = 1;
5628
                $prevent_reinit = 1;
5629
                break;
5630
            case 'single':
5631
                $sg_mode = 0;
5632
                $prevent_reinit = 1;
5633
                break;
5634
            case 'multiple':
5635
                $sg_mode = 0;
5636
                $prevent_reinit = 0;
5637
                break;
5638
            default:
5639
                $sg_mode = 0;
5640
                $prevent_reinit = 0;
5641
                break;
5642
        }
5643
        $this->prevent_reinit = $prevent_reinit;
5644
        $this->seriousgame_mode = $sg_mode;
5645
        $table = Database::get_course_table(TABLE_LP_MAIN);
5646
        $sql = "UPDATE $table SET
5647
                prevent_reinit = $prevent_reinit ,
5648
                seriousgame_mode = $sg_mode
5649
                WHERE iid = ".$this->get_id();
5650
        $res = Database::query($sql);
5651
        if ($res) {
5652
            return true;
5653
        } else {
5654
            return false;
5655
        }
5656
    }
5657
5658
    /**
5659
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5660
     *
5661
     * @author ndiechburg <[email protected]>
5662
     */
5663
    public function switch_attempt_mode()
5664
    {
5665
        $mode = $this->get_attempt_mode();
5666
        switch ($mode) {
5667
            case 'single':
5668
                $next_mode = 'multiple';
5669
                break;
5670
            case 'multiple':
5671
                $next_mode = 'seriousgame';
5672
                break;
5673
            case 'seriousgame':
5674
            default:
5675
                $next_mode = 'single';
5676
                break;
5677
        }
5678
        $this->set_attempt_mode($next_mode);
5679
    }
5680
5681
    /**
5682
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5683
     * but possibility to do again a completed item.
5684
     *
5685
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5686
     *
5687
     * @author ndiechburg <[email protected]>
5688
     */
5689
    public function set_seriousgame_mode()
5690
    {
5691
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5692
        $sql = "SELECT * FROM $lp_table
5693
                WHERE iid = ".$this->get_id();
5694
        $res = Database::query($sql);
5695
        if (Database::num_rows($res) > 0) {
5696
            $row = Database::fetch_array($res);
5697
            $force = $row['seriousgame_mode'];
5698
            if (1 == $force) {
5699
                $force = 0;
5700
            } elseif (0 == $force) {
5701
                $force = 1;
5702
            }
5703
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5704
			        WHERE iid = ".$this->get_id();
5705
            Database::query($sql);
5706
            $this->seriousgame_mode = $force;
5707
5708
            return $force;
5709
        }
5710
5711
        return -1;
5712
    }
5713
5714
    /**
5715
     * Updates the "scorm_debug" value that shows or hide the debug window.
5716
     *
5717
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5718
     */
5719
    public function update_scorm_debug()
5720
    {
5721
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5722
        $sql = "SELECT * FROM $lp_table
5723
                WHERE iid = ".$this->get_id();
5724
        $res = Database::query($sql);
5725
        if (Database::num_rows($res) > 0) {
5726
            $row = Database::fetch_array($res);
5727
            $force = $row['debug'];
5728
            if (1 == $force) {
5729
                $force = 0;
5730
            } elseif (0 == $force) {
5731
                $force = 1;
5732
            }
5733
            $sql = "UPDATE $lp_table SET debug = $force
5734
                    WHERE iid = ".$this->get_id();
5735
            Database::query($sql);
5736
            $this->scorm_debug = $force;
5737
5738
            return $force;
5739
        }
5740
5741
        return -1;
5742
    }
5743
5744
    /**
5745
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5746
     *
5747
     * @author Kevin Van Den Haute
5748
     *
5749
     * @param  array
5750
     */
5751
    public function tree_array($array)
5752
    {
5753
        $array = $this->sort_tree_array($array);
5754
        $this->create_tree_array($array);
5755
    }
5756
5757
    /**
5758
     * Creates an array with the elements of the learning path tree in it.
5759
     *
5760
     * @author Kevin Van Den Haute
5761
     *
5762
     * @param array $array
5763
     * @param int   $parent
5764
     * @param int   $depth
5765
     * @param array $tmp
5766
     */
5767
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5768
    {
5769
        if (is_array($array)) {
5770
            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...
5771
                if ($array[$i]['parent_item_id'] == $parent) {
5772
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5773
                        $tmp[] = $array[$i]['parent_item_id'];
5774
                        $depth++;
5775
                    }
5776
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5777
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5778
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5779
5780
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5781
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5782
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5783
                    $this->arrMenu[] = [
5784
                        'id' => $array[$i]['id'],
5785
                        'ref' => $ref,
5786
                        'item_type' => $array[$i]['item_type'],
5787
                        'title' => $array[$i]['title'],
5788
                        'title_raw' => $array[$i]['title_raw'],
5789
                        'path' => $path,
5790
                        'description' => $array[$i]['description'],
5791
                        'parent_item_id' => $array[$i]['parent_item_id'],
5792
                        'previous_item_id' => $array[$i]['previous_item_id'],
5793
                        'next_item_id' => $array[$i]['next_item_id'],
5794
                        'min_score' => $array[$i]['min_score'],
5795
                        'max_score' => $array[$i]['max_score'],
5796
                        'mastery_score' => $array[$i]['mastery_score'],
5797
                        'display_order' => $array[$i]['display_order'],
5798
                        'prerequisite' => $preq,
5799
                        'depth' => $depth,
5800
                        'audio' => $audio,
5801
                        'prerequisite_min_score' => $prerequisiteMinScore,
5802
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5803
                    ];
5804
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5805
                }
5806
            }
5807
        }
5808
    }
5809
5810
    /**
5811
     * Sorts a multi dimensional array by parent id and display order.
5812
     *
5813
     * @author Kevin Van Den Haute
5814
     *
5815
     * @param array $array (array with al the learning path items in it)
5816
     *
5817
     * @return array
5818
     */
5819
    public function sort_tree_array($array)
5820
    {
5821
        foreach ($array as $key => $row) {
5822
            $parent[$key] = $row['parent_item_id'];
5823
            $position[$key] = $row['display_order'];
5824
        }
5825
5826
        if (count($array) > 0) {
5827
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5828
        }
5829
5830
        return $array;
5831
    }
5832
5833
    /**
5834
     * Function that creates a html list of learning path items so that we can add audio files to them.
5835
     *
5836
     * @author Kevin Van Den Haute
5837
     *
5838
     * @return string
5839
     */
5840
    public function overview()
5841
    {
5842
        $return = '';
5843
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5844
5845
        // we need to start a form when we want to update all the mp3 files
5846
        if ('true' == $update_audio) {
5847
            $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">';
5848
        }
5849
        $return .= '<div id="message"></div>';
5850
        if (0 == count($this->items)) {
5851
            $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');
5852
        } else {
5853
            $return_audio = '<table class="data_table">';
5854
            $return_audio .= '<tr>';
5855
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5856
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5857
            $return_audio .= '</tr>';
5858
5859
            if ('true' != $update_audio) {
5860
                $return .= '<div class="col-md-12">';
5861
                $return .= self::return_new_tree($update_audio);
5862
                $return .= '</div>';
5863
                $return .= Display::div(
5864
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5865
                    ['style' => 'float:left; margin-top:15px;width:100%']
5866
                );
5867
            } else {
5868
                $return_audio .= self::return_new_tree($update_audio);
5869
                $return .= $return_audio.'</table>';
5870
            }
5871
5872
            // We need to close the form when we are updating the mp3 files.
5873
            if ('true' == $update_audio) {
5874
                $return .= '<div class="footer-audio">';
5875
                $return .= Display::button(
5876
                    'save_audio',
5877
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('Save audio and organization'),
5878
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5879
                );
5880
                $return .= '</div>';
5881
            }
5882
        }
5883
5884
        // We need to close the form when we are updating the mp3 files.
5885
        if ('true' == $update_audio && isset($this->arrMenu) && 0 != count($this->arrMenu)) {
5886
            $return .= '</form>';
5887
        }
5888
5889
        return $return;
5890
    }
5891
5892
    /**
5893
     * @param string $update_audio
5894
     *
5895
     * @return array
5896
     */
5897
    public function processBuildMenuElements($update_audio = 'false')
5898
    {
5899
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5900
        $arrLP = $this->getItemsForForm();
5901
5902
        $this->tree_array($arrLP);
5903
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5904
        unset($this->arrMenu);
5905
        $default_data = null;
5906
        $default_content = null;
5907
        $elements = [];
5908
        $return_audio = null;
5909
        $iconPath = api_get_path(SYS_PUBLIC_PATH).'img/';
5910
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5911
        $countItems = count($arrLP);
5912
5913
        $upIcon = Display::return_icon(
5914
            'up.png',
5915
            get_lang('Up'),
5916
            [],
5917
            ICON_SIZE_TINY
5918
        );
5919
5920
        $disableUpIcon = Display::return_icon(
5921
            'up_na.png',
5922
            get_lang('Up'),
5923
            [],
5924
            ICON_SIZE_TINY
5925
        );
5926
5927
        $downIcon = Display::return_icon(
5928
            'down.png',
5929
            get_lang('Down'),
5930
            [],
5931
            ICON_SIZE_TINY
5932
        );
5933
5934
        $disableDownIcon = Display::return_icon(
5935
            'down_na.png',
5936
            get_lang('Down'),
5937
            [],
5938
            ICON_SIZE_TINY
5939
        );
5940
5941
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5942
5943
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5944
        $plugin = null;
5945
        if ($pluginCalendar) {
5946
            $plugin = LearningCalendarPlugin::create();
5947
        }
5948
5949
        for ($i = 0; $i < $countItems; $i++) {
5950
            $parent_id = $arrLP[$i]['parent_item_id'];
5951
            $title = $arrLP[$i]['title'];
5952
            $title_cut = $arrLP[$i]['title_raw'];
5953
            if (false === $show) {
5954
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5955
            }
5956
            // Link for the documents
5957
            if ('document' === $arrLP[$i]['item_type'] || TOOL_READOUT_TEXT === $arrLP[$i]['item_type']) {
5958
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5959
                $title_cut = Display::url(
5960
                    $title_cut,
5961
                    $url,
5962
                    [
5963
                        'class' => 'ajax moved',
5964
                        'data-title' => $title,
5965
                        'title' => $title,
5966
                    ]
5967
                );
5968
            }
5969
5970
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5971
            if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5972
                Session::write('pathItem', $arrLP[$i]['path']);
5973
            }
5974
5975
            $oddClass = 'row_even';
5976
            if (0 == ($i % 2)) {
5977
                $oddClass = 'row_odd';
5978
            }
5979
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5980
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5981
5982
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5983
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5984
            } else {
5985
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5986
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5987
                } else {
5988
                    if (TOOL_LP_FINAL_ITEM === $arrLP[$i]['item_type']) {
5989
                        $icon = Display::return_icon('certificate.png');
5990
                    } else {
5991
                        $icon = Display::return_icon('folder_document.png');
5992
                    }
5993
                }
5994
            }
5995
5996
            // The audio column.
5997
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5998
            $audio = '';
5999
            if (!$update_audio || 'true' != $update_audio) {
6000
                if (empty($arrLP[$i]['audio'])) {
6001
                    $audio .= '';
6002
                }
6003
            } else {
6004
                $types = self::getChapterTypes();
6005
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6006
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6007
                    if (!empty($arrLP[$i]['audio'])) {
6008
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6009
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('Remove audio');
6010
                    }
6011
                }
6012
            }
6013
6014
            $return_audio .= Display::span($icon.' '.$title).
6015
                Display::tag(
6016
                    'td',
6017
                    $audio,
6018
                    ['style' => '']
6019
                );
6020
            $return_audio .= '</td>';
6021
            $move_icon = '';
6022
            $move_item_icon = '';
6023
            $edit_icon = '';
6024
            $delete_icon = '';
6025
            $audio_icon = '';
6026
            $prerequisities_icon = '';
6027
            $forumIcon = '';
6028
            $previewIcon = '';
6029
            $pluginCalendarIcon = '';
6030
            $orderIcons = '';
6031
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6032
6033
            if ($is_allowed_to_edit) {
6034
                if (!$update_audio || 'true' != $update_audio) {
6035
                    if (TOOL_LP_FINAL_ITEM !== $arrLP[$i]['item_type']) {
6036
                        $move_icon .= '<a class="moved" href="#">';
6037
                        $move_icon .= Display::return_icon(
6038
                            'move_everywhere.png',
6039
                            get_lang('Move'),
6040
                            [],
6041
                            ICON_SIZE_TINY
6042
                        );
6043
                        $move_icon .= '</a>';
6044
                    }
6045
                }
6046
6047
                // No edit for this item types
6048
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6049
                    if ('dir' != $arrLP[$i]['item_type']) {
6050
                        $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">';
6051
                        $edit_icon .= Display::return_icon(
6052
                            'edit.png',
6053
                            get_lang('Edit section description/name'),
6054
                            [],
6055
                            ICON_SIZE_TINY
6056
                        );
6057
                        $edit_icon .= '</a>';
6058
6059
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6060
                            $forumThread = null;
6061
                            if (isset($this->items[$arrLP[$i]['id']])) {
6062
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6063
                                    $this->course_int_id,
6064
                                    $this->lp_session_id
6065
                                );
6066
                            }
6067
                            if ($forumThread) {
6068
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6069
                                        'action' => 'dissociate_forum',
6070
                                        'id' => $arrLP[$i]['id'],
6071
                                        'lp_id' => $this->lp_id,
6072
                                    ]);
6073
                                $forumIcon = Display::url(
6074
                                    Display::return_icon(
6075
                                        'forum.png',
6076
                                        get_lang('Dissociate the forum of this learning path item'),
6077
                                        [],
6078
                                        ICON_SIZE_TINY
6079
                                    ),
6080
                                    $forumIconUrl,
6081
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6082
                                );
6083
                            } else {
6084
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6085
                                        'action' => 'create_forum',
6086
                                        'id' => $arrLP[$i]['id'],
6087
                                        'lp_id' => $this->lp_id,
6088
                                    ]);
6089
                                $forumIcon = Display::url(
6090
                                    Display::return_icon(
6091
                                        'forum.png',
6092
                                        get_lang('Associate a forum to this learning path item'),
6093
                                        [],
6094
                                        ICON_SIZE_TINY
6095
                                    ),
6096
                                    $forumIconUrl,
6097
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6098
                                );
6099
                            }
6100
                        }
6101
                    } else {
6102
                        $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">';
6103
                        $edit_icon .= Display::return_icon(
6104
                            'edit.png',
6105
                            get_lang('Edit section description/name'),
6106
                            [],
6107
                            ICON_SIZE_TINY
6108
                        );
6109
                        $edit_icon .= '</a>';
6110
                    }
6111
                } else {
6112
                    if (TOOL_LP_FINAL_ITEM == $arrLP[$i]['item_type']) {
6113
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6114
                        $edit_icon .= Display::return_icon(
6115
                            'edit.png',
6116
                            get_lang('Edit'),
6117
                            [],
6118
                            ICON_SIZE_TINY
6119
                        );
6120
                        $edit_icon .= '</a>';
6121
                    }
6122
                }
6123
6124
                if ($pluginCalendar) {
6125
                    $pluginLink = $pluginUrl.
6126
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6127
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6128
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6129
                    if ($itemInfo && 1 == $itemInfo['value']) {
6130
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('1 day'), [], ICON_SIZE_TINY);
6131
                    }
6132
                    $pluginCalendarIcon = Display::url(
6133
                        $iconCalendar,
6134
                        $pluginLink,
6135
                        ['class' => 'btn btn-default']
6136
                    );
6137
                }
6138
6139
                if ('final_item' != $arrLP[$i]['item_type']) {
6140
                    $orderIcons = Display::url(
6141
                        $upIcon,
6142
                        'javascript:void(0)',
6143
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6144
                    );
6145
                    $orderIcons .= Display::url(
6146
                        $downIcon,
6147
                        'javascript:void(0)',
6148
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6149
                    );
6150
                }
6151
6152
                $delete_icon .= ' <a
6153
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6154
                    onclick="return confirmation(\''.addslashes($title).'\');"
6155
                    class="btn btn-default">';
6156
                $delete_icon .= Display::return_icon(
6157
                    'delete.png',
6158
                    get_lang('Delete section'),
6159
                    [],
6160
                    ICON_SIZE_TINY
6161
                );
6162
                $delete_icon .= '</a>';
6163
6164
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6165
                $previewImage = Display::return_icon(
6166
                    'preview_view.png',
6167
                    get_lang('Preview'),
6168
                    [],
6169
                    ICON_SIZE_TINY
6170
                );
6171
6172
                switch ($arrLP[$i]['item_type']) {
6173
                    case TOOL_DOCUMENT:
6174
                    case TOOL_LP_FINAL_ITEM:
6175
                    case TOOL_READOUT_TEXT:
6176
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6177
                        $previewIcon = Display::url(
6178
                            $previewImage,
6179
                            $urlPreviewLink,
6180
                            [
6181
                                'target' => '_blank',
6182
                                'class' => 'btn btn-default',
6183
                                'data-title' => $arrLP[$i]['title'],
6184
                                'title' => $arrLP[$i]['title'],
6185
                            ]
6186
                        );
6187
                        break;
6188
                    case TOOL_THREAD:
6189
                    case TOOL_FORUM:
6190
                    case TOOL_QUIZ:
6191
                    case TOOL_STUDENTPUBLICATION:
6192
                    case TOOL_LP_FINAL_ITEM:
6193
                    case TOOL_LINK:
6194
                        $class = 'btn btn-default';
6195
                        $target = '_blank';
6196
                        $link = self::rl_get_resource_link_for_learnpath(
6197
                            $this->course_int_id,
6198
                            $this->lp_id,
6199
                            $arrLP[$i]['id'],
6200
                            0
6201
                        );
6202
                        $previewIcon = Display::url(
6203
                            $previewImage,
6204
                            $link,
6205
                            [
6206
                                'class' => $class,
6207
                                'data-title' => $arrLP[$i]['title'],
6208
                                'title' => $arrLP[$i]['title'],
6209
                                'target' => $target,
6210
                            ]
6211
                        );
6212
                        break;
6213
                    default:
6214
                        $previewIcon = Display::url(
6215
                            $previewImage,
6216
                            $url.'&action=view_item',
6217
                            ['class' => 'btn btn-default', 'target' => '_blank']
6218
                        );
6219
                        break;
6220
                }
6221
6222
                if ('dir' != $arrLP[$i]['item_type']) {
6223
                    $prerequisities_icon = Display::url(
6224
                        Display::return_icon(
6225
                            'accept.png',
6226
                            get_lang('Prerequisites'),
6227
                            [],
6228
                            ICON_SIZE_TINY
6229
                        ),
6230
                        $url.'&action=edit_item_prereq',
6231
                        ['class' => 'btn btn-default']
6232
                    );
6233
                    if ('final_item' != $arrLP[$i]['item_type']) {
6234
                        /*$move_item_icon = Display::url(
6235
                            Display::return_icon(
6236
                                'move.png',
6237
                                get_lang('Move'),
6238
                                [],
6239
                                ICON_SIZE_TINY
6240
                            ),
6241
                            $url.'&action=move_item',
6242
                            ['class' => 'btn btn-default']
6243
                        );*/
6244
                    }
6245
                    $audio_icon = Display::url(
6246
                        Display::return_icon(
6247
                            'audio.png',
6248
                            get_lang('Upload'),
6249
                            [],
6250
                            ICON_SIZE_TINY
6251
                        ),
6252
                        $url.'&action=add_audio',
6253
                        ['class' => 'btn btn-default']
6254
                    );
6255
                }
6256
            }
6257
            if ('true' != $update_audio) {
6258
                $row = $move_icon.' '.$icon.
6259
                    Display::span($title_cut).
6260
                    Display::tag(
6261
                        'div',
6262
                        "<div class=\"btn-group btn-group-xs\">
6263
                                    $previewIcon
6264
                                    $audio
6265
                                    $edit_icon
6266
                                    $pluginCalendarIcon
6267
                                    $forumIcon
6268
                                    $prerequisities_icon
6269
                                    $move_item_icon
6270
                                    $audio_icon
6271
                                    $orderIcons
6272
                                    $delete_icon
6273
                                </div>",
6274
                        ['class' => 'btn-toolbar button_actions']
6275
                    );
6276
            } else {
6277
                $row =
6278
                    Display::span($title.$icon).
6279
                    Display::span($audio, ['class' => 'button_actions']);
6280
            }
6281
6282
            $default_data[$arrLP[$i]['id']] = $row;
6283
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6284
6285
            if (empty($parent_id)) {
6286
                $elements[$arrLP[$i]['id']]['data'] = $row;
6287
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6288
            } else {
6289
                $parent_arrays = [];
6290
                if ($arrLP[$i]['depth'] > 1) {
6291
                    // Getting list of parents
6292
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6293
                        foreach ($arrLP as $item) {
6294
                            if ($item['id'] == $parent_id) {
6295
                                if (0 == $item['parent_item_id']) {
6296
                                    $parent_id = $item['id'];
6297
                                    break;
6298
                                } else {
6299
                                    $parent_id = $item['parent_item_id'];
6300
                                    if (empty($parent_arrays)) {
6301
                                        $parent_arrays[] = intval($item['id']);
6302
                                    }
6303
                                    $parent_arrays[] = $parent_id;
6304
                                    break;
6305
                                }
6306
                            }
6307
                        }
6308
                    }
6309
                }
6310
6311
                if (!empty($parent_arrays)) {
6312
                    $parent_arrays = array_reverse($parent_arrays);
6313
                    $val = '$elements';
6314
                    $x = 0;
6315
                    foreach ($parent_arrays as $item) {
6316
                        if ($x != count($parent_arrays) - 1) {
6317
                            $val .= '["'.$item.'"]["children"]';
6318
                        } else {
6319
                            $val .= '["'.$item.'"]["children"]';
6320
                        }
6321
                        $x++;
6322
                    }
6323
                    $val .= "";
6324
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6325
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6326
                } else {
6327
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6328
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6329
                }
6330
            }
6331
        }
6332
6333
        return [
6334
            'elements' => $elements,
6335
            'default_data' => $default_data,
6336
            'default_content' => $default_content,
6337
            'return_audio' => $return_audio,
6338
        ];
6339
    }
6340
6341
    /**
6342
     * @param string $updateAudio true/false strings
6343
     *
6344
     * @return string
6345
     */
6346
    public function returnLpItemList($updateAudio)
6347
    {
6348
        $result = $this->processBuildMenuElements($updateAudio);
6349
6350
        $html = self::print_recursive(
6351
            $result['elements'],
6352
            $result['default_data'],
6353
            $result['default_content']
6354
        );
6355
6356
        if (!empty($html)) {
6357
            $html .= Display::return_message(get_lang('Drag and drop an element here'));
6358
        }
6359
6360
        return $html;
6361
    }
6362
6363
    /**
6364
     * @param string $update_audio
6365
     * @param bool   $drop_element_here
6366
     *
6367
     * @return string
6368
     */
6369
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6370
    {
6371
        $result = $this->processBuildMenuElements($update_audio);
6372
6373
        $list = '<ul id="lp_item_list">';
6374
        $tree = $this->print_recursive(
6375
            $result['elements'],
6376
            $result['default_data'],
6377
            $result['default_content']
6378
        );
6379
6380
        if (!empty($tree)) {
6381
            $list .= $tree;
6382
        } else {
6383
            if ($drop_element_here) {
6384
                $list .= Display::return_message(get_lang('Drag and drop an element here'));
6385
            }
6386
        }
6387
        $list .= '</ul>';
6388
6389
        $return = Display::panelCollapse(
6390
            $this->name,
6391
            $list,
6392
            'scorm-list',
6393
            null,
6394
            'scorm-list-accordion',
6395
            'scorm-list-collapse'
6396
        );
6397
6398
        if ('true' === $update_audio) {
6399
            $return = $result['return_audio'];
6400
        }
6401
6402
        return $return;
6403
    }
6404
6405
    /**
6406
     * @param array $elements
6407
     * @param array $default_data
6408
     * @param array $default_content
6409
     *
6410
     * @return string
6411
     */
6412
    public function print_recursive($elements, $default_data, $default_content)
6413
    {
6414
        $return = '';
6415
        foreach ($elements as $key => $item) {
6416
            if (isset($item['load_data']) || empty($item['data'])) {
6417
                $item['data'] = $default_data[$item['load_data']];
6418
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6419
            }
6420
            $sub_list = '';
6421
            if (isset($item['type']) && 'dir' === $item['type']) {
6422
                // empty value
6423
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6424
            }
6425
            if (empty($item['children'])) {
6426
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6427
                $active = null;
6428
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6429
                    $active = 'active';
6430
                }
6431
                $return .= Display::tag(
6432
                    'li',
6433
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6434
                    ['id' => $key, 'class' => 'record li_container']
6435
                );
6436
            } else {
6437
                // Sections
6438
                $data = '';
6439
                if (isset($item['children'])) {
6440
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6441
                }
6442
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6443
                $return .= Display::tag(
6444
                    'li',
6445
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6446
                    ['id' => $key, 'class' => 'record li_container']
6447
                );
6448
            }
6449
        }
6450
6451
        return $return;
6452
    }
6453
6454
    /**
6455
     * This function builds the action menu.
6456
     *
6457
     * @param bool $returnContent          Optional
6458
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6459
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6460
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6461
     *
6462
     * @return string
6463
     */
6464
    public function build_action_menu(
6465
        $returnContent = false,
6466
        $showRequirementButtons = true,
6467
        $isConfigPage = false,
6468
        $allowExpand = true
6469
    ) {
6470
        $actionsRight = '';
6471
        $actionsLeft = Display::url(
6472
            Display::return_icon(
6473
                'back.png',
6474
                get_lang('Back to learning paths'),
6475
                '',
6476
                ICON_SIZE_MEDIUM
6477
            ),
6478
            'lp_controller.php?'.api_get_cidreq()
6479
        );
6480
        $actionsLeft .= Display::url(
6481
            Display::return_icon(
6482
                'preview_view.png',
6483
                get_lang('Preview'),
6484
                '',
6485
                ICON_SIZE_MEDIUM
6486
            ),
6487
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6488
                'action' => 'view',
6489
                'lp_id' => $this->lp_id,
6490
                'isStudentView' => 'true',
6491
            ])
6492
        );
6493
6494
        $actionsLeft .= Display::url(
6495
            Display::return_icon(
6496
                'upload_audio.png',
6497
                get_lang('Add audio'),
6498
                '',
6499
                ICON_SIZE_MEDIUM
6500
            ),
6501
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6502
                'action' => 'admin_view',
6503
                'lp_id' => $this->lp_id,
6504
                'updateaudio' => 'true',
6505
            ])
6506
        );
6507
6508
        $subscriptionSettings = self::getSubscriptionSettings();
6509
6510
        $request = api_request_uri();
6511
        if (false === strpos($request, 'edit')) {
6512
            $actionsLeft .= Display::url(
6513
                Display::return_icon(
6514
                    'settings.png',
6515
                    get_lang('Course settings'),
6516
                    '',
6517
                    ICON_SIZE_MEDIUM
6518
                ),
6519
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6520
                    'action' => 'edit',
6521
                    'lp_id' => $this->lp_id,
6522
                ])
6523
            );
6524
        }
6525
6526
        if (false === strpos($request, 'build') && false === strpos($request, 'add_item')) {
6527
            $actionsLeft .= Display::url(
6528
                Display::return_icon(
6529
                    'edit.png',
6530
                    get_lang('Edit'),
6531
                    '',
6532
                    ICON_SIZE_MEDIUM
6533
                ),
6534
                'lp_controller.php?'.http_build_query([
6535
                    'action' => 'build',
6536
                    'lp_id' => $this->lp_id,
6537
                ]).'&'.api_get_cidreq()
6538
            );
6539
        }
6540
6541
        if (false === strpos(api_get_self(), 'lp_subscribe_users.php')) {
6542
            if (1 == $this->subscribeUsers &&
6543
                $subscriptionSettings['allow_add_users_to_lp']) {
6544
                $actionsLeft .= Display::url(
6545
                    Display::return_icon(
6546
                        'user.png',
6547
                        get_lang('Subscribe users to learning path'),
6548
                        '',
6549
                        ICON_SIZE_MEDIUM
6550
                    ),
6551
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6552
                );
6553
            }
6554
        }
6555
6556
        if ($allowExpand) {
6557
            /*$actionsLeft .= Display::url(
6558
                Display::return_icon(
6559
                    'expand.png',
6560
                    get_lang('Expand'),
6561
                    ['id' => 'expand'],
6562
                    ICON_SIZE_MEDIUM
6563
                ).
6564
                Display::return_icon(
6565
                    'contract.png',
6566
                    get_lang('Collapse'),
6567
                    ['id' => 'contract', 'class' => 'hide'],
6568
                    ICON_SIZE_MEDIUM
6569
                ),
6570
                '#',
6571
                ['role' => 'button', 'id' => 'hide_bar_template']
6572
            );*/
6573
        }
6574
6575
        if ($showRequirementButtons) {
6576
            $buttons = [
6577
                [
6578
                    'title' => get_lang('Set previous step as prerequisite for each step'),
6579
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6580
                        'action' => 'set_previous_step_as_prerequisite',
6581
                        'lp_id' => $this->lp_id,
6582
                    ]),
6583
                ],
6584
                [
6585
                    'title' => get_lang('Clear all prerequisites'),
6586
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6587
                        'action' => 'clear_prerequisites',
6588
                        'lp_id' => $this->lp_id,
6589
                    ]),
6590
                ],
6591
            ];
6592
            $actionsRight = Display::groupButtonWithDropDown(
6593
                get_lang('Prerequisites options'),
6594
                $buttons,
6595
                true
6596
            );
6597
        }
6598
6599
        $toolbar = Display::toolbarAction(
6600
            'actions-lp-controller',
6601
            [$actionsLeft, $actionsRight]
6602
        );
6603
6604
        if ($returnContent) {
6605
            return $toolbar;
6606
        }
6607
6608
        echo $toolbar;
6609
    }
6610
6611
    /**
6612
     * Creates the default learning path folder.
6613
     *
6614
     * @param array $course
6615
     * @param int   $creatorId
6616
     *
6617
     * @return bool
6618
     */
6619
    public static function generate_learning_path_folder($course, $creatorId = 0)
6620
    {
6621
        // Creating learning_path folder
6622
        $dir = 'learning_path';
6623
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6624
        $folder = false;
6625
        $folderData = create_unexisting_directory(
6626
            $course,
6627
            $creatorId,
6628
            0,
6629
            null,
6630
            0,
6631
            '',
6632
            $dir,
6633
            get_lang('Learning paths'),
6634
            0
6635
        );
6636
6637
        if (!empty($folderData)) {
6638
            $folder = true;
6639
        }
6640
6641
        return $folder;
6642
    }
6643
6644
    /**
6645
     * @param array  $course
6646
     * @param string $lp_name
6647
     * @param int    $creatorId
6648
     *
6649
     * @return array
6650
     */
6651
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6652
    {
6653
        $filepath = '';
6654
        $dir = '/learning_path/';
6655
6656
        if (empty($lp_name)) {
6657
            $lp_name = $this->name;
6658
        }
6659
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6660
        $folder = self::generate_learning_path_folder($course, $creatorId);
6661
6662
        // Limits title size
6663
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6664
        $dir = $dir.$title;
6665
6666
        // Creating LP folder
6667
        $documentId = null;
6668
        if ($folder) {
6669
            $folderData = create_unexisting_directory(
6670
                $course,
6671
                $creatorId,
6672
                0,
6673
                0,
6674
                0,
6675
                $filepath,
6676
                $dir,
6677
                $lp_name
6678
            );
6679
            if (!empty($folderData)) {
6680
                $folder = true;
6681
            }
6682
6683
            $documentId = $folderData->getIid();
6684
            $dir = $dir.'/';
6685
            if ($folder) {
6686
                // $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6687
            }
6688
        }
6689
6690
        if (empty($documentId)) {
6691
            $dir = api_remove_trailing_slash($dir);
6692
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6693
        }
6694
6695
        $array = [
6696
            'dir' => $dir,
6697
            'filepath' => $filepath,
6698
            'folder' => $folder,
6699
            'id' => $documentId,
6700
        ];
6701
6702
        return $array;
6703
    }
6704
6705
    /**
6706
     * Create a new document //still needs some finetuning.
6707
     *
6708
     * @param array  $courseInfo
6709
     * @param string $content
6710
     * @param string $title
6711
     * @param string $extension
6712
     * @param int    $parentId
6713
     * @param int    $creatorId  creator id
6714
     *
6715
     * @return int
6716
     */
6717
    public function create_document(
6718
        $courseInfo,
6719
        $content = '',
6720
        $title = '',
6721
        $extension = 'html',
6722
        $parentId = 0,
6723
        $creatorId = 0
6724
    ) {
6725
        if (!empty($courseInfo)) {
6726
            $course_id = $courseInfo['real_id'];
6727
        } else {
6728
            $course_id = api_get_course_int_id();
6729
        }
6730
6731
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6732
        $sessionId = api_get_session_id();
6733
6734
        // Generates folder
6735
        $result = $this->generate_lp_folder($courseInfo);
6736
        $dir = $result['dir'];
6737
6738
        if (empty($parentId) || '/' == $parentId) {
6739
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6740
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6741
6742
            if ('/' === $parentId) {
6743
                $dir = '/';
6744
            }
6745
6746
            // Please, do not modify this dirname formatting.
6747
            if (strstr($dir, '..')) {
6748
                $dir = '/';
6749
            }
6750
6751
            if (!empty($dir[0]) && '.' == $dir[0]) {
6752
                $dir = substr($dir, 1);
6753
            }
6754
            if (!empty($dir[0]) && '/' != $dir[0]) {
6755
                $dir = '/'.$dir;
6756
            }
6757
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
6758
                $dir .= '/';
6759
            }
6760
        } else {
6761
            $parentInfo = DocumentManager::get_document_data_by_id(
6762
                $parentId,
6763
                $courseInfo['code']
6764
            );
6765
            if (!empty($parentInfo)) {
6766
                $dir = $parentInfo['path'].'/';
6767
            }
6768
        }
6769
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6770
        // is already escaped twice when it gets here.
6771
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6772
        if (!empty($title)) {
6773
            $title = api_replace_dangerous_char(stripslashes($title));
6774
        } else {
6775
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6776
        }
6777
6778
        $title = disable_dangerous_file($title);
6779
        $filename = $title;
6780
        $content = !empty($content) ? $content : $_POST['content_lp'];
6781
        $tmp_filename = $filename;
6782
        $filename = $tmp_filename.'.'.$extension;
6783
6784
        if ('html' === $extension) {
6785
            $content = stripslashes($content);
6786
            $content = str_replace(
6787
                api_get_path(WEB_COURSE_PATH),
6788
                api_get_path(REL_PATH).'courses/',
6789
                $content
6790
            );
6791
6792
            // Change the path of mp3 to absolute.
6793
            // The first regexp deals with :// urls.
6794
            $content = preg_replace(
6795
                "|(flashvars=\"file=)([^:/]+)/|",
6796
                "$1".api_get_path(
6797
                    REL_COURSE_PATH
6798
                ).$courseInfo['path'].'/document/',
6799
                $content
6800
            );
6801
            // The second regexp deals with audio/ urls.
6802
            $content = preg_replace(
6803
                "|(flashvars=\"file=)([^/]+)/|",
6804
                "$1".api_get_path(
6805
                    REL_COURSE_PATH
6806
                ).$courseInfo['path'].'/document/$2/',
6807
                $content
6808
            );
6809
            // For flv player: To prevent edition problem with firefox,
6810
            // we have to use a strange tip (don't blame me please).
6811
            $content = str_replace(
6812
                '</body>',
6813
                '<style type="text/css">body{}</style></body>',
6814
                $content
6815
            );
6816
        }
6817
6818
        $save_file_path = $dir.$filename;
6819
6820
        $document = DocumentManager::addDocument(
6821
            $courseInfo,
6822
            $save_file_path,
6823
            'file',
6824
            '',
6825
            $tmp_filename,
6826
            '',
6827
            0, //readonly
6828
            true,
6829
            null,
6830
            $sessionId,
6831
            $creatorId,
6832
            false,
6833
            $content,
6834
            $parentId
6835
        );
6836
6837
        $document_id = $document->getIid();
6838
        if ($document_id) {
6839
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6840
            $new_title = $originalTitle;
6841
6842
            if ($new_comment || $new_title) {
6843
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6844
                $ct = '';
6845
                if ($new_comment) {
6846
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6847
                }
6848
                if ($new_title) {
6849
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6850
                }
6851
6852
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6853
                        WHERE c_id = $course_id AND id = $document_id ";
6854
                Database::query($sql);
6855
            }
6856
        }
6857
6858
        return $document_id;
6859
    }
6860
6861
    /**
6862
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6863
     *
6864
     */
6865
    public function edit_document()
6866
    {
6867
        $repo = Container::getDocumentRepository();
6868
        if (isset($_REQUEST['document_id']) && !empty($_REQUEST['document_id'])) {
6869
            $id = (int) $_REQUEST['document_id'];
6870
            /** @var CDocument $document */
6871
            $document = $repo->find($id);
6872
6873
            if ($document->getResourceNode()->hasEditableContent()) {
6874
                $repo->updateResourceFileContent($document, $_REQUEST['content_lp']);
6875
            }
6876
6877
            $document->setTitle($_REQUEST['title']);
6878
            $repo->getEntityManager()->persist($document);
6879
            $repo->getEntityManager()->flush();
6880
        }
6881
    }
6882
6883
    /**
6884
     * Displays the selected item, with a panel for manipulating the item.
6885
     *
6886
     * @param CLpItem $lpItem
6887
     * @param string  $msg
6888
     * @param bool    $show_actions
6889
     *
6890
     * @return string
6891
     */
6892
    public function display_item($lpItem, $msg = null, $show_actions = true)
6893
    {
6894
        $course_id = api_get_course_int_id();
6895
        $return = '';
6896
6897
        if (empty($lpItem)) {
6898
            return '';
6899
        }
6900
        $item_id = $lpItem->getIid();
6901
        $itemType = $lpItem->getItemType();
6902
        $lpId = $lpItem->getLpId();
6903
        $path = $lpItem->getPath();
6904
6905
        Session::write('parent_item_id', 'dir' === $itemType ? $item_id : 0);
6906
6907
        // Prevents wrong parent selection for document, see Bug#1251.
6908
        if ('dir' !== $itemType) {
6909
            Session::write('parent_item_id', $lpItem->getParentItemId());
6910
        }
6911
6912
        if ($show_actions) {
6913
            $return .= $this->displayItemMenu($lpItem);
6914
        }
6915
        $return .= '<div style="padding:10px;">';
6916
6917
        if ('' != $msg) {
6918
            $return .= $msg;
6919
        }
6920
6921
        $return .= '<h3>'.$lpItem->getTitle().'</h3>';
6922
6923
        switch ($itemType) {
6924
            case TOOL_THREAD:
6925
                $link = $this->rl_get_resource_link_for_learnpath(
6926
                    $course_id,
6927
                    $lpId,
6928
                    $item_id,
6929
                    0
6930
                );
6931
                $return .= Display::url(
6932
                    get_lang('Go to thread'),
6933
                    $link,
6934
                    ['class' => 'btn btn-primary']
6935
                );
6936
                break;
6937
            case TOOL_FORUM:
6938
                $return .= Display::url(
6939
                    get_lang('Go to the forum'),
6940
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$path,
6941
                    ['class' => 'btn btn-primary']
6942
                );
6943
                break;
6944
            case TOOL_QUIZ:
6945
                if (!empty($path)) {
6946
                    $exercise = new Exercise();
6947
                    $exercise->read($path);
6948
                    $return .= $exercise->description.'<br />';
6949
                    $return .= Display::url(
6950
                        get_lang('Go to exercise'),
6951
                        api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6952
                        ['class' => 'btn btn-primary']
6953
                    );
6954
                }
6955
                break;
6956
            case TOOL_LP_FINAL_ITEM:
6957
                $return .= $this->getSavedFinalItem();
6958
                break;
6959
            case TOOL_DOCUMENT:
6960
            case TOOL_READOUT_TEXT:
6961
                $repo = Container::getDocumentRepository();
6962
                /** @var CDocument $document */
6963
                $document = $repo->find($lpItem->getPath());
6964
                $return .= $this->display_document($document, true, true);
6965
                break;
6966
            case TOOL_HOTPOTATOES:
6967
                $return .= $this->display_document($document, false, true);
6968
                break;
6969
        }
6970
        $return .= '</div>';
6971
6972
        return $return;
6973
    }
6974
6975
    /**
6976
     * Shows the needed forms for editing a specific item.
6977
     *
6978
     * @param CLpItem $lpItem
6979
     *
6980
     * @throws Exception
6981
     * @throws HTML_QuickForm_Error
6982
     *
6983
     * @return string
6984
     */
6985
    public function display_edit_item($lpItem)
6986
    {
6987
        $course_id = api_get_course_int_id();
6988
        $return = '';
6989
6990
        if (empty($lpItem)) {
6991
            return '';
6992
        }
6993
        $item_id  = $lpItem->getIid();
6994
        $itemType = $lpItem->getItemType();
6995
        $path = $lpItem->getPath();
6996
6997
        switch ($itemType) {
6998
            case 'dir':
6999
            case 'asset':
7000
            case 'sco':
7001
                if (isset($_GET['view']) && 'build' === $_GET['view']) {
7002
                    $return .= $this->displayItemMenu($lpItem);
7003
                    $return .= $this->display_item_form(
7004
                        $lpItem,
7005
                        'edit'
7006
                    );
7007
                } else {
7008
                    $return .= $this->display_item_form(
7009
                        $lpItem,
7010
                        'edit_item'
7011
                    );
7012
                }
7013
                break;
7014
            case TOOL_LP_FINAL_ITEM:
7015
            case TOOL_DOCUMENT:
7016
            case TOOL_READOUT_TEXT:
7017
                $return .= $this->displayItemMenu($lpItem);
7018
                $return .= $this->displayDocumentForm('edit', $lpItem);
7019
                break;
7020
            case TOOL_LINK:
7021
                $link = null;
7022
                if (!empty($path)) {
7023
                    $repo = Container::getLinkRepository();
7024
                    $link = $repo->find($path);
7025
                }
7026
                $return .= $this->displayItemMenu($lpItem);
7027
                $return .= $this->display_link_form('edit', $lpItem, $link);
7028
7029
                break;
7030
            case TOOL_QUIZ:
7031
                if (!empty($path)) {
7032
                    $repo = Container::getExerciseRepository();
7033
                    $resource = $repo->find($path);
7034
                }
7035
                $return .= $this->displayItemMenu($lpItem);
7036
                $return .= $this->display_quiz_form('edit', $lpItem, $resource);
7037
                break;
7038
            /*case TOOL_HOTPOTATOES:
7039
                $return .= $this->displayItemMenu($lpItem);
7040
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7041
                break;*/
7042
            case TOOL_STUDENTPUBLICATION:
7043
                if (!empty($path)) {
7044
                    $repo = Container::getStudentPublicationRepository();
7045
                    $resource = $repo->find($path);
7046
                }
7047
                $return .= $this->displayItemMenu($lpItem);
7048
                $return .= $this->display_student_publication_form('edit', $lpItem, $resource);
7049
                break;
7050
            case TOOL_FORUM:
7051
                if (!empty($path)) {
7052
                    $repo = Container::getForumRepository();
7053
                    $resource = $repo->find($path);
7054
                }
7055
                $return .= $this->displayItemMenu($lpItem);
7056
                $return .= $this->display_forum_form('edit', $lpItem, $resource);
7057
                break;
7058
            case TOOL_THREAD:
7059
                if (!empty($path)) {
7060
                    $repo = Container::getForumPostRepository();
7061
                    $resource = $repo->find($path);
7062
                }
7063
                $return .= $this->displayItemMenu($lpItem);
7064
                $return .= $this->display_thread_form('edit', $lpItem, $resource);
7065
                break;
7066
        }
7067
7068
        return $return;
7069
    }
7070
7071
    /**
7072
     * Function that displays a list with al the resources that
7073
     * could be added to the learning path.
7074
     *
7075
     * @throws Exception
7076
     * @throws HTML_QuickForm_Error
7077
     *
7078
     * @return bool
7079
     */
7080
    public function displayResources()
7081
    {
7082
        $course_code = api_get_course_id();
7083
7084
        // Get all the docs.
7085
        $documents = $this->get_documents(true);
7086
7087
        // Get all the exercises.
7088
        $exercises = $this->get_exercises();
7089
7090
        // Get all the links.
7091
        $links = $this->get_links();
7092
7093
        // Get all the student publications.
7094
        $works = $this->get_student_publications();
7095
7096
        // Get all the forums.
7097
        $forums = $this->get_forums(null, $course_code);
7098
7099
        // Get the final item form (see BT#11048) .
7100
        $finish = $this->getFinalItemForm();
7101
7102
        $headers = [
7103
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7104
            Display::return_icon('quiz.png', get_lang('Tests'), [], ICON_SIZE_BIG),
7105
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7106
            Display::return_icon('works.png', get_lang('Assignments'), [], ICON_SIZE_BIG),
7107
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7108
            Display::return_icon('add_learnpath_section.png', get_lang('Add section'), [], ICON_SIZE_BIG),
7109
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7110
        ];
7111
7112
        echo Display::return_message(get_lang('Click on the [Learner view] button to see your learning path'), 'normal');
7113
        $section = $this->displayNewSectionForm();
7114
7115
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7116
7117
        echo Display::tabs(
7118
            $headers,
7119
            [
7120
                $documents,
7121
                $exercises,
7122
                $links,
7123
                $works,
7124
                $forums,
7125
                $section,
7126
                $finish,
7127
            ],
7128
            'resource_tab',
7129
            [],
7130
            [],
7131
            $selected
7132
        );
7133
7134
        return true;
7135
    }
7136
7137
    /**
7138
     * Returns the extension of a document.
7139
     *
7140
     * @param string $filename
7141
     *
7142
     * @return string Extension (part after the last dot)
7143
     */
7144
    public function get_extension($filename)
7145
    {
7146
        $explode = explode('.', $filename);
7147
7148
        return $explode[count($explode) - 1];
7149
    }
7150
7151
    /**
7152
     * @return string
7153
     */
7154
    public function getCurrentBuildingModeURL()
7155
    {
7156
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
7157
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
7158
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
7159
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
7160
7161
        $currentUrl = api_get_self().'?'.api_get_cidreq().
7162
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
7163
7164
        return $currentUrl;
7165
    }
7166
7167
    /**
7168
     * Displays a document by id.
7169
     *
7170
     * @param CDocument  $document
7171
     * @param bool $show_title
7172
     * @param bool $iframe
7173
     * @param bool $edit_link
7174
     *
7175
     * @return string
7176
     */
7177
    public function display_document($document, $show_title = false, $iframe = true, $edit_link = false)
7178
    {
7179
        $return = '';
7180
        if (!$document) {
7181
            return '';
7182
        }
7183
7184
        $repo = Container::getDocumentRepository();
7185
7186
        // TODO: Add a path filter.
7187
        if ($iframe) {
7188
            //$url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq();
7189
            $url = $repo->getResourceFileUrl($document);
7190
7191
            $return .= '<iframe
7192
                id="learnpath_preview_frame"
7193
                frameborder="0"
7194
                height="400"
7195
                width="100%"
7196
                scrolling="auto"
7197
                src="'.$url.'"></iframe>';
7198
        } else {
7199
            $return = $repo->getResourceFileContent($document);
7200
        }
7201
7202
        return $return;
7203
    }
7204
7205
    /**
7206
     * Return HTML form to add/edit a link item.
7207
     *
7208
     * @param string $action     (add/edit)
7209
     * @param CLpItem    $lpItem
7210
     * @param CLink  $link
7211
     *
7212
     * @throws Exception
7213
     * @throws HTML_QuickForm_Error
7214
     *
7215
     * @return string HTML form
7216
     */
7217
    public function display_link_form($action = 'add', $lpItem, $link)
7218
    {
7219
        $item_url = '';
7220
        if ($link) {
7221
            $item_url = stripslashes($link->getUrl());
7222
        }
7223
        $form = new FormValidator(
7224
            'edit_link',
7225
            'POST',
7226
            $this->getCurrentBuildingModeURL()
7227
        );
7228
7229
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7230
7231
        $urlAttributes = ['class' => 'learnpath_item_form'];
7232
        $urlAttributes['disabled'] = 'disabled';
7233
        $form->addElement('url', 'url', get_lang('URL'), $urlAttributes);
7234
        $form->setDefault('url', $item_url);
7235
7236
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7237
7238
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7239
    }
7240
7241
    /**
7242
     * Return HTML form to add/edit a quiz.
7243
     *
7244
     * @param string $action     Action (add/edit)
7245
     * @param CLpItem    $lpItem         Item ID if already exists
7246
     * @param CQuiz  $exercise Extra information (quiz ID if integer)
7247
     *
7248
     * @throws Exception
7249
     *
7250
     * @return string HTML form
7251
     */
7252
    public function display_quiz_form($action = 'add', $lpItem, $exercise)
7253
    {
7254
        $form = new FormValidator(
7255
            'quiz_form',
7256
            'POST',
7257
            $this->getCurrentBuildingModeURL()
7258
        );
7259
7260
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7261
7262
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7263
7264
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7265
    }
7266
7267
    /**
7268
     * Return the form to display the forum edit/add option.
7269
     *
7270
     * @param CLpItem $lpItem
7271
     *
7272
     * @throws Exception
7273
     *
7274
     * @return string HTML form
7275
     */
7276
    public function display_forum_form($action = 'add', $lpItem, $resource)
7277
    {
7278
        $form = new FormValidator(
7279
            'forum_form',
7280
            'POST',
7281
            $this->getCurrentBuildingModeURL()
7282
        );
7283
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7284
7285
        if ('add' === $action) {
7286
            $form->addButtonSave(get_lang('Add forum to course'), 'submit_button');
7287
        } else {
7288
            $form->addButtonSave(get_lang('Edit the current forum'), 'submit_button');
7289
        }
7290
7291
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7292
    }
7293
7294
    /**
7295
     * Return HTML form to add/edit forum threads.
7296
     *
7297
     * @param string $action
7298
     * @param CLpItem    $lpItem
7299
     * @param string $resource
7300
     *
7301
     * @throws Exception
7302
     *
7303
     * @return string HTML form
7304
     */
7305
    public function display_thread_form($action = 'add', $lpItem, $resource)
7306
    {
7307
        $form = new FormValidator(
7308
            'thread_form',
7309
            'POST',
7310
            $this->getCurrentBuildingModeURL()
7311
        );
7312
7313
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7314
7315
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7316
7317
        return $form->returnForm();
7318
    }
7319
7320
    /**
7321
     * Return the HTML form to display an item (generally a dir item).
7322
     *
7323
     * @param CLpItem $lpItem
7324
     * @param string $title
7325
     * @param string $action
7326
     * @param string $extra_info
7327
     *
7328
     * @throws Exception
7329
     * @throws HTML_QuickForm_Error
7330
     *
7331
     * @return string HTML form
7332
     */
7333
    public function display_item_form(
7334
        $lpItem,
7335
        $action = 'add_item'
7336
    ) {
7337
        $item_type = $lpItem->getItemType();
7338
7339
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7340
7341
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7342
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7343
7344
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7345
7346
        return $form->returnForm();
7347
    }
7348
7349
7350
    /**
7351
     * Return HTML form to add/edit a student publication (work).
7352
     *
7353
     * @param string $action
7354
     * @param CLpItem    $lpItem
7355
     * @param CStudentPublication $resource
7356
     *
7357
     * @throws Exception
7358
     *
7359
     * @return string HTML form
7360
     */
7361
    public function display_student_publication_form(
7362
        $action = 'add',
7363
        CLpItem $lpItem,
7364
        $resource
7365
    ) {
7366
        $form = new FormValidator('frm_student_publication', 'post', '#');
7367
        LearnPathItemForm::setForm($form, 'edit', $this, $lpItem);
7368
7369
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7370
7371
        $return = '<div class="sectioncomment">';
7372
        $return .= $form->returnForm();
7373
        $return .= '</div>';
7374
7375
        return $return;
7376
    }
7377
7378
    public function displayNewSectionForm()
7379
    {
7380
        $action = 'add_item';
7381
        $item_type = 'dir';
7382
7383
        $lpItem = new CLpItem();
7384
        $lpItem->setItemType('dir');
7385
7386
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
7387
7388
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
7389
        LearnPathItemForm::setForm($form, 'add', $this, $lpItem);
7390
7391
        $form->addButtonSave(get_lang('Save section'), 'submit_button');
7392
        $form->addElement('hidden', 'type', 'dir');
7393
7394
        return $form->returnForm();
7395
    }
7396
7397
7398
    /**
7399
     * Returns the form to update or create a document.
7400
     *
7401
     * @param string  $action (add/edit)
7402
     * @param CLpItem $lpItem
7403
     *
7404
     * @return string HTML form
7405
     * @throws HTML_QuickForm_Error
7406
     *
7407
     * @throws Exception
7408
     */
7409
    public function displayDocumentForm($action = 'add', $lpItem = null)
7410
    {
7411
        if (empty($lpItem)) {
7412
            return '';
7413
        }
7414
7415
        $courseInfo = api_get_course_info();
7416
7417
        $form = new FormValidator(
7418
            'form',
7419
            'POST',
7420
            $this->getCurrentBuildingModeURL(),
7421
            '',
7422
            ['enctype' => 'multipart/form-data']
7423
        );
7424
7425
        $data = $this->generate_lp_folder($courseInfo);
7426
7427
        LearnPathItemForm::setForm($form, $action, $this, $lpItem);
7428
7429
        switch ($action) {
7430
            case 'add':
7431
                $folders = DocumentManager::get_all_document_folders(
7432
                    $courseInfo,
7433
                    0,
7434
                    true
7435
                );
7436
                DocumentManager::build_directory_selector(
7437
                    $folders,
7438
                    '',
7439
                    [],
7440
                    true,
7441
                    $form,
7442
                    'directory_parent_id'
7443
                );
7444
7445
                if (isset($data['id'])) {
7446
                    $defaults['directory_parent_id'] = $data['id'];
7447
                }
7448
7449
                break;
7450
        }
7451
7452
        $form->addButtonSave(get_lang('Save'), 'submit_button');
7453
7454
        return $form->returnForm();
7455
    }
7456
7457
    /**
7458
     * @param array  $courseInfo
7459
     * @param string $content
7460
     * @param string $title
7461
     * @param int    $parentId
7462
     *
7463
     * @throws \Doctrine\ORM\ORMException
7464
     * @throws \Doctrine\ORM\OptimisticLockException
7465
     * @throws \Doctrine\ORM\TransactionRequiredException
7466
     *
7467
     * @return int
7468
     */
7469
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
7470
    {
7471
        $creatorId = api_get_user_id();
7472
        $sessionId = api_get_session_id();
7473
7474
        // Generates folder
7475
        $result = $this->generate_lp_folder($courseInfo);
7476
        $dir = $result['dir'];
7477
7478
        if (empty($parentId) || '/' == $parentId) {
7479
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7480
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7481
7482
            if ('/' === $parentId) {
7483
                $dir = '/';
7484
            }
7485
7486
            // Please, do not modify this dirname formatting.
7487
            if (strstr($dir, '..')) {
7488
                $dir = '/';
7489
            }
7490
7491
            if (!empty($dir[0]) && '.' == $dir[0]) {
7492
                $dir = substr($dir, 1);
7493
            }
7494
            if (!empty($dir[0]) && '/' != $dir[0]) {
7495
                $dir = '/'.$dir;
7496
            }
7497
            if (isset($dir[strlen($dir) - 1]) && '/' != $dir[strlen($dir) - 1]) {
7498
                $dir .= '/';
7499
            }
7500
        } else {
7501
            $parentInfo = DocumentManager::get_document_data_by_id(
7502
                $parentId,
7503
                $courseInfo['code']
7504
            );
7505
            if (!empty($parentInfo)) {
7506
                $dir = $parentInfo['path'].'/';
7507
            }
7508
        }
7509
7510
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7511
7512
        if (!is_dir($filepath)) {
7513
            $dir = '/';
7514
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7515
        }
7516
7517
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7518
7519
        if (!empty($title)) {
7520
            $title = api_replace_dangerous_char(stripslashes($title));
7521
        } else {
7522
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7523
        }
7524
7525
        $title = disable_dangerous_file($title);
7526
        $filename = $title;
7527
        $content = !empty($content) ? $content : $_POST['content_lp'];
7528
        $tmpFileName = $filename;
7529
7530
        $i = 0;
7531
        while (file_exists($filepath.$tmpFileName.'.html')) {
7532
            $tmpFileName = $filename.'_'.++$i;
7533
        }
7534
7535
        $filename = $tmpFileName.'.html';
7536
        $content = stripslashes($content);
7537
7538
        if (file_exists($filepath.$filename)) {
7539
            return 0;
7540
        }
7541
7542
        $putContent = file_put_contents($filepath.$filename, $content);
7543
7544
        if (false === $putContent) {
7545
            return 0;
7546
        }
7547
7548
        $fileSize = filesize($filepath.$filename);
7549
        $saveFilePath = $dir.$filename;
7550
7551
        $document = DocumentManager::addDocument(
7552
            $courseInfo,
7553
            $saveFilePath,
7554
            'file',
7555
            $fileSize,
7556
            $tmpFileName,
7557
            '',
7558
            0, //readonly
7559
            true,
7560
            null,
7561
            $sessionId,
7562
            $creatorId
7563
        );
7564
7565
        $documentId = $document->getId();
7566
7567
        if (!$document) {
7568
            return 0;
7569
        }
7570
7571
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7572
        $newTitle = $originalTitle;
7573
7574
        if ($newComment || $newTitle) {
7575
            $em = Database::getManager();
7576
7577
            if ($newComment) {
7578
                $document->setComment($newComment);
7579
            }
7580
7581
            if ($newTitle) {
7582
                $document->setTitle($newTitle);
7583
            }
7584
7585
            $em->persist($document);
7586
            $em->flush();
7587
        }
7588
7589
        return $documentId;
7590
    }
7591
7592
    /**
7593
     * Displays the menu for manipulating a step.
7594
     *
7595
     * @return string
7596
     */
7597
    public function displayItemMenu(CLpItem $lpItem)
7598
    {
7599
        $item_id = $lpItem->getIid();
7600
        $audio = $lpItem->getAudio();
7601
        $itemType = $lpItem->getItemType();
7602
        $path = $lpItem->getPath();
7603
7604
        $return = '<div class="actions">';
7605
        $audio_player = null;
7606
        // We display an audio player if needed.
7607
        if (!empty($audio)) {
7608
            /*$webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
7609
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
7610
                .'<audio src="'.$webAudioPath.'" controls>'
7611
                .'</div><br>';*/
7612
        }
7613
7614
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
7615
7616
        if (TOOL_LP_FINAL_ITEM !== $itemType) {
7617
            $return .= Display::url(
7618
                Display::return_icon(
7619
                    'edit.png',
7620
                    get_lang('Edit'),
7621
                    [],
7622
                    ICON_SIZE_SMALL
7623
                ),
7624
                $url.'&action=edit_item&path_item='.$path
7625
            );
7626
7627
            /*$return .= Display::url(
7628
                Display::return_icon(
7629
                    'move.png',
7630
                    get_lang('Move'),
7631
                    [],
7632
                    ICON_SIZE_SMALL
7633
                ),
7634
                $url.'&action=move_item'
7635
            );*/
7636
        }
7637
7638
        // Commented for now as prerequisites cannot be added to chapters.
7639
        if ('dir' !== $itemType) {
7640
            $return .= Display::url(
7641
                Display::return_icon(
7642
                    'accept.png',
7643
                    get_lang('Prerequisites'),
7644
                    [],
7645
                    ICON_SIZE_SMALL
7646
                ),
7647
                $url.'&action=edit_item_prereq'
7648
            );
7649
        }
7650
        $return .= Display::url(
7651
            Display::return_icon(
7652
                'delete.png',
7653
                get_lang('Delete'),
7654
                [],
7655
                ICON_SIZE_SMALL
7656
            ),
7657
            $url.'&action=delete_item'
7658
        );
7659
7660
        /*if (in_array($itemType, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
7661
            $documentData = DocumentManager::get_document_data_by_id($path, $course_code);
7662
            if (empty($documentData)) {
7663
                // Try with iid
7664
                $table = Database::get_course_table(TABLE_DOCUMENT);
7665
                $sql = "SELECT path FROM $table
7666
                        WHERE
7667
                              c_id = ".api_get_course_int_id()." AND
7668
                              iid = ".$path." AND
7669
                              path NOT LIKE '%_DELETED_%'";
7670
                $result = Database::query($sql);
7671
                $documentData = Database::fetch_array($result);
7672
                if ($documentData) {
7673
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
7674
                }
7675
            }
7676
            if (isset($documentData['absolute_path_from_document'])) {
7677
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
7678
            }
7679
        }*/
7680
7681
        $return .= '</div>';
7682
7683
        if (!empty($audio_player)) {
7684
            $return .= $audio_player;
7685
        }
7686
7687
        return $return;
7688
    }
7689
7690
    /**
7691
     * Creates the javascript needed for filling up the checkboxes without page reload.
7692
     *
7693
     * @return string
7694
     */
7695
    public function get_js_dropdown_array()
7696
    {
7697
        $course_id = api_get_course_int_id();
7698
        $return = 'var child_name = new Array();'."\n";
7699
        $return .= 'var child_value = new Array();'."\n\n";
7700
        $return .= 'child_name[0] = new Array();'."\n";
7701
        $return .= 'child_value[0] = new Array();'."\n\n";
7702
7703
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7704
        $sql = "SELECT * FROM ".$tbl_lp_item."
7705
                WHERE
7706
                    c_id = $course_id AND
7707
                    lp_id = ".$this->lp_id." AND
7708
                    parent_item_id = 0
7709
                ORDER BY display_order ASC";
7710
        Database::query($sql);
7711
        $i = 0;
7712
7713
        $list = $this->getItemsForForm(true);
7714
7715
        foreach ($list as $row_zero) {
7716
            if (TOOL_LP_FINAL_ITEM !== $row_zero['item_type']) {
7717
                if (TOOL_QUIZ == $row_zero['item_type']) {
7718
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
7719
                }
7720
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
7721
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
7722
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
7723
            }
7724
        }
7725
7726
        $return .= "\n";
7727
        $sql = "SELECT * FROM $tbl_lp_item
7728
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7729
        $res = Database::query($sql);
7730
        while ($row = Database::fetch_array($res)) {
7731
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
7732
                           WHERE
7733
                                c_id = ".$course_id." AND
7734
                                parent_item_id = ".$row['iid']."
7735
                           ORDER BY display_order ASC";
7736
            $res_parent = Database::query($sql_parent);
7737
            $i = 0;
7738
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
7739
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
7740
7741
            while ($row_parent = Database::fetch_array($res_parent)) {
7742
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
7743
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
7744
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
7745
            }
7746
            $return .= "\n";
7747
        }
7748
7749
        $return .= "
7750
            function load_cbo(id) {
7751
                if (!id) {
7752
                    return false;
7753
                }
7754
7755
                var cbo = document.getElementById('previous');
7756
                for(var i = cbo.length - 1; i > 0; i--) {
7757
                    cbo.options[i] = null;
7758
                }
7759
7760
                var k=0;
7761
                for(var i = 1; i <= child_name[id].length; i++){
7762
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
7763
                    option.style.paddingLeft = '40px';
7764
                    cbo.options[i] = option;
7765
                    k = i;
7766
                }
7767
7768
                cbo.options[k].selected = true;
7769
                $('#previous').selectpicker('refresh');
7770
            }";
7771
7772
        return $return;
7773
    }
7774
7775
    /**
7776
     * Display the form to allow moving an item.
7777
     *
7778
     * @param CLpItem $lpItem
7779
     *
7780
     * @throws Exception
7781
     * @throws HTML_QuickForm_Error
7782
     *
7783
     * @return string HTML form
7784
     */
7785
    public function display_move_item($lpItem)
7786
    {
7787
        $return = '';
7788
        $path = $lpItem->getPath();
7789
7790
        if ($lpItem) {
7791
            $itemType = $lpItem->getItemType();
7792
            switch ($itemType) {
7793
                case 'dir':
7794
                case 'asset':
7795
                    $return .= $this->displayItemMenu($lpItem);
7796
                    $return .= $this->display_item_form(
7797
                        $lpItem,
7798
                        get_lang('Move the current section'),
7799
                        'move',
7800
                        $row
7801
                    );
7802
                    break;
7803
                case TOOL_DOCUMENT:
7804
                    $return .= $this->displayItemMenu($lpItem);
7805
                    $return .= $this->displayDocumentForm('move', $lpItem);
7806
                    break;
7807
                case TOOL_LINK:
7808
                    $link = null;
7809
                    if (!empty($path)) {
7810
                        $repo = Container::getLinkRepository();
7811
                        $link = $repo->find($path);
7812
                    }
7813
                    $return .= $this->displayItemMenu($lpItem);
7814
                    $return .= $this->display_link_form('move', $lpItem, $link);
7815
                    break;
7816
                case TOOL_HOTPOTATOES:
7817
                    $return .= $this->displayItemMenu($lpItem);
7818
                    $return .= $this->display_link_form('move', $lpItem, $row);
7819
                    break;
7820
                case TOOL_QUIZ:
7821
                    $return .= $this->displayItemMenu($lpItem);
7822
                    $return .= $this->display_quiz_form('move', $lpItem, $row);
7823
                    break;
7824
                case TOOL_STUDENTPUBLICATION:
7825
                    $return .= $this->displayItemMenu($lpItem);
7826
                    $return .= $this->display_student_publication_form('move', $lpItem, $row);
7827
                    break;
7828
                case TOOL_FORUM:
7829
                    $return .= $this->displayItemMenu($lpItem);
7830
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7831
                    break;
7832
                case TOOL_THREAD:
7833
                    $return .= $this->displayItemMenu($lpItem);
7834
                    $return .= $this->display_forum_form('move', $lpItem, $row);
7835
                    break;
7836
            }
7837
        }
7838
7839
        return $return;
7840
    }
7841
7842
    /**
7843
     * Return HTML form to allow prerequisites selection.
7844
     *
7845
     * @todo use FormValidator
7846
     *
7847
     * @param CLpItem $lpItem
7848
     *
7849
     * @return string HTML form
7850
     */
7851
    public function display_item_prerequisites_form(CLpItem $lpItem)
7852
    {
7853
        $course_id = api_get_course_int_id();
7854
        $prerequisiteId = $lpItem->getPrerequisite();
7855
        $itemId = $lpItem->getIid();
7856
7857
        $return = '<legend>';
7858
        $return .= get_lang('Add/edit prerequisites');
7859
        $return .= '</legend>';
7860
        $return .= '<form method="POST">';
7861
        $return .= '<div class="table-responsive">';
7862
        $return .= '<table class="table table-hover">';
7863
        $return .= '<thead>';
7864
        $return .= '<tr>';
7865
        $return .= '<th>'.get_lang('Prerequisites').'</th>';
7866
        $return .= '<th width="140">'.get_lang('minimum').'</th>';
7867
        $return .= '<th width="140">'.get_lang('maximum').'</th>';
7868
        $return .= '</tr>';
7869
        $return .= '</thead>';
7870
7871
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
7872
        $return .= '<tbody>';
7873
        $return .= '<tr>';
7874
        $return .= '<td colspan="3">';
7875
        $return .= '<div class="radio learnpath"><label for="idnone">';
7876
        $return .= '<input checked="checked" id="idnone" name="prerequisites" type="radio" />';
7877
        $return .= get_lang('none').'</label>';
7878
        $return .= '</div>';
7879
        $return .= '</tr>';
7880
7881
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7882
        $sql = "SELECT * FROM $tbl_lp_item
7883
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7884
        $result = Database::query($sql);
7885
7886
        $selectedMinScore = [];
7887
        $selectedMaxScore = [];
7888
        $masteryScore = [];
7889
        while ($row = Database::fetch_array($result)) {
7890
            if ($row['iid'] == $itemId) {
7891
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
7892
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
7893
            }
7894
            $masteryScore[$row['iid']] = $row['mastery_score'];
7895
        }
7896
7897
        $arrLP = $this->getItemsForForm();
7898
        $this->tree_array($arrLP);
7899
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7900
        unset($this->arrMenu);
7901
7902
        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...
7903
            $item = $arrLP[$i];
7904
7905
            if ($item['id'] == $itemId) {
7906
                break;
7907
            }
7908
7909
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
7910
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
7911
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
7912
7913
            $return .= '<tr>';
7914
            $return .= '<td '.((TOOL_QUIZ != $item['item_type'] && TOOL_HOTPOTATOES != $item['item_type']) ? ' colspan="3"' : '').'>';
7915
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
7916
            $return .= '<label for="id'.$item['id'].'">';
7917
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').('dir' == $item['item_type'] ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
7918
7919
            $icon_name = str_replace(' ', '', $item['item_type']);
7920
7921
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
7922
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
7923
            } else {
7924
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
7925
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
7926
                } else {
7927
                    $return .= Display::return_icon('folder_document.png');
7928
                }
7929
            }
7930
7931
            $return .= $item['title'].'</label>';
7932
            $return .= '</div>';
7933
            $return .= '</td>';
7934
7935
            if (TOOL_QUIZ == $item['item_type']) {
7936
                // lets update max_score Tests information depending of the Tests Advanced properties
7937
                $lpItemObj = new LpItem($course_id, $item['id']);
7938
                $exercise = new Exercise($course_id);
7939
                $exercise->read($lpItemObj->path);
7940
                $lpItemObj->max_score = $exercise->get_max_score();
7941
                $lpItemObj->update();
7942
                $item['max_score'] = $lpItemObj->max_score;
7943
7944
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
7945
                    // Backwards compatibility with 1.9.x use mastery_score as min value
7946
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
7947
                }
7948
7949
                $return .= '<td>';
7950
                $return .= '<input
7951
                    class="form-control"
7952
                    size="4" maxlength="3"
7953
                    name="min_'.$item['id'].'"
7954
                    type="number"
7955
                    min="0"
7956
                    step="1"
7957
                    max="'.$item['max_score'].'"
7958
                    value="'.$selectedMinScoreValue.'"
7959
                />';
7960
                $return .= '</td>';
7961
                $return .= '<td>';
7962
                $return .= '<input
7963
                    class="form-control"
7964
                    size="4"
7965
                    maxlength="3"
7966
                    name="max_'.$item['id'].'"
7967
                    type="number"
7968
                    min="0"
7969
                    step="1"
7970
                    max="'.$item['max_score'].'"
7971
                    value="'.$selectedMaxScoreValue.'"
7972
                />';
7973
                $return .= '</td>';
7974
            }
7975
7976
            if (TOOL_HOTPOTATOES == $item['item_type']) {
7977
                $return .= '<td>';
7978
                $return .= '<input
7979
                    size="4"
7980
                    maxlength="3"
7981
                    name="min_'.$item['id'].'"
7982
                    type="number"
7983
                    min="0"
7984
                    step="1"
7985
                    max="'.$item['max_score'].'"
7986
                    value="'.$selectedMinScoreValue.'"
7987
                />';
7988
                $return .= '</td>';
7989
                $return .= '<td>';
7990
                $return .= '<input
7991
                    size="4"
7992
                    maxlength="3"
7993
                    name="max_'.$item['id'].'"
7994
                    type="number"
7995
                    min="0"
7996
                    step="1"
7997
                    max="'.$item['max_score'].'"
7998
                    value="'.$selectedMaxScoreValue.'"
7999
                />';
8000
                $return .= '</td>';
8001
            }
8002
            $return .= '</tr>';
8003
        }
8004
        $return .= '<tr>';
8005
        $return .= '</tr>';
8006
        $return .= '</tbody>';
8007
        $return .= '</table>';
8008
        $return .= '</div>';
8009
        $return .= '<div class="form-group">';
8010
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
8011
            get_lang('Save prerequisites settings').'</button>';
8012
        $return .= '</form>';
8013
8014
        return $return;
8015
    }
8016
8017
    /**
8018
     * Return HTML list to allow prerequisites selection for lp.
8019
     *
8020
     * @return string HTML form
8021
     */
8022
    public function display_lp_prerequisites_list()
8023
    {
8024
        $course_id = api_get_course_int_id();
8025
        $lp_id = $this->lp_id;
8026
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
8027
8028
        // get current prerequisite
8029
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
8030
        $result = Database::query($sql);
8031
        $row = Database::fetch_array($result);
8032
        $prerequisiteId = $row['prerequisite'];
8033
        $session_id = api_get_session_id();
8034
        $session_condition = api_get_session_condition($session_id, true, true);
8035
        $sql = "SELECT * FROM $tbl_lp
8036
                WHERE c_id = $course_id $session_condition
8037
                ORDER BY display_order ";
8038
        $rs = Database::query($sql);
8039
        $return = '';
8040
        $return .= '<select name="prerequisites" class="form-control">';
8041
        $return .= '<option value="0">'.get_lang('none').'</option>';
8042
        if (Database::num_rows($rs) > 0) {
8043
            while ($row = Database::fetch_array($rs)) {
8044
                if ($row['id'] == $lp_id) {
8045
                    continue;
8046
                }
8047
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
8048
            }
8049
        }
8050
        $return .= '</select>';
8051
8052
        return $return;
8053
    }
8054
8055
    /**
8056
     * Creates a list with all the documents in it.
8057
     *
8058
     * @param bool $showInvisibleFiles
8059
     *
8060
     * @throws Exception
8061
     * @throws HTML_QuickForm_Error
8062
     *
8063
     * @return string
8064
     */
8065
    public function get_documents($showInvisibleFiles = false)
8066
    {
8067
        $course_info = api_get_course_info();
8068
        $sessionId = api_get_session_id();
8069
        $documentTree = DocumentManager::get_document_preview(
8070
            $course_info,
8071
            $this->lp_id,
8072
            null,
8073
            $sessionId,
8074
            true,
8075
            null,
8076
            null,
8077
            $showInvisibleFiles,
8078
            true
8079
        );
8080
8081
        $form = new FormValidator(
8082
            'form_upload',
8083
            'POST',
8084
            $this->getCurrentBuildingModeURL(),
8085
            '',
8086
            ['enctype' => 'multipart/form-data']
8087
        );
8088
8089
        $folders = DocumentManager::get_all_document_folders(
8090
            api_get_course_info(),
8091
            0,
8092
            true
8093
        );
8094
8095
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
8096
8097
        DocumentManager::build_directory_selector(
8098
            $folders,
8099
            $lpPathInfo['id'],
8100
            [],
8101
            true,
8102
            $form,
8103
            'directory_parent_id'
8104
        );
8105
8106
        $group = [
8107
            $form->createElement(
8108
                'radio',
8109
                'if_exists',
8110
                get_lang('If file exists:'),
8111
                get_lang('Do nothing'),
8112
                'nothing'
8113
            ),
8114
            $form->createElement(
8115
                'radio',
8116
                'if_exists',
8117
                null,
8118
                get_lang('Overwrite the existing file'),
8119
                'overwrite'
8120
            ),
8121
            $form->createElement(
8122
                'radio',
8123
                'if_exists',
8124
                null,
8125
                get_lang('Rename the uploaded file if it exists'),
8126
                'rename'
8127
            ),
8128
        ];
8129
        $form->addGroup($group, null, get_lang('If file exists:'));
8130
8131
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
8132
        $defaultFileExistsOption = 'rename';
8133
        if (!empty($fileExistsOption)) {
8134
            $defaultFileExistsOption = $fileExistsOption;
8135
        }
8136
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
8137
8138
        // Check box options
8139
        $form->addElement(
8140
            'checkbox',
8141
            'unzip',
8142
            get_lang('Options'),
8143
            get_lang('Uncompress zip')
8144
        );
8145
8146
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
8147
        $form->addMultipleUpload($url);
8148
8149
        $lpItem = new CLpItem();
8150
        $lpItem->setItemType(TOOL_DOCUMENT);
8151
        $new = $this->displayDocumentForm('add', $lpItem);
8152
8153
        /*$lpItem = new CLpItem();
8154
        $lpItem->setItemType(TOOL_READOUT_TEXT);
8155
        $frmReadOutText = $this->displayDocumentForm('add');*/
8156
8157
        $headers = [
8158
            get_lang('Files'),
8159
            get_lang('Create a new document'),
8160
            get_lang('Create read-out text'),
8161
            get_lang('Upload'),
8162
        ];
8163
8164
        return Display::tabs(
8165
            $headers,
8166
            [$documentTree, $new, $form->returnForm()],
8167
            'subtab'
8168
        );
8169
    }
8170
8171
    /**
8172
     * Creates a list with all the exercises (quiz) in it.
8173
     *
8174
     * @return string
8175
     */
8176
    public function get_exercises()
8177
    {
8178
        $course_id = api_get_course_int_id();
8179
        $session_id = api_get_session_id();
8180
        $userInfo = api_get_user_info();
8181
8182
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
8183
        $condition_session = api_get_session_condition($session_id, true, true);
8184
        $setting = 'true' === api_get_setting('lp.show_invisible_exercise_in_lp_toc');
8185
8186
        $activeCondition = ' active <> -1 ';
8187
        if ($setting) {
8188
            $activeCondition = ' active = 1 ';
8189
        }
8190
8191
        $categoryCondition = '';
8192
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
8193
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
8194
            $categoryCondition = " AND exercise_category_id = $categoryId ";
8195
        }
8196
8197
        $keywordCondition = '';
8198
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
8199
8200
        if (!empty($keyword)) {
8201
            $keyword = Database::escape_string($keyword);
8202
            $keywordCondition = " AND title LIKE '%$keyword%' ";
8203
        }
8204
8205
        $sql_quiz = "SELECT * FROM $tbl_quiz
8206
                     WHERE
8207
                            c_id = $course_id AND
8208
                            $activeCondition
8209
                            $condition_session
8210
                            $categoryCondition
8211
                            $keywordCondition
8212
                     ORDER BY title ASC";
8213
        $res_quiz = Database::query($sql_quiz);
8214
8215
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
8216
8217
        // Create a search-box
8218
        $form = new FormValidator('search_simple', 'get', $currentUrl);
8219
        $form->addHidden('action', 'add_item');
8220
        $form->addHidden('type', 'step');
8221
        $form->addHidden('lp_id', $this->lp_id);
8222
        $form->addHidden('lp_build_selected', '2');
8223
8224
        $form->addCourseHiddenParams();
8225
        $form->addText(
8226
            'keyword',
8227
            get_lang('Search'),
8228
            false,
8229
            [
8230
                'aria-label' => get_lang('Search'),
8231
            ]
8232
        );
8233
8234
        if (api_get_configuration_value('allow_exercise_categories')) {
8235
            $manager = new ExerciseCategoryManager();
8236
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
8237
            if (!empty($options)) {
8238
                $form->addSelect(
8239
                    'category_id',
8240
                    get_lang('Category'),
8241
                    $options,
8242
                    ['placeholder' => get_lang('Please select an option')]
8243
                );
8244
            }
8245
        }
8246
8247
        $form->addButtonSearch(get_lang('Search'));
8248
        $return = $form->returnForm();
8249
8250
        $return .= '<ul class="lp_resource">';
8251
        $return .= '<li class="lp_resource_element">';
8252
        $return .= Display::return_icon('new_exercice.png');
8253
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
8254
            get_lang('New test').'</a>';
8255
        $return .= '</li>';
8256
8257
        $previewIcon = Display::return_icon(
8258
            'preview_view.png',
8259
            get_lang('Preview')
8260
        );
8261
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
8262
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8263
8264
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
8265
        $repo = Container::getExerciseRepository();
8266
        $courseEntity = api_get_course_entity();
8267
        $sessionEntity = api_get_session_entity();
8268
        while ($row_quiz = Database::fetch_array($res_quiz)) {
8269
            /** @var CQuiz $exercise */
8270
            $exercise = $repo->find($row_quiz['id']);
8271
            $title = strip_tags(
8272
                api_html_entity_decode($row_quiz['title'])
8273
            );
8274
8275
            $visibility = $exercise->isVisible($courseEntity, $sessionEntity);
8276
            /*$visibility = api_get_item_visibility(
8277
                ['real_id' => $course_id],
8278
                TOOL_QUIZ,
8279
                $row_quiz['iid'],
8280
                $session_id
8281
            );*/
8282
8283
            $link = Display::url(
8284
                $previewIcon,
8285
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
8286
                ['target' => '_blank']
8287
            );
8288
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
8289
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
8290
            $return .= $quizIcon;
8291
            $sessionStar = api_get_session_image(
8292
                $row_quiz['session_id'],
8293
                $userInfo['status']
8294
            );
8295
            $return .= Display::url(
8296
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
8297
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
8298
                [
8299
                    'class' => false === $visibility ? 'moved text-muted' : 'moved',
8300
                ]
8301
            );
8302
            $return .= '</li>';
8303
        }
8304
8305
        $return .= '</ul>';
8306
8307
        return $return;
8308
    }
8309
8310
    /**
8311
     * Creates a list with all the links in it.
8312
     *
8313
     * @return string
8314
     */
8315
    public function get_links()
8316
    {
8317
        $sessionId = api_get_session_id();
8318
        $repo = Container::getLinkRepository();
8319
8320
        $course = api_get_course_entity();
8321
        $session = api_get_session_entity($sessionId);
8322
        $qb = $repo->getResourcesByCourse($course, $session);
8323
        /** @var CLink[] $links */
8324
        $links = $qb->getQuery()->getResult();
8325
8326
        $selfUrl = api_get_self();
8327
        $courseIdReq = api_get_cidreq();
8328
        $userInfo = api_get_user_info();
8329
8330
        $moveEverywhereIcon = Display::return_icon(
8331
            'move_everywhere.png',
8332
            get_lang('Move'),
8333
            [],
8334
            ICON_SIZE_TINY
8335
        );
8336
8337
        /*$condition_session = api_get_session_condition(
8338
            $session_id,
8339
            true,
8340
            true,
8341
            'link.session_id'
8342
        );
8343
8344
        $sql = "SELECT
8345
                    link.id as link_id,
8346
                    link.title as link_title,
8347
                    link.session_id as link_session_id,
8348
                    link.category_id as category_id,
8349
                    link_category.category_title as category_title
8350
                FROM $tbl_link as link
8351
                LEFT JOIN $linkCategoryTable as link_category
8352
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
8353
                WHERE link.c_id = $course_id $condition_session
8354
                ORDER BY link_category.category_title ASC, link.title ASC";
8355
        $result = Database::query($sql);*/
8356
        $categorizedLinks = [];
8357
        $categories = [];
8358
8359
        foreach ($links as $link) {
8360
            $categoryId = null !== $link->getCategory() ? $link->getCategory()->getIid() : 0;
8361
8362
            if (empty($categoryId)) {
8363
                $categories[0] = get_lang('Uncategorized');
8364
            } else {
8365
                $category = $link->getCategory();
8366
                $categories[$categoryId] = $category->getCategoryTitle();
8367
            }
8368
            $categorizedLinks[$categoryId][$link->getIid()] = $link;
8369
        }
8370
8371
        $linksHtmlCode =
8372
            '<script>
8373
            function toggle_tool(tool, id) {
8374
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
8375
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
8376
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8377
                } else {
8378
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
8379
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8380
                }
8381
            }
8382
        </script>
8383
8384
        <ul class="lp_resource">
8385
            <li class="lp_resource_element">
8386
                '.Display::return_icon('linksnew.gif').'
8387
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('Add a link').'">'.
8388
                get_lang('Add a link').'
8389
                </a>
8390
            </li>';
8391
8392
        foreach ($categorizedLinks as $categoryId => $links) {
8393
            $linkNodes = null;
8394
            /** @var CLink $link */
8395
            foreach ($links as $key => $link) {
8396
                $title = $link->getTitle();
8397
                $linkSessionId = $link->getSessionId();
8398
8399
                $linkUrl = Display::url(
8400
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8401
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
8402
                    ['target' => '_blank']
8403
                );
8404
8405
                if ($link->isVisible($course, $session)) {
8406
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
8407
                    $linkNodes .=
8408
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
8409
                        <a class="moved" href="#">'.
8410
                            $moveEverywhereIcon.
8411
                        '</a>
8412
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
8413
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
8414
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
8415
                        Security::remove_XSS($title).$sessionStar.$linkUrl.
8416
                        '</a>
8417
                    </li>';
8418
                }
8419
            }
8420
            $linksHtmlCode .=
8421
                '<li>
8422
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
8423
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
8424
                    align="absbottom" />
8425
                </a>
8426
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
8427
            </li>
8428
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
8429
        }
8430
        $linksHtmlCode .= '</ul>';
8431
8432
        return $linksHtmlCode;
8433
    }
8434
8435
    /**
8436
     * Creates a list with all the student publications in it.
8437
     *
8438
     * @return string
8439
     */
8440
    public function get_student_publications()
8441
    {
8442
        $return = '<ul class="lp_resource">';
8443
        $return .= '<li class="lp_resource_element">';
8444
        /*
8445
        $return .= Display::return_icon('works_new.gif');
8446
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
8447
            get_lang('Add the Assignments tool to the course').'</a>';
8448
        $return .= '</li>';*/
8449
8450
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
8451
        $works = getWorkListTeacher(0, 100, null, null, null);
8452
        if (!empty($works)) {
8453
            foreach ($works as $work) {
8454
                $link = Display::url(
8455
                    Display::return_icon('preview_view.png', get_lang('Preview')),
8456
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
8457
                    ['target' => '_blank']
8458
                );
8459
8460
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
8461
                $return .= '<a class="moved" href="#">';
8462
                $return .= Display::return_icon(
8463
                    'move_everywhere.png',
8464
                    get_lang('Move'),
8465
                    [],
8466
                    ICON_SIZE_TINY
8467
                );
8468
                $return .= '</a> ';
8469
8470
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
8471
                $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
8472
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
8473
                </a>';
8474
8475
                $return .= '</li>';
8476
            }
8477
        }
8478
8479
        $return .= '</ul>';
8480
8481
        return $return;
8482
    }
8483
8484
    /**
8485
     * Creates a list with all the forums in it.
8486
     *
8487
     * @return string
8488
     */
8489
    public function get_forums()
8490
    {
8491
        require_once '../forum/forumfunction.inc.php';
8492
8493
        $forumCategories = get_forum_categories();
8494
        $forumsInNoCategory = get_forums_in_category(0);
8495
        if (!empty($forumsInNoCategory)) {
8496
            $forumCategories = array_merge(
8497
                $forumCategories,
8498
                [
8499
                    [
8500
                        'cat_id' => 0,
8501
                        'session_id' => 0,
8502
                        'visibility' => 1,
8503
                        'cat_comment' => null,
8504
                    ],
8505
                ]
8506
            );
8507
        }
8508
8509
        $forumList = get_forums();
8510
        $a_forums = [];
8511
        foreach ($forumCategories as $forumCategory) {
8512
            // The forums in this category.
8513
            $forumsInCategory = get_forums_in_category($forumCategory->getIid());
8514
            if (!empty($forumsInCategory)) {
8515
                foreach ($forumList as $forum) {
8516
                    $a_forums[] = $forum;
8517
                }
8518
            }
8519
        }
8520
8521
        $return = '<ul class="lp_resource">';
8522
8523
        // First add link
8524
        $return .= '<li class="lp_resource_element">';
8525
        $return .= Display::return_icon('new_forum.png');
8526
        $return .= Display::url(
8527
            get_lang('Create a new forum'),
8528
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
8529
                'action' => 'add',
8530
                'content' => 'forum',
8531
                'lp_id' => $this->lp_id,
8532
            ]),
8533
            ['title' => get_lang('Create a new forum')]
8534
        );
8535
        $return .= '</li>';
8536
8537
        $return .= '<script>
8538
            function toggle_forum(forum_id) {
8539
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
8540
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
8541
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
8542
                } else {
8543
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
8544
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
8545
                }
8546
            }
8547
        </script>';
8548
8549
        foreach ($a_forums as $forum) {
8550
            $forumId = $forum->getIid();
8551
            $title = Security::remove_XSS($forum->getForumTitle());
8552
            $link = Display::url(
8553
                Display::return_icon('preview_view.png', get_lang('Preview')),
8554
                api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forumId,
8555
                ['target' => '_blank']
8556
            );
8557
8558
            $return .= '<li class="lp_resource_element" data_id="'.$forumId.'" data_type="'.TOOL_FORUM.'" title="'.$title.'" >';
8559
            $return .= '<a class="moved" href="#">';
8560
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8561
            $return .= ' </a>';
8562
            $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
8563
            $return .= '<a onclick="javascript:toggle_forum('.$forumId.');" style="cursor:hand; vertical-align:middle">
8564
                            <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forumId.'_opener" align="absbottom" />
8565
                        </a>
8566
                        <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forumId.'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
8567
                $title.' '.$link.'</a>';
8568
8569
            $return .= '</li>';
8570
8571
            $return .= '<div style="display:none" id="forum_'.$forumId.'_content">';
8572
            $a_threads = get_threads($forumId);
8573
            if (is_array($a_threads)) {
8574
                foreach ($a_threads as $thread) {
8575
                    $threadId = $thread->getIid();
8576
                    $link = Display::url(
8577
                        Display::return_icon('preview_view.png', get_lang('Preview')),
8578
                        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forumId.'&thread='.$threadId,
8579
                        ['target' => '_blank']
8580
                    );
8581
8582
                    $return .= '<li class="lp_resource_element" data_id="'.$thread->getIid().'" data_type="'.TOOL_THREAD.'" title="'.$thread->getThreadTitle().'" >';
8583
                    $return .= '&nbsp;<a class="moved" href="#">';
8584
                    $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
8585
                    $return .= ' </a>';
8586
                    $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
8587
                    $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$threadId.'&lp_id='.$this->lp_id.'">'.
8588
                        Security::remove_XSS($thread->getThreadTitle()).' '.$link.'</a>';
8589
                    $return .= '</li>';
8590
                }
8591
            }
8592
            $return .= '</div>';
8593
        }
8594
        $return .= '</ul>';
8595
8596
        return $return;
8597
    }
8598
8599
    /**
8600
     * // TODO: The output encoding should be equal to the system encoding.
8601
     *
8602
     * Exports the learning path as a SCORM package. This is the main function that
8603
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
8604
     * whole thing and returns the zip.
8605
     *
8606
     * This method needs to be called in PHP5, as it will fail with non-adequate
8607
     * XML package (like the ones for PHP4), and it is *not* a static method, so
8608
     * you need to call it on a learnpath object.
8609
     *
8610
     * @TODO The method might be redefined later on in the scorm class itself to avoid
8611
     * creating a SCORM structure if there is one already. However, if the initial SCORM
8612
     * path has been modified, it should use the generic method here below.
8613
     *
8614
     * @return string Returns the zip package string, or null if error
8615
     */
8616
    public function scormExport()
8617
    {
8618
        api_set_more_memory_and_time_limits();
8619
8620
        $_course = api_get_course_info();
8621
        $course_id = $_course['real_id'];
8622
        // Create the zip handler (this will remain available throughout the method).
8623
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
8624
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
8625
        $temp_dir_short = uniqid('scorm_export', true);
8626
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
8627
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
8628
        $zip_folder = new PclZip($temp_zip_file);
8629
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
8630
        $root_path = $main_path = api_get_path(SYS_PATH);
8631
        $files_cleanup = [];
8632
8633
        // Place to temporarily stash the zip file.
8634
        // create the temp dir if it doesn't exist
8635
        // or do a cleanup before creating the zip file.
8636
        if (!is_dir($temp_zip_dir)) {
8637
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
8638
        } else {
8639
            // Cleanup: Check the temp dir for old files and delete them.
8640
            $handle = opendir($temp_zip_dir);
8641
            while (false !== ($file = readdir($handle))) {
8642
                if ('.' != $file && '..' != $file) {
8643
                    unlink("$temp_zip_dir/$file");
8644
                }
8645
            }
8646
            closedir($handle);
8647
        }
8648
        $zip_files = $zip_files_abs = $zip_files_dist = [];
8649
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
8650
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
8651
        ) {
8652
            // Remove the possible . at the end of the path.
8653
            $dest_path_to_lp = '.' == substr($this->path, -1) ? substr($this->path, 0, -1) : $this->path;
8654
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
8655
            mkdir(
8656
                $dest_path_to_scorm_folder,
8657
                api_get_permissions_for_new_directories(),
8658
                true
8659
            );
8660
            copyr(
8661
                $current_course_path.'/scorm/'.$this->path,
8662
                $dest_path_to_scorm_folder,
8663
                ['imsmanifest'],
8664
                $zip_files
8665
            );
8666
        }
8667
8668
        // Build a dummy imsmanifest structure.
8669
        // Do not add to the zip yet (we still need it).
8670
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
8671
        // Aggregation Model official document, section "2.3 Content Packaging".
8672
        // We are going to build a UTF-8 encoded manifest.
8673
        // Later we will recode it to the desired (and supported) encoding.
8674
        $xmldoc = new DOMDocument('1.0');
8675
        $root = $xmldoc->createElement('manifest');
8676
        $root->setAttribute('identifier', 'SingleCourseManifest');
8677
        $root->setAttribute('version', '1.1');
8678
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
8679
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
8680
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
8681
        $root->setAttribute(
8682
            'xsi:schemaLocation',
8683
            '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'
8684
        );
8685
        // Build mandatory sub-root container elements.
8686
        $metadata = $xmldoc->createElement('metadata');
8687
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
8688
        $metadata->appendChild($md_schema);
8689
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
8690
        $metadata->appendChild($md_schemaversion);
8691
        $root->appendChild($metadata);
8692
8693
        $organizations = $xmldoc->createElement('organizations');
8694
        $resources = $xmldoc->createElement('resources');
8695
8696
        // Build the only organization we will use in building our learnpaths.
8697
        $organizations->setAttribute('default', 'chamilo_scorm_export');
8698
        $organization = $xmldoc->createElement('organization');
8699
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
8700
        // To set the title of the SCORM entity (=organization), we take the name given
8701
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
8702
        // learning path charset) as it is the encoding that defines how it is stored
8703
        // in the database. Then we convert it to HTML entities again as the "&" character
8704
        // alone is not authorized in XML (must be &amp;).
8705
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
8706
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
8707
        $organization->appendChild($org_title);
8708
        $folder_name = 'document';
8709
8710
        // Removes the learning_path/scorm_folder path when exporting see #4841
8711
        $path_to_remove = '';
8712
        $path_to_replace = '';
8713
        $result = $this->generate_lp_folder($_course);
8714
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
8715
            $path_to_remove = 'document'.$result['dir'];
8716
            $path_to_replace = $folder_name.'/';
8717
        }
8718
8719
        // Fixes chamilo scorm exports
8720
        if ('chamilo_scorm_export' === $this->ref) {
8721
            $path_to_remove = 'scorm/'.$this->path.'/document/';
8722
        }
8723
8724
        // For each element, add it to the imsmanifest structure, then add it to the zip.
8725
        $link_updates = [];
8726
        $links_to_create = [];
8727
        foreach ($this->ordered_items as $index => $itemId) {
8728
            /** @var learnpathItem $item */
8729
            $item = $this->items[$itemId];
8730
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
8731
                // Get included documents from this item.
8732
                if ('sco' === $item->type) {
8733
                    $inc_docs = $item->get_resources_from_source(
8734
                        null,
8735
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
8736
                    );
8737
                } else {
8738
                    $inc_docs = $item->get_resources_from_source();
8739
                }
8740
8741
                // Give a child element <item> to the <organization> element.
8742
                $my_item_id = $item->get_id();
8743
                $my_item = $xmldoc->createElement('item');
8744
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
8745
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
8746
                $my_item->setAttribute('isvisible', 'true');
8747
                // Give a child element <title> to the <item> element.
8748
                $my_title = $xmldoc->createElement(
8749
                    'title',
8750
                    htmlspecialchars(
8751
                        api_utf8_encode($item->get_title()),
8752
                        ENT_QUOTES,
8753
                        'UTF-8'
8754
                    )
8755
                );
8756
                $my_item->appendChild($my_title);
8757
                // Give a child element <adlcp:prerequisites> to the <item> element.
8758
                $my_prereqs = $xmldoc->createElement(
8759
                    'adlcp:prerequisites',
8760
                    $this->get_scorm_prereq_string($my_item_id)
8761
                );
8762
                $my_prereqs->setAttribute('type', 'aicc_script');
8763
                $my_item->appendChild($my_prereqs);
8764
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
8765
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
8766
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
8767
                //$xmldoc->createElement('adlcp:timelimitaction','');
8768
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
8769
                //$xmldoc->createElement('adlcp:datafromlms','');
8770
                // Give a child element <adlcp:masteryscore> to the <item> element.
8771
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
8772
                $my_item->appendChild($my_masteryscore);
8773
8774
                // Attach this item to the organization element or hits parent if there is one.
8775
                if (!empty($item->parent) && 0 != $item->parent) {
8776
                    $children = $organization->childNodes;
8777
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
8778
                    if (is_object($possible_parent)) {
8779
                        $possible_parent->appendChild($my_item);
8780
                    } else {
8781
                        if ($this->debug > 0) {
8782
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
8783
                        }
8784
                    }
8785
                } else {
8786
                    if ($this->debug > 0) {
8787
                        error_log('No parent');
8788
                    }
8789
                    $organization->appendChild($my_item);
8790
                }
8791
8792
                // Get the path of the file(s) from the course directory root.
8793
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
8794
                $my_xml_file_path = $my_file_path;
8795
                if (!empty($path_to_remove)) {
8796
                    // From docs
8797
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
8798
8799
                    // From quiz
8800
                    if ('chamilo_scorm_export' === $this->ref) {
8801
                        $path_to_remove = 'scorm/'.$this->path.'/';
8802
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
8803
                    }
8804
                }
8805
8806
                $my_sub_dir = dirname($my_file_path);
8807
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
8808
                $my_xml_sub_dir = $my_sub_dir;
8809
                // Give a <resource> child to the <resources> element
8810
                $my_resource = $xmldoc->createElement('resource');
8811
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
8812
                $my_resource->setAttribute('type', 'webcontent');
8813
                $my_resource->setAttribute('href', $my_xml_file_path);
8814
                // adlcp:scormtype can be either 'sco' or 'asset'.
8815
                if ('sco' === $item->type) {
8816
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
8817
                } else {
8818
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
8819
                }
8820
                // xml:base is the base directory to find the files declared in this resource.
8821
                $my_resource->setAttribute('xml:base', '');
8822
                // Give a <file> child to the <resource> element.
8823
                $my_file = $xmldoc->createElement('file');
8824
                $my_file->setAttribute('href', $my_xml_file_path);
8825
                $my_resource->appendChild($my_file);
8826
8827
                // Dependency to other files - not yet supported.
8828
                $i = 1;
8829
                if ($inc_docs) {
8830
                    foreach ($inc_docs as $doc_info) {
8831
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
8832
                            continue;
8833
                        }
8834
                        $my_dep = $xmldoc->createElement('resource');
8835
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
8836
                        $my_dep->setAttribute('identifier', $res_id);
8837
                        $my_dep->setAttribute('type', 'webcontent');
8838
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
8839
                        $my_dep_file = $xmldoc->createElement('file');
8840
                        // Check type of URL.
8841
                        if ('remote' == $doc_info[1]) {
8842
                            // Remote file. Save url as is.
8843
                            $my_dep_file->setAttribute('href', $doc_info[0]);
8844
                            $my_dep->setAttribute('xml:base', '');
8845
                        } elseif ('local' === $doc_info[1]) {
8846
                            switch ($doc_info[2]) {
8847
                                case 'url':
8848
                                    // Local URL - save path as url for now, don't zip file.
8849
                                    $abs_path = api_get_path(SYS_PATH).
8850
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8851
                                    $current_dir = dirname($abs_path);
8852
                                    $current_dir = str_replace('\\', '/', $current_dir);
8853
                                    $file_path = realpath($abs_path);
8854
                                    $file_path = str_replace('\\', '/', $file_path);
8855
                                    $my_dep_file->setAttribute('href', $file_path);
8856
                                    $my_dep->setAttribute('xml:base', '');
8857
                                    if (false !== strstr($file_path, $main_path)) {
8858
                                        // The calculated real path is really inside Chamilo's root path.
8859
                                        // Reduce file path to what's under the DocumentRoot.
8860
                                        $replace = $file_path;
8861
                                        $file_path = substr($file_path, strlen($root_path) - 1);
8862
                                        $destinationFile = $file_path;
8863
8864
                                        if (false !== strstr($file_path, 'upload/users')) {
8865
                                            $pos = strpos($file_path, 'my_files/');
8866
                                            if (false !== $pos) {
8867
                                                $onlyDirectory = str_replace(
8868
                                                    'upload/users/',
8869
                                                    '',
8870
                                                    substr($file_path, $pos, strlen($file_path))
8871
                                                );
8872
                                            }
8873
                                            $replace = $onlyDirectory;
8874
                                            $destinationFile = $replace;
8875
                                        }
8876
                                        $zip_files_abs[] = $file_path;
8877
                                        $link_updates[$my_file_path][] = [
8878
                                            'orig' => $doc_info[0],
8879
                                            'dest' => $destinationFile,
8880
                                            'replace' => $replace,
8881
                                        ];
8882
                                        $my_dep_file->setAttribute('href', $file_path);
8883
                                        $my_dep->setAttribute('xml:base', '');
8884
                                    } elseif (empty($file_path)) {
8885
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
8886
                                        $file_path = str_replace('//', '/', $file_path);
8887
                                        if (file_exists($file_path)) {
8888
                                            // We get the relative path.
8889
                                            $file_path = substr($file_path, strlen($current_dir));
8890
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
8891
                                            $link_updates[$my_file_path][] = [
8892
                                                'orig' => $doc_info[0],
8893
                                                'dest' => $file_path,
8894
                                            ];
8895
                                            $my_dep_file->setAttribute('href', $file_path);
8896
                                            $my_dep->setAttribute('xml:base', '');
8897
                                        }
8898
                                    }
8899
                                    break;
8900
                                case 'abs':
8901
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
8902
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
8903
                                    $my_dep->setAttribute('xml:base', '');
8904
8905
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
8906
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
8907
                                    $abs_img_path_without_subdir = $doc_info[0];
8908
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
8909
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
8910
                                    if (0 === $pos) {
8911
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
8912
                                    }
8913
8914
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
8915
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
8916
8917
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
8918
                                    $cur_path = '/' == substr($current_course_path, -1) ? $current_course_path : $current_course_path.'/';
8919
                                    // Check if the current document is in that path.
8920
                                    if (false !== strstr($file_path, $cur_path)) {
8921
                                        $destinationFile = substr($file_path, strlen($cur_path));
8922
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
8923
8924
                                        $fileToTest = $cur_path.$my_file_path;
8925
                                        if (!empty($path_to_remove)) {
8926
                                            $fileToTest = str_replace(
8927
                                                $path_to_remove.'/',
8928
                                                $path_to_replace,
8929
                                                $cur_path.$my_file_path
8930
                                            );
8931
                                        }
8932
8933
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
8934
8935
                                        // Put the current document in the zip (this array is the array
8936
                                        // that will manage documents already in the course folder - relative).
8937
                                        $zip_files[] = $filePathNoCoursePart;
8938
                                        // Update the links to the current document in the
8939
                                        // containing document (make them relative).
8940
                                        $link_updates[$my_file_path][] = [
8941
                                            'orig' => $doc_info[0],
8942
                                            'dest' => $destinationFile,
8943
                                            'replace' => $relative_path,
8944
                                        ];
8945
8946
                                        $my_dep_file->setAttribute('href', $file_path);
8947
                                        $my_dep->setAttribute('xml:base', '');
8948
                                    } elseif (false !== strstr($file_path, $main_path)) {
8949
                                        // The calculated real path is really inside Chamilo's root path.
8950
                                        // Reduce file path to what's under the DocumentRoot.
8951
                                        $file_path = substr($file_path, strlen($root_path));
8952
                                        $zip_files_abs[] = $file_path;
8953
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
8954
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
8955
                                        $my_dep->setAttribute('xml:base', '');
8956
                                    } elseif (empty($file_path)) {
8957
                                        // Probably this is an image inside "/main" directory
8958
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
8959
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
8960
8961
                                        if (file_exists($file_path)) {
8962
                                            if (false !== strstr($file_path, 'main/default_course_document')) {
8963
                                                // We get the relative path.
8964
                                                $pos = strpos($file_path, 'main/default_course_document/');
8965
                                                if (false !== $pos) {
8966
                                                    $onlyDirectory = str_replace(
8967
                                                        'main/default_course_document/',
8968
                                                        '',
8969
                                                        substr($file_path, $pos, strlen($file_path))
8970
                                                    );
8971
                                                }
8972
8973
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
8974
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
8975
                                                $zip_files_abs[] = $fileAbs;
8976
                                                $link_updates[$my_file_path][] = [
8977
                                                    'orig' => $doc_info[0],
8978
                                                    'dest' => $destinationFile,
8979
                                                ];
8980
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
8981
                                                $my_dep->setAttribute('xml:base', '');
8982
                                            }
8983
                                        }
8984
                                    }
8985
                                    break;
8986
                                case 'rel':
8987
                                    // Path relative to the current document.
8988
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
8989
                                    if ('..' === substr($doc_info[0], 0, 2)) {
8990
                                        // Relative path going up.
8991
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
8992
                                        $current_dir = str_replace('\\', '/', $current_dir);
8993
                                        $file_path = realpath($current_dir.$doc_info[0]);
8994
                                        $file_path = str_replace('\\', '/', $file_path);
8995
                                        if (false !== strstr($file_path, $main_path)) {
8996
                                            // The calculated real path is really inside Chamilo's root path.
8997
                                            // Reduce file path to what's under the DocumentRoot.
8998
                                            $file_path = substr($file_path, strlen($root_path));
8999
                                            $zip_files_abs[] = $file_path;
9000
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
9001
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9002
                                            $my_dep->setAttribute('xml:base', '');
9003
                                        }
9004
                                    } else {
9005
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9006
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
9007
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9008
                                    }
9009
                                    break;
9010
                                default:
9011
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
9012
                                    $my_dep->setAttribute('xml:base', '');
9013
                                    break;
9014
                            }
9015
                        }
9016
                        $my_dep->appendChild($my_dep_file);
9017
                        $resources->appendChild($my_dep);
9018
                        $dependency = $xmldoc->createElement('dependency');
9019
                        $dependency->setAttribute('identifierref', $res_id);
9020
                        $my_resource->appendChild($dependency);
9021
                        $i++;
9022
                    }
9023
                }
9024
                $resources->appendChild($my_resource);
9025
                $zip_files[] = $my_file_path;
9026
            } else {
9027
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
9028
                switch ($item->type) {
9029
                    case TOOL_LINK:
9030
                        $my_item = $xmldoc->createElement('item');
9031
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9032
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9033
                        $my_item->setAttribute('isvisible', 'true');
9034
                        // Give a child element <title> to the <item> element.
9035
                        $my_title = $xmldoc->createElement(
9036
                            'title',
9037
                            htmlspecialchars(
9038
                                api_utf8_encode($item->get_title()),
9039
                                ENT_QUOTES,
9040
                                'UTF-8'
9041
                            )
9042
                        );
9043
                        $my_item->appendChild($my_title);
9044
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9045
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9046
                        $my_prereqs->setAttribute('type', 'aicc_script');
9047
                        $my_item->appendChild($my_prereqs);
9048
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
9049
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
9050
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
9051
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
9052
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
9053
                        //$xmldoc->createElement('adlcp:datafromlms', '');
9054
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9055
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9056
                        $my_item->appendChild($my_masteryscore);
9057
9058
                        // Attach this item to the organization element or its parent if there is one.
9059
                        if (!empty($item->parent) && 0 != $item->parent) {
9060
                            $children = $organization->childNodes;
9061
                            for ($i = 0; $i < $children->length; $i++) {
9062
                                $item_temp = $children->item($i);
9063
                                if ('item' == $item_temp->nodeName) {
9064
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
9065
                                        $item_temp->appendChild($my_item);
9066
                                    }
9067
                                }
9068
                            }
9069
                        } else {
9070
                            $organization->appendChild($my_item);
9071
                        }
9072
9073
                        $my_file_path = 'link_'.$item->get_id().'.html';
9074
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
9075
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
9076
                        $rs = Database::query($sql);
9077
                        if ($link = Database::fetch_array($rs)) {
9078
                            $url = $link['url'];
9079
                            $title = stripslashes($link['title']);
9080
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
9081
                            $my_xml_file_path = $my_file_path;
9082
                            $my_sub_dir = dirname($my_file_path);
9083
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9084
                            $my_xml_sub_dir = $my_sub_dir;
9085
                            // Give a <resource> child to the <resources> element.
9086
                            $my_resource = $xmldoc->createElement('resource');
9087
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9088
                            $my_resource->setAttribute('type', 'webcontent');
9089
                            $my_resource->setAttribute('href', $my_xml_file_path);
9090
                            // adlcp:scormtype can be either 'sco' or 'asset'.
9091
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
9092
                            // xml:base is the base directory to find the files declared in this resource.
9093
                            $my_resource->setAttribute('xml:base', '');
9094
                            // give a <file> child to the <resource> element.
9095
                            $my_file = $xmldoc->createElement('file');
9096
                            $my_file->setAttribute('href', $my_xml_file_path);
9097
                            $my_resource->appendChild($my_file);
9098
                            $resources->appendChild($my_resource);
9099
                        }
9100
                        break;
9101
                    case TOOL_QUIZ:
9102
                        $exe_id = $item->path;
9103
                        // Should be using ref when everything will be cleaned up in this regard.
9104
                        $exe = new Exercise();
9105
                        $exe->read($exe_id);
9106
                        $my_item = $xmldoc->createElement('item');
9107
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
9108
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
9109
                        $my_item->setAttribute('isvisible', 'true');
9110
                        // Give a child element <title> to the <item> element.
9111
                        $my_title = $xmldoc->createElement(
9112
                            'title',
9113
                            htmlspecialchars(
9114
                                api_utf8_encode($item->get_title()),
9115
                                ENT_QUOTES,
9116
                                'UTF-8'
9117
                            )
9118
                        );
9119
                        $my_item->appendChild($my_title);
9120
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
9121
                        $my_item->appendChild($my_max_score);
9122
                        // Give a child element <adlcp:prerequisites> to the <item> element.
9123
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
9124
                        $my_prereqs->setAttribute('type', 'aicc_script');
9125
                        $my_item->appendChild($my_prereqs);
9126
                        // Give a child element <adlcp:masteryscore> to the <item> element.
9127
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
9128
                        $my_item->appendChild($my_masteryscore);
9129
9130
                        // Attach this item to the organization element or hits parent if there is one.
9131
                        if (!empty($item->parent) && 0 != $item->parent) {
9132
                            $children = $organization->childNodes;
9133
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
9134
                            if ($possible_parent) {
9135
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
9136
                                    $possible_parent->appendChild($my_item);
9137
                                }
9138
                            }
9139
                        } else {
9140
                            $organization->appendChild($my_item);
9141
                        }
9142
9143
                        // Get the path of the file(s) from the course directory root
9144
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
9145
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
9146
                        // Write the contents of the exported exercise into a (big) html file
9147
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
9148
                        $scormExercise = new ScormExercise($exe, true);
9149
                        $contents = $scormExercise->export();
9150
9151
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
9152
                        $res = file_put_contents($tmp_file_path, $contents);
9153
                        if (false === $res) {
9154
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
9155
                        }
9156
                        $files_cleanup[] = $tmp_file_path;
9157
                        $my_xml_file_path = $my_file_path;
9158
                        $my_sub_dir = dirname($my_file_path);
9159
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9160
                        $my_xml_sub_dir = $my_sub_dir;
9161
                        // Give a <resource> child to the <resources> element.
9162
                        $my_resource = $xmldoc->createElement('resource');
9163
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9164
                        $my_resource->setAttribute('type', 'webcontent');
9165
                        $my_resource->setAttribute('href', $my_xml_file_path);
9166
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9167
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
9168
                        // xml:base is the base directory to find the files declared in this resource.
9169
                        $my_resource->setAttribute('xml:base', '');
9170
                        // Give a <file> child to the <resource> element.
9171
                        $my_file = $xmldoc->createElement('file');
9172
                        $my_file->setAttribute('href', $my_xml_file_path);
9173
                        $my_resource->appendChild($my_file);
9174
9175
                        // Get included docs.
9176
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
9177
9178
                        // Dependency to other files - not yet supported.
9179
                        $i = 1;
9180
                        foreach ($inc_docs as $doc_info) {
9181
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
9182
                                continue;
9183
                            }
9184
                            $my_dep = $xmldoc->createElement('resource');
9185
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
9186
                            $my_dep->setAttribute('identifier', $res_id);
9187
                            $my_dep->setAttribute('type', 'webcontent');
9188
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
9189
                            $my_dep_file = $xmldoc->createElement('file');
9190
                            // Check type of URL.
9191
                            if ('remote' == $doc_info[1]) {
9192
                                // Remote file. Save url as is.
9193
                                $my_dep_file->setAttribute('href', $doc_info[0]);
9194
                                $my_dep->setAttribute('xml:base', '');
9195
                            } elseif ('local' == $doc_info[1]) {
9196
                                switch ($doc_info[2]) {
9197
                                    case 'url': // Local URL - save path as url for now, don't zip file.
9198
                                        // Save file but as local file (retrieve from URL).
9199
                                        $abs_path = api_get_path(SYS_PATH).
9200
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
9201
                                        $current_dir = dirname($abs_path);
9202
                                        $current_dir = str_replace('\\', '/', $current_dir);
9203
                                        $file_path = realpath($abs_path);
9204
                                        $file_path = str_replace('\\', '/', $file_path);
9205
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
9206
                                        $my_dep->setAttribute('xml:base', '');
9207
                                        if (false !== strstr($file_path, $main_path)) {
9208
                                            // The calculated real path is really inside the chamilo root path.
9209
                                            // Reduce file path to what's under the DocumentRoot.
9210
                                            $file_path = substr($file_path, strlen($root_path));
9211
                                            $zip_files_abs[] = $file_path;
9212
                                            $link_updates[$my_file_path][] = [
9213
                                                'orig' => $doc_info[0],
9214
                                                'dest' => 'document/'.$file_path,
9215
                                            ];
9216
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9217
                                            $my_dep->setAttribute('xml:base', '');
9218
                                        } elseif (empty($file_path)) {
9219
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
9220
                                            $file_path = str_replace('//', '/', $file_path);
9221
                                            if (file_exists($file_path)) {
9222
                                                $file_path = substr($file_path, strlen($current_dir));
9223
                                                // We get the relative path.
9224
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9225
                                                $link_updates[$my_file_path][] = [
9226
                                                    'orig' => $doc_info[0],
9227
                                                    'dest' => 'document/'.$file_path,
9228
                                                ];
9229
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9230
                                                $my_dep->setAttribute('xml:base', '');
9231
                                            }
9232
                                        }
9233
                                        break;
9234
                                    case 'abs':
9235
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
9236
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9237
                                        $current_dir = str_replace('\\', '/', $current_dir);
9238
                                        $file_path = realpath($doc_info[0]);
9239
                                        $file_path = str_replace('\\', '/', $file_path);
9240
                                        $my_dep_file->setAttribute('href', $file_path);
9241
                                        $my_dep->setAttribute('xml:base', '');
9242
9243
                                        if (false !== strstr($file_path, $main_path)) {
9244
                                            // The calculated real path is really inside the chamilo root path.
9245
                                            // Reduce file path to what's under the DocumentRoot.
9246
                                            $file_path = substr($file_path, strlen($root_path));
9247
                                            $zip_files_abs[] = $file_path;
9248
                                            $link_updates[$my_file_path][] = [
9249
                                                'orig' => $doc_info[0],
9250
                                                'dest' => $file_path,
9251
                                            ];
9252
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
9253
                                            $my_dep->setAttribute('xml:base', '');
9254
                                        } elseif (empty($file_path)) {
9255
                                            $docSysPartPath = str_replace(
9256
                                                api_get_path(REL_COURSE_PATH),
9257
                                                '',
9258
                                                $doc_info[0]
9259
                                            );
9260
9261
                                            $docSysPartPathNoCourseCode = str_replace(
9262
                                                $_course['directory'].'/',
9263
                                                '',
9264
                                                $docSysPartPath
9265
                                            );
9266
9267
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
9268
                                            if (file_exists($docSysPath)) {
9269
                                                $file_path = $docSysPartPathNoCourseCode;
9270
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
9271
                                                $link_updates[$my_file_path][] = [
9272
                                                    'orig' => $doc_info[0],
9273
                                                    'dest' => $file_path,
9274
                                                ];
9275
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9276
                                                $my_dep->setAttribute('xml:base', '');
9277
                                            }
9278
                                        }
9279
                                        break;
9280
                                    case 'rel':
9281
                                        // Path relative to the current document. Save xml:base as current document's
9282
                                        // directory and save file in zip as subdir.file_path
9283
                                        if ('..' === substr($doc_info[0], 0, 2)) {
9284
                                            // Relative path going up.
9285
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
9286
                                            $current_dir = str_replace('\\', '/', $current_dir);
9287
                                            $file_path = realpath($current_dir.$doc_info[0]);
9288
                                            $file_path = str_replace('\\', '/', $file_path);
9289
                                            if (false !== strstr($file_path, $main_path)) {
9290
                                                // The calculated real path is really inside Chamilo's root path.
9291
                                                // Reduce file path to what's under the DocumentRoot.
9292
9293
                                                $file_path = substr($file_path, strlen($root_path));
9294
                                                $file_path_dest = $file_path;
9295
9296
                                                // File path is courses/CHAMILO/document/....
9297
                                                $info_file_path = explode('/', $file_path);
9298
                                                if ('courses' == $info_file_path[0]) {
9299
                                                    // Add character "/" in file path.
9300
                                                    $file_path_dest = 'document/'.$file_path;
9301
                                                }
9302
                                                $zip_files_abs[] = $file_path;
9303
9304
                                                $link_updates[$my_file_path][] = [
9305
                                                    'orig' => $doc_info[0],
9306
                                                    'dest' => $file_path_dest,
9307
                                                ];
9308
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
9309
                                                $my_dep->setAttribute('xml:base', '');
9310
                                            }
9311
                                        } else {
9312
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
9313
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
9314
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
9315
                                        }
9316
                                        break;
9317
                                    default:
9318
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
9319
                                        $my_dep->setAttribute('xml:base', '');
9320
                                        break;
9321
                                }
9322
                            }
9323
                            $my_dep->appendChild($my_dep_file);
9324
                            $resources->appendChild($my_dep);
9325
                            $dependency = $xmldoc->createElement('dependency');
9326
                            $dependency->setAttribute('identifierref', $res_id);
9327
                            $my_resource->appendChild($dependency);
9328
                            $i++;
9329
                        }
9330
                        $resources->appendChild($my_resource);
9331
                        $zip_files[] = $my_file_path;
9332
                        break;
9333
                    default:
9334
                        // Get the path of the file(s) from the course directory root
9335
                        $my_file_path = 'non_exportable.html';
9336
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
9337
                        $my_xml_file_path = $my_file_path;
9338
                        $my_sub_dir = dirname($my_file_path);
9339
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
9340
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
9341
                        $my_xml_sub_dir = $my_sub_dir;
9342
                        // Give a <resource> child to the <resources> element.
9343
                        $my_resource = $xmldoc->createElement('resource');
9344
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
9345
                        $my_resource->setAttribute('type', 'webcontent');
9346
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
9347
                        // adlcp:scormtype can be either 'sco' or 'asset'.
9348
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
9349
                        // xml:base is the base directory to find the files declared in this resource.
9350
                        $my_resource->setAttribute('xml:base', '');
9351
                        // Give a <file> child to the <resource> element.
9352
                        $my_file = $xmldoc->createElement('file');
9353
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
9354
                        $my_resource->appendChild($my_file);
9355
                        $resources->appendChild($my_resource);
9356
                        break;
9357
                }
9358
            }
9359
        }
9360
        $organizations->appendChild($organization);
9361
        $root->appendChild($organizations);
9362
        $root->appendChild($resources);
9363
        $xmldoc->appendChild($root);
9364
9365
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
9366
9367
        // then add the file to the zip, then destroy the file (this is done automatically).
9368
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
9369
        foreach ($zip_files as $file_path) {
9370
            if (empty($file_path)) {
9371
                continue;
9372
            }
9373
9374
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
9375
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
9376
9377
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
9378
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
9379
            }
9380
9381
            $this->create_path($dest_file);
9382
            @copy($filePath, $dest_file);
9383
9384
            // Check if the file needs a link update.
9385
            if (in_array($file_path, array_keys($link_updates))) {
9386
                $string = file_get_contents($dest_file);
9387
                unlink($dest_file);
9388
                foreach ($link_updates[$file_path] as $old_new) {
9389
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9390
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9391
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9392
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9393
                    if ('flv' === substr($old_new['dest'], -3) &&
9394
                        'main/' === substr($old_new['dest'], 0, 5)
9395
                    ) {
9396
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9397
                    } elseif ('flv' === substr($old_new['dest'], -3) &&
9398
                        'video/' === substr($old_new['dest'], 0, 6)
9399
                    ) {
9400
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
9401
                    }
9402
9403
                    // Fix to avoid problems with default_course_document
9404
                    if (false === strpos('main/default_course_document', $old_new['dest'])) {
9405
                        $newDestination = $old_new['dest'];
9406
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
9407
                            $newDestination = $old_new['replace'];
9408
                        }
9409
                    } else {
9410
                        $newDestination = str_replace('document/', '', $old_new['dest']);
9411
                    }
9412
                    $string = str_replace($old_new['orig'], $newDestination, $string);
9413
9414
                    // Add files inside the HTMLs
9415
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
9416
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
9417
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
9418
                        copy(
9419
                            $sys_course_path.$new_path,
9420
                            $destinationFile
9421
                        );
9422
                    }
9423
                }
9424
                file_put_contents($dest_file, $string);
9425
            }
9426
9427
            if (file_exists($filePath) && $copyAll) {
9428
                $extension = $this->get_extension($filePath);
9429
                if (in_array($extension, ['html', 'html'])) {
9430
                    $containerOrigin = dirname($filePath);
9431
                    $containerDestination = dirname($dest_file);
9432
9433
                    $finder = new Finder();
9434
                    $finder->files()->in($containerOrigin)
9435
                        ->notName('*_DELETED_*')
9436
                        ->exclude('share_folder')
9437
                        ->exclude('chat_files')
9438
                        ->exclude('certificates')
9439
                    ;
9440
9441
                    if (is_dir($containerOrigin) &&
9442
                        is_dir($containerDestination)
9443
                    ) {
9444
                        $fs = new Filesystem();
9445
                        $fs->mirror(
9446
                            $containerOrigin,
9447
                            $containerDestination,
9448
                            $finder
9449
                        );
9450
                    }
9451
                }
9452
            }
9453
        }
9454
9455
        foreach ($zip_files_abs as $file_path) {
9456
            if (empty($file_path)) {
9457
                continue;
9458
            }
9459
9460
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
9461
                continue;
9462
            }
9463
9464
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
9465
            if (false !== strstr($file_path, 'upload/users')) {
9466
                $pos = strpos($file_path, 'my_files/');
9467
                if (false !== $pos) {
9468
                    $onlyDirectory = str_replace(
9469
                        'upload/users/',
9470
                        '',
9471
                        substr($file_path, $pos, strlen($file_path))
9472
                    );
9473
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
9474
                }
9475
            }
9476
9477
            if (false !== strstr($file_path, 'default_course_document/')) {
9478
                $replace = str_replace('/main', '', $file_path);
9479
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
9480
            }
9481
9482
            if (empty($dest_file)) {
9483
                continue;
9484
            }
9485
9486
            $this->create_path($dest_file);
9487
            copy($main_path.$file_path, $dest_file);
9488
            // Check if the file needs a link update.
9489
            if (in_array($file_path, array_keys($link_updates))) {
9490
                $string = file_get_contents($dest_file);
9491
                unlink($dest_file);
9492
                foreach ($link_updates[$file_path] as $old_new) {
9493
                    // This is an ugly hack that allows .flv files to be found by the flv player that
9494
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
9495
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
9496
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
9497
                    if ('flv' == substr($old_new['dest'], -3) &&
9498
                        'main/' == substr($old_new['dest'], 0, 5)
9499
                    ) {
9500
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
9501
                    }
9502
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
9503
                }
9504
                file_put_contents($dest_file, $string);
9505
            }
9506
        }
9507
9508
        if (is_array($links_to_create)) {
9509
            foreach ($links_to_create as $file => $link) {
9510
                $content = '<!DOCTYPE html><head>
9511
                            <meta charset="'.api_get_language_isocode().'" />
9512
                            <title>'.$link['title'].'</title>
9513
                            </head>
9514
                            <body dir="'.api_get_text_direction().'">
9515
                            <div style="text-align:center">
9516
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
9517
                            </body>
9518
                            </html>';
9519
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
9520
            }
9521
        }
9522
9523
        // Add non exportable message explanation.
9524
        $lang_not_exportable = get_lang('This learning object or activity is not SCORM compliant. That\'s why it is not exportable.');
9525
        $file_content = '<!DOCTYPE html><head>
9526
                        <meta charset="'.api_get_language_isocode().'" />
9527
                        <title>'.$lang_not_exportable.'</title>
9528
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
9529
                        </head>
9530
                        <body dir="'.api_get_text_direction().'">';
9531
        $file_content .=
9532
            <<<EOD
9533
                    <style>
9534
            .error-message {
9535
                font-family: arial, verdana, helvetica, sans-serif;
9536
                border-width: 1px;
9537
                border-style: solid;
9538
                left: 50%;
9539
                margin: 10px auto;
9540
                min-height: 30px;
9541
                padding: 5px;
9542
                right: 50%;
9543
                width: 500px;
9544
                background-color: #FFD1D1;
9545
                border-color: #FF0000;
9546
                color: #000;
9547
            }
9548
        </style>
9549
    <body>
9550
        <div class="error-message">
9551
            $lang_not_exportable
9552
        </div>
9553
    </body>
9554
</html>
9555
EOD;
9556
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
9557
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
9558
        }
9559
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
9560
9561
        // Add the extra files that go along with a SCORM package.
9562
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
9563
9564
        $fs = new Filesystem();
9565
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
9566
9567
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
9568
        $manifest = @$xmldoc->saveXML();
9569
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
9570
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
9571
        $zip_folder->add(
9572
            $archivePath.'/'.$temp_dir_short,
9573
            PCLZIP_OPT_REMOVE_PATH,
9574
            $archivePath.'/'.$temp_dir_short.'/'
9575
        );
9576
9577
        // Clean possible temporary files.
9578
        foreach ($files_cleanup as $file) {
9579
            $res = unlink($file);
9580
            if (false === $res) {
9581
                error_log(
9582
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
9583
                    0
9584
                );
9585
            }
9586
        }
9587
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
9588
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
9589
    }
9590
9591
    /**
9592
     * @param int $lp_id
9593
     *
9594
     * @return bool
9595
     */
9596
    public function scorm_export_to_pdf($lp_id)
9597
    {
9598
        $lp_id = (int) $lp_id;
9599
        $files_to_export = [];
9600
9601
        $sessionId = api_get_session_id();
9602
        $course_data = api_get_course_info($this->cc);
9603
9604
        if (!empty($course_data)) {
9605
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
9606
            $list = self::get_flat_ordered_items_list($lp_id);
9607
            if (!empty($list)) {
9608
                foreach ($list as $item_id) {
9609
                    $item = $this->items[$item_id];
9610
                    switch ($item->type) {
9611
                        case 'document':
9612
                            // Getting documents from a LP with chamilo documents
9613
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
9614
                            // Try loading document from the base course.
9615
                            if (empty($file_data) && !empty($sessionId)) {
9616
                                $file_data = DocumentManager::get_document_data_by_id(
9617
                                    $item->path,
9618
                                    $this->cc,
9619
                                    false,
9620
                                    0
9621
                                );
9622
                            }
9623
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
9624
                            if (file_exists($file_path)) {
9625
                                $files_to_export[] = [
9626
                                    'title' => $item->get_title(),
9627
                                    'path' => $file_path,
9628
                                ];
9629
                            }
9630
                            break;
9631
                        case 'asset': //commes from a scorm package generated by chamilo
9632
                        case 'sco':
9633
                            $file_path = $scorm_path.'/'.$item->path;
9634
                            if (file_exists($file_path)) {
9635
                                $files_to_export[] = [
9636
                                    'title' => $item->get_title(),
9637
                                    'path' => $file_path,
9638
                                ];
9639
                            }
9640
                            break;
9641
                        case 'dir':
9642
                            $files_to_export[] = [
9643
                                'title' => $item->get_title(),
9644
                                'path' => null,
9645
                            ];
9646
                            break;
9647
                    }
9648
                }
9649
            }
9650
9651
            $pdf = new PDF();
9652
            $result = $pdf->html_to_pdf(
9653
                $files_to_export,
9654
                $this->name,
9655
                $this->cc,
9656
                true,
9657
                true,
9658
                true,
9659
                $this->get_name()
9660
            );
9661
9662
            return $result;
9663
        }
9664
9665
        return false;
9666
    }
9667
9668
    /**
9669
     * Temp function to be moved in main_api or the best place around for this.
9670
     * Creates a file path if it doesn't exist.
9671
     *
9672
     * @param string $path
9673
     */
9674
    public function create_path($path)
9675
    {
9676
        $path_bits = explode('/', dirname($path));
9677
9678
        // IS_WINDOWS_OS has been defined in main_api.lib.php
9679
        $path_built = IS_WINDOWS_OS ? '' : '/';
9680
        foreach ($path_bits as $bit) {
9681
            if (!empty($bit)) {
9682
                $new_path = $path_built.$bit;
9683
                if (is_dir($new_path)) {
9684
                    $path_built = $new_path.'/';
9685
                } else {
9686
                    mkdir($new_path, api_get_permissions_for_new_directories());
9687
                    $path_built = $new_path.'/';
9688
                }
9689
            }
9690
        }
9691
    }
9692
9693
    /**
9694
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
9695
     *
9696
     * @return bool The results of the unlink function, or false if there was no image to start with
9697
     */
9698
    public function delete_lp_image()
9699
    {
9700
        $img = $this->get_preview_image();
9701
        if ('' != $img) {
9702
            $del_file = $this->get_preview_image_path(null, 'sys');
9703
            if (isset($del_file) && file_exists($del_file)) {
9704
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
9705
                if (file_exists($del_file_2)) {
9706
                    unlink($del_file_2);
9707
                }
9708
                $this->set_preview_image('');
9709
9710
                return @unlink($del_file);
9711
            }
9712
        }
9713
9714
        return false;
9715
    }
9716
9717
    /**
9718
     * Uploads an author image to the upload/learning_path/images directory.
9719
     *
9720
     * @param array    The image array, coming from the $_FILES superglobal
9721
     *
9722
     * @return bool True on success, false on error
9723
     */
9724
    public function upload_image($image_array)
9725
    {
9726
        if (!empty($image_array['name'])) {
9727
            $upload_ok = process_uploaded_file($image_array);
9728
            $has_attachment = true;
9729
        }
9730
9731
        if ($upload_ok && $has_attachment) {
9732
            $courseDir = api_get_course_path().'/upload/learning_path/images';
9733
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
9734
            $updir = $sys_course_path.$courseDir;
9735
            // Try to add an extension to the file if it hasn't one.
9736
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
9737
9738
            if (filter_extension($new_file_name)) {
9739
                $file_extension = explode('.', $image_array['name']);
9740
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
9741
                $filename = uniqid('');
9742
                $new_file_name = $filename.'.'.$file_extension;
9743
                $new_path = $updir.'/'.$new_file_name;
9744
9745
                // Resize the image.
9746
                $temp = new Image($image_array['tmp_name']);
9747
                $temp->resize(104);
9748
                $result = $temp->send_image($new_path);
9749
9750
                // Storing the image filename.
9751
                if ($result) {
9752
                    $this->set_preview_image($new_file_name);
9753
9754
                    //Resize to 64px to use on course homepage
9755
                    $temp->resize(64);
9756
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
9757
9758
                    return true;
9759
                }
9760
            }
9761
        }
9762
9763
        return false;
9764
    }
9765
9766
    /**
9767
     * @param int    $lp_id
9768
     * @param string $status
9769
     */
9770
    public function set_autolaunch($lp_id, $status)
9771
    {
9772
        $course_id = api_get_course_int_id();
9773
        $lp_id = (int) $lp_id;
9774
        $status = (int) $status;
9775
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
9776
9777
        // Setting everything to autolaunch = 0
9778
        $attributes['autolaunch'] = 0;
9779
        $where = [
9780
            'session_id = ? AND c_id = ? ' => [
9781
                api_get_session_id(),
9782
                $course_id,
9783
            ],
9784
        ];
9785
        Database::update($lp_table, $attributes, $where);
9786
        if (1 == $status) {
9787
            //Setting my lp_id to autolaunch = 1
9788
            $attributes['autolaunch'] = 1;
9789
            $where = [
9790
                'iid = ? AND session_id = ? AND c_id = ?' => [
9791
                    $lp_id,
9792
                    api_get_session_id(),
9793
                    $course_id,
9794
                ],
9795
            ];
9796
            Database::update($lp_table, $attributes, $where);
9797
        }
9798
    }
9799
9800
    /**
9801
     * Gets previous_item_id for the next element of the lp_item table.
9802
     *
9803
     * @author Isaac flores paz
9804
     *
9805
     * @return int Previous item ID
9806
     */
9807
    public function select_previous_item_id()
9808
    {
9809
        $course_id = api_get_course_int_id();
9810
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9811
9812
        // Get the max order of the items
9813
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
9814
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9815
        $rs_max_order = Database::query($sql);
9816
        $row_max_order = Database::fetch_object($rs_max_order);
9817
        $max_order = $row_max_order->display_order;
9818
        // Get the previous item ID
9819
        $sql = "SELECT iid as previous FROM $table_lp_item
9820
                WHERE
9821
                    c_id = $course_id AND
9822
                    lp_id = ".$this->lp_id." AND
9823
                    display_order = '$max_order' ";
9824
        $rs_max = Database::query($sql);
9825
        $row_max = Database::fetch_object($rs_max);
9826
9827
        // Return the previous item ID
9828
        return $row_max->previous;
9829
    }
9830
9831
    /**
9832
     * Copies an LP.
9833
     */
9834
    public function copy()
9835
    {
9836
        // Course builder
9837
        $cb = new CourseBuilder();
9838
9839
        //Setting tools that will be copied
9840
        $cb->set_tools_to_build(['learnpaths']);
9841
9842
        //Setting elements that will be copied
9843
        $cb->set_tools_specific_id_list(
9844
            ['learnpaths' => [$this->lp_id]]
9845
        );
9846
9847
        $course = $cb->build();
9848
9849
        //Course restorer
9850
        $course_restorer = new CourseRestorer($course);
9851
        $course_restorer->set_add_text_in_items(true);
9852
        $course_restorer->set_tool_copy_settings(
9853
            ['learnpaths' => ['reset_dates' => true]]
9854
        );
9855
        $course_restorer->restore(
9856
            api_get_course_id(),
9857
            api_get_session_id(),
9858
            false,
9859
            false
9860
        );
9861
    }
9862
9863
    /**
9864
     * Verify document size.
9865
     *
9866
     * @param string $s
9867
     *
9868
     * @return bool
9869
     */
9870
    public static function verify_document_size($s)
9871
    {
9872
        $post_max = ini_get('post_max_size');
9873
        if ('M' == substr($post_max, -1, 1)) {
9874
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
9875
        } elseif ('G' == substr($post_max, -1, 1)) {
9876
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
9877
        }
9878
        $upl_max = ini_get('upload_max_filesize');
9879
        if ('M' == substr($upl_max, -1, 1)) {
9880
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
9881
        } elseif ('G' == substr($upl_max, -1, 1)) {
9882
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
9883
        }
9884
9885
        $repo = Container::getDocumentRepository();
9886
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
9887
9888
        $course_max_space = DocumentManager::get_course_quota();
9889
        $total_size = filesize($s) + $documents_total_space;
9890
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
9891
            return true;
9892
        }
9893
9894
        return false;
9895
    }
9896
9897
    /**
9898
     * Clear LP prerequisites.
9899
     */
9900
    public function clear_prerequisites()
9901
    {
9902
        $course_id = $this->get_course_int_id();
9903
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9904
        $lp_id = $this->get_id();
9905
        // Cleaning prerequisites
9906
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
9907
                WHERE c_id = $course_id AND lp_id = $lp_id";
9908
        Database::query($sql);
9909
9910
        // Cleaning mastery score for exercises
9911
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
9912
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
9913
        Database::query($sql);
9914
    }
9915
9916
    public function set_previous_step_as_prerequisite_for_all_items()
9917
    {
9918
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9919
        $course_id = $this->get_course_int_id();
9920
        $lp_id = $this->get_id();
9921
9922
        if (!empty($this->items)) {
9923
            $previous_item_id = null;
9924
            $previous_item_max = 0;
9925
            $previous_item_type = null;
9926
            $last_item_not_dir = null;
9927
            $last_item_not_dir_type = null;
9928
            $last_item_not_dir_max = null;
9929
9930
            foreach ($this->ordered_items as $itemId) {
9931
                $item = $this->getItem($itemId);
9932
                // if there was a previous item... (otherwise jump to set it)
9933
                if (!empty($previous_item_id)) {
9934
                    $current_item_id = $item->get_id(); //save current id
9935
                    if ('dir' != $item->get_type()) {
9936
                        // Current item is not a folder, so it qualifies to get a prerequisites
9937
                        if ('quiz' == $last_item_not_dir_type) {
9938
                            // if previous is quiz, mark its max score as default score to be achieved
9939
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
9940
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
9941
                            Database::query($sql);
9942
                        }
9943
                        // now simply update the prerequisite to set it to the last non-chapter item
9944
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
9945
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
9946
                        Database::query($sql);
9947
                        // record item as 'non-chapter' reference
9948
                        $last_item_not_dir = $item->get_id();
9949
                        $last_item_not_dir_type = $item->get_type();
9950
                        $last_item_not_dir_max = $item->get_max();
9951
                    }
9952
                } else {
9953
                    if ('dir' != $item->get_type()) {
9954
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
9955
                        $last_item_not_dir = $item->get_id();
9956
                        $last_item_not_dir_type = $item->get_type();
9957
                        $last_item_not_dir_max = $item->get_max();
9958
                    }
9959
                }
9960
                // Saving the item as "previous item" for the next loop
9961
                $previous_item_id = $item->get_id();
9962
                $previous_item_max = $item->get_max();
9963
                $previous_item_type = $item->get_type();
9964
            }
9965
        }
9966
    }
9967
9968
    /**
9969
     * @param array $params
9970
     *
9971
     * @throws \Doctrine\ORM\OptimisticLockException
9972
     *
9973
     * @return int
9974
     */
9975
    public static function createCategory($params)
9976
    {
9977
        $item = new CLpCategory();
9978
        $item->setName($params['name']);
9979
        $item->setCId($params['c_id']);
9980
9981
        $repo = Container::getLpCategoryRepository();
9982
        $em = $repo->getEntityManager();
9983
        $em->persist($item);
9984
        $courseEntity = api_get_course_entity(api_get_course_int_id());
9985
9986
        $repo->addResourceToCourse(
9987
            $item,
9988
            ResourceLink::VISIBILITY_PUBLISHED,
9989
            api_get_user_entity(api_get_user_id()),
9990
            $courseEntity,
9991
            api_get_session_entity(),
9992
            api_get_group_entity()
9993
        );
9994
9995
        $em->flush();
9996
9997
        /*api_item_property_update(
9998
            api_get_course_info(),
9999
            TOOL_LEARNPATH_CATEGORY,
10000
            $item->getId(),
10001
            'visible',
10002
            api_get_user_id()
10003
        );*/
10004
10005
        return $item->getId();
10006
    }
10007
10008
    /**
10009
     * @param array $params
10010
     *
10011
     * @throws \Doctrine\ORM\ORMException
10012
     * @throws \Doctrine\ORM\OptimisticLockException
10013
     * @throws \Doctrine\ORM\TransactionRequiredException
10014
     */
10015
    public static function updateCategory($params)
10016
    {
10017
        $em = Database::getManager();
10018
        /** @var CLpCategory $item */
10019
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
10020
        if ($item) {
10021
            $item->setName($params['name']);
10022
            $em->merge($item);
10023
            $em->flush();
10024
        }
10025
    }
10026
10027
    /**
10028
     * @param int $id
10029
     */
10030
    public static function moveUpCategory($id)
10031
    {
10032
        $id = (int) $id;
10033
        $em = Database::getManager();
10034
        /** @var CLpCategory $item */
10035
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10036
        if ($item) {
10037
            $position = $item->getPosition() - 1;
10038
            $item->setPosition($position);
10039
            $em->persist($item);
10040
            $em->flush();
10041
        }
10042
    }
10043
10044
    /**
10045
     * @param int $id
10046
     */
10047
    public static function moveDownCategory($id)
10048
    {
10049
        $id = (int) $id;
10050
        $em = Database::getManager();
10051
        /** @var CLpCategory $item */
10052
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10053
        if ($item) {
10054
            $position = $item->getPosition() + 1;
10055
            $item->setPosition($position);
10056
            $em->persist($item);
10057
            $em->flush();
10058
        }
10059
    }
10060
10061
    /**
10062
     * @param int $courseId
10063
     *
10064
     * @throws \Doctrine\ORM\Query\QueryException
10065
     *
10066
     * @return int|mixed
10067
     */
10068
    public static function getCountCategories($courseId)
10069
    {
10070
        if (empty($courseId)) {
10071
            return 0;
10072
        }
10073
        $em = Database::getManager();
10074
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
10075
        $query->setParameter('id', $courseId);
10076
10077
        return $query->getSingleScalarResult();
10078
    }
10079
10080
    /**
10081
     * @param int $courseId
10082
     *
10083
     * @return mixed
10084
     */
10085
    public static function getCategories($courseId)
10086
    {
10087
        $em = Database::getManager();
10088
10089
        // Using doctrine extensions
10090
        /** @var SortableRepository $repo */
10091
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
10092
10093
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
10094
    }
10095
10096
    /**
10097
     * @param int $id
10098
     *
10099
     * @return CLpCategory
10100
     */
10101
    public static function getCategory($id)
10102
    {
10103
        $id = (int) $id;
10104
        $em = Database::getManager();
10105
10106
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
10107
    }
10108
10109
    /**
10110
     * @param int $courseId
10111
     *
10112
     * @return array
10113
     */
10114
    public static function getCategoryByCourse($courseId)
10115
    {
10116
        $em = Database::getManager();
10117
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
10118
            ['cId' => $courseId]
10119
        );
10120
10121
        return $items;
10122
    }
10123
10124
    /**
10125
     * @param int $id
10126
     *
10127
     * @return mixed
10128
     */
10129
    public static function deleteCategory($id)
10130
    {
10131
        $em = Database::getManager();
10132
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
10133
        if ($item) {
10134
            $courseId = $item->getCId();
10135
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
10136
            $query->setParameter('id', $courseId);
10137
            $query->setParameter('catId', $item->getId());
10138
            $lps = $query->getResult();
10139
10140
            // Setting category = 0.
10141
            if ($lps) {
10142
                foreach ($lps as $lpItem) {
10143
                    $lpItem->setCategoryId(0);
10144
                }
10145
            }
10146
10147
            // Removing category.
10148
            $em->remove($item);
10149
            $em->flush();
10150
10151
            $courseInfo = api_get_course_info_by_id($courseId);
10152
            $sessionId = api_get_session_id();
10153
10154
            // Delete link tool
10155
            /*$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
10156
            $link = 'lp/lp_controller.php?cid='.$courseInfo['real_id'].'&sid='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
10157
            // Delete tools
10158
            $sql = "DELETE FROM $tbl_tool
10159
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
10160
            Database::query($sql);*/
10161
10162
            return true;
10163
        }
10164
10165
        return false;
10166
    }
10167
10168
    /**
10169
     * @param int  $courseId
10170
     * @param bool $addSelectOption
10171
     *
10172
     * @return mixed
10173
     */
10174
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
10175
    {
10176
        $items = self::getCategoryByCourse($courseId);
10177
        $cats = [];
10178
        if ($addSelectOption) {
10179
            $cats = [get_lang('Select a category')];
10180
        }
10181
10182
        if (!empty($items)) {
10183
            foreach ($items as $cat) {
10184
                $cats[$cat->getId()] = $cat->getName();
10185
            }
10186
        }
10187
10188
        return $cats;
10189
    }
10190
10191
    /**
10192
     * @param string $courseCode
10193
     * @param int    $lpId
10194
     * @param int    $user_id
10195
     *
10196
     * @return learnpath
10197
     */
10198
    public static function getLpFromSession($courseCode, $lpId, $user_id)
10199
    {
10200
        $debug = 0;
10201
        $learnPath = null;
10202
        $lpObject = Session::read('lpobject');
10203
        if (null !== $lpObject) {
10204
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
10205
            if ($debug) {
10206
                error_log('getLpFromSession: unserialize');
10207
                error_log('------getLpFromSession------');
10208
                error_log('------unserialize------');
10209
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10210
                error_log("api_get_sessionid: ".api_get_session_id());
10211
            }
10212
        }
10213
10214
        if (!is_object($learnPath)) {
10215
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
10216
            if ($debug) {
10217
                error_log('------getLpFromSession------');
10218
                error_log('getLpFromSession: create new learnpath');
10219
                error_log("create new LP with $courseCode - $lpId - $user_id");
10220
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
10221
                error_log("api_get_sessionid: ".api_get_session_id());
10222
            }
10223
        }
10224
10225
        return $learnPath;
10226
    }
10227
10228
    /**
10229
     * @param int $itemId
10230
     *
10231
     * @return learnpathItem|false
10232
     */
10233
    public function getItem($itemId)
10234
    {
10235
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
10236
            return $this->items[$itemId];
10237
        }
10238
10239
        return false;
10240
    }
10241
10242
    /**
10243
     * @return int
10244
     */
10245
    public function getCurrentAttempt()
10246
    {
10247
        $attempt = $this->getItem($this->get_current_item_id());
10248
        if ($attempt) {
10249
            return $attempt->get_attempt_id();
10250
        }
10251
10252
        return 0;
10253
    }
10254
10255
    /**
10256
     * @return int
10257
     */
10258
    public function getCategoryId()
10259
    {
10260
        return (int) $this->categoryId;
10261
    }
10262
10263
    /**
10264
     * @param int $categoryId
10265
     *
10266
     * @return bool
10267
     */
10268
    public function setCategoryId($categoryId)
10269
    {
10270
        $this->categoryId = (int) $categoryId;
10271
        $table = Database::get_course_table(TABLE_LP_MAIN);
10272
        $lp_id = $this->get_id();
10273
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
10274
                WHERE iid = $lp_id";
10275
        Database::query($sql);
10276
10277
        return true;
10278
    }
10279
10280
    /**
10281
     * Get whether this is a learning path with the possibility to subscribe
10282
     * users or not.
10283
     *
10284
     * @return int
10285
     */
10286
    public function getSubscribeUsers()
10287
    {
10288
        return $this->subscribeUsers;
10289
    }
10290
10291
    /**
10292
     * Set whether this is a learning path with the possibility to subscribe
10293
     * users or not.
10294
     *
10295
     * @param int $value (0 = false, 1 = true)
10296
     *
10297
     * @return bool
10298
     */
10299
    public function setSubscribeUsers($value)
10300
    {
10301
        $this->subscribeUsers = (int) $value;
10302
        $table = Database::get_course_table(TABLE_LP_MAIN);
10303
        $lp_id = $this->get_id();
10304
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
10305
                WHERE iid = $lp_id";
10306
        Database::query($sql);
10307
10308
        return true;
10309
    }
10310
10311
    /**
10312
     * Calculate the count of stars for a user in this LP
10313
     * This calculation is based on the following rules:
10314
     * - the student gets one star when he gets to 50% of the learning path
10315
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
10316
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
10317
     * - the student gets the final star when the score for the *last* test is >= 80%.
10318
     *
10319
     * @param int $sessionId Optional. The session ID
10320
     *
10321
     * @return int The count of stars
10322
     */
10323
    public function getCalculateStars($sessionId = 0)
10324
    {
10325
        $stars = 0;
10326
        $progress = self::getProgress(
10327
            $this->lp_id,
10328
            $this->user_id,
10329
            $this->course_int_id,
10330
            $sessionId
10331
        );
10332
10333
        if ($progress >= 50) {
10334
            $stars++;
10335
        }
10336
10337
        // Calculate stars chapters evaluation
10338
        $exercisesItems = $this->getExercisesItems();
10339
10340
        if (!empty($exercisesItems)) {
10341
            $totalResult = 0;
10342
10343
            foreach ($exercisesItems as $exerciseItem) {
10344
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10345
                    $this->user_id,
10346
                    $exerciseItem->path,
10347
                    $this->course_int_id,
10348
                    $sessionId,
10349
                    $this->lp_id,
10350
                    $exerciseItem->db_id
10351
                );
10352
10353
                $exerciseResultInfo = end($exerciseResultInfo);
10354
10355
                if (!$exerciseResultInfo) {
10356
                    continue;
10357
                }
10358
10359
                if (!empty($exerciseResultInfo['max_score'])) {
10360
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
10361
                } else {
10362
                    $exerciseResult = 0;
10363
                }
10364
                $totalResult += $exerciseResult;
10365
            }
10366
10367
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
10368
10369
            if ($totalExerciseAverage >= 50) {
10370
                $stars++;
10371
            }
10372
10373
            if ($totalExerciseAverage >= 80) {
10374
                $stars++;
10375
            }
10376
        }
10377
10378
        // Calculate star for final evaluation
10379
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10380
10381
        if (!empty($finalEvaluationItem)) {
10382
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10383
                $this->user_id,
10384
                $finalEvaluationItem->path,
10385
                $this->course_int_id,
10386
                $sessionId,
10387
                $this->lp_id,
10388
                $finalEvaluationItem->db_id
10389
            );
10390
10391
            $evaluationResultInfo = end($evaluationResultInfo);
10392
10393
            if ($evaluationResultInfo) {
10394
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
10395
10396
                if ($evaluationResult >= 80) {
10397
                    $stars++;
10398
                }
10399
            }
10400
        }
10401
10402
        return $stars;
10403
    }
10404
10405
    /**
10406
     * Get the items of exercise type.
10407
     *
10408
     * @return array The items. Otherwise return false
10409
     */
10410
    public function getExercisesItems()
10411
    {
10412
        $exercises = [];
10413
        foreach ($this->items as $item) {
10414
            if ('quiz' != $item->type) {
10415
                continue;
10416
            }
10417
            $exercises[] = $item;
10418
        }
10419
10420
        array_pop($exercises);
10421
10422
        return $exercises;
10423
    }
10424
10425
    /**
10426
     * Get the item of exercise type (evaluation type).
10427
     *
10428
     * @return array The final evaluation. Otherwise return false
10429
     */
10430
    public function getFinalEvaluationItem()
10431
    {
10432
        $exercises = [];
10433
        foreach ($this->items as $item) {
10434
            if (TOOL_QUIZ !== $item->type) {
10435
                continue;
10436
            }
10437
10438
            $exercises[] = $item;
10439
        }
10440
10441
        return array_pop($exercises);
10442
    }
10443
10444
    /**
10445
     * Calculate the total points achieved for the current user in this learning path.
10446
     *
10447
     * @param int $sessionId Optional. The session Id
10448
     *
10449
     * @return int
10450
     */
10451
    public function getCalculateScore($sessionId = 0)
10452
    {
10453
        // Calculate stars chapters evaluation
10454
        $exercisesItems = $this->getExercisesItems();
10455
        $finalEvaluationItem = $this->getFinalEvaluationItem();
10456
        $totalExercisesResult = 0;
10457
        $totalEvaluationResult = 0;
10458
10459
        if (false !== $exercisesItems) {
10460
            foreach ($exercisesItems as $exerciseItem) {
10461
                $exerciseResultInfo = Event::getExerciseResultsByUser(
10462
                    $this->user_id,
10463
                    $exerciseItem->path,
10464
                    $this->course_int_id,
10465
                    $sessionId,
10466
                    $this->lp_id,
10467
                    $exerciseItem->db_id
10468
                );
10469
10470
                $exerciseResultInfo = end($exerciseResultInfo);
10471
10472
                if (!$exerciseResultInfo) {
10473
                    continue;
10474
                }
10475
10476
                $totalExercisesResult += $exerciseResultInfo['score'];
10477
            }
10478
        }
10479
10480
        if (!empty($finalEvaluationItem)) {
10481
            $evaluationResultInfo = Event::getExerciseResultsByUser(
10482
                $this->user_id,
10483
                $finalEvaluationItem->path,
10484
                $this->course_int_id,
10485
                $sessionId,
10486
                $this->lp_id,
10487
                $finalEvaluationItem->db_id
10488
            );
10489
10490
            $evaluationResultInfo = end($evaluationResultInfo);
10491
10492
            if ($evaluationResultInfo) {
10493
                $totalEvaluationResult += $evaluationResultInfo['score'];
10494
            }
10495
        }
10496
10497
        return $totalExercisesResult + $totalEvaluationResult;
10498
    }
10499
10500
    /**
10501
     * Check if URL is not allowed to be show in a iframe.
10502
     *
10503
     * @param string $src
10504
     *
10505
     * @return string
10506
     */
10507
    public function fixBlockedLinks($src)
10508
    {
10509
        $urlInfo = parse_url($src);
10510
10511
        $platformProtocol = 'https';
10512
        if (false === strpos(api_get_path(WEB_CODE_PATH), 'https')) {
10513
            $platformProtocol = 'http';
10514
        }
10515
10516
        $protocolFixApplied = false;
10517
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
10518
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
10519
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
10520
10521
        if ($platformProtocol != $scheme) {
10522
            Session::write('x_frame_source', $src);
10523
            $src = 'blank.php?error=x_frames_options';
10524
            $protocolFixApplied = true;
10525
        }
10526
10527
        if (false == $protocolFixApplied) {
10528
            if (false === strpos(api_get_path(WEB_PATH), $host)) {
10529
                // Check X-Frame-Options
10530
                $ch = curl_init();
10531
                $options = [
10532
                    CURLOPT_URL => $src,
10533
                    CURLOPT_RETURNTRANSFER => true,
10534
                    CURLOPT_HEADER => true,
10535
                    CURLOPT_FOLLOWLOCATION => true,
10536
                    CURLOPT_ENCODING => "",
10537
                    CURLOPT_AUTOREFERER => true,
10538
                    CURLOPT_CONNECTTIMEOUT => 120,
10539
                    CURLOPT_TIMEOUT => 120,
10540
                    CURLOPT_MAXREDIRS => 10,
10541
                ];
10542
10543
                $proxySettings = api_get_configuration_value('proxy_settings');
10544
                if (!empty($proxySettings) &&
10545
                    isset($proxySettings['curl_setopt_array'])
10546
                ) {
10547
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
10548
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
10549
                }
10550
10551
                curl_setopt_array($ch, $options);
10552
                $response = curl_exec($ch);
10553
                $httpCode = curl_getinfo($ch);
10554
                $headers = substr($response, 0, $httpCode['header_size']);
10555
10556
                $error = false;
10557
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
10558
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
10559
                ) {
10560
                    $error = true;
10561
                }
10562
10563
                if ($error) {
10564
                    Session::write('x_frame_source', $src);
10565
                    $src = 'blank.php?error=x_frames_options';
10566
                }
10567
            }
10568
        }
10569
10570
        return $src;
10571
    }
10572
10573
    /**
10574
     * Check if this LP has a created forum in the basis course.
10575
     *
10576
     * @return bool
10577
     */
10578
    public function lpHasForum()
10579
    {
10580
        $forumTable = Database::get_course_table(TABLE_FORUM);
10581
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
10582
10583
        $fakeFrom = "
10584
            $forumTable f
10585
            INNER JOIN $itemProperty ip
10586
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
10587
        ";
10588
10589
        $resultData = Database::select(
10590
            'COUNT(f.iid) AS qty',
10591
            $fakeFrom,
10592
            [
10593
                'where' => [
10594
                    'ip.visibility != ? AND ' => 2,
10595
                    'ip.tool = ? AND ' => TOOL_FORUM,
10596
                    'f.c_id = ? AND ' => intval($this->course_int_id),
10597
                    'f.lp_id = ?' => intval($this->lp_id),
10598
                ],
10599
            ],
10600
            'first'
10601
        );
10602
10603
        return $resultData['qty'] > 0;
10604
    }
10605
10606
    /**
10607
     * Get the forum for this learning path.
10608
     *
10609
     * @param int $sessionId
10610
     *
10611
     * @return array
10612
     */
10613
    public function getForum($sessionId = 0)
10614
    {
10615
        $repo = Container::getForumRepository();
10616
10617
        $course = api_get_course_entity();
10618
        $session = api_get_session_entity($sessionId);
10619
        $qb = $repo->getResourcesByCourse($course, $session);
10620
10621
        return $qb->getQuery()->getResult();
10622
    }
10623
10624
    /**
10625
     * Create a forum for this learning path.
10626
     *
10627
     * @param int $forumCategoryId
10628
     *
10629
     * @return int The forum ID if was created. Otherwise return false
10630
     */
10631
    public function createForum($forumCategoryId)
10632
    {
10633
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
10634
10635
        return store_forum(
10636
            [
10637
                'lp_id' => $this->lp_id,
10638
                'forum_title' => $this->name,
10639
                'forum_comment' => null,
10640
                'forum_category' => (int) $forumCategoryId,
10641
                'students_can_edit_group' => ['students_can_edit' => 0],
10642
                'allow_new_threads_group' => ['allow_new_threads' => 0],
10643
                'default_view_type_group' => ['default_view_type' => 'flat'],
10644
                'group_forum' => 0,
10645
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
10646
            ],
10647
            [],
10648
            true
10649
        );
10650
    }
10651
10652
    /**
10653
     * Get the LP Final Item form.
10654
     *
10655
     * @throws Exception
10656
     * @throws HTML_QuickForm_Error
10657
     *
10658
     * @return string
10659
     */
10660
    public function getFinalItemForm()
10661
    {
10662
        $finalItem = $this->getFinalItem();
10663
        $title = '';
10664
10665
        if ($finalItem) {
10666
            $title = $finalItem->get_title();
10667
            $buttonText = get_lang('Save');
10668
            $content = $this->getSavedFinalItem();
10669
        } else {
10670
            $buttonText = get_lang('Add this document to the course');
10671
            $content = $this->getFinalItemTemplate();
10672
        }
10673
10674
        $editorConfig = [
10675
            'ToolbarSet' => 'LearningPathDocuments',
10676
            'Width' => '100%',
10677
            'Height' => '500',
10678
            'FullPage' => true,
10679
//            'CreateDocumentDir' => $relative_prefix,
10680
    //            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
10681
  //          'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
10682
        ];
10683
10684
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
10685
            'type' => 'document',
10686
            'lp_id' => $this->lp_id,
10687
        ]);
10688
10689
        $form = new FormValidator('final_item', 'POST', $url);
10690
        $form->addText('title', get_lang('Title'));
10691
        $form->addButtonSave($buttonText);
10692
        $form->addHtml(
10693
            Display::return_message(
10694
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
10695
                'normal',
10696
                false
10697
            )
10698
        );
10699
10700
        $renderer = $form->defaultRenderer();
10701
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
10702
10703
        $form->addHtmlEditor(
10704
            'content_lp_certificate',
10705
            null,
10706
            true,
10707
            false,
10708
            $editorConfig,
10709
            true
10710
        );
10711
        $form->addHidden('action', 'add_final_item');
10712
        $form->addHidden('path', Session::read('pathItem'));
10713
        $form->addHidden('previous', $this->get_last());
10714
        $form->setDefaults(
10715
            ['title' => $title, 'content_lp_certificate' => $content]
10716
        );
10717
10718
        if ($form->validate()) {
10719
            $values = $form->exportValues();
10720
            $lastItemId = $this->getLastInFirstLevel();
10721
10722
            if (!$finalItem) {
10723
                $documentId = $this->create_document(
10724
                    $this->course_info,
10725
                    $values['content_lp_certificate'],
10726
                    $values['title']
10727
                );
10728
                $this->add_item(
10729
                    0,
10730
                    $lastItemId,
10731
                    'final_item',
10732
                    $documentId,
10733
                    $values['title'],
10734
                    ''
10735
                );
10736
10737
                Display::addFlash(
10738
                    Display::return_message(get_lang('Added'))
10739
                );
10740
            } else {
10741
                $this->edit_document($this->course_info);
10742
            }
10743
        }
10744
10745
        return $form->returnForm();
10746
    }
10747
10748
    /**
10749
     * Check if the current lp item is first, both, last or none from lp list.
10750
     *
10751
     * @param int $currentItemId
10752
     *
10753
     * @return string
10754
     */
10755
    public function isFirstOrLastItem($currentItemId)
10756
    {
10757
        $lpItemId = [];
10758
        $typeListNotToVerify = self::getChapterTypes();
10759
10760
        // Using get_toc() function instead $this->items because returns the correct order of the items
10761
        foreach ($this->get_toc() as $item) {
10762
            if (!in_array($item['type'], $typeListNotToVerify)) {
10763
                $lpItemId[] = $item['id'];
10764
            }
10765
        }
10766
10767
        $lastLpItemIndex = count($lpItemId) - 1;
10768
        $position = array_search($currentItemId, $lpItemId);
10769
10770
        switch ($position) {
10771
            case 0:
10772
                if (!$lastLpItemIndex) {
10773
                    $answer = 'both';
10774
                    break;
10775
                }
10776
10777
                $answer = 'first';
10778
                break;
10779
            case $lastLpItemIndex:
10780
                $answer = 'last';
10781
                break;
10782
            default:
10783
                $answer = 'none';
10784
        }
10785
10786
        return $answer;
10787
    }
10788
10789
    /**
10790
     * Get whether this is a learning path with the accumulated SCORM time or not.
10791
     *
10792
     * @return int
10793
     */
10794
    public function getAccumulateScormTime()
10795
    {
10796
        return $this->accumulateScormTime;
10797
    }
10798
10799
    /**
10800
     * Set whether this is a learning path with the accumulated SCORM time or not.
10801
     *
10802
     * @param int $value (0 = false, 1 = true)
10803
     *
10804
     * @return bool Always returns true
10805
     */
10806
    public function setAccumulateScormTime($value)
10807
    {
10808
        $this->accumulateScormTime = (int) $value;
10809
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
10810
        $lp_id = $this->get_id();
10811
        $sql = "UPDATE $lp_table
10812
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
10813
                WHERE iid = $lp_id";
10814
        Database::query($sql);
10815
10816
        return true;
10817
    }
10818
10819
    /**
10820
     * Returns an HTML-formatted link to a resource, to incorporate directly into
10821
     * the new learning path tool.
10822
     *
10823
     * The function is a big switch on tool type.
10824
     * In each case, we query the corresponding table for information and build the link
10825
     * with that information.
10826
     *
10827
     * @author Yannick Warnier <[email protected]> - rebranding based on
10828
     * previous work (display_addedresource_link_in_learnpath())
10829
     *
10830
     * @param int $course_id      Course code
10831
     * @param int $learningPathId The learning path ID (in lp table)
10832
     * @param int $id_in_path     the unique index in the items table
10833
     * @param int $lpViewId
10834
     *
10835
     * @return string
10836
     */
10837
    public static function rl_get_resource_link_for_learnpath(
10838
        $course_id,
10839
        $learningPathId,
10840
        $id_in_path,
10841
        $lpViewId
10842
    ) {
10843
        $session_id = api_get_session_id();
10844
        $course_info = api_get_course_info_by_id($course_id);
10845
10846
        $learningPathId = (int) $learningPathId;
10847
        $id_in_path = (int) $id_in_path;
10848
        $lpViewId = (int) $lpViewId;
10849
10850
        $em = Database::getManager();
10851
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
10852
10853
        /** @var CLpItem $rowItem */
10854
        $rowItem = $lpItemRepo->findOneBy([
10855
            'cId' => $course_id,
10856
            'lpId' => $learningPathId,
10857
            'iid' => $id_in_path,
10858
        ]);
10859
10860
        if (!$rowItem) {
10861
            // Try one more time with "id"
10862
            /** @var CLpItem $rowItem */
10863
            $rowItem = $lpItemRepo->findOneBy([
10864
                'cId' => $course_id,
10865
                'lpId' => $learningPathId,
10866
                'id' => $id_in_path,
10867
            ]);
10868
10869
            if (!$rowItem) {
10870
                return -1;
10871
            }
10872
        }
10873
10874
        $type = $rowItem->getItemType();
10875
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
10876
        $main_dir_path = api_get_path(WEB_CODE_PATH);
10877
        //$main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
10878
        $link = '';
10879
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
10880
10881
        switch ($type) {
10882
            case 'dir':
10883
                return $main_dir_path.'lp/blank.php';
10884
            case TOOL_CALENDAR_EVENT:
10885
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
10886
            case TOOL_ANNOUNCEMENT:
10887
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
10888
            case TOOL_LINK:
10889
                $linkInfo = Link::getLinkInfo($id);
10890
                if (isset($linkInfo['url'])) {
10891
                    return $linkInfo['url'];
10892
                }
10893
10894
                return '';
10895
            case TOOL_QUIZ:
10896
                if (empty($id)) {
10897
                    return '';
10898
                }
10899
10900
                // Get the lp_item_view with the highest view_count.
10901
                $learnpathItemViewResult = $em
10902
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
10903
                    ->findBy(
10904
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
10905
                        ['viewCount' => 'DESC'],
10906
                        1
10907
                    );
10908
                /** @var CLpItemView $learnpathItemViewData */
10909
                $learnpathItemViewData = current($learnpathItemViewResult);
10910
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
10911
10912
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
10913
                    .http_build_query([
10914
                        'lp_init' => 1,
10915
                        'learnpath_item_view_id' => $learnpathItemViewId,
10916
                        'learnpath_id' => $learningPathId,
10917
                        'learnpath_item_id' => $id_in_path,
10918
                        'exerciseId' => $id,
10919
                    ]);
10920
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
10921
                /*$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
10922
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
10923
                $myrow = Database::fetch_array($result);
10924
                $path = $myrow['path'];
10925
10926
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
10927
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
10928
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;*/
10929
            case TOOL_FORUM:
10930
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
10931
            case TOOL_THREAD:
10932
                // forum post
10933
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
10934
                if (empty($id)) {
10935
                    return '';
10936
                }
10937
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
10938
                $result = Database::query($sql);
10939
                $myrow = Database::fetch_array($result);
10940
10941
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
10942
                    .$extraParams;
10943
            case TOOL_POST:
10944
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
10945
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
10946
                $myrow = Database::fetch_array($result);
10947
10948
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
10949
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
10950
            case TOOL_READOUT_TEXT:
10951
                return api_get_path(WEB_CODE_PATH).
10952
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
10953
            case TOOL_DOCUMENT:
10954
                $repo = Container::getDocumentRepository();
10955
                $document = $repo->find($rowItem->getPath());
10956
                $file = $repo->getResourceFileUrl($document, [], UrlGeneratorInterface::ABSOLUTE_URL);
10957
10958
                return $file;
10959
10960
                $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...
10961
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
10962
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
10963
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
10964
10965
                $openmethod = 2;
10966
                $officedoc = false;
10967
                Session::write('openmethod', $openmethod);
10968
                Session::write('officedoc', $officedoc);
10969
10970
                if ($showDirectUrl) {
10971
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
10972
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
10973
                        if (Link::isPdfLink($file)) {
10974
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
10975
10976
                            return $pdfUrl;
10977
                        }
10978
                    }
10979
10980
                    return $file;
10981
                }
10982
10983
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
10984
            case TOOL_LP_FINAL_ITEM:
10985
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
10986
                    .$extraParams;
10987
            case 'assignments':
10988
                return $main_dir_path.'work/work.php?'.$extraParams;
10989
            case TOOL_DROPBOX:
10990
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
10991
            case 'introduction_text': //DEPRECATED
10992
                return '';
10993
            case TOOL_COURSE_DESCRIPTION:
10994
                return $main_dir_path.'course_description?'.$extraParams;
10995
            case TOOL_GROUP:
10996
                return $main_dir_path.'group/group.php?'.$extraParams;
10997
            case TOOL_USER:
10998
                return $main_dir_path.'user/user.php?'.$extraParams;
10999
            case TOOL_STUDENTPUBLICATION:
11000
                if (!empty($rowItem->getPath())) {
11001
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
11002
                }
11003
11004
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
11005
        }
11006
11007
        return $link;
11008
    }
11009
11010
    /**
11011
     * Gets the name of a resource (generally used in learnpath when no name is provided).
11012
     *
11013
     * @author Yannick Warnier <[email protected]>
11014
     *
11015
     * @param string $course_code    Course code
11016
     * @param int    $learningPathId
11017
     * @param int    $id_in_path     The resource ID
11018
     *
11019
     * @return string
11020
     */
11021
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
11022
    {
11023
        $_course = api_get_course_info($course_code);
11024
        if (empty($_course)) {
11025
            return '';
11026
        }
11027
        $course_id = $_course['real_id'];
11028
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11029
        $learningPathId = (int) $learningPathId;
11030
        $id_in_path = (int) $id_in_path;
11031
11032
        $sql = "SELECT item_type, title, ref
11033
                FROM $tbl_lp_item
11034
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
11035
        $res_item = Database::query($sql);
11036
11037
        if (Database::num_rows($res_item) < 1) {
11038
            return '';
11039
        }
11040
        $row_item = Database::fetch_array($res_item);
11041
        $type = strtolower($row_item['item_type']);
11042
        $id = $row_item['ref'];
11043
        $output = '';
11044
11045
        switch ($type) {
11046
            case TOOL_CALENDAR_EVENT:
11047
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
11048
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
11049
                $myrow = Database::fetch_array($result);
11050
                $output = $myrow['title'];
11051
                break;
11052
            case TOOL_ANNOUNCEMENT:
11053
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
11054
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
11055
                $myrow = Database::fetch_array($result);
11056
                $output = $myrow['title'];
11057
                break;
11058
            case TOOL_LINK:
11059
                // Doesn't take $target into account.
11060
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
11061
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
11062
                $myrow = Database::fetch_array($result);
11063
                $output = $myrow['title'];
11064
                break;
11065
            case TOOL_QUIZ:
11066
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
11067
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
11068
                $myrow = Database::fetch_array($result);
11069
                $output = $myrow['title'];
11070
                break;
11071
            case TOOL_FORUM:
11072
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
11073
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
11074
                $myrow = Database::fetch_array($result);
11075
                $output = $myrow['forum_name'];
11076
                break;
11077
            case TOOL_THREAD:
11078
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11079
                // Grabbing the title of the post.
11080
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
11081
                $result_title = Database::query($sql_title);
11082
                $myrow_title = Database::fetch_array($result_title);
11083
                $output = $myrow_title['post_title'];
11084
                break;
11085
            case TOOL_POST:
11086
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
11087
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
11088
                $result = Database::query($sql);
11089
                $post = Database::fetch_array($result);
11090
                $output = $post['post_title'];
11091
                break;
11092
            case 'dir':
11093
            case TOOL_DOCUMENT:
11094
                $title = $row_item['title'];
11095
                $output = '-';
11096
                if (!empty($title)) {
11097
                    $output = $title;
11098
                }
11099
                break;
11100
            case 'hotpotatoes':
11101
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
11102
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
11103
                $myrow = Database::fetch_array($result);
11104
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
11105
                $last = count($pathname) - 1; // Making a correct name for the link.
11106
                $filename = $pathname[$last]; // Making a correct name for the link.
11107
                $myrow['path'] = rawurlencode($myrow['path']);
11108
                $output = $filename;
11109
                break;
11110
        }
11111
11112
        return stripslashes($output);
11113
    }
11114
11115
    /**
11116
     * Get the parent names for the current item.
11117
     *
11118
     * @param int $newItemId Optional. The item ID
11119
     *
11120
     * @return array
11121
     */
11122
    public function getCurrentItemParentNames($newItemId = 0)
11123
    {
11124
        $newItemId = $newItemId ?: $this->get_current_item_id();
11125
        $return = [];
11126
        $item = $this->getItem($newItemId);
11127
        $parent = $this->getItem($item->get_parent());
11128
11129
        while ($parent) {
11130
            $return[] = $parent->get_title();
11131
            $parent = $this->getItem($parent->get_parent());
11132
        }
11133
11134
        return array_reverse($return);
11135
    }
11136
11137
    /**
11138
     * Reads and process "lp_subscription_settings" setting.
11139
     *
11140
     * @return array
11141
     */
11142
    public static function getSubscriptionSettings()
11143
    {
11144
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
11145
        if (empty($subscriptionSettings)) {
11146
            // By default allow both settings
11147
            $subscriptionSettings = [
11148
                'allow_add_users_to_lp' => true,
11149
                'allow_add_users_to_lp_category' => true,
11150
            ];
11151
        } else {
11152
            $subscriptionSettings = $subscriptionSettings['options'];
11153
        }
11154
11155
        return $subscriptionSettings;
11156
    }
11157
11158
    /**
11159
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
11160
     */
11161
    public function exportToCourseBuildFormat()
11162
    {
11163
        if (!api_is_allowed_to_edit()) {
11164
            return false;
11165
        }
11166
11167
        $courseBuilder = new CourseBuilder();
11168
        $itemList = [];
11169
        /** @var learnpathItem $item */
11170
        foreach ($this->items as $item) {
11171
            $itemList[$item->get_type()][] = $item->get_path();
11172
        }
11173
11174
        if (empty($itemList)) {
11175
            return false;
11176
        }
11177
11178
        if (isset($itemList['document'])) {
11179
            // Get parents
11180
            foreach ($itemList['document'] as $documentId) {
11181
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
11182
                if (!empty($documentInfo['parents'])) {
11183
                    foreach ($documentInfo['parents'] as $parentInfo) {
11184
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
11185
                            continue;
11186
                        }
11187
                        $itemList['document'][] = $parentInfo['iid'];
11188
                    }
11189
                }
11190
            }
11191
11192
            $courseInfo = api_get_course_info();
11193
            foreach ($itemList['document'] as $documentId) {
11194
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
11195
                $items = DocumentManager::get_resources_from_source_html(
11196
                    $documentInfo['absolute_path'],
11197
                    true,
11198
                    TOOL_DOCUMENT
11199
                );
11200
11201
                if (!empty($items)) {
11202
                    foreach ($items as $item) {
11203
                        // Get information about source url
11204
                        $url = $item[0]; // url
11205
                        $scope = $item[1]; // scope (local, remote)
11206
                        $type = $item[2]; // type (rel, abs, url)
11207
11208
                        $origParseUrl = parse_url($url);
11209
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
11210
11211
                        if ('local' == $scope) {
11212
                            if ('abs' == $type || 'rel' == $type) {
11213
                                $documentFile = strstr($realOrigPath, 'document');
11214
                                if (false !== strpos($realOrigPath, $documentFile)) {
11215
                                    $documentFile = str_replace('document', '', $documentFile);
11216
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
11217
                                    // Document found! Add it to the list
11218
                                    if ($itemDocumentId) {
11219
                                        $itemList['document'][] = $itemDocumentId;
11220
                                    }
11221
                                }
11222
                            }
11223
                        }
11224
                    }
11225
                }
11226
            }
11227
11228
            $courseBuilder->build_documents(
11229
                api_get_session_id(),
11230
                $this->get_course_int_id(),
11231
                true,
11232
                $itemList['document']
11233
            );
11234
        }
11235
11236
        if (isset($itemList['quiz'])) {
11237
            $courseBuilder->build_quizzes(
11238
                api_get_session_id(),
11239
                $this->get_course_int_id(),
11240
                true,
11241
                $itemList['quiz']
11242
            );
11243
        }
11244
11245
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
11246
11247
        /*if (!empty($itemList['thread'])) {
11248
            $postList = [];
11249
            foreach ($itemList['thread'] as $postId) {
11250
                $post = get_post_information($postId);
11251
                if ($post) {
11252
                    if (!isset($itemList['forum'])) {
11253
                        $itemList['forum'] = [];
11254
                    }
11255
                    $itemList['forum'][] = $post['forum_id'];
11256
                    $postList[] = $postId;
11257
                }
11258
            }
11259
11260
            if (!empty($postList)) {
11261
                $courseBuilder->build_forum_posts(
11262
                    $this->get_course_int_id(),
11263
                    null,
11264
                    null,
11265
                    $postList
11266
                );
11267
            }
11268
        }*/
11269
11270
        if (!empty($itemList['thread'])) {
11271
            $threadList = [];
11272
            $em = Database::getManager();
11273
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
11274
            foreach ($itemList['thread'] as $threadId) {
11275
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
11276
                $thread = $repo->find($threadId);
11277
                if ($thread) {
11278
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
11279
                    $threadList[] = $thread->getIid();
11280
                }
11281
            }
11282
11283
            if (!empty($threadList)) {
11284
                $courseBuilder->build_forum_topics(
11285
                    api_get_session_id(),
11286
                    $this->get_course_int_id(),
11287
                    null,
11288
                    $threadList
11289
                );
11290
            }
11291
        }
11292
11293
        $forumCategoryList = [];
11294
        if (isset($itemList['forum'])) {
11295
            foreach ($itemList['forum'] as $forumId) {
11296
                $forumInfo = get_forums($forumId);
11297
                $forumCategoryList[] = $forumInfo['forum_category'];
11298
            }
11299
        }
11300
11301
        if (!empty($forumCategoryList)) {
11302
            $courseBuilder->build_forum_category(
11303
                api_get_session_id(),
11304
                $this->get_course_int_id(),
11305
                true,
11306
                $forumCategoryList
11307
            );
11308
        }
11309
11310
        if (!empty($itemList['forum'])) {
11311
            $courseBuilder->build_forums(
11312
                api_get_session_id(),
11313
                $this->get_course_int_id(),
11314
                true,
11315
                $itemList['forum']
11316
            );
11317
        }
11318
11319
        if (isset($itemList['link'])) {
11320
            $courseBuilder->build_links(
11321
                api_get_session_id(),
11322
                $this->get_course_int_id(),
11323
                true,
11324
                $itemList['link']
11325
            );
11326
        }
11327
11328
        if (!empty($itemList['student_publication'])) {
11329
            $courseBuilder->build_works(
11330
                api_get_session_id(),
11331
                $this->get_course_int_id(),
11332
                true,
11333
                $itemList['student_publication']
11334
            );
11335
        }
11336
11337
        $courseBuilder->build_learnpaths(
11338
            api_get_session_id(),
11339
            $this->get_course_int_id(),
11340
            true,
11341
            [$this->get_id()],
11342
            false
11343
        );
11344
11345
        $courseBuilder->restoreDocumentsFromList();
11346
11347
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
11348
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
11349
        $result = DocumentManager::file_send_for_download(
11350
            $zipPath,
11351
            true,
11352
            $this->get_name().'.zip'
11353
        );
11354
11355
        if ($result) {
11356
            api_not_allowed();
11357
        }
11358
11359
        return true;
11360
    }
11361
11362
    /**
11363
     * Get whether this is a learning path with the accumulated work time or not.
11364
     *
11365
     * @return int
11366
     */
11367
    public function getAccumulateWorkTime()
11368
    {
11369
        return (int) $this->accumulateWorkTime;
11370
    }
11371
11372
    /**
11373
     * Get whether this is a learning path with the accumulated work time or not.
11374
     *
11375
     * @return int
11376
     */
11377
    public function getAccumulateWorkTimeTotalCourse()
11378
    {
11379
        $table = Database::get_course_table(TABLE_LP_MAIN);
11380
        $sql = "SELECT SUM(accumulate_work_time) AS total
11381
                FROM $table
11382
                WHERE c_id = ".$this->course_int_id;
11383
        $result = Database::query($sql);
11384
        $row = Database::fetch_array($result);
11385
11386
        return (int) $row['total'];
11387
    }
11388
11389
    /**
11390
     * Set whether this is a learning path with the accumulated work time or not.
11391
     *
11392
     * @param int $value (0 = false, 1 = true)
11393
     *
11394
     * @return bool
11395
     */
11396
    public function setAccumulateWorkTime($value)
11397
    {
11398
        if (!api_get_configuration_value('lp_minimum_time')) {
11399
            return false;
11400
        }
11401
11402
        $this->accumulateWorkTime = (int) $value;
11403
        $table = Database::get_course_table(TABLE_LP_MAIN);
11404
        $lp_id = $this->get_id();
11405
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
11406
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
11407
        Database::query($sql);
11408
11409
        return true;
11410
    }
11411
11412
    /**
11413
     * @param int $lpId
11414
     * @param int $courseId
11415
     *
11416
     * @return mixed
11417
     */
11418
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
11419
    {
11420
        $lpId = (int) $lpId;
11421
        $courseId = (int) $courseId;
11422
11423
        $table = Database::get_course_table(TABLE_LP_MAIN);
11424
        $sql = "SELECT accumulate_work_time
11425
                FROM $table
11426
                WHERE c_id = $courseId AND id = $lpId";
11427
        $result = Database::query($sql);
11428
        $row = Database::fetch_array($result);
11429
11430
        return $row['accumulate_work_time'];
11431
    }
11432
11433
    /**
11434
     * @param int $courseId
11435
     *
11436
     * @return int
11437
     */
11438
    public static function getAccumulateWorkTimeTotal($courseId)
11439
    {
11440
        $table = Database::get_course_table(TABLE_LP_MAIN);
11441
        $courseId = (int) $courseId;
11442
        $sql = "SELECT SUM(accumulate_work_time) AS total
11443
                FROM $table
11444
                WHERE c_id = $courseId";
11445
        $result = Database::query($sql);
11446
        $row = Database::fetch_array($result);
11447
11448
        return (int) $row['total'];
11449
    }
11450
11451
    /**
11452
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
11453
     * and put the images in.
11454
     *
11455
     * @return array
11456
     */
11457
    public static function getIconSelect()
11458
    {
11459
        $theme = api_get_visual_theme();
11460
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
11461
        $icons = ['' => get_lang('Please select an option')];
11462
11463
        if (is_dir($path)) {
11464
            $finder = new Finder();
11465
            $finder->files()->in($path);
11466
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
11467
            /** @var SplFileInfo $file */
11468
            foreach ($finder as $file) {
11469
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
11470
                    $icons[$file->getFilename()] = $file->getFilename();
11471
                }
11472
            }
11473
        }
11474
11475
        return $icons;
11476
    }
11477
11478
    /**
11479
     * @param int $lpId
11480
     *
11481
     * @return string
11482
     */
11483
    public static function getSelectedIcon($lpId)
11484
    {
11485
        $extraFieldValue = new ExtraFieldValue('lp');
11486
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
11487
        $icon = '';
11488
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
11489
            $icon = $lpIcon['value'];
11490
        }
11491
11492
        return $icon;
11493
    }
11494
11495
    /**
11496
     * @param int $lpId
11497
     *
11498
     * @return string
11499
     */
11500
    public static function getSelectedIconHtml($lpId)
11501
    {
11502
        $icon = self::getSelectedIcon($lpId);
11503
11504
        if (empty($icon)) {
11505
            return '';
11506
        }
11507
11508
        $theme = api_get_visual_theme();
11509
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
11510
11511
        return Display::img($path);
11512
    }
11513
11514
    /**
11515
     * @param string $value
11516
     *
11517
     * @return string
11518
     */
11519
    public function cleanItemTitle($value)
11520
    {
11521
        $value = Security::remove_XSS(strip_tags($value));
11522
11523
        return $value;
11524
    }
11525
11526
    public function setItemTitle(FormValidator $form)
11527
    {
11528
        if (api_get_configuration_value('save_titles_as_html')) {
11529
            $form->addHtmlEditor(
11530
                'title',
11531
                get_lang('Title'),
11532
                true,
11533
                false,
11534
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
11535
            );
11536
        } else {
11537
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
11538
            $form->applyFilter('title', 'trim');
11539
            $form->applyFilter('title', 'html_filter');
11540
        }
11541
    }
11542
11543
    /**
11544
     * @return array
11545
     */
11546
    public function getItemsForForm($addParentCondition = false)
11547
    {
11548
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11549
        $course_id = api_get_course_int_id();
11550
11551
        $sql = "SELECT * FROM $tbl_lp_item
11552
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11553
11554
        if ($addParentCondition) {
11555
            $sql .= ' AND parent_item_id = 0 ';
11556
        }
11557
        $sql .= ' ORDER BY display_order ASC';
11558
11559
        $result = Database::query($sql);
11560
        $arrLP = [];
11561
        while ($row = Database::fetch_array($result)) {
11562
            $arrLP[] = [
11563
                'iid' => $row['iid'],
11564
                'id' => $row['iid'],
11565
                'item_type' => $row['item_type'],
11566
                'title' => $this->cleanItemTitle($row['title']),
11567
                'title_raw' => $row['title'],
11568
                'path' => $row['path'],
11569
                'description' => Security::remove_XSS($row['description']),
11570
                'parent_item_id' => $row['parent_item_id'],
11571
                'previous_item_id' => $row['previous_item_id'],
11572
                'next_item_id' => $row['next_item_id'],
11573
                'display_order' => $row['display_order'],
11574
                'max_score' => $row['max_score'],
11575
                'min_score' => $row['min_score'],
11576
                'mastery_score' => $row['mastery_score'],
11577
                'prerequisite' => $row['prerequisite'],
11578
                'max_time_allowed' => $row['max_time_allowed'],
11579
                'prerequisite_min_score' => $row['prerequisite_min_score'],
11580
                'prerequisite_max_score' => $row['prerequisite_max_score'],
11581
            ];
11582
        }
11583
11584
        return $arrLP;
11585
    }
11586
11587
    /**
11588
     * Get the depth level of LP item.
11589
     *
11590
     * @param array $items
11591
     * @param int   $currentItemId
11592
     *
11593
     * @return int
11594
     */
11595
    private static function get_level_for_item($items, $currentItemId)
11596
    {
11597
        $parentItemId = 0;
11598
        if (isset($items[$currentItemId])) {
11599
            $parentItemId = $items[$currentItemId]->parent;
11600
        }
11601
11602
        if (0 == $parentItemId) {
11603
            return 0;
11604
        } else {
11605
            return self::get_level_for_item($items, $parentItemId) + 1;
11606
        }
11607
    }
11608
11609
    /**
11610
     * Generate the link for a learnpath category as course tool.
11611
     *
11612
     * @param int $categoryId
11613
     *
11614
     * @return string
11615
     */
11616
    private static function getCategoryLinkForTool($categoryId)
11617
    {
11618
        $categoryId = (int) $categoryId;
11619
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
11620
            .http_build_query(
11621
                [
11622
                    'action' => 'view_category',
11623
                    'id' => $categoryId,
11624
                ]
11625
            );
11626
11627
        return $link;
11628
    }
11629
11630
    /**
11631
     * Return the scorm item type object with spaces replaced with _
11632
     * The return result is use to build a css classname like scorm_type_$return.
11633
     *
11634
     * @param $in_type
11635
     *
11636
     * @return mixed
11637
     */
11638
    private static function format_scorm_type_item($in_type)
11639
    {
11640
        return str_replace(' ', '_', $in_type);
11641
    }
11642
11643
    /**
11644
     * Check and obtain the lp final item if exist.
11645
     *
11646
     * @return learnpathItem
11647
     */
11648
    private function getFinalItem()
11649
    {
11650
        if (empty($this->items)) {
11651
            return null;
11652
        }
11653
11654
        foreach ($this->items as $item) {
11655
            if ('final_item' !== $item->type) {
11656
                continue;
11657
            }
11658
11659
            return $item;
11660
        }
11661
    }
11662
11663
    /**
11664
     * Get the LP Final Item Template.
11665
     *
11666
     * @return string
11667
     */
11668
    private function getFinalItemTemplate()
11669
    {
11670
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
11671
    }
11672
11673
    /**
11674
     * Get the LP Final Item Url.
11675
     *
11676
     * @return string
11677
     */
11678
    private function getSavedFinalItem()
11679
    {
11680
        $finalItem = $this->getFinalItem();
11681
11682
        $repo = Container::getDocumentRepository();
11683
        /** @var CDocument $document */
11684
        $document = $repo->find($finalItem->path);
11685
11686
        if ($document && $document->getResourceNode()->hasResourceFile()) {
11687
            return  $repo->getResourceFileContent($document);
11688
        }
11689
11690
        return '';
11691
    }
11692
}
11693